From 0559de6678b233da08ea2f532912ee9c61dff212 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Wed, 7 Aug 2024 09:11:49 +0100 Subject: [PATCH 01/44] [APM][ECO] Logs callout when logs.level is not available (#189644) closes https://github.com/elastic/kibana/issues/189731 - Adding dismiss button to APM section - Adding logs callout https://github.com/user-attachments/assets/902409de-7d9b-47bf-b57d-3cdb199301f0 - N/A popover Screenshot 2024-07-31 at 15 03 43 Screenshot 2024-08-06 at 15 01 43 Screenshot 2024-08-06 at 15 01 52 --- .../apm/common/entities/types.ts | 1 + .../app/entities/logs/add_apm_callout.tsx | 84 ++++++++++------ .../entities/logs/logs_service_overview.tsx | 97 +++++++++++++++++-- .../table/get_service_columns.tsx | 18 +++- .../shared/environment_badge/index.tsx | 2 +- .../not_available_apm_metrics.tsx | 8 +- .../not_available_environment.tsx | 6 +- .../not_available_log_metrics.tsx | 53 ++++++++++ .../apm/public/utils/get_signal_type.ts | 2 +- .../server/routes/entities/get_entities.ts | 12 ++- .../entities/get_service_entity_summary.ts | 40 ++++++++ .../server/routes/entities/services/routes.ts | 32 ++++++ .../apm/server/routes/entities/types.ts | 2 + .../utils/calculate_avg_metrics.test.ts | 6 ++ .../entities/utils/merge_entities.test.ts | 22 +++++ .../routes/entities/utils/merge_entities.ts | 2 + 16 files changed, 332 insertions(+), 55 deletions(-) rename x-pack/plugins/observability_solution/apm/public/components/shared/{ => not_available_popover}/not_available_apm_metrics.tsx (81%) rename x-pack/plugins/observability_solution/apm/public/components/shared/{ => not_available_popover}/not_available_environment.tsx (92%) create mode 100644 x-pack/plugins/observability_solution/apm/public/components/shared/not_available_popover/not_available_log_metrics.tsx create mode 100644 x-pack/plugins/observability_solution/apm/server/routes/entities/get_service_entity_summary.ts diff --git a/x-pack/plugins/observability_solution/apm/common/entities/types.ts b/x-pack/plugins/observability_solution/apm/common/entities/types.ts index f953fb5c593ed..bdca62bc66824 100644 --- a/x-pack/plugins/observability_solution/apm/common/entities/types.ts +++ b/x-pack/plugins/observability_solution/apm/common/entities/types.ts @@ -27,4 +27,5 @@ export interface EntityServiceListItem { environments: string[]; serviceName: string; agentName: AgentName; + hasLogMetrics: boolean; } diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/entities/logs/add_apm_callout.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/entities/logs/add_apm_callout.tsx index c73526a5350c5..49856327dd703 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/entities/logs/add_apm_callout.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/entities/logs/add_apm_callout.tsx @@ -16,6 +16,7 @@ import { EuiTitle, EuiButtonEmpty, useEuiTheme, + EuiButtonIcon, } from '@elastic/eui'; import { apmLight } from '@kbn/shared-svg'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -23,7 +24,11 @@ import { useKibana } from '../../../../context/kibana_context/use_kibana'; import { ApmPluginStartDeps, ApmServices } from '../../../../plugin'; import { AddApmData } from '../../../shared/add_data_buttons/buttons'; -export function AddAPMCallOut() { +interface Props { + onClose: () => void; +} + +export function AddAPMCallOut({ onClose }: Props) { const { euiTheme } = useEuiTheme(); const { services } = useKibana(); @@ -35,41 +40,56 @@ export function AddAPMCallOut() { return ( - - - - - - -

- + + + + -

-
+
+ + +

+ +

+
- + - -

- +

+ +

+
+ +
+
+ + + + + -

- - +
+
diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/entities/logs/logs_service_overview.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/entities/logs/logs_service_overview.tsx index d1f08b16eaf15..c89487b527fbe 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/entities/logs/logs_service_overview.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/entities/logs/logs_service_overview.tsx @@ -12,17 +12,29 @@ * 2.0. */ +import { + EuiCallOut, + EuiFlexGroup, + EuiFlexGroupProps, + EuiFlexItem, + EuiLink, + EuiLoadingSpinner, + EuiSpacer, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; import React from 'react'; -import { EuiFlexGroupProps, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { AnnotationsContextProvider } from '../../../../context/annotations/annotations_context'; import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; import { ChartPointerEventContextProvider } from '../../../../context/chart_pointer_event/chart_pointer_event_context'; -import { useBreakpoints } from '../../../../hooks/use_breakpoints'; import { useApmParams } from '../../../../hooks/use_apm_params'; +import { useBreakpoints } from '../../../../hooks/use_breakpoints'; import { useTimeRange } from '../../../../hooks/use_time_range'; -import { AddAPMCallOut } from './add_apm_callout'; -import { LogRateChart } from '../charts/log_rate_chart'; import { LogErrorRateChart } from '../charts/log_error_rate_chart'; +import { LogRateChart } from '../charts/log_rate_chart'; +import { AddAPMCallOut } from './add_apm_callout'; +import { useLocalStorage } from '../../../../hooks/use_local_storage'; +import { isPending, useFetcher } from '../../../../hooks/use_fetcher'; /** * The height a chart should be if it's next to a table with 5 rows and a title. * Add the height of the pagination row. @@ -32,6 +44,10 @@ const chartHeight = 400; export function LogsServiceOverview() { const { serviceName } = useApmServiceContext(); + const [isLogsApmCalloutEnabled, setIsLogsApmCalloutEnabled] = useLocalStorage( + 'apm.isLogsApmCalloutEnabled', + true + ); const { query: { environment, rangeFrom, rangeTo }, @@ -39,11 +55,28 @@ export function LogsServiceOverview() { const { start, end } = useTimeRange({ rangeFrom, rangeTo }); + const { data, status } = useFetcher( + (callAPI) => { + return callAPI('GET /internal/apm/entities/services/{serviceName}/summary', { + params: { path: { serviceName }, query: { end, environment, start } }, + }); + }, + [end, environment, serviceName, start] + ); + const { isLarge } = useBreakpoints(); const isSingleColumn = isLarge; const rowDirection: EuiFlexGroupProps['direction'] = isSingleColumn ? 'column' : 'row'; + if (isPending(status)) { + return ( +
+ +
+ ); + } + return ( - - + {isLogsApmCalloutEnabled ? ( + <> + { + setIsLogsApmCalloutEnabled(false); + }} + /> + + + ) : null} + {data?.entity?.hasLogMetrics === false ? ( + <> + + + {i18n.translate('xpack.apm.logsServiceOverview.logLevelLink', { + defaultMessage: 'log.level', + })} + + ), + learnMoreLink: ( + + {i18n.translate('xpack.apm.logsServiceOverview.learnMoreLink', { + defaultMessage: 'Learn more', + })} + + ), + }} + /> + + + + ) : null} diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/multi_signal_inventory/table/get_service_columns.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/multi_signal_inventory/table/get_service_columns.tsx index d0a2cfa898b79..45e8a9ca859a0 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/multi_signal_inventory/table/get_service_columns.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/multi_signal_inventory/table/get_service_columns.tsx @@ -32,13 +32,14 @@ import { EnvironmentBadge } from '../../../../shared/environment_badge'; import { ServiceLink } from '../../../../shared/links/apm/service_link'; import { ListMetric } from '../../../../shared/list_metric'; import { ITableColumn } from '../../../../shared/managed_table'; -import { NotAvailableApmMetrics } from '../../../../shared/not_available_apm_metrics'; +import { NotAvailableApmMetrics } from '../../../../shared/not_available_popover/not_available_apm_metrics'; import { TruncateWithTooltip } from '../../../../shared/truncate_with_tooltip'; import { ServiceInventoryFieldName } from './multi_signal_services_table'; import { EntityServiceListItem, SignalTypes } from '../../../../../../common/entities/types'; -import { isApmSignal } from '../../../../../utils/get_signal_type'; +import { isApmSignal, isLogsSignal } from '../../../../../utils/get_signal_type'; import { ColumnHeader } from './column_header'; import { APIReturnType } from '../../../../../services/rest/create_call_apm_api'; +import { NotAvailableLogsMetrics } from '../../../../shared/not_available_popover/not_available_log_metrics'; type ServicesDetailedStatisticsAPIResponse = APIReturnType<'POST /internal/apm/entities/services/detailed_statistics'>; @@ -205,9 +206,12 @@ export function getServiceColumns({ sortable: true, dataType: 'number', align: RIGHT_ALIGNMENT, - render: (_, { metrics, serviceName }) => { - const { currentPeriodColor } = getTimeSeriesColor(ChartType.LOG_RATE); + render: (_, { metrics, serviceName, signalTypes, hasLogMetrics }) => { + if (isLogsSignal(signalTypes) && !hasLogMetrics) { + return ; + } + const { currentPeriodColor } = getTimeSeriesColor(ChartType.LOG_RATE); return ( { + render: (_, { metrics, serviceName, signalTypes, hasLogMetrics }) => { + if (isLogsSignal(signalTypes) && !hasLogMetrics) { + return ; + } + const { currentPeriodColor } = getTimeSeriesColor(ChartType.LOG_ERROR_RATE); return ( diff --git a/x-pack/plugins/observability_solution/apm/public/components/shared/environment_badge/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/shared/environment_badge/index.tsx index 135848bae13d4..8e3752e5ce0bf 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/shared/environment_badge/index.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/shared/environment_badge/index.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { ItemsBadge } from '../item_badge'; -import { NotAvailableEnvironment } from '../not_available_environment'; +import { NotAvailableEnvironment } from '../not_available_popover/not_available_environment'; interface Props { environments: string[]; diff --git a/x-pack/plugins/observability_solution/apm/public/components/shared/not_available_apm_metrics.tsx b/x-pack/plugins/observability_solution/apm/public/components/shared/not_available_popover/not_available_apm_metrics.tsx similarity index 81% rename from x-pack/plugins/observability_solution/apm/public/components/shared/not_available_apm_metrics.tsx rename to x-pack/plugins/observability_solution/apm/public/components/shared/not_available_popover/not_available_apm_metrics.tsx index c4d2eb8563af6..66c466502bdd1 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/shared/not_available_apm_metrics.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/shared/not_available_popover/not_available_apm_metrics.tsx @@ -7,10 +7,10 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { PopoverBadge } from './popover_badge'; -import { useKibana } from '../../context/kibana_context/use_kibana'; -import { ApmPluginStartDeps, ApmServices } from '../../plugin'; -import { AddApmData } from './add_data_buttons/buttons'; +import { PopoverBadge } from '../popover_badge'; +import { useKibana } from '../../../context/kibana_context/use_kibana'; +import { ApmPluginStartDeps, ApmServices } from '../../../plugin'; +import { AddApmData } from '../add_data_buttons/buttons'; export function NotAvailableApmMetrics() { const { services } = useKibana(); diff --git a/x-pack/plugins/observability_solution/apm/public/components/shared/not_available_environment.tsx b/x-pack/plugins/observability_solution/apm/public/components/shared/not_available_popover/not_available_environment.tsx similarity index 92% rename from x-pack/plugins/observability_solution/apm/public/components/shared/not_available_environment.tsx rename to x-pack/plugins/observability_solution/apm/public/components/shared/not_available_popover/not_available_environment.tsx index aecc9f2586e71..af4cdd58ec1f9 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/shared/not_available_environment.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/shared/not_available_popover/not_available_environment.tsx @@ -9,9 +9,9 @@ import React from 'react'; import { EuiCode, EuiLink } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { PopoverBadge } from './popover_badge'; +import { PopoverBadge } from '../popover_badge'; -export const NotAvailableEnvironment = () => { +export function NotAvailableEnvironment() { return ( { } /> ); -}; +} diff --git a/x-pack/plugins/observability_solution/apm/public/components/shared/not_available_popover/not_available_log_metrics.tsx b/x-pack/plugins/observability_solution/apm/public/components/shared/not_available_popover/not_available_log_metrics.tsx new file mode 100644 index 0000000000000..480795c533944 --- /dev/null +++ b/x-pack/plugins/observability_solution/apm/public/components/shared/not_available_popover/not_available_log_metrics.tsx @@ -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 { EuiLink } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import React from 'react'; +import { PopoverBadge } from '../popover_badge'; + +export function NotAvailableLogsMetrics() { + return ( + + {i18n.translate( + 'xpack.apm.servicesTable.notAvailableLogsMetrics.content.logLevelLink', + { defaultMessage: 'log.level' } + )} + + ), + }} + /> + } + footer={ + + {i18n.translate('xpack.apm.servicesTable.notAvailableLogsMetrics.footer.learnMore', { + defaultMessage: 'Learn more', + })} + + } + /> + ); +} diff --git a/x-pack/plugins/observability_solution/apm/public/utils/get_signal_type.ts b/x-pack/plugins/observability_solution/apm/public/utils/get_signal_type.ts index a72bb4d782e9c..e8a3c4c4313df 100644 --- a/x-pack/plugins/observability_solution/apm/public/utils/get_signal_type.ts +++ b/x-pack/plugins/observability_solution/apm/public/utils/get_signal_type.ts @@ -11,5 +11,5 @@ export function isApmSignal(signalTypes: SignalTypes[]) { return signalTypes.includes(SignalTypes.METRICS) || signalTypes.includes(SignalTypes.TRACES); } export function isLogsSignal(signalTypes: SignalTypes[]) { - return signalTypes.includes(SignalTypes.LOGS) && !isApmSignal(signalTypes); + return signalTypes.includes(SignalTypes.LOGS); } diff --git a/x-pack/plugins/observability_solution/apm/server/routes/entities/get_entities.ts b/x-pack/plugins/observability_solution/apm/server/routes/entities/get_entities.ts index 40704fd3fed9f..a69a906454ff7 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/entities/get_entities.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/entities/get_entities.ts @@ -17,6 +17,7 @@ import { environmentQuery } from '../../../common/utils/environment_query'; import { EntitiesESClient } from '../../lib/helpers/create_es_client/create_assets_es_client/create_assets_es_clients'; import { getServiceEntitiesHistoryMetrics } from './get_service_entities_history_metrics'; import { EntitiesRaw, EntityType, ServiceEntities } from './types'; +import { isFiniteNumber } from '../../../common/utils/is_finite_number'; export function entitiesRangeQuery(start: number, end: number): QueryDslQueryContainer[] { return [ @@ -44,14 +45,16 @@ export async function getEntities({ environment, kuery, size, + serviceName, }: { entitiesESClient: EntitiesESClient; start: number; end: number; environment: string; - kuery: string; + kuery?: string; size: number; -}) { + serviceName?: string; +}): Promise { const entities = ( await entitiesESClient.searchLatest(`get_entities`, { body: { @@ -65,6 +68,7 @@ export async function getEntities({ ...environmentQuery(environment, SERVICE_ENVIRONMENT), ...entitiesRangeQuery(start, end), ...termQuery(ENTITY_TYPE, EntityType.SERVICE), + ...termQuery(SERVICE_NAME, serviceName), ], }, }, @@ -82,7 +86,8 @@ export async function getEntities({ }) : undefined; - return entities.map((entity): ServiceEntities => { + return entities.map((entity) => { + const historyLogRate = serviceEntitiesHistoryMetricsMap?.[entity.entity.id]?.logRate; return { serviceName: entity.service.name, environment: Array.isArray(entity.service?.environment) // TODO fix this in the EEM @@ -92,6 +97,7 @@ export async function getEntities({ signalTypes: entity.data_stream.type, entity: { ...entity.entity, + hasLogMetrics: isFiniteNumber(historyLogRate) ? historyLogRate > 0 : false, // History metrics undefined means that for the selected time range there was no ingestion happening. metrics: serviceEntitiesHistoryMetricsMap?.[entity.entity.id] || { latency: null, diff --git a/x-pack/plugins/observability_solution/apm/server/routes/entities/get_service_entity_summary.ts b/x-pack/plugins/observability_solution/apm/server/routes/entities/get_service_entity_summary.ts new file mode 100644 index 0000000000000..0ea5d27e68971 --- /dev/null +++ b/x-pack/plugins/observability_solution/apm/server/routes/entities/get_service_entity_summary.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 { EntitiesESClient } from '../../lib/helpers/create_es_client/create_assets_es_client/create_assets_es_clients'; +import { withApmSpan } from '../../utils/with_apm_span'; +import { getEntities } from './get_entities'; +import { ServiceEntities } from './types'; + +interface Params { + entitiesESClient: EntitiesESClient; + serviceName: string; + environment: string; + start: number; + end: number; +} + +export async function getServiceEntitySummary({ + end, + entitiesESClient, + environment, + serviceName, + start, +}: Params): Promise { + return withApmSpan('get_service_entity_summary', async () => { + const entities = await getEntities({ + end, + entitiesESClient, + environment, + size: 1, + start, + serviceName, + }); + + return entities[0]; + }); +} diff --git a/x-pack/plugins/observability_solution/apm/server/routes/entities/services/routes.ts b/x-pack/plugins/observability_solution/apm/server/routes/entities/services/routes.ts index c3894a38da2c4..0f52a9956af5b 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/entities/services/routes.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/entities/services/routes.ts @@ -13,12 +13,43 @@ import { createEntitiesESClient } from '../../../lib/helpers/create_es_client/cr import { createApmServerRoute } from '../../apm_routes/create_apm_server_route'; import { environmentRt, kueryRt, rangeRt } from '../../default_api_types'; import { getServiceEntities } from './get_service_entities'; +import { getServiceEntitySummary } from '../get_service_entity_summary'; +import { ServiceEntities } from '../types'; import { getServiceEntitiesHistoryTimeseries } from '../get_service_entities_history_timeseries'; export interface EntityServicesResponse { services: EntityServiceListItem[]; } +const serviceEntitiesSummaryRoute = createApmServerRoute({ + endpoint: 'GET /internal/apm/entities/services/{serviceName}/summary', + params: t.type({ + path: t.type({ serviceName: t.string }), + query: t.intersection([environmentRt, rangeRt]), + }), + options: { tags: ['access:apm'] }, + async handler(resources): Promise { + const { context, params, request } = resources; + const coreContext = await context.core; + + const entitiesESClient = await createEntitiesESClient({ + request, + esClient: coreContext.elasticsearch.client.asCurrentUser, + }); + + const { serviceName } = params.path; + const { start, end, environment } = params.query; + + return getServiceEntitySummary({ + entitiesESClient, + start, + end, + serviceName, + environment, + }); + }, +}); + const servicesEntitiesRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/entities/services', params: t.type({ @@ -161,4 +192,5 @@ export const servicesEntitiesRoutesRepository = { ...serviceLogRateTimeseriesRoute, ...serviceLogErrorRateTimeseriesRoute, ...servicesEntitiesDetailedStatisticsRoute, + ...serviceEntitiesSummaryRoute, }; diff --git a/x-pack/plugins/observability_solution/apm/server/routes/entities/types.ts b/x-pack/plugins/observability_solution/apm/server/routes/entities/types.ts index 35a19a218eea0..f98cef107a3d1 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/entities/types.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/entities/types.ts @@ -16,6 +16,7 @@ export interface Entity { latestTimestamp: string; identityFields: string[]; metrics: EntityMetrics; + hasLogMetrics: boolean; } export interface TraceMetrics { @@ -52,4 +53,5 @@ export interface MergedServiceEntities { signalTypes: SignalTypes[]; environments: string[]; metrics: EntityMetrics[]; + hasLogMetrics: boolean; } diff --git a/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/calculate_avg_metrics.test.ts b/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/calculate_avg_metrics.test.ts index a7d18a93bd854..f73d8cd5f4f8d 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/calculate_avg_metrics.test.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/calculate_avg_metrics.test.ts @@ -34,6 +34,7 @@ describe('calculateAverageMetrics', () => { }, ], serviceName: 'service-1', + hasLogMetrics: true, }, { agentName: 'java' as AgentName, @@ -57,6 +58,7 @@ describe('calculateAverageMetrics', () => { }, ], serviceName: 'service-2', + hasLogMetrics: true, }, ]; @@ -76,6 +78,7 @@ describe('calculateAverageMetrics', () => { throughput: 7.5, }, serviceName: 'service-1', + hasLogMetrics: true, }, { agentName: 'java' as AgentName, @@ -90,6 +93,7 @@ describe('calculateAverageMetrics', () => { throughput: 10, }, serviceName: 'service-2', + hasLogMetrics: true, }, ]); }); @@ -117,6 +121,7 @@ describe('calculateAverageMetrics', () => { }, ], serviceName: 'service-1', + hasLogMetrics: true, }, ]; @@ -135,6 +140,7 @@ describe('calculateAverageMetrics', () => { throughput: 7.5, }, serviceName: 'service-1', + hasLogMetrics: true, }, ]); }); diff --git a/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/merge_entities.test.ts b/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/merge_entities.test.ts index 544513fd501d2..dc52a8369c1cc 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/merge_entities.test.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/merge_entities.test.ts @@ -27,6 +27,7 @@ describe('mergeEntities', () => { }, identityFields: ['service.name', 'service.environment'], id: 'service-1:test', + hasLogMetrics: true, }, }, ]; @@ -47,6 +48,7 @@ describe('mergeEntities', () => { }, ], serviceName: 'service-1', + hasLogMetrics: true, }, ]); }); @@ -69,6 +71,7 @@ describe('mergeEntities', () => { }, identityFields: ['service.name', 'service.environment'], id: 'service-1:env-service-1', + hasLogMetrics: true, }, }, { @@ -87,6 +90,7 @@ describe('mergeEntities', () => { }, identityFields: ['service.name', 'service.environment'], id: 'apm-only-1:synthtrace-env-2', + hasLogMetrics: true, }, }, { @@ -105,6 +109,7 @@ describe('mergeEntities', () => { }, identityFields: ['service.name', 'service.environment'], id: 'service-2:env-service-3', + hasLogMetrics: true, }, }, { @@ -123,6 +128,7 @@ describe('mergeEntities', () => { }, identityFields: ['service.name', 'service.environment'], id: 'service-2:env-service-3', + hasLogMetrics: true, }, }, ]; @@ -151,6 +157,7 @@ describe('mergeEntities', () => { }, ], serviceName: 'service-1', + hasLogMetrics: true, }, { agentName: 'java' as AgentName, @@ -174,6 +181,7 @@ describe('mergeEntities', () => { }, ], serviceName: 'service-2', + hasLogMetrics: true, }, ]); }); @@ -195,6 +203,7 @@ describe('mergeEntities', () => { }, identityFields: ['service.name', 'service.environment'], id: 'service-1:test', + hasLogMetrics: true, }, }, { @@ -213,6 +222,7 @@ describe('mergeEntities', () => { }, identityFields: ['service.name', 'service.environment'], id: 'service-1:test', + hasLogMetrics: true, }, }, { @@ -231,6 +241,7 @@ describe('mergeEntities', () => { }, identityFields: ['service.name', 'service.environment'], id: 'service-1:prod', + hasLogMetrics: true, }, }, ]; @@ -265,6 +276,7 @@ describe('mergeEntities', () => { }, ], serviceName: 'service-1', + hasLogMetrics: true, }, ]); }); @@ -286,6 +298,7 @@ describe('mergeEntities', () => { }, identityFields: ['service.name'], id: 'service-1:test', + hasLogMetrics: true, }, }, ]; @@ -306,6 +319,7 @@ describe('mergeEntities', () => { }, ], serviceName: 'service-1', + hasLogMetrics: true, }, ]); @@ -325,6 +339,7 @@ describe('mergeEntities', () => { }, identityFields: ['service.name'], id: 'service-1:test', + hasLogMetrics: true, }, }, { @@ -342,6 +357,7 @@ describe('mergeEntities', () => { }, identityFields: ['service.name'], id: 'service-1:test', + hasLogMetrics: true, }, }, ]; @@ -369,6 +385,7 @@ describe('mergeEntities', () => { }, ], serviceName: 'service-1', + hasLogMetrics: true, }, ]); }); @@ -390,6 +407,7 @@ describe('mergeEntities', () => { }, identityFields: ['service.name'], id: 'service-1:test', + hasLogMetrics: true, }, }, ]; @@ -410,6 +428,7 @@ describe('mergeEntities', () => { }, ], serviceName: 'service-1', + hasLogMetrics: true, }, ]); @@ -429,6 +448,7 @@ describe('mergeEntities', () => { }, identityFields: ['service.name'], id: 'service-1:test', + hasLogMetrics: true, }, }, { @@ -446,6 +466,7 @@ describe('mergeEntities', () => { }, identityFields: ['service.name'], id: 'service-1:test', + hasLogMetrics: true, }, }, ]; @@ -473,6 +494,7 @@ describe('mergeEntities', () => { }, ], serviceName: 'service-1', + hasLogMetrics: true, }, ]); }); diff --git a/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/merge_entities.ts b/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/merge_entities.ts index 7dd8bfdace7bf..6f96a25c63a93 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/merge_entities.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/merge_entities.ts @@ -36,6 +36,7 @@ function mergeFunc(entity: ServiceEntities, existingEntity?: MergedServiceEntiti environments: compact([entity?.environment]), latestTimestamp: entity.entity.latestTimestamp, metrics: [entity.entity.metrics], + hasLogMetrics: entity.entity.hasLogMetrics, }; } return { @@ -45,5 +46,6 @@ function mergeFunc(entity: ServiceEntities, existingEntity?: MergedServiceEntiti environments: uniq(compact([...existingEntity?.environments, entity?.environment])), latestTimestamp: entity.entity.latestTimestamp, metrics: [...existingEntity?.metrics, entity.entity.metrics], + hasLogMetrics: entity.entity.hasLogMetrics || existingEntity.hasLogMetrics, }; } From 59abfd3bc22a860565c2613390d07b84a5ffdefb Mon Sep 17 00:00:00 2001 From: Kerry Gallagher Date: Wed, 7 Aug 2024 10:38:48 +0100 Subject: [PATCH 02/44] [Logs+] Use central log sources setting in the Logs UI (#188020) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Implements https://github.com/elastic/logs-dev/issues/170 ## High-level overview of changes - Log Views now have a new log indices reference type of `kibana_advanced_setting`. - The Log View clients have been amended to be able to fully resolve this new type. - **All** consumers of the Log Stream Component have had to be updated to require the logs data access plugin, this is due to the component relying on consumers correctly setting up dependencies in a wrapping Kibana Context Provider. This facilitates the LSC instantiating it's own Log Views client (the log sources service is a dependency of the Log View client). I had some issues setting up Enterprise Search locally, would really appreciate if someone from that team could check that usage. - Originally I'd implemented this so that if using the defaults (advanced setting) or switching to the `kibana_advanced_setting` type the other two deprecated options would be hidden, however this breaks a lot of functional tests, so all three remain selectable (but noted as deprecated), realistically we're always going to have to deal with the migration path anyway. ## 🎨 UI / UX changes ![Screenshot 2024-07-24 at 11 55 46](https://github.com/user-attachments/assets/e04dfd31-155a-4026-8355-895854e54aab) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/enterprise_search/kibana.jsonc | 1 + x-pack/plugins/fleet/kibana.jsonc | 3 +- .../common/source_configuration/defaults.ts | 5 +- .../source_configuration.ts | 11 +- .../observability_solution/infra/kibana.jsonc | 3 +- .../indices_configuration_form_state.ts | 10 +- .../settings/indices_configuration_panel.tsx | 53 ++++++- ...a_advanced_setting_configuration_panel.tsx | 137 ++++++++++++++++++ .../source_configuration_form_state.tsx | 3 +- .../infra/public/types.ts | 2 + .../lib/adapters/framework/adapter_types.ts | 2 + ...nventory_metric_threshold_executor.test.ts | 12 +- .../inventory_metric_threshold_executor.ts | 8 +- .../log_threshold/log_threshold_executor.ts | 7 +- .../infra/server/lib/sources/types.ts | 5 + .../infra/tsconfig.json | 2 + .../logs_data_access/common/constants.ts | 2 +- .../log_sources_service.mocks.ts | 24 +++ .../services/log_sources_service/types.ts | 15 ++ .../logs_data_access/common/types.ts | 4 +- .../logs_data_access/common/ui_settings.ts | 2 +- .../logs_data_access/public/index.ts | 3 + .../services/log_sources_service/index.ts | 9 +- .../get_logs_error_rate_timeseries.ts | 2 +- .../get_logs_rate_timeseries.ts | 2 +- .../services/log_sources_service/index.ts | 55 ++++--- .../server/services/register_services.ts | 4 +- .../server/utils/es_queries.ts | 23 +++ .../logs_data_access/tsconfig.json | 5 +- .../logs_shared/common/index.ts | 2 + .../log_views/resolved_log_view.mock.ts | 2 + .../common/log_views/resolved_log_view.ts | 57 +++++++- .../logs_shared/common/log_views/types.ts | 15 +- .../logs_shared/kibana.jsonc | 3 +- .../components/log_stream/log_stream.tsx | 17 ++- .../logs_shared/public/plugin.ts | 3 +- .../services/log_views/log_views_client.ts | 10 +- .../services/log_views/log_views_service.ts | 15 +- .../public/services/log_views/types.ts | 2 + .../logs_shared/public/types.ts | 2 + .../log_entries_domain/log_entries_domain.ts | 24 ++- .../logs_shared/server/plugin.ts | 1 + .../server/saved_objects/log_view/types.ts | 5 + .../log_views/log_views_client.test.ts | 3 + .../services/log_views/log_views_client.ts | 10 +- .../log_views/log_views_service.mock.ts | 5 +- .../services/log_views/log_views_service.ts | 9 +- .../server/services/log_views/types.ts | 4 + .../logs_shared/server/types.ts | 2 + .../logs_shared/tsconfig.json | 1 + .../infra/logs/logs_source_configuration.ts | 2 + .../infra_source_configuration_form.ts | 4 +- 52 files changed, 533 insertions(+), 79 deletions(-) create mode 100644 x-pack/plugins/observability_solution/infra/public/pages/logs/settings/kibana_advanced_setting_configuration_panel.tsx create mode 100644 x-pack/plugins/observability_solution/logs_data_access/common/services/log_sources_service/log_sources_service.mocks.ts create mode 100644 x-pack/plugins/observability_solution/logs_data_access/common/services/log_sources_service/types.ts create mode 100644 x-pack/plugins/observability_solution/logs_data_access/server/utils/es_queries.ts diff --git a/x-pack/plugins/enterprise_search/kibana.jsonc b/x-pack/plugins/enterprise_search/kibana.jsonc index 0dc2562ff2fe3..91221ef0ed95e 100644 --- a/x-pack/plugins/enterprise_search/kibana.jsonc +++ b/x-pack/plugins/enterprise_search/kibana.jsonc @@ -15,6 +15,7 @@ "features", "licensing", "logsShared", + "logsDataAccess", "esUiShared", "navigation", ], diff --git a/x-pack/plugins/fleet/kibana.jsonc b/x-pack/plugins/fleet/kibana.jsonc index 54b4a4928594c..dded2caf4c7e2 100644 --- a/x-pack/plugins/fleet/kibana.jsonc +++ b/x-pack/plugins/fleet/kibana.jsonc @@ -24,7 +24,8 @@ "files", "uiActions", "dashboard", - "fieldsMetadata" + "fieldsMetadata", + "logsDataAccess" ], "optionalPlugins": [ "features", diff --git a/x-pack/plugins/observability_solution/infra/common/source_configuration/defaults.ts b/x-pack/plugins/observability_solution/infra/common/source_configuration/defaults.ts index ac3724c80d70a..05988909e3f36 100644 --- a/x-pack/plugins/observability_solution/infra/common/source_configuration/defaults.ts +++ b/x-pack/plugins/observability_solution/infra/common/source_configuration/defaults.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { LOGS_INDEX_PATTERN, METRICS_INDEX_PATTERN } from '../constants'; +import { METRICS_INDEX_PATTERN } from '../constants'; import { InfraSourceConfiguration } from './source_configuration'; export const defaultSourceConfiguration: InfraSourceConfiguration = { @@ -13,8 +13,7 @@ export const defaultSourceConfiguration: InfraSourceConfiguration = { description: '', metricAlias: METRICS_INDEX_PATTERN, logIndices: { - type: 'index_name', - indexName: LOGS_INDEX_PATTERN, + type: 'kibana_advanced_setting', }, inventoryDefaultView: '0', metricsExplorerDefaultView: '0', diff --git a/x-pack/plugins/observability_solution/infra/common/source_configuration/source_configuration.ts b/x-pack/plugins/observability_solution/infra/common/source_configuration/source_configuration.ts index 116c30d8274e3..e9779c192788d 100644 --- a/x-pack/plugins/observability_solution/infra/common/source_configuration/source_configuration.ts +++ b/x-pack/plugins/observability_solution/infra/common/source_configuration/source_configuration.ts @@ -80,7 +80,16 @@ export const logIndexNameReferenceRT = rt.type({ }); export type LogIndexNameReference = rt.TypeOf; -export const logIndexReferenceRT = rt.union([logIndexPatternReferenceRT, logIndexNameReferenceRT]); +// Kibana advanced setting +export const logSourcesKibanaAdvancedSettingRT = rt.type({ + type: rt.literal('kibana_advanced_setting'), +}); + +export const logIndexReferenceRT = rt.union([ + logIndexPatternReferenceRT, + logIndexNameReferenceRT, + logSourcesKibanaAdvancedSettingRT, +]); export type LogIndexReference = rt.TypeOf; export const SourceConfigurationRT = rt.type({ diff --git a/x-pack/plugins/observability_solution/infra/kibana.jsonc b/x-pack/plugins/observability_solution/infra/kibana.jsonc index 973acf3e75d55..cf73b1636d93e 100644 --- a/x-pack/plugins/observability_solution/infra/kibana.jsonc +++ b/x-pack/plugins/observability_solution/infra/kibana.jsonc @@ -34,7 +34,8 @@ "unifiedSearch", "usageCollection", "visTypeTimeseries", - "apmDataAccess" + "apmDataAccess", + "logsDataAccess" ], "optionalPlugins": [ "spaces", diff --git a/x-pack/plugins/observability_solution/infra/public/pages/logs/settings/indices_configuration_form_state.ts b/x-pack/plugins/observability_solution/infra/public/pages/logs/settings/indices_configuration_form_state.ts index 46b5ce97ee4ca..581d8d3011cd6 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/logs/settings/indices_configuration_form_state.ts +++ b/x-pack/plugins/observability_solution/infra/public/pages/logs/settings/indices_configuration_form_state.ts @@ -12,6 +12,8 @@ import { LogDataViewReference, LogIndexNameReference, logIndexNameReferenceRT, + LogSourcesKibanaAdvancedSettingReference, + logSourcesKibanaAdvancedSettingRT, } from '@kbn/logs-shared-plugin/common'; import { useKibanaIndexPatternService } from '../../../hooks/use_kibana_index_patterns'; import { useFormElement } from './form_elements'; @@ -22,7 +24,11 @@ import { validateStringNoSpaces, } from './validation_errors'; -export type LogIndicesFormState = LogIndexNameReference | LogDataViewReference | undefined; +export type LogIndicesFormState = + | LogIndexNameReference + | LogDataViewReference + | LogSourcesKibanaAdvancedSettingReference + | undefined; export const useLogIndicesFormElement = (initialValue: LogIndicesFormState) => { const indexPatternService = useKibanaIndexPatternService(); @@ -35,6 +41,8 @@ export const useLogIndicesFormElement = (initialValue: LogIndicesFormState) => { () => async (logIndices) => { if (logIndices == null) { return validateStringNotEmpty('log data view', ''); + } else if (logSourcesKibanaAdvancedSettingRT.is(logIndices)) { + return []; } else if (logIndexNameReferenceRT.is(logIndices)) { return [ ...validateStringNotEmpty('log indices', logIndices.indexName), diff --git a/x-pack/plugins/observability_solution/infra/public/pages/logs/settings/indices_configuration_panel.tsx b/x-pack/plugins/observability_solution/infra/public/pages/logs/settings/indices_configuration_panel.tsx index bcde2cf84bb4a..0aad03315c8e1 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/logs/settings/indices_configuration_panel.tsx +++ b/x-pack/plugins/observability_solution/infra/public/pages/logs/settings/indices_configuration_panel.tsx @@ -14,6 +14,7 @@ import { LogDataViewReference, logDataViewReferenceRT, LogIndexReference, + logSourcesKibanaAdvancedSettingRT, } from '@kbn/logs-shared-plugin/common'; import { EuiCallOut } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -27,6 +28,7 @@ import { FormElement, isFormElementForType } from './form_elements'; import { IndexNamesConfigurationPanel } from './index_names_configuration_panel'; import { IndexPatternConfigurationPanel } from './index_pattern_configuration_panel'; import { FormValidationError } from './validation_errors'; +import { KibanaAdvancedSettingConfigurationPanel } from './kibana_advanced_setting_configuration_panel'; export const IndicesConfigurationPanel = React.memo<{ isLoading: boolean; @@ -75,6 +77,17 @@ export const IndicesConfigurationPanel = React.memo<{ }); }, [indicesFormElement, trackChangeIndexSourceType]); + const changeToKibanaAdvancedSettingType = useCallback(() => { + // This is always a readonly value, synced with the setting, we just reset back to the correct type. + indicesFormElement.updateValue(() => ({ + type: 'kibana_advanced_setting', + })); + + trackChangeIndexSourceType({ + metric: 'configuration_switch_to_kibana_advanced_setting_reference', + }); + }, [indicesFormElement, trackChangeIndexSourceType]); + useEffect(() => { const getNumberOfInfraRules = async () => { if (http) { @@ -107,6 +120,34 @@ export const IndicesConfigurationPanel = React.memo<{ ), }} > + {' '} + +

+ +

+ + } + name="kibanaAdvancedSetting" + value="kibanaAdvancedSetting" + checked={isKibanaAdvancedSettingFormElement(indicesFormElement)} + onChange={changeToKibanaAdvancedSettingType} + disabled={isReadOnly} + > + {isKibanaAdvancedSettingFormElement(indicesFormElement) && ( + + )} +
+ @@ -134,15 +175,14 @@ export const IndicesConfigurationPanel = React.memo<{ )} -

@@ -152,6 +192,7 @@ export const IndicesConfigurationPanel = React.memo<{ checked={isIndexNamesFormElement(indicesFormElement)} onChange={changeToIndexNameType} disabled={isReadOnly} + data-test-subj="logIndicesCheckableCard" > {isIndexNamesFormElement(indicesFormElement) && ( ; +}> = ({ isLoading, isReadOnly, advancedSettingFormElement }) => { + const { + services: { application, logsDataAccess }, + } = useKibanaContextForPlugin(); + + useTrackPageview({ app: 'infra_logs', path: 'log_source_configuration_kibana_advanced_setting' }); + useTrackPageview({ + app: 'infra_logs', + path: 'log_source_configuration_kibana_advanced_setting', + delay: 15000, + }); + + const advancedSettingsHref = useMemo( + () => getKibanaAdvancedSettingsHref(application), + [application] + ); + + const [logSourcesSettingValue, setLogSourcesSettingValue] = useState( + undefined + ); + + const [getLogSourcesRequest, getLogSources] = useTrackedPromise( + { + cancelPreviousOn: 'resolution', + createPromise: async () => { + return await logsDataAccess.services.logSourcesService.getLogSources(); + }, + onResolve: (response) => { + setLogSourcesSettingValue(response.map((logSource) => logSource.indexPattern).join(',')); + }, + }, + [] + ); + + const isLoadingLogSourcesSetting = useMemo( + () => getLogSourcesRequest.state === 'pending', + [getLogSourcesRequest.state] + ); + + useEffect(() => { + getLogSources(); + }, [getLogSources]); + + return ( + <> + + + + } + description={ + + + + ), + }} + /> + } + > + + } + label={ + + } + {...getFormRowProps(advancedSettingFormElement)} + > + + + + + ); +}; diff --git a/x-pack/plugins/observability_solution/infra/public/pages/logs/settings/source_configuration_form_state.tsx b/x-pack/plugins/observability_solution/infra/public/pages/logs/settings/source_configuration_form_state.tsx index 5ea244e5e0a52..01035f8259a0f 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/logs/settings/source_configuration_form_state.tsx +++ b/x-pack/plugins/observability_solution/infra/public/pages/logs/settings/source_configuration_form_state.tsx @@ -19,8 +19,7 @@ export const useLogSourceConfigurationFormState = (logViewAttributes?: LogViewAt useMemo( () => logViewAttributes?.logIndices ?? { - type: 'index_name', - indexName: '', + type: 'kibana_advanced_setting', }, [logViewAttributes] ) diff --git a/x-pack/plugins/observability_solution/infra/public/types.ts b/x-pack/plugins/observability_solution/infra/public/types.ts index 982f191941ab4..d7a7d339f41be 100644 --- a/x-pack/plugins/observability_solution/infra/public/types.ts +++ b/x-pack/plugins/observability_solution/infra/public/types.ts @@ -49,6 +49,7 @@ import type { CloudSetup } from '@kbn/cloud-plugin/public'; import type { LicenseManagementUIPluginSetup } from '@kbn/license-management-plugin/public'; import type { ServerlessPluginStart } from '@kbn/serverless/public'; import type { DashboardStart } from '@kbn/dashboard-plugin/public'; +import { LogsDataAccessPluginStart } from '@kbn/logs-data-access-plugin/public'; import type { UnwrapPromise } from '../common/utility_types'; import { InventoryViewsServiceStart } from './services/inventory_views'; import { MetricsExplorerViewsServiceStart } from './services/metrics_explorer_views'; @@ -95,6 +96,7 @@ export interface InfraClientStartDeps { embeddable?: EmbeddableStart; lens: LensPublicStart; logsShared: LogsSharedClientStartExports; + logsDataAccess: LogsDataAccessPluginStart; ml?: MlPluginStart; observability: ObservabilityPublicStart; observabilityShared: ObservabilitySharedPluginStart; diff --git a/x-pack/plugins/observability_solution/infra/server/lib/adapters/framework/adapter_types.ts b/x-pack/plugins/observability_solution/infra/server/lib/adapters/framework/adapter_types.ts index 8fe4101d7ebca..7b068424a7cc8 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/adapters/framework/adapter_types.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/adapters/framework/adapter_types.ts @@ -38,6 +38,7 @@ import { ApmDataAccessPluginSetup, ApmDataAccessPluginStart, } from '@kbn/apm-data-access-plugin/server'; +import { LogsDataAccessPluginStart } from '@kbn/logs-data-access-plugin/server'; export interface InfraServerPluginSetupDeps { alerting: AlertingPluginContract; @@ -64,6 +65,7 @@ export interface InfraServerPluginStartDeps { profilingDataAccess?: ProfilingDataAccessPluginStart; ruleRegistry: RuleRegistryPluginStartContract; apmDataAccess: ApmDataAccessPluginStart; + logsDataAccess: LogsDataAccessPluginStart; } export interface CallWithRequestParams extends estypes.RequestBase { diff --git a/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.test.ts b/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.test.ts index d109669aa90f0..f76a6e82e67d5 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.test.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.test.ts @@ -22,6 +22,7 @@ import { ConditionResult } from './evaluate_condition'; import { InfraBackendLibs } from '../../infra_types'; import { infraPluginMock } from '../../../mocks'; import { logsSharedPluginMock } from '@kbn/logs-shared-plugin/server/mocks'; +import { createLogSourcesServiceMock } from '@kbn/logs-data-access-plugin/common/services/log_sources_service/log_sources_service.mocks'; jest.mock('./evaluate_condition', () => ({ evaluateCondition: jest.fn() })); @@ -115,7 +116,16 @@ const mockLibs = { }, getStartServices: () => [ null, - { logsShared: logsSharedPluginMock.createStartContract() }, + { + logsShared: logsSharedPluginMock.createStartContract(), + logsDataAccess: { + services: { + logSourcesServiceFactory: { + getLogSourcesService: () => createLogSourcesServiceMock(), + }, + }, + }, + }, infraPluginMock.createStartContract(), ], configuration: createMockStaticConfiguration({}), diff --git a/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts b/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts index e6ed1750eea3a..80da1034df5ac 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts @@ -165,9 +165,13 @@ export const createInventoryMetricThresholdExecutor = } const source = await libs.sources.getSourceConfiguration(savedObjectsClient, sourceId); - const [, { logsShared }] = await libs.getStartServices(); + const [, { logsShared, logsDataAccess }] = await libs.getStartServices(); + + const logSourcesService = + logsDataAccess.services.logSourcesServiceFactory.getLogSourcesService(savedObjectsClient); + const logQueryFields: LogQueryFields | undefined = await logsShared.logViews - .getClient(savedObjectsClient, esClient) + .getClient(savedObjectsClient, esClient, logSourcesService) .getResolvedLogView({ type: 'log-view-reference', logViewId: sourceId, diff --git a/x-pack/plugins/observability_solution/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts b/x-pack/plugins/observability_solution/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts index c757f374f8855..0ac06618a3ba2 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts @@ -203,13 +203,16 @@ export const createLogThresholdExecutor = } }; - const [, { logsShared }] = await libs.getStartServices(); + const [, { logsShared, logsDataAccess }] = await libs.getStartServices(); try { const validatedParams = decodeOrThrow(ruleParamsRT)(params); + const logSourcesService = + logsDataAccess.services.logSourcesServiceFactory.getLogSourcesService(savedObjectsClient); + const { indices, timestampField, runtimeMappings } = await logsShared.logViews - .getClient(savedObjectsClient, scopedClusterClient.asCurrentUser) + .getClient(savedObjectsClient, scopedClusterClient.asCurrentUser, logSourcesService) .getResolvedLogView(validatedParams.logView); if (!isRatioRuleParams(validatedParams)) { diff --git a/x-pack/plugins/observability_solution/infra/server/lib/sources/types.ts b/x-pack/plugins/observability_solution/infra/server/lib/sources/types.ts index 22cc5108c35c9..9d32269548632 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/sources/types.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/sources/types.ts @@ -61,9 +61,14 @@ export const logIndexNameSavedObjectReferenceRT = rt.type({ indexName: rt.string, }); +export const kibanaAdvancedSettingSavedObjectReferenceRT = rt.type({ + type: rt.literal('kibana_advanced_setting'), +}); + export const logIndexSavedObjectReferenceRT = rt.union([ logIndexPatternSavedObjectReferenceRT, logIndexNameSavedObjectReferenceRT, + kibanaAdvancedSettingSavedObjectReferenceRT, ]); export const SourceConfigurationSavedObjectAttributesRT = rt.type({ diff --git a/x-pack/plugins/observability_solution/infra/tsconfig.json b/x-pack/plugins/observability_solution/infra/tsconfig.json index 82f125911abef..e0e6750550fbb 100644 --- a/x-pack/plugins/observability_solution/infra/tsconfig.json +++ b/x-pack/plugins/observability_solution/infra/tsconfig.json @@ -106,8 +106,10 @@ "@kbn/presentation-containers", "@kbn/deeplinks-observability", "@kbn/event-annotation-common", + "@kbn/logs-data-access-plugin", "@kbn/core-analytics-browser", "@kbn/observability-alerting-rule-utils", + "@kbn/core-application-browser", "@kbn/shared-ux-page-no-data-types" ], "exclude": ["target/**/*"] diff --git a/x-pack/plugins/observability_solution/logs_data_access/common/constants.ts b/x-pack/plugins/observability_solution/logs_data_access/common/constants.ts index 83acb8bcfff15..c0caaa846f56e 100644 --- a/x-pack/plugins/observability_solution/logs_data_access/common/constants.ts +++ b/x-pack/plugins/observability_solution/logs_data_access/common/constants.ts @@ -5,4 +5,4 @@ * 2.0. */ -export const DEFAULT_LOG_SOURCES = ['logs-*-*']; +export const DEFAULT_LOG_SOURCES = ['logs-*-*,logs-*,filebeat-*,kibana_sample_data_logs*']; diff --git a/x-pack/plugins/observability_solution/logs_data_access/common/services/log_sources_service/log_sources_service.mocks.ts b/x-pack/plugins/observability_solution/logs_data_access/common/services/log_sources_service/log_sources_service.mocks.ts new file mode 100644 index 0000000000000..3f1f8b9db5979 --- /dev/null +++ b/x-pack/plugins/observability_solution/logs_data_access/common/services/log_sources_service/log_sources_service.mocks.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { LogSource, LogSourcesService } from './types'; + +const LOG_SOURCES: LogSource[] = [{ indexPattern: 'logs-*-*' }]; +export const createLogSourcesServiceMock = ( + logSources: LogSource[] = LOG_SOURCES +): LogSourcesService => { + let sources = logSources; + return { + async getLogSources() { + return Promise.resolve(sources); + }, + async setLogSources(nextLogSources: LogSource[]) { + sources = nextLogSources; + return Promise.resolve(); + }, + }; +}; diff --git a/x-pack/plugins/observability_solution/logs_data_access/common/services/log_sources_service/types.ts b/x-pack/plugins/observability_solution/logs_data_access/common/services/log_sources_service/types.ts new file mode 100644 index 0000000000000..0d4cb51051237 --- /dev/null +++ b/x-pack/plugins/observability_solution/logs_data_access/common/services/log_sources_service/types.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. + */ + +export interface LogSource { + indexPattern: string; +} + +export interface LogSourcesService { + getLogSources: () => Promise; + setLogSources: (sources: LogSource[]) => Promise; +} diff --git a/x-pack/plugins/observability_solution/logs_data_access/common/types.ts b/x-pack/plugins/observability_solution/logs_data_access/common/types.ts index d021617f294ae..0600b162e1b3d 100644 --- a/x-pack/plugins/observability_solution/logs_data_access/common/types.ts +++ b/x-pack/plugins/observability_solution/logs_data_access/common/types.ts @@ -5,6 +5,4 @@ * 2.0. */ -export interface LogSource { - indexPattern: string; -} +export * from './services/log_sources_service/types'; diff --git a/x-pack/plugins/observability_solution/logs_data_access/common/ui_settings.ts b/x-pack/plugins/observability_solution/logs_data_access/common/ui_settings.ts index 500011231ee38..97259784971c1 100644 --- a/x-pack/plugins/observability_solution/logs_data_access/common/ui_settings.ts +++ b/x-pack/plugins/observability_solution/logs_data_access/common/ui_settings.ts @@ -23,7 +23,7 @@ export const uiSettings: Record = { value: DEFAULT_LOG_SOURCES, description: i18n.translate('xpack.logsDataAccess.logSourcesDescription', { defaultMessage: - 'Sources to be used for logs data. If the data contained in these indices is not logs data, you may experience degraded functionality.', + 'Sources to be used for logs data. If the data contained in these indices is not logs data, you may experience degraded functionality. Changes to this setting can potentially impact the sources queried in Log Threshold rules.', }), type: 'array', schema: schema.arrayOf(schema.string()), diff --git a/x-pack/plugins/observability_solution/logs_data_access/public/index.ts b/x-pack/plugins/observability_solution/logs_data_access/public/index.ts index ed4a2be8a1b09..bdb45d3d0a490 100644 --- a/x-pack/plugins/observability_solution/logs_data_access/public/index.ts +++ b/x-pack/plugins/observability_solution/logs_data_access/public/index.ts @@ -11,6 +11,9 @@ import { LogsDataAccessPluginSetup, LogsDataAccessPluginStart, } from './plugin'; + +export type { LogsDataAccessPluginSetup, LogsDataAccessPluginStart }; + import { LogsDataAccessPluginSetupDeps, LogsDataAccessPluginStartDeps } from './types'; export const plugin: PluginInitializer< diff --git a/x-pack/plugins/observability_solution/logs_data_access/public/services/log_sources_service/index.ts b/x-pack/plugins/observability_solution/logs_data_access/public/services/log_sources_service/index.ts index 3fd4674ea5509..a75bbd65c26e3 100644 --- a/x-pack/plugins/observability_solution/logs_data_access/public/services/log_sources_service/index.ts +++ b/x-pack/plugins/observability_solution/logs_data_access/public/services/log_sources_service/index.ts @@ -6,23 +6,24 @@ */ import { OBSERVABILITY_LOGS_DATA_ACCESS_LOG_SOURCES_ID } from '@kbn/management-settings-ids'; -import { LogSource } from '../../../common/types'; +import { LogSource, LogSourcesService } from '../../../common/services/log_sources_service/types'; import { RegisterServicesParams } from '../register_services'; -export function createLogSourcesService(params: RegisterServicesParams) { +export function createLogSourcesService(params: RegisterServicesParams): LogSourcesService { const { uiSettings } = params.deps; return { - getLogSources: (): LogSource[] => { + getLogSources: async () => { const logSources = uiSettings.get(OBSERVABILITY_LOGS_DATA_ACCESS_LOG_SOURCES_ID); return logSources.map((logSource) => ({ indexPattern: logSource, })); }, setLogSources: async (sources: LogSource[]) => { - return await uiSettings.set( + await uiSettings.set( OBSERVABILITY_LOGS_DATA_ACCESS_LOG_SOURCES_ID, sources.map((source) => source.indexPattern) ); + return; }, }; } diff --git a/x-pack/plugins/observability_solution/logs_data_access/server/services/get_logs_error_rate_timeseries/get_logs_error_rate_timeseries.ts b/x-pack/plugins/observability_solution/logs_data_access/server/services/get_logs_error_rate_timeseries/get_logs_error_rate_timeseries.ts index 1d80171e7d0b0..ab8ae16ba4455 100644 --- a/x-pack/plugins/observability_solution/logs_data_access/server/services/get_logs_error_rate_timeseries/get_logs_error_rate_timeseries.ts +++ b/x-pack/plugins/observability_solution/logs_data_access/server/services/get_logs_error_rate_timeseries/get_logs_error_rate_timeseries.ts @@ -7,10 +7,10 @@ import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; import type { AggregationOptionsByType, AggregationResultOf } from '@kbn/es-types'; import { ElasticsearchClient } from '@kbn/core/server'; -import { existsQuery, kqlQuery } from '@kbn/observability-plugin/server'; import { estypes } from '@elastic/elasticsearch'; import { getBucketSizeFromTimeRangeAndBucketCount, getLogErrorRate } from '../../utils'; import { LOG_LEVEL } from '../../es_fields'; +import { existsQuery, kqlQuery } from '../../utils/es_queries'; export interface LogsErrorRateTimeseries { esClient: ElasticsearchClient; diff --git a/x-pack/plugins/observability_solution/logs_data_access/server/services/get_logs_rate_timeseries/get_logs_rate_timeseries.ts b/x-pack/plugins/observability_solution/logs_data_access/server/services/get_logs_rate_timeseries/get_logs_rate_timeseries.ts index 49b2d9cb578fb..3341170832a39 100644 --- a/x-pack/plugins/observability_solution/logs_data_access/server/services/get_logs_rate_timeseries/get_logs_rate_timeseries.ts +++ b/x-pack/plugins/observability_solution/logs_data_access/server/services/get_logs_rate_timeseries/get_logs_rate_timeseries.ts @@ -7,10 +7,10 @@ import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; import type { AggregationOptionsByType, AggregationResultOf } from '@kbn/es-types'; import { ElasticsearchClient } from '@kbn/core/server'; -import { existsQuery, kqlQuery } from '@kbn/observability-plugin/server'; import { estypes } from '@elastic/elasticsearch'; import { getBucketSizeFromTimeRangeAndBucketCount } from '../../utils'; import { LOG_LEVEL } from '../../es_fields'; +import { existsQuery, kqlQuery } from '../../utils/es_queries'; export interface LogsRateTimeseries { esClient: ElasticsearchClient; diff --git a/x-pack/plugins/observability_solution/logs_data_access/server/services/log_sources_service/index.ts b/x-pack/plugins/observability_solution/logs_data_access/server/services/log_sources_service/index.ts index c6075d1d20834..e8907d7537932 100644 --- a/x-pack/plugins/observability_solution/logs_data_access/server/services/log_sources_service/index.ts +++ b/x-pack/plugins/observability_solution/logs_data_access/server/services/log_sources_service/index.ts @@ -5,31 +5,40 @@ * 2.0. */ -import { KibanaRequest } from '@kbn/core-http-server'; +import type { KibanaRequest } from '@kbn/core-http-server'; +import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; import { OBSERVABILITY_LOGS_DATA_ACCESS_LOG_SOURCES_ID } from '@kbn/management-settings-ids'; -import { LogSource } from '../../../common/types'; +import { LogSource, LogSourcesService } from '../../../common/services/log_sources_service/types'; import { RegisterServicesParams } from '../register_services'; -export function createGetLogSourcesService(params: RegisterServicesParams) { - return async (request: KibanaRequest) => { - const { savedObjects, uiSettings } = params.deps; - const soClient = savedObjects.getScopedClient(request); - const uiSettingsClient = uiSettings.asScopedToClient(soClient); - return { - getLogSources: async (): Promise => { - const logSources = await uiSettingsClient.get( - OBSERVABILITY_LOGS_DATA_ACCESS_LOG_SOURCES_ID - ); - return logSources.map((logSource) => ({ - indexPattern: logSource, - })); - }, - setLogSources: async (sources: LogSource[]) => { - return await uiSettingsClient.set( - OBSERVABILITY_LOGS_DATA_ACCESS_LOG_SOURCES_ID, - sources.map((source) => source.indexPattern) - ); - }, - }; +export function createLogSourcesServiceFactory(params: RegisterServicesParams) { + return { + async getLogSourcesService( + savedObjectsClient: SavedObjectsClientContract + ): Promise { + const { uiSettings } = params.deps; + const uiSettingsClient = uiSettings.asScopedToClient(savedObjectsClient); + return { + getLogSources: async () => { + const logSources = await uiSettingsClient.get( + OBSERVABILITY_LOGS_DATA_ACCESS_LOG_SOURCES_ID + ); + return logSources.map((logSource) => ({ + indexPattern: logSource, + })); + }, + setLogSources: async (sources: LogSource[]) => { + return await uiSettingsClient.set( + OBSERVABILITY_LOGS_DATA_ACCESS_LOG_SOURCES_ID, + sources.map((source) => source.indexPattern) + ); + }, + }; + }, + async getScopedLogSourcesService(request: KibanaRequest): Promise { + const { savedObjects } = params.deps; + const soClient = savedObjects.getScopedClient(request); + return this.getLogSourcesService(soClient); + }, }; } diff --git a/x-pack/plugins/observability_solution/logs_data_access/server/services/register_services.ts b/x-pack/plugins/observability_solution/logs_data_access/server/services/register_services.ts index 4756bb17f25b1..98c0c95530672 100644 --- a/x-pack/plugins/observability_solution/logs_data_access/server/services/register_services.ts +++ b/x-pack/plugins/observability_solution/logs_data_access/server/services/register_services.ts @@ -11,7 +11,7 @@ import { Logger } from '@kbn/logging'; import { createGetLogsRateTimeseries } from './get_logs_rate_timeseries/get_logs_rate_timeseries'; import { createGetLogErrorRateTimeseries } from './get_logs_error_rate_timeseries/get_logs_error_rate_timeseries'; import { createGetLogsRatesService } from './get_logs_rates_service'; -import { createGetLogSourcesService } from './log_sources_service'; +import { createLogSourcesServiceFactory } from './log_sources_service'; export interface RegisterServicesParams { logger: Logger; @@ -26,6 +26,6 @@ export function registerServices(params: RegisterServicesParams) { getLogsRatesService: createGetLogsRatesService(), getLogsRateTimeseries: createGetLogsRateTimeseries(), getLogsErrorRateTimeseries: createGetLogErrorRateTimeseries(), - getLogSourcesService: createGetLogSourcesService(params), + logSourcesServiceFactory: createLogSourcesServiceFactory(params), }; } diff --git a/x-pack/plugins/observability_solution/logs_data_access/server/utils/es_queries.ts b/x-pack/plugins/observability_solution/logs_data_access/server/utils/es_queries.ts new file mode 100644 index 0000000000000..282b21af495c9 --- /dev/null +++ b/x-pack/plugins/observability_solution/logs_data_access/server/utils/es_queries.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 { estypes } from '@elastic/elasticsearch'; +import { QueryDslQueryContainer } from '@kbn/data-views-plugin/common/types'; +import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; + +export function existsQuery(field: string): QueryDslQueryContainer[] { + return [{ exists: { field } }]; +} + +export function kqlQuery(kql?: string): estypes.QueryDslQueryContainer[] { + if (!kql) { + return []; + } + + const ast = fromKueryExpression(kql); + return [toElasticsearchQuery(ast)]; +} diff --git a/x-pack/plugins/observability_solution/logs_data_access/tsconfig.json b/x-pack/plugins/observability_solution/logs_data_access/tsconfig.json index 09a57dea36e49..45c96d862d9ff 100644 --- a/x-pack/plugins/observability_solution/logs_data_access/tsconfig.json +++ b/x-pack/plugins/observability_solution/logs_data_access/tsconfig.json @@ -9,7 +9,6 @@ "@kbn/core", "@kbn/data-plugin", "@kbn/data-views-plugin", - "@kbn/observability-plugin", "@kbn/calculate-auto", "@kbn/es-types", "@kbn/core-http-server", @@ -20,6 +19,8 @@ "@kbn/core-saved-objects-server", "@kbn/core-ui-settings-server", "@kbn/core-ui-settings-browser", - "@kbn/logging" + "@kbn/logging", + "@kbn/core-saved-objects-api-server", + "@kbn/es-query" ] } diff --git a/x-pack/plugins/observability_solution/logs_shared/common/index.ts b/x-pack/plugins/observability_solution/logs_shared/common/index.ts index f6b1e9ea27e43..104b68ce2fddf 100644 --- a/x-pack/plugins/observability_solution/logs_shared/common/index.ts +++ b/x-pack/plugins/observability_solution/logs_shared/common/index.ts @@ -17,12 +17,14 @@ export { logViewReferenceRT, persistedLogViewReferenceRT, defaultLogViewAttributes, + logSourcesKibanaAdvancedSettingRT, } from './log_views'; // LogView types export type { LogDataViewReference, LogIndexNameReference, + LogSourcesKibanaAdvancedSettingReference, LogIndexReference, LogView, LogViewAttributes, diff --git a/x-pack/plugins/observability_solution/logs_shared/common/log_views/resolved_log_view.mock.ts b/x-pack/plugins/observability_solution/logs_shared/common/log_views/resolved_log_view.mock.ts index 8c09f16e3b53e..568a25ecef1d7 100644 --- a/x-pack/plugins/observability_solution/logs_shared/common/log_views/resolved_log_view.mock.ts +++ b/x-pack/plugins/observability_solution/logs_shared/common/log_views/resolved_log_view.mock.ts @@ -11,6 +11,7 @@ import { defaultLogViewsStaticConfig } from './defaults'; import { ResolvedLogView, resolveLogView } from './resolved_log_view'; import { LogViewAttributes } from './types'; import { DataViewSpec } from '@kbn/data-views-plugin/common'; +import { createLogSourcesServiceMock } from '@kbn/logs-data-access-plugin/common/services/log_sources_service/log_sources_service.mocks'; export const createResolvedLogViewMock = ( resolvedLogViewOverrides: Partial = {} @@ -63,5 +64,6 @@ export const createResolvedLogViewMockFromAttributes = (logViewAttributes: LogVi spec, }), } as unknown as DataViewsContract, + createLogSourcesServiceMock(), defaultLogViewsStaticConfig ); diff --git a/x-pack/plugins/observability_solution/logs_shared/common/log_views/resolved_log_view.ts b/x-pack/plugins/observability_solution/logs_shared/common/log_views/resolved_log_view.ts index b9419fbd51f22..1521aa67e3d92 100644 --- a/x-pack/plugins/observability_solution/logs_shared/common/log_views/resolved_log_view.ts +++ b/x-pack/plugins/observability_solution/logs_shared/common/log_views/resolved_log_view.ts @@ -7,6 +7,7 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { DataView, DataViewsContract, FieldSpec } from '@kbn/data-views-plugin/common'; +import { LogSourcesService } from '@kbn/logs-data-access-plugin/common/services/log_sources_service/types'; import { TIEBREAKER_FIELD, TIMESTAMP_FIELD } from '../constants'; import { defaultLogViewsStaticConfig } from './defaults'; import { ResolveLogViewError } from './errors'; @@ -31,12 +32,20 @@ export const resolveLogView = ( logViewId: string, logViewAttributes: LogViewAttributes, dataViewsService: DataViewsContract, + logSourcesService: LogSourcesService, config: LogViewsStaticConfig ): Promise => { if (logViewAttributes.logIndices.type === 'index_name') { return resolveLegacyReference(logViewId, logViewAttributes, dataViewsService, config); - } else { + } else if (logViewAttributes.logIndices.type === 'data_view') { return resolveDataViewReference(logViewAttributes, dataViewsService); + } else { + return resolveKibanaAdvancedSettingReference( + logViewId, + logViewAttributes, + dataViewsService, + logSourcesService + ); } }; @@ -110,6 +119,52 @@ const resolveDataViewReference = async ( }; }; +const resolveKibanaAdvancedSettingReference = async ( + logViewId: string, + logViewAttributes: LogViewAttributes, + dataViewsService: DataViewsContract, + logSourcesService: LogSourcesService +): Promise => { + if (logViewAttributes.logIndices.type !== 'kibana_advanced_setting') { + throw new Error( + 'This function can only resolve references to the Log Sources Kibana advanced setting' + ); + } + + const indices = (await logSourcesService.getLogSources()) + .map((logSource) => logSource.indexPattern) + .join(','); + + const dataViewReference = await dataViewsService + .create( + { + id: `log-view-${logViewId}`, + name: logViewAttributes.name, + title: indices, + timeFieldName: TIMESTAMP_FIELD, + allowNoIndex: true, + }, + false, + false + ) + .catch((error) => { + throw new ResolveLogViewError(`Failed to create Data View reference: ${error}`, error); + }); + + return { + indices, + timestampField: TIMESTAMP_FIELD, + tiebreakerField: TIEBREAKER_FIELD, + messageField: ['message'], + fields: dataViewReference.fields, + runtimeMappings: {}, + columns: logViewAttributes.logColumns, + name: logViewAttributes.name, + description: logViewAttributes.description, + dataViewReference, + }; +}; + // this might take other sources of runtime fields into account in the future const resolveRuntimeMappings = (dataView: DataView): estypes.MappingRuntimeFields => { return dataView.getRuntimeMappings(); diff --git a/x-pack/plugins/observability_solution/logs_shared/common/log_views/types.ts b/x-pack/plugins/observability_solution/logs_shared/common/log_views/types.ts index f94601f9e0f84..6cc8d60191a34 100644 --- a/x-pack/plugins/observability_solution/logs_shared/common/log_views/types.ts +++ b/x-pack/plugins/observability_solution/logs_shared/common/log_views/types.ts @@ -38,7 +38,20 @@ export const logIndexNameReferenceRT = rt.type({ }); export type LogIndexNameReference = rt.TypeOf; -export const logIndexReferenceRT = rt.union([logDataViewReferenceRT, logIndexNameReferenceRT]); +// Kibana advanced setting +export const logSourcesKibanaAdvancedSettingRT = rt.type({ + type: rt.literal('kibana_advanced_setting'), +}); + +export type LogSourcesKibanaAdvancedSettingReference = rt.TypeOf< + typeof logSourcesKibanaAdvancedSettingRT +>; + +export const logIndexReferenceRT = rt.union([ + logDataViewReferenceRT, + logIndexNameReferenceRT, + logSourcesKibanaAdvancedSettingRT, +]); export type LogIndexReference = rt.TypeOf; const logViewCommonColumnConfigurationRT = rt.strict({ diff --git a/x-pack/plugins/observability_solution/logs_shared/kibana.jsonc b/x-pack/plugins/observability_solution/logs_shared/kibana.jsonc index 7e79614b56e5a..ea93fd326dac7 100644 --- a/x-pack/plugins/observability_solution/logs_shared/kibana.jsonc +++ b/x-pack/plugins/observability_solution/logs_shared/kibana.jsonc @@ -14,7 +14,8 @@ "discoverShared", "usageCollection", "observabilityShared", - "share" + "share", + "logsDataAccess" ], "optionalPlugins": [ "observabilityAIAssistant", diff --git a/x-pack/plugins/observability_solution/logs_shared/public/components/log_stream/log_stream.tsx b/x-pack/plugins/observability_solution/logs_shared/public/components/log_stream/log_stream.tsx index 5ba062912d8f3..f0c9c411249a9 100644 --- a/x-pack/plugins/observability_solution/logs_shared/public/components/log_stream/log_stream.tsx +++ b/x-pack/plugins/observability_solution/logs_shared/public/components/log_stream/log_stream.tsx @@ -15,6 +15,7 @@ import { JsonValue } from '@kbn/utility-types'; import { noop } from 'lodash'; import React, { useCallback, useEffect, useMemo } from 'react'; import usePrevious from 'react-use/lib/usePrevious'; +import type { LogsDataAccessPluginStart } from '@kbn/logs-data-access-plugin/public'; import { LogEntryCursor } from '../../../common/log_entry'; import { defaultLogViewsStaticConfig, LogViewReference } from '../../../common/log_views'; import { BuiltEsQuery, useLogStream } from '../../containers/logs/log_stream'; @@ -28,6 +29,7 @@ import { LogStreamErrorBoundary } from './log_stream_error_boundary'; interface LogStreamPluginDeps { data: DataPublicPluginStart; + logsDataAccess: LogsDataAccessPluginStart; http: HttpStart; share: SharePluginStart; } @@ -113,9 +115,9 @@ export const LogStreamContent = ({ ); const { - services: { http, data, share }, + services: { http, data, share, logsDataAccess }, } = useKibana(); - if (http == null || data == null || share == null) { + if (http == null || data == null || share == null || logsDataAccess == null) { throw new Error( ` cannot access kibana core services. @@ -130,8 +132,15 @@ Read more at https://github.com/elastic/kibana/blob/main/src/plugins/kibana_reac const kibanaQuerySettings = useKibanaQuerySettings(); const logViews = useMemo( - () => new LogViewsClient(data.dataViews, http, data.search.search, defaultLogViewsStaticConfig), - [data.dataViews, data.search.search, http] + () => + new LogViewsClient( + data.dataViews, + logsDataAccess.services.logSourcesService, + http, + data.search.search, + defaultLogViewsStaticConfig + ), + [data.dataViews, data.search.search, http, logsDataAccess.services.logSourcesService] ); const { diff --git a/x-pack/plugins/observability_solution/logs_shared/public/plugin.ts b/x-pack/plugins/observability_solution/logs_shared/public/plugin.ts index 4655a7a62f087..d6f4ac81fe266 100644 --- a/x-pack/plugins/observability_solution/logs_shared/public/plugin.ts +++ b/x-pack/plugins/observability_solution/logs_shared/public/plugin.ts @@ -52,11 +52,12 @@ export class LogsSharedPlugin implements LogsSharedClientPluginClass { public start(core: CoreStart, plugins: LogsSharedClientStartDeps) { const { http } = core; - const { data, dataViews, discoverShared, observabilityAIAssistant } = plugins; + const { data, dataViews, discoverShared, observabilityAIAssistant, logsDataAccess } = plugins; const logViews = this.logViews.start({ http, dataViews, + logSourcesService: logsDataAccess.services.logSourcesService, search: data.search, }); diff --git a/x-pack/plugins/observability_solution/logs_shared/public/services/log_views/log_views_client.ts b/x-pack/plugins/observability_solution/logs_shared/public/services/log_views/log_views_client.ts index a53ac542c5f03..b1a71cea73cb1 100644 --- a/x-pack/plugins/observability_solution/logs_shared/public/services/log_views/log_views_client.ts +++ b/x-pack/plugins/observability_solution/logs_shared/public/services/log_views/log_views_client.ts @@ -10,6 +10,7 @@ import { HttpStart } from '@kbn/core/public'; import type { ISearchGeneric } from '@kbn/search-types'; import { DataViewsContract } from '@kbn/data-views-plugin/public'; import { lastValueFrom } from 'rxjs'; +import { LogSourcesService } from '@kbn/logs-data-access-plugin/common/types'; import { getLogViewResponsePayloadRT, putLogViewRequestPayloadRT } from '../../../common/http_api'; import { getLogViewUrl } from '../../../common/http_api/log_views'; import { @@ -31,6 +32,7 @@ import { ILogViewsClient } from './types'; export class LogViewsClient implements ILogViewsClient { constructor( private readonly dataViews: DataViewsContract, + private readonly logSourcesService: LogSourcesService, private readonly http: HttpStart, private readonly search: ISearchGeneric, private readonly config: LogViewsStaticConfig @@ -152,7 +154,13 @@ export class LogViewsClient implements ILogViewsClient { logViewId: string, logViewAttributes: LogViewAttributes ): Promise { - return await resolveLogView(logViewId, logViewAttributes, this.dataViews, this.config); + return await resolveLogView( + logViewId, + logViewAttributes, + this.dataViews, + this.logSourcesService, + this.config + ); } } diff --git a/x-pack/plugins/observability_solution/logs_shared/public/services/log_views/log_views_service.ts b/x-pack/plugins/observability_solution/logs_shared/public/services/log_views/log_views_service.ts index 712196c95205c..66ddfde911176 100644 --- a/x-pack/plugins/observability_solution/logs_shared/public/services/log_views/log_views_service.ts +++ b/x-pack/plugins/observability_solution/logs_shared/public/services/log_views/log_views_service.ts @@ -20,8 +20,19 @@ export class LogViewsService { }; } - public start({ dataViews, http, search }: LogViewsServiceStartDeps): LogViewsServiceStart { - const client = new LogViewsClient(dataViews, http, search.search, this.logViewsStaticConfig); + public start({ + dataViews, + http, + search, + logSourcesService, + }: LogViewsServiceStartDeps): LogViewsServiceStart { + const client = new LogViewsClient( + dataViews, + logSourcesService, + http, + search.search, + this.logViewsStaticConfig + ); return { client, diff --git a/x-pack/plugins/observability_solution/logs_shared/public/services/log_views/types.ts b/x-pack/plugins/observability_solution/logs_shared/public/services/log_views/types.ts index 58a504be789bc..a6d516f00669d 100644 --- a/x-pack/plugins/observability_solution/logs_shared/public/services/log_views/types.ts +++ b/x-pack/plugins/observability_solution/logs_shared/public/services/log_views/types.ts @@ -8,6 +8,7 @@ import { HttpStart } from '@kbn/core/public'; import { ISearchStart } from '@kbn/data-plugin/public'; import { DataViewsContract } from '@kbn/data-views-plugin/public'; +import { LogSourcesService } from '@kbn/logs-data-access-plugin/common/types'; import { LogView, LogViewAttributes, @@ -29,6 +30,7 @@ export interface LogViewsServiceStartDeps { dataViews: DataViewsContract; http: HttpStart; search: ISearchStart; + logSourcesService: LogSourcesService; } export interface ILogViewsClient { diff --git a/x-pack/plugins/observability_solution/logs_shared/public/types.ts b/x-pack/plugins/observability_solution/logs_shared/public/types.ts index 9f0d344294880..58b180ee8b6ef 100644 --- a/x-pack/plugins/observability_solution/logs_shared/public/types.ts +++ b/x-pack/plugins/observability_solution/logs_shared/public/types.ts @@ -9,6 +9,7 @@ import type { CoreSetup, CoreStart, Plugin as PluginClass } from '@kbn/core/publ import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import type { DiscoverSharedPublicStart } from '@kbn/discover-shared-plugin/public'; +import { LogsDataAccessPluginStart } from '@kbn/logs-data-access-plugin/public'; import type { ObservabilityAIAssistantPublicStart } from '@kbn/observability-ai-assistant-plugin/public'; import type { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public'; import type { UiActionsStart } from '@kbn/ui-actions-plugin/public'; @@ -37,6 +38,7 @@ export interface LogsSharedClientStartDeps { data: DataPublicPluginStart; dataViews: DataViewsPublicPluginStart; discoverShared: DiscoverSharedPublicStart; + logsDataAccess: LogsDataAccessPluginStart; observabilityAIAssistant?: ObservabilityAIAssistantPublicStart; share: SharePluginStart; uiActions: UiActionsStart; diff --git a/x-pack/plugins/observability_solution/logs_shared/server/lib/domains/log_entries_domain/log_entries_domain.ts b/x-pack/plugins/observability_solution/logs_shared/server/lib/domains/log_entries_domain/log_entries_domain.ts index 2601167f2d988..f3cbfb57b09c4 100644 --- a/x-pack/plugins/observability_solution/logs_shared/server/lib/domains/log_entries_domain/log_entries_domain.ts +++ b/x-pack/plugins/observability_solution/logs_shared/server/lib/domains/log_entries_domain/log_entries_domain.ts @@ -172,10 +172,13 @@ export class LogsSharedLogEntriesDomain implements ILogsSharedLogEntriesDomain { params: LogEntriesParams, columnOverrides?: LogViewColumnConfiguration[] ): Promise<{ entries: LogEntry[]; hasMoreBefore?: boolean; hasMoreAfter?: boolean }> { - const [, , { logViews }] = await this.libs.getStartServices(); + const [, { logsDataAccess }, { logViews }] = await this.libs.getStartServices(); const { savedObjects, elasticsearch } = await requestContext.core; + const logSourcesService = logsDataAccess.services.logSourcesServiceFactory.getLogSourcesService( + savedObjects.client + ); const resolvedLogView = await logViews - .getClient(savedObjects.client, elasticsearch.client.asCurrentUser) + .getClient(savedObjects.client, elasticsearch.client.asCurrentUser, logSourcesService) .getResolvedLogView(logView); const columnDefinitions = columnOverrides ?? resolvedLogView.columns; @@ -232,11 +235,15 @@ export class LogsSharedLogEntriesDomain implements ILogsSharedLogEntriesDomain { bucketSize: number, filterQuery?: LogEntryQuery ): Promise { - const [, , { logViews }] = await this.libs.getStartServices(); + const [, { logsDataAccess }, { logViews }] = await this.libs.getStartServices(); const { savedObjects, elasticsearch } = await requestContext.core; + const logSourcesService = logsDataAccess.services.logSourcesServiceFactory.getLogSourcesService( + savedObjects.client + ); const resolvedLogView = await logViews - .getClient(savedObjects.client, elasticsearch.client.asCurrentUser) + .getClient(savedObjects.client, elasticsearch.client.asCurrentUser, logSourcesService) .getResolvedLogView(logView); + const dateRangeBuckets = await this.adapter.getContainedLogSummaryBuckets( requestContext, resolvedLogView, @@ -257,11 +264,16 @@ export class LogsSharedLogEntriesDomain implements ILogsSharedLogEntriesDomain { highlightQueries: string[], filterQuery?: LogEntryQuery ): Promise { - const [, , { logViews }] = await this.libs.getStartServices(); + const [, { logsDataAccess }, { logViews }] = await this.libs.getStartServices(); const { savedObjects, elasticsearch } = await requestContext.core; + const logSourcesService = logsDataAccess.services.logSourcesServiceFactory.getLogSourcesService( + savedObjects.client + ); + const resolvedLogView = await logViews - .getClient(savedObjects.client, elasticsearch.client.asCurrentUser) + .getClient(savedObjects.client, elasticsearch.client.asCurrentUser, logSourcesService) .getResolvedLogView(logView); + const messageFormattingRules = compileFormattingRules( getBuiltinRules(resolvedLogView.messageField) ); diff --git a/x-pack/plugins/observability_solution/logs_shared/server/plugin.ts b/x-pack/plugins/observability_solution/logs_shared/server/plugin.ts index 7e3fad84e4c30..6bc9560764a7b 100644 --- a/x-pack/plugins/observability_solution/logs_shared/server/plugin.ts +++ b/x-pack/plugins/observability_solution/logs_shared/server/plugin.ts @@ -97,6 +97,7 @@ export class LogsSharedPlugin const logViews = this.logViews.start({ savedObjects: core.savedObjects, dataViews: plugins.dataViews, + logsDataAccess: plugins.logsDataAccess, elasticsearch: core.elasticsearch, }); diff --git a/x-pack/plugins/observability_solution/logs_shared/server/saved_objects/log_view/types.ts b/x-pack/plugins/observability_solution/logs_shared/server/saved_objects/log_view/types.ts index fb8bf49781a2d..341e9bbed608b 100644 --- a/x-pack/plugins/observability_solution/logs_shared/server/saved_objects/log_view/types.ts +++ b/x-pack/plugins/observability_solution/logs_shared/server/saved_objects/log_view/types.ts @@ -19,9 +19,14 @@ export const logIndexNameSavedObjectReferenceRT = rt.type({ indexName: rt.string, }); +export const logSourcesKibanaAdvancedSettingSavedObjectRT = rt.type({ + type: rt.literal('kibana_advanced_setting'), +}); + export const logIndexSavedObjectReferenceRT = rt.union([ logDataViewSavedObjectReferenceRT, logIndexNameSavedObjectReferenceRT, + logSourcesKibanaAdvancedSettingSavedObjectRT, ]); const logViewSavedObjectCommonColumnConfigurationRT = rt.strict({ diff --git a/x-pack/plugins/observability_solution/logs_shared/server/services/log_views/log_views_client.test.ts b/x-pack/plugins/observability_solution/logs_shared/server/services/log_views/log_views_client.test.ts index a1175432a4ac7..f6df48b22ba7e 100644 --- a/x-pack/plugins/observability_solution/logs_shared/server/services/log_views/log_views_client.test.ts +++ b/x-pack/plugins/observability_solution/logs_shared/server/services/log_views/log_views_client.test.ts @@ -22,6 +22,7 @@ import { logViewSavedObjectName, } from '../../saved_objects/log_view'; import { LogViewsClient } from './log_views_client'; +import { createLogSourcesServiceMock } from '@kbn/logs-data-access-plugin/common/services/log_sources_service/log_sources_service.mocks'; describe('LogViewsClient class', () => { it('getLogView resolves the default id to a real saved object id if it exists', async () => { @@ -341,6 +342,7 @@ describe('LogViewsClient class', () => { const createLogViewsClient = () => { const logger = loggerMock.create(); const dataViews = dataViewsServiceMock; + const logSourcesService = createLogSourcesServiceMock(); const savedObjectsClient = savedObjectsClientMock.create(); const logViewFallbackHandler = jest.fn(); const internalLogViews = new Map(); @@ -351,6 +353,7 @@ const createLogViewsClient = () => { const logViewsClient = new LogViewsClient( logger, Promise.resolve(dataViews), + Promise.resolve(logSourcesService), savedObjectsClient, logViewFallbackHandler, internalLogViews, diff --git a/x-pack/plugins/observability_solution/logs_shared/server/services/log_views/log_views_client.ts b/x-pack/plugins/observability_solution/logs_shared/server/services/log_views/log_views_client.ts index 0b9bfe9febf4a..cea43f02b358d 100644 --- a/x-pack/plugins/observability_solution/logs_shared/server/services/log_views/log_views_client.ts +++ b/x-pack/plugins/observability_solution/logs_shared/server/services/log_views/log_views_client.ts @@ -13,6 +13,7 @@ import { SavedObjectsUtils, SavedObjectsErrorHelpers, } from '@kbn/core/server'; +import { LogSourcesService } from '@kbn/logs-data-access-plugin/common/types'; import { defaultLogViewAttributes, defaultLogViewId, @@ -44,6 +45,7 @@ export class LogViewsClient implements ILogViewsClient { constructor( private readonly logger: Logger, private readonly dataViews: DataViewsService, + private readonly logSourcesService: Promise, private readonly savedObjectsClient: SavedObjectsClientContract, private readonly logViewFallbackHandler: LogViewFallbackHandler, private readonly internalLogViews: Map, @@ -117,7 +119,13 @@ export class LogViewsClient implements ILogViewsClient { logViewId: string, logViewAttributes: LogViewAttributes ): Promise { - return await resolveLogView(logViewId, logViewAttributes, await this.dataViews, this.config); + return await resolveLogView( + logViewId, + logViewAttributes, + await this.dataViews, + await this.logSourcesService, + this.config + ); } private async getSavedLogView(logViewId: string): Promise { diff --git a/x-pack/plugins/observability_solution/logs_shared/server/services/log_views/log_views_service.mock.ts b/x-pack/plugins/observability_solution/logs_shared/server/services/log_views/log_views_service.mock.ts index 295b1fd77452f..11fbe43a6b9cb 100644 --- a/x-pack/plugins/observability_solution/logs_shared/server/services/log_views/log_views_service.mock.ts +++ b/x-pack/plugins/observability_solution/logs_shared/server/services/log_views/log_views_service.mock.ts @@ -15,8 +15,9 @@ export const createLogViewsServiceSetupMock = (): jest.Mocked => ({ - getClient: jest.fn((_savedObjectsClient: any, _elasticsearchClient: any) => - createLogViewsClientMock() + getClient: jest.fn( + (_savedObjectsClient: any, _elasticsearchClient: any, _logSourcesService: any) => + createLogViewsClientMock() ), getScopedClient: jest.fn((_request: any) => createLogViewsClientMock()), }); diff --git a/x-pack/plugins/observability_solution/logs_shared/server/services/log_views/log_views_service.ts b/x-pack/plugins/observability_solution/logs_shared/server/services/log_views/log_views_service.ts index 5479c16dff411..2f429dc7612ff 100644 --- a/x-pack/plugins/observability_solution/logs_shared/server/services/log_views/log_views_service.ts +++ b/x-pack/plugins/observability_solution/logs_shared/server/services/log_views/log_views_service.ts @@ -11,6 +11,7 @@ import { Logger, SavedObjectsClientContract, } from '@kbn/core/server'; +import { LogSourcesService } from '@kbn/logs-data-access-plugin/common/types'; import { defaultLogViewAttributes, defaultLogViewsStaticConfig, @@ -56,6 +57,7 @@ export class LogViewsService { public start({ dataViews, + logsDataAccess, elasticsearch, savedObjects, }: LogViewsServiceStartDeps): LogViewsServiceStart { @@ -65,11 +67,13 @@ export class LogViewsService { getClient( savedObjectsClient: SavedObjectsClientContract, elasticsearchClient: ElasticsearchClient, + logSourcesService: Promise, request?: KibanaRequest ) { return new LogViewsClient( logger, dataViews.dataViewsServiceFactory(savedObjectsClient, elasticsearchClient, request), + logSourcesService, savedObjectsClient, logViewFallbackHandler, internalLogViews, @@ -79,8 +83,9 @@ export class LogViewsService { getScopedClient(request: KibanaRequest) { const savedObjectsClient = savedObjects.getScopedClient(request); const elasticsearchClient = elasticsearch.client.asScoped(request).asCurrentUser; - - return this.getClient(savedObjectsClient, elasticsearchClient, request); + const logSourcesService = + logsDataAccess.services.logSourcesServiceFactory.getScopedLogSourcesService(request); + return this.getClient(savedObjectsClient, elasticsearchClient, logSourcesService, request); }, }; } diff --git a/x-pack/plugins/observability_solution/logs_shared/server/services/log_views/types.ts b/x-pack/plugins/observability_solution/logs_shared/server/services/log_views/types.ts index b0aa2784e4cd2..becf32881acf8 100644 --- a/x-pack/plugins/observability_solution/logs_shared/server/services/log_views/types.ts +++ b/x-pack/plugins/observability_solution/logs_shared/server/services/log_views/types.ts @@ -13,6 +13,8 @@ import { SavedObjectsServiceStart, } from '@kbn/core/server'; import { PluginStart as DataViewsServerPluginStart } from '@kbn/data-views-plugin/server'; +import { LogSourcesService } from '@kbn/logs-data-access-plugin/common/types'; +import { LogsDataAccessPluginStart } from '@kbn/logs-data-access-plugin/server'; import { LogView, LogViewAttributes, @@ -23,6 +25,7 @@ import { export interface LogViewsServiceStartDeps { dataViews: DataViewsServerPluginStart; + logsDataAccess: LogsDataAccessPluginStart; elasticsearch: ElasticsearchServiceStart; savedObjects: SavedObjectsServiceStart; } @@ -45,6 +48,7 @@ export interface LogViewsServiceStart { getClient( savedObjectsClient: SavedObjectsClientContract, elasticsearchClient: ElasticsearchClient, + logSourcesService: Promise, request?: KibanaRequest ): ILogViewsClient; getScopedClient(request: KibanaRequest): ILogViewsClient; diff --git a/x-pack/plugins/observability_solution/logs_shared/server/types.ts b/x-pack/plugins/observability_solution/logs_shared/server/types.ts index 2e922eceeb183..73365ece21a14 100644 --- a/x-pack/plugins/observability_solution/logs_shared/server/types.ts +++ b/x-pack/plugins/observability_solution/logs_shared/server/types.ts @@ -11,6 +11,7 @@ import { PluginStart as DataPluginStart, } from '@kbn/data-plugin/server'; import { PluginStart as DataViewsPluginStart } from '@kbn/data-views-plugin/server'; +import { LogsDataAccessPluginStart } from '@kbn/logs-data-access-plugin/server'; import { LogsSharedDomainLibs } from './lib/logs_shared_types'; import { LogViewsServiceSetup, LogViewsServiceStart } from './services/log_views/types'; @@ -36,6 +37,7 @@ export interface LogsSharedServerPluginSetupDeps { export interface LogsSharedServerPluginStartDeps { data: DataPluginStart; dataViews: DataViewsPluginStart; + logsDataAccess: LogsDataAccessPluginStart; } export interface UsageCollector { diff --git a/x-pack/plugins/observability_solution/logs_shared/tsconfig.json b/x-pack/plugins/observability_solution/logs_shared/tsconfig.json index d826cd78618c5..f1bb2527f9311 100644 --- a/x-pack/plugins/observability_solution/logs_shared/tsconfig.json +++ b/x-pack/plugins/observability_solution/logs_shared/tsconfig.json @@ -41,5 +41,6 @@ "@kbn/react-kibana-context-theme", "@kbn/test-jest-helpers", "@kbn/router-utils", + "@kbn/logs-data-access-plugin", ] } diff --git a/x-pack/test/functional/apps/infra/logs/logs_source_configuration.ts b/x-pack/test/functional/apps/infra/logs/logs_source_configuration.ts index 358095e4350b8..22ed2bd035ee1 100644 --- a/x-pack/test/functional/apps/infra/logs/logs_source_configuration.ts +++ b/x-pack/test/functional/apps/infra/logs/logs_source_configuration.ts @@ -76,6 +76,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await pageObjects.header.waitUntilLoadingHasFinished(); + await infraSourceConfigurationForm.selectIndicesPanel(); + const nameInput = await infraSourceConfigurationForm.getNameInput(); await nameInput.clearValueWithKeyboard({ charByChar: true }); await nameInput.type('Modified Source'); diff --git a/x-pack/test/functional/services/infra_source_configuration_form.ts b/x-pack/test/functional/services/infra_source_configuration_form.ts index da39347c36389..c8d28e0e3656b 100644 --- a/x-pack/test/functional/services/infra_source_configuration_form.ts +++ b/x-pack/test/functional/services/infra_source_configuration_form.ts @@ -30,7 +30,9 @@ export function InfraSourceConfigurationFormProvider({ async getMetricIndicesInput(): Promise { return await testSubjects.findDescendant('~metricIndicesInput', await this.getForm()); }, - + async selectIndicesPanel(): Promise { + return await testSubjects.click('logIndicesCheckableCard'); + }, /** * Logs */ From 5926f64492dc0c8018a0ac63bf57b0b03d38b0cd Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Wed, 7 Aug 2024 13:05:46 +0200 Subject: [PATCH 03/44] [Obs ai assistant][ES|QL] Initial render of the ES|QL table in a more compact format (#190027) ## Summary Nothing super crucial, it has to do with the initial render of the ES|QL datable. In order to be aligned with the table we render in the flyout and minimize the space it takes, we initialize in single row. The users can still change the row height from the settings. It is actually this: image instead of: image The users can always adapt the settings: image --- .../public/functions/visualize_esql.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/functions/visualize_esql.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/functions/visualize_esql.tsx index e81e30b0beec7..404ff9e32a4db 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/functions/visualize_esql.tsx +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/functions/visualize_esql.tsx @@ -353,6 +353,7 @@ export function VisualizeESQL({ query={{ esql: query }} flyoutType="overlay" isTableView + initialRowHeight={0} /> ) : (
)} From 087ff58f661f703590a6061f4afad0391666fc67 Mon Sep 17 00:00:00 2001 From: Miriam <31922082+MiriamAparicio@users.noreply.github.com> Date: Wed, 7 Aug 2024 13:18:20 +0100 Subject: [PATCH 04/44] [ObsUX] [Infra] Create new formula for CPU Usage metric (#189261) Closes https://github.com/elastic/kibana/issues/188634 #### What was done - Create a new formula to compute the CPU Usage metric: average(system.cpu.total.norm.pct) - Keep backward compatibility for alerts and update UI - Inventory UI and Inventory alert rule shows both old and new CPU Usage metrics Screenshot 2024-08-01 at 13 42 08 Screenshot 2024-08-01 at 13 41 53 Screenshot 2024-08-01 at 13 44 20 - Hosts View/Asset Details shows only the new CPU Usage metric Screenshot 2024-08-01 at 13 45 10 Screenshot 2024-08-01 at 13 42 35 - Alerts are fired for both CPU Usage metrics Screenshot 2024-08-01 at 13 45 27 --------- Co-authored-by: Carlos Crespo Co-authored-by: Carlos Crespo Co-authored-by: Jenny --- .../formatters/snapshot_metric_formats.ts | 4 + .../http_api/infra/get_infra_metrics.ts | 1 + .../infra/common/inventory_views/defaults.ts | 2 +- .../infra/common/snapshot_metric_i18n.ts | 16 +++- .../inventory/components/expression.tsx | 1 + .../inventory/components/expression_chart.tsx | 1 + .../hooks/use_inventory_alert_prefill.ts | 2 +- .../common/visualizations/translations.ts | 2 +- .../hosts/hooks/use_hosts_table.test.ts | 8 +- .../metrics/hosts/hooks/use_hosts_table.tsx | 4 +- .../metrics/hosts/hooks/use_hosts_view.ts | 2 +- .../conditional_tooltip.test.tsx.snap | 16 ++++ .../waffle/conditional_tooltip.test.tsx | 10 +- .../hooks/use_waffle_options.ts | 2 +- .../lib/create_inventory_metric_formatter.ts | 4 + .../lib/convert_metric_value.ts | 1 + .../routes/infra/lib/host/get_all_hosts.ts | 4 +- .../common/inventory_models/host/index.ts | 3 +- .../host/metrics/formulas/cpu.ts | 2 +- .../inventory_models/host/metrics/index.ts | 2 +- .../host/metrics/snapshot/cpu_total.ts | 16 ++++ .../host/metrics/snapshot/index.ts | 2 + .../common/inventory_models/types.ts | 2 + .../translations/translations/fr-FR.json | 1 - .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - .../api_integration/apis/metrics_ui/infra.ts | 17 ++-- .../test/functional/apps/infra/home_page.ts | 13 ++- .../test/functional/apps/infra/hosts_view.ts | 92 +++++++++---------- .../functional/apps/infra/node_details.ts | 2 +- .../page_objects/infra_home_page.ts | 2 +- .../page_objects/infra_hosts_view.ts | 10 +- 32 files changed, 152 insertions(+), 94 deletions(-) create mode 100644 x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/snapshot/cpu_total.ts diff --git a/x-pack/plugins/observability_solution/infra/common/formatters/snapshot_metric_formats.ts b/x-pack/plugins/observability_solution/infra/common/formatters/snapshot_metric_formats.ts index 72c68d47e9776..8ca585709db21 100644 --- a/x-pack/plugins/observability_solution/infra/common/formatters/snapshot_metric_formats.ts +++ b/x-pack/plugins/observability_solution/infra/common/formatters/snapshot_metric_formats.ts @@ -29,6 +29,10 @@ export const METRIC_FORMATTERS: MetricFormatters = { formatter: InfraFormatterType.percent, template: '{{value}}', }, + ['cpuTotal']: { + formatter: InfraFormatterType.percent, + template: '{{value}}', + }, ['memory']: { formatter: InfraFormatterType.percent, template: '{{value}}', diff --git a/x-pack/plugins/observability_solution/infra/common/http_api/infra/get_infra_metrics.ts b/x-pack/plugins/observability_solution/infra/common/http_api/infra/get_infra_metrics.ts index 8cbd09470ee71..d4f7f1f7d3635 100644 --- a/x-pack/plugins/observability_solution/infra/common/http_api/infra/get_infra_metrics.ts +++ b/x-pack/plugins/observability_solution/infra/common/http_api/infra/get_infra_metrics.ts @@ -10,6 +10,7 @@ import * as rt from 'io-ts'; export const InfraMetricTypeRT = rt.keyof({ cpu: null, + cpuTotal: null, normalizedLoad1m: null, diskSpaceUsage: null, memory: null, diff --git a/x-pack/plugins/observability_solution/infra/common/inventory_views/defaults.ts b/x-pack/plugins/observability_solution/infra/common/inventory_views/defaults.ts index 305c322ba0d23..047b79cada84e 100644 --- a/x-pack/plugins/observability_solution/infra/common/inventory_views/defaults.ts +++ b/x-pack/plugins/observability_solution/infra/common/inventory_views/defaults.ts @@ -18,7 +18,7 @@ export const staticInventoryViewAttributes: InventoryViewAttributes = { isDefault: false, isStatic: true, metric: { - type: 'cpu', + type: 'cpuTotal', }, groupBy: [], nodeType: 'host', diff --git a/x-pack/plugins/observability_solution/infra/common/snapshot_metric_i18n.ts b/x-pack/plugins/observability_solution/infra/common/snapshot_metric_i18n.ts index 0561ea0c3add1..75a8b9cb2d0f1 100644 --- a/x-pack/plugins/observability_solution/infra/common/snapshot_metric_i18n.ts +++ b/x-pack/plugins/observability_solution/infra/common/snapshot_metric_i18n.ts @@ -12,10 +12,14 @@ import type { InventoryItemType, SnapshotMetricType } from '@kbn/metrics-data-ac // Lowercase versions of all metrics, for when they need to be used in the middle of a sentence; // these may need to be translated differently depending on language, e.g. still capitalizing "CPU" const TranslationsLowercase = { - CPUUsage: i18n.translate('xpack.infra.waffle.metricOptions.cpuUsageText', { + CPUUsageTotal: i18n.translate('xpack.infra.waffle.metricOptions.cpuUsageTotalText', { defaultMessage: 'CPU usage', }), + CPUUsageLegacy: i18n.translate('xpack.infra.waffle.metricOptions.cpuUsageLegacyText', { + defaultMessage: 'CPU usage (legacy)', + }), + MemoryUsage: i18n.translate('xpack.infra.waffle.metricOptions.memoryUsageText', { defaultMessage: 'memory usage', }), @@ -109,10 +113,16 @@ export const toMetricOpt = ( nodeType?: InventoryItemType ): { text: string; textLC: string; value: SnapshotMetricType } | undefined => { switch (metric) { + case 'cpuTotal': + return { + text: Translations.CPUUsageTotal, + textLC: TranslationsLowercase.CPUUsageTotal, + value: 'cpuTotal', + }; case 'cpu': return { - text: Translations.CPUUsage, - textLC: TranslationsLowercase.CPUUsage, + text: Translations.CPUUsageLegacy, + textLC: TranslationsLowercase.CPUUsageLegacy, value: 'cpu', }; case 'memory': diff --git a/x-pack/plugins/observability_solution/infra/public/alerting/inventory/components/expression.tsx b/x-pack/plugins/observability_solution/infra/public/alerting/inventory/components/expression.tsx index c8e537621c81b..e3ae562fe80f3 100644 --- a/x-pack/plugins/observability_solution/infra/public/alerting/inventory/components/expression.tsx +++ b/x-pack/plugins/observability_solution/infra/public/alerting/inventory/components/expression.tsx @@ -772,6 +772,7 @@ export const nodeTypes: { [key: string]: any } = { const metricUnit: Record = { count: { label: '' }, cpu: { label: '%' }, + cpuTotal: { label: '%' }, memory: { label: '%' }, rx: { label: 'bits/s' }, tx: { label: 'bits/s' }, diff --git a/x-pack/plugins/observability_solution/infra/public/alerting/inventory/components/expression_chart.tsx b/x-pack/plugins/observability_solution/infra/public/alerting/inventory/components/expression_chart.tsx index 5bea3d01ca231..b2b280d182a4b 100644 --- a/x-pack/plugins/observability_solution/infra/public/alerting/inventory/components/expression_chart.tsx +++ b/x-pack/plugins/observability_solution/infra/public/alerting/inventory/components/expression_chart.tsx @@ -231,6 +231,7 @@ const convertMetricValue = (metric: SnapshotMetricType, value: number) => { }; const converters: Record number> = { cpu: (n) => Number(n) / 100, + cpuTotal: (n) => Number(n) / 100, memory: (n) => Number(n) / 100, tx: (n) => Number(n) / 8, rx: (n) => Number(n) / 8, diff --git a/x-pack/plugins/observability_solution/infra/public/alerting/inventory/hooks/use_inventory_alert_prefill.ts b/x-pack/plugins/observability_solution/infra/public/alerting/inventory/hooks/use_inventory_alert_prefill.ts index 2ba9f1f36180a..4c3ade788e25e 100644 --- a/x-pack/plugins/observability_solution/infra/public/alerting/inventory/hooks/use_inventory_alert_prefill.ts +++ b/x-pack/plugins/observability_solution/infra/public/alerting/inventory/hooks/use_inventory_alert_prefill.ts @@ -15,7 +15,7 @@ import { export const useInventoryAlertPrefill = () => { const [nodeType, setNodeType] = useState('host'); const [filterQuery, setFilterQuery] = useState(); - const [metric, setMetric] = useState({ type: 'cpu' }); + const [metric, setMetric] = useState({ type: 'cpuTotal' }); const [customMetrics, setCustomMetrics] = useState([]); // only shows for AWS when there are regions info const [region, setRegion] = useState(''); diff --git a/x-pack/plugins/observability_solution/infra/public/common/visualizations/translations.ts b/x-pack/plugins/observability_solution/infra/public/common/visualizations/translations.ts index 1a0446584e944..de1e8e16d1d51 100644 --- a/x-pack/plugins/observability_solution/infra/public/common/visualizations/translations.ts +++ b/x-pack/plugins/observability_solution/infra/public/common/visualizations/translations.ts @@ -17,7 +17,7 @@ export const METRICS_TOOLTIP = { cpuUsage: i18n.translate('xpack.infra.hostsViewPage.metrics.tooltip.cpuUsage', { defaultMessage: - 'Percentage of CPU time spent in states other than Idle and IOWait, normalized by the number of CPU cores. This includes both time spent on user space and kernel space.', + 'Average of percentage of CPU time spent in states other than Idle and IOWait, normalised by the number of CPU cores. Includes both time spent on user space and kernel space. 100% means all CPUs of the host are busy.', }), diskUsage: i18n.translate('xpack.infra.hostsViewPage.metrics.tooltip.diskSpaceUsage', { defaultMessage: 'Percentage of disk space used.', diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_hosts_table.test.ts b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_hosts_table.test.ts index 5df80a9e3634d..2596d984c6b3a 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_hosts_table.test.ts +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_hosts_table.test.ts @@ -41,7 +41,7 @@ const mockHostNode: InfraAssetMetricsItem[] = [ { metrics: [ { - name: 'cpu', + name: 'cpuTotal', value: 0.6353277777777777, }, { @@ -79,7 +79,7 @@ const mockHostNode: InfraAssetMetricsItem[] = [ { metrics: [ { - name: 'cpu', + name: 'cpuTotal', value: 0.8647805555555556, }, { @@ -169,7 +169,7 @@ describe('useHostTable hook', () => { rx: 252456.92916666667, tx: 252758.425, memory: 0.94525, - cpu: 0.6353277777777777, + cpuTotal: 0.6353277777777777, diskSpaceUsage: 0.2040001, memoryFree: 34359.738368, normalizedLoad1m: 239.2040001, @@ -187,7 +187,7 @@ describe('useHostTable hook', () => { rx: 95.86339715321859, tx: 110.38566859563191, memory: 0.5400000214576721, - cpu: 0.8647805555555556, + cpuTotal: 0.8647805555555556, diskSpaceUsage: 0.5400000214576721, memoryFree: 9.194304, normalizedLoad1m: 100, diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx index cdafb71784ab9..ed5b29d10b9ed 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx @@ -287,10 +287,10 @@ export const useHostsTable = () => { /> ), width: metricColumnsWidth, - field: 'cpu', + field: 'cpuTotal', sortable: true, 'data-test-subj': 'hostsView-tableRow-cpuUsage', - render: (avg: number) => formatMetric('cpu', avg), + render: (avg: number) => formatMetric('cpuTotal', avg), align: 'right', }, { diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_hosts_view.ts b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_hosts_view.ts index 9ed1989cfa85f..655db7941d753 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_hosts_view.ts +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_hosts_view.ts @@ -26,7 +26,7 @@ import { import { StringDateRange } from './use_unified_search_url_state'; const HOST_TABLE_METRICS: Array<{ type: InfraAssetMetricType }> = [ - { type: 'cpu' }, + { type: 'cpuTotal' }, { type: 'diskSpaceUsage' }, { type: 'memory' }, { type: 'memoryFree' }, diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/components/waffle/__snapshots__/conditional_tooltip.test.tsx.snap b/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/components/waffle/__snapshots__/conditional_tooltip.test.tsx.snap index f3368be815f1b..70b15408b38df 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/components/waffle/__snapshots__/conditional_tooltip.test.tsx.snap +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/components/waffle/__snapshots__/conditional_tooltip.test.tsx.snap @@ -26,6 +26,22 @@ exports[`ConditionalToolTip renders correctly 1`] = ` 10% +
+
+ CPU usage (legacy) +
+
+ 10% +
+
diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/components/waffle/conditional_tooltip.test.tsx b/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/components/waffle/conditional_tooltip.test.tsx index 439999d5b143f..b15779cbd2e9e 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/components/waffle/conditional_tooltip.test.tsx +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/components/waffle/conditional_tooltip.test.tsx @@ -30,7 +30,7 @@ const NODE: InfraWaffleMapNode = { id: 'host-01', name: 'host-01', path: [{ value: 'host-01', label: 'host-01' }], - metrics: [{ name: 'cpu' }], + metrics: [{ name: 'cpuTotal' }], }; export const nextTick = () => new Promise((res) => process.nextTick(res)); @@ -45,6 +45,7 @@ describe('ConditionalToolTip', () => { name: 'host-01', path: [{ label: 'host-01', value: 'host-01', ip: '192.168.1.10' }], metrics: [ + { name: 'cpuTotal', value: 0.1, avg: 0.4, max: 0.7 }, { name: 'cpu', value: 0.1, avg: 0.4, max: 0.7 }, { name: 'memory', value: 0.8, avg: 0.8, max: 1 }, { name: 'txV2', value: 1000000, avg: 1000000, max: 1000000 }, @@ -78,13 +79,14 @@ describe('ConditionalToolTip', () => { }, }); const expectedMetrics = [ + { type: 'cpuTotal' }, { type: 'cpu' }, { type: 'memory' }, { type: 'txV2' }, { type: 'rxV2' }, { aggregation: 'avg', - field: 'host.cpu.pct', + field: 'host.cpuTotal.pct', id: 'cedd6ca0-5775-11eb-a86f-adb714b6c486', label: 'My Custom Label', type: 'custom', @@ -139,11 +141,11 @@ const mockedUseWaffleOptionsContexReturnValue: ReturnType }; const converters: Record number> = { cpu: (n) => Number(n) / 100, + cpuTotal: (n) => Number(n) / 100, memory: (n) => Number(n) / 100, tx: (n) => Number(n) / 8, rx: (n) => Number(n) / 8, diff --git a/x-pack/plugins/observability_solution/infra/server/routes/infra/lib/host/get_all_hosts.ts b/x-pack/plugins/observability_solution/infra/server/routes/infra/lib/host/get_all_hosts.ts index 7172a0c3da4d4..15bc0df8d76e5 100644 --- a/x-pack/plugins/observability_solution/infra/server/routes/infra/lib/host/get_all_hosts.ts +++ b/x-pack/plugins/observability_solution/infra/server/routes/infra/lib/host/get_all_hosts.ts @@ -23,8 +23,8 @@ export const getAllHosts = async ( const result = (response.aggregations?.nodes.buckets ?? []) .sort((a, b) => { - const aValue = getMetricValue(a?.cpu) ?? 0; - const bValue = getMetricValue(b?.cpu) ?? 0; + const aValue = getMetricValue(a?.cpuTotal) ?? 0; + const bValue = getMetricValue(b?.cpuTotal) ?? 0; return bValue - aValue; }) .map((bucket) => { diff --git a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/index.ts b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/index.ts index 42e8a0b81e6c8..d45fbfb5477f1 100644 --- a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/index.ts +++ b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/index.ts @@ -42,6 +42,7 @@ export const host: InventoryModel = { metrics, requiredMetrics: [ 'hostSystemOverview', + 'hostCpuUsageTotal', 'hostCpuUsage', 'hostLoad', 'hostMemoryUsage', @@ -54,5 +55,5 @@ export const host: InventoryModel = { ...awsRequiredMetrics, ...nginxRequireMetrics, ], - tooltipMetrics: ['cpu', 'memory', 'txV2', 'rxV2'], + tooltipMetrics: ['cpuTotal', 'cpu', 'memory', 'txV2', 'rxV2'], }; diff --git a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/formulas/cpu.ts b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/formulas/cpu.ts index fa75dc071666c..3ee08c028f8fb 100644 --- a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/formulas/cpu.ts +++ b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/formulas/cpu.ts @@ -80,7 +80,7 @@ export const cpuUsageUser: LensBaseLayer = { export const cpuUsage: LensBaseLayer = { label: CPU_USAGE_LABEL, - value: '(average(system.cpu.user.pct) + average(system.cpu.system.pct)) / max(system.cpu.cores)', + value: 'average(system.cpu.total.norm.pct)', format: 'percent', decimals: 0, }; diff --git a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/index.ts b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/index.ts index 6d2bc3381fdf1..5983b2a56f817 100644 --- a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/index.ts +++ b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/index.ts @@ -22,6 +22,6 @@ export const metrics: InventoryMetricsWithCharts = { snapshot, getFormulas: async () => await import('./formulas').then(({ formulas }) => formulas), getCharts: async () => await import('./charts').then(({ charts }) => charts), - defaultSnapshot: 'cpu', + defaultSnapshot: 'cpuTotal', defaultTimeRangeInSeconds: 3600, // 1 hour }; diff --git a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/snapshot/cpu_total.ts b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/snapshot/cpu_total.ts new file mode 100644 index 0000000000000..f7b4661b65d99 --- /dev/null +++ b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/snapshot/cpu_total.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 { MetricsUIAggregation } from '../../../types'; + +export const cpuTotal: MetricsUIAggregation = { + cpuTotal: { + avg: { + field: 'system.cpu.total.norm.pct', + }, + }, +}; diff --git a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/snapshot/index.ts b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/snapshot/index.ts index e6f1cf6ac1912..4bf48e287fab7 100644 --- a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/snapshot/index.ts +++ b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/snapshot/index.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { cpuTotal } from './cpu_total'; import { cpu } from './cpu'; import { diskLatency } from './disk_latency'; import { diskSpaceUsage } from './disk_space_usage'; @@ -21,6 +22,7 @@ import { txV2 } from './tx_v2'; import { rxV2 } from './rx_v2'; export const snapshot = { + cpuTotal, cpu, diskLatency, diskSpaceUsage, diff --git a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/types.ts b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/types.ts index c9accaca490f6..2ced976e564a8 100644 --- a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/types.ts +++ b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/types.ts @@ -43,6 +43,7 @@ export type InventoryItemType = rt.TypeOf; export const InventoryMetricRT = rt.keyof({ hostSystemOverview: null, + hostCpuUsageTotal: null, hostCpuUsage: null, hostFilesystem: null, hostK8sOverview: null, @@ -348,6 +349,7 @@ export type MetricsUIAggregation = rt.TypeOf; export const SnapshotMetricTypeKeys = { count: null, + cpuTotal: null, cpu: null, diskLatency: null, diskSpaceUsage: null, diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 2e29605ad256d..9abcbd4c40cde 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -22340,7 +22340,6 @@ "xpack.infra.waffle.maxGroupByTooltip": "Seuls deux regroupements peuvent être sélectionnés en même temps", "xpack.infra.waffle.metriclabel": "Indicateur", "xpack.infra.waffle.metricOptions.countText": "compte", - "xpack.infra.waffle.metricOptions.cpuUsageText": "Utilisation CPU", "xpack.infra.waffle.metricOptions.diskIOReadBytes": "lectures du disque", "xpack.infra.waffle.metricOptions.diskIOWriteBytes": "écritures sur le disque", "xpack.infra.waffle.metricOptions.hostLogRateText": "taux de log", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 119a79a5b8369..c5be020320a04 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -22264,7 +22264,6 @@ "xpack.infra.waffle.maxGroupByTooltip": "一度に選択できるグループは 2 つのみです", "xpack.infra.waffle.metriclabel": "メトリック", "xpack.infra.waffle.metricOptions.countText": "カウント", - "xpack.infra.waffle.metricOptions.cpuUsageText": "CPU 使用状況", "xpack.infra.waffle.metricOptions.diskIOReadBytes": "ディスク読み取り", "xpack.infra.waffle.metricOptions.diskIOWriteBytes": "ディスク書き込み", "xpack.infra.waffle.metricOptions.hostLogRateText": "ログレート", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index fd316f8055f38..0497f0f96fe31 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -22368,7 +22368,6 @@ "xpack.infra.waffle.maxGroupByTooltip": "一次只能选择两个分组", "xpack.infra.waffle.metriclabel": "指标", "xpack.infra.waffle.metricOptions.countText": "计数", - "xpack.infra.waffle.metricOptions.cpuUsageText": "CPU 使用", "xpack.infra.waffle.metricOptions.diskIOReadBytes": "磁盘读取数", "xpack.infra.waffle.metricOptions.diskIOWriteBytes": "磁盘写入数", "xpack.infra.waffle.metricOptions.hostLogRateText": "日志速率", diff --git a/x-pack/test/api_integration/apis/metrics_ui/infra.ts b/x-pack/test/api_integration/apis/metrics_ui/infra.ts index dfb6744a94583..641f61c9126fe 100644 --- a/x-pack/test/api_integration/apis/metrics_ui/infra.ts +++ b/x-pack/test/api_integration/apis/metrics_ui/infra.ts @@ -30,6 +30,9 @@ export default function ({ getService }: FtrProviderContext) { { type: 'cpu', }, + { + type: 'cpuTotal', + }, { type: 'diskSpaceUsage', }, @@ -95,6 +98,7 @@ export default function ({ getService }: FtrProviderContext) { ], metrics: [ { name: 'cpu', value: 0.44708333333333333 }, + { name: 'cpuTotal', value: 0 }, { name: 'diskSpaceUsage', value: 0 }, { name: 'memory', value: 0.4563333333333333 }, { name: 'memoryFree', value: 8573890560 }, @@ -155,7 +159,7 @@ export default function ({ getService }: FtrProviderContext) { ...basePayload, metrics: [ { - type: 'cpu', + type: 'cpuTotal', }, ], query: { bool: { filter: [{ term: { 'host.os.name': 'CentOS Linux' } }] } }, @@ -164,8 +168,8 @@ export default function ({ getService }: FtrProviderContext) { const names = (response.body as GetInfraMetricsResponsePayload).nodes.map((p) => p.name); expect(names).eql([ - 'gke-observability-8--observability-8--bc1afd95-ngmh', 'gke-observability-8--observability-8--bc1afd95-f0zc', + 'gke-observability-8--observability-8--bc1afd95-ngmh', 'gke-observability-8--observability-8--bc1afd95-nhhw', ]); }); @@ -175,7 +179,7 @@ export default function ({ getService }: FtrProviderContext) { ...basePayload, metrics: [ { - type: 'cpu', + type: 'cpuTotal', }, ], query: { bool: { filter: [{ term: { 'host.os.name': 'Ubuntu' } }] } }, @@ -192,7 +196,7 @@ export default function ({ getService }: FtrProviderContext) { ...basePayload, metrics: [ { - type: 'cpu', + type: 'cpuTotal', }, ], query: { @@ -207,9 +211,8 @@ export default function ({ getService }: FtrProviderContext) { const names = (response.body as GetInfraMetricsResponsePayload).nodes.map((p) => p.name); expect(names).eql([ - 'gke-observability-8--observability-8--bc1afd95-ngmh', 'gke-observability-8--observability-8--bc1afd95-f0zc', - , + 'gke-observability-8--observability-8--bc1afd95-ngmh', ]); }); @@ -246,7 +249,7 @@ export default function ({ getService }: FtrProviderContext) { const response = await makeRequest({ invalidBody, expectedHTTPCode: 400 }); expect(normalizeNewLine(response.body.message)).to.be( - '[request body]: Failed to validate: in metrics/0/type: "any" does not match expected type "cpu" | "normalizedLoad1m" | "diskSpaceUsage" | "memory" | "memoryFree" | "rx" | "tx" | "rxV2" | "txV2"' + '[request body]: Failed to validate: in metrics/0/type: "any" does not match expected type "cpu" | "cpuTotal" | "normalizedLoad1m" | "diskSpaceUsage" | "memory" | "memoryFree" | "rx" | "tx" | "rxV2" | "txV2"' ); }); diff --git a/x-pack/test/functional/apps/infra/home_page.ts b/x-pack/test/functional/apps/infra/home_page.ts index 0de16bc6af3f4..29cfa9bb37e0b 100644 --- a/x-pack/test/functional/apps/infra/home_page.ts +++ b/x-pack/test/functional/apps/infra/home_page.ts @@ -173,7 +173,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); [ - { metric: 'cpuUsage', value: '0.8%' }, + { metric: 'cpuUsage', value: 'N/A' }, { metric: 'normalizedLoad1m', value: '1.4%' }, { metric: 'memoryUsage', value: '18.0%' }, { metric: 'diskUsage', value: '35.0%' }, @@ -406,7 +406,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await pageObjects.infraHome.clearSearchTerm(); }); - it('sort nodes by descending value', async () => { + it.skip('sort nodes by descending value', async () => { await pageObjects.infraHome.goToTime(DATE_WITH_DATA); await pageObjects.infraHome.getWaffleMap(); await pageObjects.infraHome.sortNodesBy('value'); @@ -423,7 +423,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); }); - it('sort nodes by ascending value', async () => { + it.skip('sort nodes by ascending value', async () => { await pageObjects.infraHome.goToTime(DATE_WITH_DATA); await pageObjects.infraHome.getWaffleMap(); await pageObjects.infraHome.sortNodesBy('value'); @@ -450,7 +450,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); }); - it('filter nodes by search term', async () => { + it.skip('filter nodes by search term', async () => { await pageObjects.infraHome.goToTime(DATE_WITH_DATA); await pageObjects.infraHome.getWaffleMap(); await pageObjects.infraHome.enterSearchTerm('host.name: "demo-stack-apache-01"'); @@ -463,7 +463,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await pageObjects.infraHome.clearSearchTerm(); }); - it('change color palette', async () => { + it.skip('change color palette', async () => { await pageObjects.infraHome.openLegendControls(); await pageObjects.infraHome.changePalette('temperature'); await pageObjects.infraHome.applyLegendControls(); @@ -581,14 +581,13 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { 'system.core.softirq.pct', 'system.core.steal.pct', 'system.cpu.nice.pct', - 'system.cpu.idle.pct', ]; for (const field of fields) { await pageObjects.infraHome.addCustomMetric(field); } const metricsCount = await pageObjects.infraHome.getMetricsContextMenuItemsCount(); - // there are 6 default metrics in the context menu for hosts + // there are 7 default metrics in the context menu for hosts expect(metricsCount).to.eql(20); await pageObjects.infraHome.ensureCustomMetricAddButtonIsDisabled(); diff --git a/x-pack/test/functional/apps/infra/hosts_view.ts b/x-pack/test/functional/apps/infra/hosts_view.ts index f185e479421e4..a016fbec64ecd 100644 --- a/x-pack/test/functional/apps/infra/hosts_view.ts +++ b/x-pack/test/functional/apps/infra/hosts_view.ts @@ -33,7 +33,7 @@ const tableEntries = [ { alertsCount: 2, title: 'demo-stack-apache-01', - cpuUsage: '1.2%', + cpuUsage: '0%', normalizedLoad: '0.5%', memoryUsage: '18.4%', memoryFree: '3.2 GB', @@ -44,7 +44,7 @@ const tableEntries = [ { alertsCount: 2, title: 'demo-stack-mysql-01', - cpuUsage: '0.9%', + cpuUsage: '0%', normalizedLoad: '0%', memoryUsage: '18.2%', memoryFree: '3.2 GB', @@ -55,7 +55,7 @@ const tableEntries = [ { alertsCount: 2, title: 'demo-stack-redis-01', - cpuUsage: '0.8%', + cpuUsage: '0%', normalizedLoad: '0%', memoryUsage: '15.9%', memoryFree: '3.3 GB', @@ -65,19 +65,19 @@ const tableEntries = [ }, { alertsCount: 0, - title: 'demo-stack-nginx-01', - cpuUsage: '0.8%', - normalizedLoad: '1.4%', - memoryUsage: '18%', - memoryFree: '3.2 GB', - diskSpaceUsage: '35%', + title: 'demo-stack-client-01', + cpuUsage: '0%', + normalizedLoad: '0.1%', + memoryUsage: '13.8%', + memoryFree: '3.3 GB', + diskSpaceUsage: '33.8%', rx: '0 bit/s', tx: '0 bit/s', }, { alertsCount: 0, title: 'demo-stack-haproxy-01', - cpuUsage: '0.8%', + cpuUsage: '0%', normalizedLoad: '0%', memoryUsage: '16.5%', memoryFree: '3.2 GB', @@ -87,12 +87,12 @@ const tableEntries = [ }, { alertsCount: 0, - title: 'demo-stack-client-01', - cpuUsage: '0.5%', - normalizedLoad: '0.1%', - memoryUsage: '13.8%', - memoryFree: '3.3 GB', - diskSpaceUsage: '33.8%', + title: 'demo-stack-nginx-01', + cpuUsage: '0%', + normalizedLoad: '1.4%', + memoryUsage: '18%', + memoryFree: '3.2 GB', + diskSpaceUsage: '35%', rx: '0 bit/s', tx: '0 bit/s', }, @@ -477,11 +477,10 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); it('should render the computed metrics for each host entry', async () => { - hostRows.forEach((row, position) => { - pageObjects.infraHostsView - .getHostsRowData(row) - .then((hostRowData) => expect(hostRowData).to.eql(tableEntries[position])); - }); + for (let i = 0; i < hostRows.length; i++) { + const hostRowData = await pageObjects.infraHostsView.getHostsRowData(hostRows[i]); + expect(hostRowData).to.eql(tableEntries[i]); + } }); it('should select and filter hosts inside the table', async () => { @@ -550,7 +549,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { describe('KPIs', () => { [ { metric: 'hostsCount', value: '6' }, - { metric: 'cpuUsage', value: '0.8%' }, + { metric: 'cpuUsage', value: 'N/A' }, { metric: 'normalizedLoad1m', value: '0.3%' }, { metric: 'memoryUsage', value: '16.8%' }, { metric: 'diskUsage', value: '35.7%' }, @@ -701,18 +700,17 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { expect(hostRows.length).to.equal(3); - hostRows.forEach((row, position) => { - pageObjects.infraHostsView - .getHostsRowData(row) - .then((hostRowData) => expect(hostRowData).to.eql(filtererEntries[position])); - }); + for (let i = 0; i < hostRows.length; i++) { + const hostRowData = await pageObjects.infraHostsView.getHostsRowData(hostRows[i]); + expect(hostRowData).to.eql(filtererEntries[i]); + } }); it('should update the KPIs content on a search submit', async () => { await Promise.all( [ { metric: 'hostsCount', value: '3' }, - { metric: 'cpuUsage', value: '0.9%' }, + { metric: 'cpuUsage', value: 'N/A' }, { metric: 'normalizedLoad1m', value: '0.2%' }, { metric: 'memoryUsage', value: '17.5%' }, { metric: 'diskUsage', value: '35.7%' }, @@ -782,38 +780,38 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('should show 5 rows on the first page', async () => { const hostRows = await pageObjects.infraHostsView.getHostsTableData(); - hostRows.forEach((row, position) => { - pageObjects.infraHostsView - .getHostsRowData(row) - .then((hostRowData) => expect(hostRowData).to.eql(tableEntries[position])); - }); + + for (let i = 0; i < hostRows.length; i++) { + const hostRowData = await pageObjects.infraHostsView.getHostsRowData(hostRows[i]); + expect(hostRowData).to.eql(tableEntries[i]); + } }); it('should paginate to the last page', async () => { await pageObjects.infraHostsView.paginateTo(2); const hostRows = await pageObjects.infraHostsView.getHostsTableData(); - hostRows.forEach((row) => { - pageObjects.infraHostsView - .getHostsRowData(row) - .then((hostRowData) => expect(hostRowData).to.eql(tableEntries[5])); - }); + + expect(hostRows.length).to.equal(1); + + const hostRowData = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]); + expect(hostRowData).to.eql(tableEntries[5]); }); it('should show all hosts on the same page', async () => { await pageObjects.infraHostsView.changePageSize(10); const hostRows = await pageObjects.infraHostsView.getHostsTableData(); - hostRows.forEach((row, position) => { - pageObjects.infraHostsView - .getHostsRowData(row) - .then((hostRowData) => expect(hostRowData).to.eql(tableEntries[position])); - }); + + for (let i = 0; i < hostRows.length; i++) { + const hostRowData = await pageObjects.infraHostsView.getHostsRowData(hostRows[i]); + expect(hostRowData).to.eql(tableEntries[i]); + } }); it('should sort by a numeric field asc', async () => { - await pageObjects.infraHostsView.sortByCpuUsage(); + await pageObjects.infraHostsView.sortByMemoryUsage(); let hostRows = await pageObjects.infraHostsView.getHostsTableData(); const hostDataFirtPage = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]); - expect(hostDataFirtPage).to.eql(tableEntries[5]); + expect(hostDataFirtPage).to.eql(tableEntries[3]); await pageObjects.infraHostsView.paginateTo(2); hostRows = await pageObjects.infraHostsView.getHostsTableData(); @@ -822,7 +820,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); it('should sort by a numeric field desc', async () => { - await pageObjects.infraHostsView.sortByCpuUsage(); + await pageObjects.infraHostsView.sortByMemoryUsage(); let hostRows = await pageObjects.infraHostsView.getHostsTableData(); const hostDataFirtPage = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]); expect(hostDataFirtPage).to.eql(tableEntries[0]); @@ -830,7 +828,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await pageObjects.infraHostsView.paginateTo(2); hostRows = await pageObjects.infraHostsView.getHostsTableData(); const hostDataLastPage = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]); - expect(hostDataLastPage).to.eql(tableEntries[5]); + expect(hostDataLastPage).to.eql(tableEntries[3]); }); it('should sort by text field asc', async () => { diff --git a/x-pack/test/functional/apps/infra/node_details.ts b/x-pack/test/functional/apps/infra/node_details.ts index bba769b6b9764..fbc442b5079c0 100644 --- a/x-pack/test/functional/apps/infra/node_details.ts +++ b/x-pack/test/functional/apps/infra/node_details.ts @@ -558,7 +558,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); [ - { metric: 'cpuUsage', value: '99.6%' }, + { metric: 'cpuUsage', value: '100.0%' }, { metric: 'normalizedLoad1m', value: '1,300.3%' }, { metric: 'memoryUsage', value: '42.2%' }, { metric: 'diskUsage', value: '36.0%' }, diff --git a/x-pack/test/functional/page_objects/infra_home_page.ts b/x-pack/test/functional/page_objects/infra_home_page.ts index 03995ac6e7a6d..ec7908d916e9b 100644 --- a/x-pack/test/functional/page_objects/infra_home_page.ts +++ b/x-pack/test/functional/page_objects/infra_home_page.ts @@ -94,7 +94,7 @@ export function InfraHomePageProvider({ getService, getPageObjects }: FtrProvide async clickOnFirstNode() { const firstNode = await this.getFirstNode(); - firstNode.click(); + return firstNode.click(); }, async clickOnGoToNodeDetails() { diff --git a/x-pack/test/functional/page_objects/infra_hosts_view.ts b/x-pack/test/functional/page_objects/infra_hosts_view.ts index 2ee8bf266f843..d92cf51892a5f 100644 --- a/x-pack/test/functional/page_objects/infra_hosts_view.ts +++ b/x-pack/test/functional/page_objects/infra_hosts_view.ts @@ -247,17 +247,17 @@ export function InfraHostsViewProvider({ getService }: FtrProviderContext) { }, // Sorting - getCpuUsageHeader() { - return testSubjects.find('tableHeaderCell_cpu_3'); + getMemoryHeader() { + return testSubjects.find('tableHeaderCell_memory_5'); }, getTitleHeader() { return testSubjects.find('tableHeaderCell_title_2'); }, - async sortByCpuUsage() { - const diskLatency = await this.getCpuUsageHeader(); - const button = await testSubjects.findDescendant('tableHeaderSortButton', diskLatency); + async sortByMemoryUsage() { + const memory = await this.getMemoryHeader(); + const button = await testSubjects.findDescendant('tableHeaderSortButton', memory); await button.click(); }, From 432710065d619aeff0b54eec15320ceb3c3af517 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 7 Aug 2024 07:33:03 -0500 Subject: [PATCH 05/44] Reapply "[ci] Fix sonarqube scan (#189936)" (#190011) I reverted the locations.yml change - I'll leave it for a PR that migrates the remaining entries in that file. --- .buildkite/scripts/common/env.sh | 12 +++++++----- catalog-info.yaml | 3 +++ .../infra/server/routes/ip_to_hostname.ts | 6 +++--- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/.buildkite/scripts/common/env.sh b/.buildkite/scripts/common/env.sh index 1d401bab4282a..34b71b7004725 100755 --- a/.buildkite/scripts/common/env.sh +++ b/.buildkite/scripts/common/env.sh @@ -23,12 +23,14 @@ if [[ -d /opt/local-ssd/buildkite ]]; then mkdir -p "$TMPDIR" fi -KIBANA_PKG_BRANCH="$(jq -r .branch "$KIBANA_DIR/package.json")" -export KIBANA_PKG_BRANCH -export KIBANA_BASE_BRANCH="$KIBANA_PKG_BRANCH" +if command -v jq >/dev/null 2>&1; then + KIBANA_PKG_BRANCH="$(jq -r .branch "$KIBANA_DIR/package.json")" + export KIBANA_PKG_BRANCH + export KIBANA_BASE_BRANCH="$KIBANA_PKG_BRANCH" -KIBANA_PKG_VERSION="$(jq -r .version "$KIBANA_DIR/package.json")" -export KIBANA_PKG_VERSION + KIBANA_PKG_VERSION="$(jq -r .version "$KIBANA_DIR/package.json")" + export KIBANA_PKG_VERSION +fi # Detects and exports the final target branch when using a merge queue if [[ "${BUILDKITE_BRANCH:-}" == "gh-readonly-queue"* ]]; then diff --git a/catalog-info.yaml b/catalog-info.yaml index 4af2698ca6cca..47fd1049e2fed 100644 --- a/catalog-info.yaml +++ b/catalog-info.yaml @@ -113,6 +113,9 @@ spec: metadata: name: kibana / sonarqube spec: + env: + SLACK_NOTIFICATIONS_CHANNEL: '#kibana-operations-alerts' + ELASTIC_SLACK_NOTIFICATIONS_ENABLED: 'true' repository: elastic/kibana provider_settings: trigger_mode: none diff --git a/x-pack/plugins/observability_solution/infra/server/routes/ip_to_hostname.ts b/x-pack/plugins/observability_solution/infra/server/routes/ip_to_hostname.ts index 7bafa78750690..0f8c00c51b0db 100644 --- a/x-pack/plugins/observability_solution/infra/server/routes/ip_to_hostname.ts +++ b/x-pack/plugins/observability_solution/infra/server/routes/ip_to_hostname.ts @@ -52,10 +52,10 @@ export const initIpToHostName = ({ framework }: InfraBackendLibs) => { } const hostDoc = first(hits.hits)!; return response.ok({ body: { host: hostDoc._source.host.name } }); - } catch ({ statusCode = 500, message = 'Unknown error occurred' }) { + } catch (error) { return response.customError({ - statusCode, - body: { message }, + statusCode: error.statusCode || 500, + body: { message: error.message || 'Unknown error occurred' }, }); } } From 8e3126ee8bba90c9034656726fafc7aae78da22f Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 7 Aug 2024 07:42:28 -0500 Subject: [PATCH 06/44] Use wolfi as the base image for project deployments (#183328) Updates the base image for serverless project deployments from `ubuntu:20.04` to `chainguard-base` https://buildkite.com/elastic/appex-qa-serverless-kibana-ftr-tests/builds/2158 ### Testing Download docker-context in the artifacts tab of the `Build Project` step or ``` cd $KIBANA_HOME mkdir target node scripts/build \ --skip-initialize \ --skip-generic-folders \ --skip-platform-folders \ --skip-archives \ --skip-cdn-assets TEST_CONTEXT=$(mktemp -d) tar -xf target/kibana-serverless-8.15.0-SNAPSHOT-docker-build-context.tar.gz -C "$TEST_CONTEXT" cd "$TEST_CONTEXT" docker build . -t docker.elastic.co/employees/jbudz/kibana-serverless docker run --rm docker.elastic.co/employees/jbudz/kibana-serverless --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Elastic Machine --- src/dev/build/tasks/os_packages/create_os_package_tasks.ts | 6 +++--- src/dev/build/tasks/os_packages/docker_generator/run.ts | 2 +- .../os_packages/docker_generator/templates/base/Dockerfile | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/dev/build/tasks/os_packages/create_os_package_tasks.ts b/src/dev/build/tasks/os_packages/create_os_package_tasks.ts index 052d7592024d7..1a3708a3d5bcd 100644 --- a/src/dev/build/tasks/os_packages/create_os_package_tasks.ts +++ b/src/dev/build/tasks/os_packages/create_os_package_tasks.ts @@ -111,7 +111,7 @@ export const CreateDockerServerless: Task = { async run(config, log, build) { await runDockerGenerator(config, log, build, { architecture: 'x64', - baseImage: 'ubuntu', + baseImage: 'wolfi', context: false, serverless: true, image: true, @@ -119,7 +119,7 @@ export const CreateDockerServerless: Task = { }); await runDockerGenerator(config, log, build, { architecture: 'aarch64', - baseImage: 'ubuntu', + baseImage: 'wolfi', context: false, serverless: true, image: true, @@ -210,7 +210,7 @@ export const CreateDockerContexts: Task = { image: false, }); await runDockerGenerator(config, log, build, { - baseImage: 'ubuntu', + baseImage: 'wolfi', serverless: true, context: true, image: false, diff --git a/src/dev/build/tasks/os_packages/docker_generator/run.ts b/src/dev/build/tasks/os_packages/docker_generator/run.ts index 62d17f0455723..a772650586c7b 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/run.ts +++ b/src/dev/build/tasks/os_packages/docker_generator/run.ts @@ -48,7 +48,7 @@ export async function runDockerGenerator( let imageFlavor = ''; if (flags.baseImage === 'ubi') imageFlavor += `-ubi`; - if (flags.baseImage === 'wolfi') imageFlavor += `-wolfi`; + if (flags.baseImage === 'wolfi' && !flags.serverless) imageFlavor += `-wolfi`; if (flags.ironbank) imageFlavor += '-ironbank'; if (flags.cloud) imageFlavor += '-cloud'; if (flags.serverless) imageFlavor += '-serverless'; diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/base/Dockerfile b/src/dev/build/tasks/os_packages/docker_generator/templates/base/Dockerfile index 0195825313b57..acd5b54a74f1b 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/templates/base/Dockerfile +++ b/src/dev/build/tasks/os_packages/docker_generator/templates/base/Dockerfile @@ -134,7 +134,7 @@ RUN for iter in {1..10}; do \ (exit $exit_code) {{/ubuntu}} {{#wolfi}} -RUN apk --no-cache add bash curl fontconfig libstdc++ freetype nss findutils shadow +RUN apk --no-cache add bash curl fontconfig libstdc++ nss findutils shadow {{/wolfi}} # Bring in Kibana from the initial stage. From 2990ce0b6526773c1697af022e29f410243099dd Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 7 Aug 2024 22:42:33 +1000 Subject: [PATCH 07/44] skip failing test suite (#190030) --- x-pack/test/functional/apps/lens/group2/fields_list.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/functional/apps/lens/group2/fields_list.ts b/x-pack/test/functional/apps/lens/group2/fields_list.ts index 14151f481913f..21e22ebab60d1 100644 --- a/x-pack/test/functional/apps/lens/group2/fields_list.ts +++ b/x-pack/test/functional/apps/lens/group2/fields_list.ts @@ -20,7 +20,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const queryBar = getService('queryBar'); const dataViews = getService('dataViews'); - describe('lens fields list tests', () => { + // Failing: See https://github.com/elastic/kibana/issues/190030 + describe.skip('lens fields list tests', () => { for (const datasourceType of ['form-based', 'ad-hoc', 'ad-hoc-no-timefield']) { describe(`${datasourceType} datasource`, () => { before(async () => { From 6a3c98dbe15c50fa4205fc41de5f2c8954b1fba9 Mon Sep 17 00:00:00 2001 From: Dmitrii Shevchenko Date: Wed, 7 Aug 2024 14:45:52 +0200 Subject: [PATCH 08/44] [Security Solution] Detection rules bootstrap endpoint (#189518) **Resolves: https://github.com/elastic/kibana/issues/187647** ## Summary Added a new API endpoint `POST /internal/detection_engine/prebuilt_rules/_bootstrap`. This endpoint is responsible for installing the necessary packages for prebuilt detection rules to function properly. This allows us to avoid calling Fleet directly from FE and helps encapsulate complex logic of the package version selection in a single place on the backend. Currently, it installs or upgrades (if already installed) two packages: `endpoint` and `security_detection_engine`. The response looks like this: ```json5 { packages: [ { name: 'detection_engine', version: '1.0.0', status: 'installed', }, { name: 'endpoint', version: '1.0.0', status: 'already_installed', }, ], } ``` We call this endpoint from Kibana every time a user lands on any security solution page. The endpoint checks if the required packages are missing or if a newer version is available. If so, the newer version is installed, and the package status will be `installed` in the response. If all packages are up-to-date, the package status will be `already_installed` in the response. This allows us to invalidate prebuilt rule endpoints more efficiently and avoid sending extra requests from Kibana: ```ts if ( response?.packages.find((pkg) => pkg.name === PREBUILT_RULES_PACKAGE_NAME)?.status === 'installed' ) { // Invalidate other pre-packaged rules related queries. We need to do // that only if the prebuilt rules package was installed, indicating // that there might be new rules to install. invalidatePrePackagedRulesStatus(); invalidatePrebuiltRulesInstallReview(); invalidatePrebuiltRulesUpdateReview(); } ``` The performance gain is that we do not invalidate prebuilt rules when the package is already installed. Previously: `Fetch rules initially -> Upgrade rules package -(always)-> Re-fetch rules` Now: `Fetch rules initially -> Upgrade rules package -(only if there's a new package version)-> Re-fetch rules` This will result in fewer redundant API requests from Kibana. --- .../server/services/epm/package_service.ts | 6 +- .../server/services/epm/packages/install.ts | 17 +- .../server/routes/flow/route.ts | 3 +- .../install_index_templates.ts | 4 +- .../bootstrap_prebuilt_rules.gen.ts | 41 ++++ .../bootstrap_prebuilt_rules.schema.yaml | 51 +++++ .../detection_engine/prebuilt_rules/urls.ts | 19 +- .../common/detection_engine/constants.ts | 1 + .../rule_management/api/api.ts | 71 +------ ...ion.ts => use_bootstrap_prebuilt_rules.ts} | 32 ++- .../use_install_fleet_package_mutation.ts | 47 ----- .../use_upgrade_secuirty_packages.test.tsx | 198 ------------------ .../logic/use_upgrade_security_packages.ts | 77 +------ .../server/client/client.test.ts | 2 +- .../security_solution/server/client/client.ts | 12 +- .../server/client/factory.test.ts | 4 + .../server/client/factory.ts | 24 ++- .../bootstrap_prebuilt_rules.test.ts | 141 +++++++++++++ .../bootstrap_prebuilt_rules.ts | 66 ++++++ .../install_prebuilt_rules_package.ts | 44 +++- .../prebuilt_rules/api/register_routes.ts | 2 + .../routes/__mocks__/request_context.ts | 13 +- .../routes/__mocks__/request_responses.ts | 7 + .../plugins/security_solution/server/mocks.ts | 3 + .../security_solution/server/plugin.ts | 2 + .../server/request_context_factory.ts | 3 + .../plugins/security_solution/tsconfig.json | 1 + .../services/security_solution_api.gen.ts | 10 + .../bootstrap_prebuilt_rules.ts | 64 ++++++ .../trial_license_complete_tier/index.ts | 1 + .../delete_endpoint_fleet_package.ts | 30 +++ .../delete_prebuilt_rules_fleet_package.ts | 5 +- .../sourcerer/sourcerer_timeline.cy.ts | 4 +- .../cypress/tasks/api_calls/prebuilt_rules.ts | 4 +- .../cypress/tasks/fleet_integrations.ts | 7 +- 35 files changed, 578 insertions(+), 438 deletions(-) create mode 100644 x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.gen.ts create mode 100644 x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.schema.yaml rename x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/{use_bulk_install_fleet_packages_mutation.ts => use_bootstrap_prebuilt_rules.ts} (59%) delete mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_install_fleet_package_mutation.ts delete mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_upgrade_secuirty_packages.test.tsx create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.test.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/bootstrap_prebuilt_rules.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/delete_endpoint_fleet_package.ts diff --git a/x-pack/plugins/fleet/server/services/epm/package_service.ts b/x-pack/plugins/fleet/server/services/epm/package_service.ts index ae2f5721aae8b..1911ed14a7c80 100644 --- a/x-pack/plugins/fleet/server/services/epm/package_service.ts +++ b/x-pack/plugins/fleet/server/services/epm/package_service.ts @@ -40,7 +40,7 @@ import type { InstallResult } from '../../../common'; import { appContextService } from '..'; -import type { CustomPackageDatasetConfiguration } from './packages/install'; +import type { CustomPackageDatasetConfiguration, EnsurePackageResult } from './packages/install'; import type { FetchFindLatestPackageOptions } from './registry'; import { getPackageFieldsMetadata } from './registry'; @@ -73,7 +73,7 @@ export interface PackageClient { pkgVersion?: string; spaceId?: string; force?: boolean; - }): Promise; + }): Promise; installPackage(options: { pkgName: string; @@ -201,7 +201,7 @@ class PackageClientImpl implements PackageClient { pkgVersion?: string; spaceId?: string; force?: boolean; - }): Promise { + }): Promise { await this.#runPreflight(INSTALL_PACKAGES_AUTHZ); return ensureInstalledPackage({ 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 f27cb794475c9..ce406773a0635 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/install.ts @@ -158,6 +158,11 @@ export async function isPackageVersionOrLaterInstalled(options: { }); } +export interface EnsurePackageResult { + status: InstallResultStatus; + package: Installation; +} + export async function ensureInstalledPackage(options: { savedObjectsClient: SavedObjectsClientContract; pkgName: string; @@ -166,7 +171,7 @@ export async function ensureInstalledPackage(options: { spaceId?: string; force?: boolean; authorizationHeader?: HTTPAuthorizationHeader | null; -}): Promise { +}): Promise { const { savedObjectsClient, pkgName, @@ -189,7 +194,10 @@ export async function ensureInstalledPackage(options: { }); if (installedPackageResult) { - return installedPackageResult.package; + return { + status: 'already_installed', + package: installedPackageResult.package, + }; } const pkgkey = Registry.pkgToPkgKey(pkgKeyProps); const installResult = await installPackage({ @@ -226,7 +234,10 @@ export async function ensureInstalledPackage(options: { const installation = await getInstallation({ savedObjectsClient, pkgName }); if (!installation) throw new FleetError(`Could not get installation for ${pkgName}`); - return installation; + return { + status: 'installed', + package: installation, + }; } export async function handleInstallPackageFailure({ diff --git a/x-pack/plugins/observability_solution/observability_onboarding/server/routes/flow/route.ts b/x-pack/plugins/observability_solution/observability_onboarding/server/routes/flow/route.ts index d035a7fa02716..b4b2541416e2f 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/server/routes/flow/route.ts +++ b/x-pack/plugins/observability_solution/observability_onboarding/server/routes/flow/route.ts @@ -383,7 +383,8 @@ async function ensureInstalledIntegrations( const { pkgName, installSource } = integration; if (installSource === 'registry') { - const pkg = await packageClient.ensureInstalledPackage({ pkgName }); + const installation = await packageClient.ensureInstalledPackage({ pkgName }); + const pkg = installation.package; const inputs = await packageClient.getAgentPolicyInputs(pkg.name, pkg.version); const { packageInfo } = await packageClient.getPackage(pkg.name, pkg.version); diff --git a/x-pack/plugins/observability_solution/synthetics/server/routes/synthetics_service/install_index_templates.ts b/x-pack/plugins/observability_solution/synthetics/server/routes/synthetics_service/install_index_templates.ts index c9ee257335c99..cfbfef34f16da 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/routes/synthetics_service/install_index_templates.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/routes/synthetics_service/install_index_templates.ts @@ -27,9 +27,9 @@ export async function installSyntheticsIndexTemplates(server: SyntheticsServerSe pkgName: 'synthetics', }); - if (!installation) { + if (!installation.package) { return Promise.reject('No package installation found.'); } - return installation; + return installation.package; } diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.gen.ts new file mode 100644 index 0000000000000..5613a117aceb5 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Bootstrap Prebuilt Rules API endpoint + * version: 1 + */ + +import { z } from 'zod'; + +export type PackageInstallStatus = z.infer; +export const PackageInstallStatus = z.object({ + /** + * The name of the package + */ + name: z.string(), + /** + * The version of the package + */ + version: z.string(), + /** + * The status of the package installation + */ + status: z.enum(['installed', 'already_installed']), +}); + +export type BootstrapPrebuiltRulesResponse = z.infer; +export const BootstrapPrebuiltRulesResponse = z.object({ + /** + * The list of packages that were installed or upgraded + */ + packages: z.array(PackageInstallStatus), +}); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.schema.yaml new file mode 100644 index 0000000000000..92cb6ccaf2ad1 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.schema.yaml @@ -0,0 +1,51 @@ +openapi: 3.0.0 +info: + title: Bootstrap Prebuilt Rules API endpoint + version: '1' +paths: + /internal/detection_engine/prebuilt_rules/_bootstrap: + post: + x-labels: [ess, serverless] + x-codegen-enabled: true + operationId: BootstrapPrebuiltRules + summary: Bootstrap Prebuilt Rules + description: Ensures that the packages needed for prebuilt detection rules to work are installed and up to date + tags: + - Prebuilt Rules API + responses: + 200: + description: Indicates a successful call + content: + application/json: + schema: + type: object + properties: + packages: + type: array + description: The list of packages that were installed or upgraded + items: + $ref: '#/components/schemas/PackageInstallStatus' + required: + - packages + +components: + schemas: + PackageInstallStatus: + type: object + properties: + name: + type: string + description: The name of the package + version: + type: string + description: The version of the package + status: + type: string + description: The status of the package installation + enum: + - installed + - already_installed + required: + - name + - version + - status diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/urls.ts b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/urls.ts index ae62433d93a35..3f744dffc9447 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/urls.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/urls.ts @@ -10,14 +10,15 @@ import { INTERNAL_DETECTION_ENGINE_URL as INTERNAL, } from '../../../constants'; -const OLD_BASE_URL = `${RULES}/prepackaged` as const; -const NEW_BASE_URL = `${INTERNAL}/prebuilt_rules` as const; +const LEGACY_BASE_URL = `${RULES}/prepackaged` as const; +const BASE_URL = `${INTERNAL}/prebuilt_rules` as const; -export const PREBUILT_RULES_URL = OLD_BASE_URL; -export const PREBUILT_RULES_STATUS_URL = `${OLD_BASE_URL}/_status` as const; +export const PREBUILT_RULES_URL = LEGACY_BASE_URL; +export const PREBUILT_RULES_STATUS_URL = `${LEGACY_BASE_URL}/_status` as const; -export const GET_PREBUILT_RULES_STATUS_URL = `${NEW_BASE_URL}/status` as const; -export const REVIEW_RULE_UPGRADE_URL = `${NEW_BASE_URL}/upgrade/_review` as const; -export const PERFORM_RULE_UPGRADE_URL = `${NEW_BASE_URL}/upgrade/_perform` as const; -export const REVIEW_RULE_INSTALLATION_URL = `${NEW_BASE_URL}/installation/_review` as const; -export const PERFORM_RULE_INSTALLATION_URL = `${NEW_BASE_URL}/installation/_perform` as const; +export const GET_PREBUILT_RULES_STATUS_URL = `${BASE_URL}/status` as const; +export const BOOTSTRAP_PREBUILT_RULES_URL = `${BASE_URL}/_bootstrap` as const; +export const REVIEW_RULE_UPGRADE_URL = `${BASE_URL}/upgrade/_review` as const; +export const PERFORM_RULE_UPGRADE_URL = `${BASE_URL}/upgrade/_perform` as const; +export const REVIEW_RULE_INSTALLATION_URL = `${BASE_URL}/installation/_review` as const; +export const PERFORM_RULE_INSTALLATION_URL = `${BASE_URL}/installation/_perform` as const; diff --git a/x-pack/plugins/security_solution/common/detection_engine/constants.ts b/x-pack/plugins/security_solution/common/detection_engine/constants.ts index 8e06f46f1f46d..7057e3c8b3091 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/constants.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/constants.ts @@ -29,6 +29,7 @@ export enum RULE_PREVIEW_FROM { } export const PREBUILT_RULES_PACKAGE_NAME = 'security_detection_engine'; +export const ENDPOINT_PACKAGE_NAME = 'endpoint'; /** * Rule signature id (`rule.rule_id`) of the prebuilt "Endpoint Security" rule. diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts index 57f6c5f4e8305..3513c9a21aa7b 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts @@ -13,9 +13,6 @@ import { INTERNAL_ALERTING_API_FIND_RULES_PATH } from '@kbn/alerting-plugin/comm import { BASE_ACTION_API_PATH } from '@kbn/actions-plugin/common'; import type { ActionType, AsApiContract } from '@kbn/actions-plugin/common'; import type { ActionResult } from '@kbn/actions-plugin/server'; -import type { BulkInstallPackagesResponse } from '@kbn/fleet-plugin/common'; -import { epmRouteService } from '@kbn/fleet-plugin/common'; -import type { InstallPackageResponse } from '@kbn/fleet-plugin/common/types'; import { convertRulesFilterToKQL } from '../../../../common/detection_engine/rule_management/rule_filtering'; import type { UpgradeSpecificRulesRequest, @@ -48,6 +45,7 @@ import { } from '../../../../common/constants'; import { + BOOTSTRAP_PREBUILT_RULES_URL, GET_PREBUILT_RULES_STATUS_URL, PERFORM_RULE_INSTALLATION_URL, PERFORM_RULE_UPGRADE_URL, @@ -81,6 +79,7 @@ import type { RulesSnoozeSettingsMap, UpdateRulesProps, } from '../logic/types'; +import type { BootstrapPrebuiltRulesResponse } from '../../../../common/api/detection_engine/prebuilt_rules/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.gen'; /** * Create provided Rule @@ -594,66 +593,6 @@ export const addRuleExceptions = async ({ } ); -export interface InstallFleetPackageProps { - packageName: string; - packageVersion: string; - prerelease?: boolean; - force?: boolean; -} - -/** - * Install a Fleet package from the registry - * - * @param packageName Name of the package to install - * @param packageVersion Version of the package to install - * @param prerelease Whether to install a prerelease version of the package - * @param force Whether to force install the package. If false, the package will only be installed if it is not already installed - * - * @returns The response from the Fleet API - */ -export const installFleetPackage = ({ - packageName, - packageVersion, - prerelease = false, - force = true, -}: InstallFleetPackageProps): Promise => { - return KibanaServices.get().http.post( - epmRouteService.getInstallPath(packageName, packageVersion), - { - query: { prerelease }, - version: '2023-10-31', - body: JSON.stringify({ force }), - } - ); -}; - -export interface BulkInstallFleetPackagesProps { - packages: string[]; - prerelease?: boolean; -} - -/** - * Install multiple Fleet packages from the registry - * - * @param packages Array of package names to install - * @param prerelease Whether to install prerelease versions of the packages - * - * @returns The response from the Fleet API - */ -export const bulkInstallFleetPackages = ({ - packages, - prerelease = false, -}: BulkInstallFleetPackagesProps): Promise => { - return KibanaServices.get().http.post( - epmRouteService.getBulkInstallPath(), - { - query: { prerelease }, - version: '2023-10-31', - body: JSON.stringify({ packages }), - } - ); -}; - /** * NEW PREBUILT RULES ROUTES START HERE! 👋 * USE THESE ONES! THEY'RE THE NICE ONES, PROMISE! @@ -759,3 +698,9 @@ export const performUpgradeSpecificRules = async ( pick_version: 'TARGET', // Setting fixed 'TARGET' temporarily for Milestone 2 }), }); + +export const bootstrapPrebuiltRules = async (): Promise => + KibanaServices.get().http.fetch(BOOTSTRAP_PREBUILT_RULES_URL, { + method: 'POST', + version: '1', + }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bulk_install_fleet_packages_mutation.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bootstrap_prebuilt_rules.ts similarity index 59% rename from x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bulk_install_fleet_packages_mutation.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bootstrap_prebuilt_rules.ts index d47f4799fcc30..9f2b810138a6c 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bulk_install_fleet_packages_mutation.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bootstrap_prebuilt_rules.ts @@ -4,39 +4,37 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { EPM_API_ROUTES } from '@kbn/fleet-plugin/common'; -import type { BulkInstallPackagesResponse } from '@kbn/fleet-plugin/common/types'; import type { UseMutationOptions } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query'; +import { BOOTSTRAP_PREBUILT_RULES_URL } from '../../../../../common/api/detection_engine'; +import type { BootstrapPrebuiltRulesResponse } from '../../../../../common/api/detection_engine/prebuilt_rules/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.gen'; import { PREBUILT_RULES_PACKAGE_NAME } from '../../../../../common/detection_engine/constants'; -import type { BulkInstallFleetPackagesProps } from '../api'; -import { bulkInstallFleetPackages } from '../api'; +import { bootstrapPrebuiltRules } from '../api'; import { useInvalidateFetchPrebuiltRulesInstallReviewQuery } from './prebuilt_rules/use_fetch_prebuilt_rules_install_review_query'; import { useInvalidateFetchPrebuiltRulesStatusQuery } from './prebuilt_rules/use_fetch_prebuilt_rules_status_query'; import { useInvalidateFetchPrebuiltRulesUpgradeReviewQuery } from './prebuilt_rules/use_fetch_prebuilt_rules_upgrade_review_query'; -export const BULK_INSTALL_FLEET_PACKAGES_MUTATION_KEY = [ - 'POST', - EPM_API_ROUTES.BULK_INSTALL_PATTERN, -]; +export const BOOTSTRAP_PREBUILT_RULES_KEY = ['POST', BOOTSTRAP_PREBUILT_RULES_URL]; -export const useBulkInstallFleetPackagesMutation = ( - options?: UseMutationOptions +export const useBootstrapPrebuiltRulesMutation = ( + options?: UseMutationOptions ) => { const invalidatePrePackagedRulesStatus = useInvalidateFetchPrebuiltRulesStatusQuery(); const invalidatePrebuiltRulesInstallReview = useInvalidateFetchPrebuiltRulesInstallReviewQuery(); const invalidatePrebuiltRulesUpdateReview = useInvalidateFetchPrebuiltRulesUpgradeReviewQuery(); - return useMutation((props: BulkInstallFleetPackagesProps) => bulkInstallFleetPackages(props), { + return useMutation(() => bootstrapPrebuiltRules(), { ...options, - mutationKey: BULK_INSTALL_FLEET_PACKAGES_MUTATION_KEY, + mutationKey: BOOTSTRAP_PREBUILT_RULES_KEY, onSettled: (...args) => { const response = args[0]; - const rulesPackage = response?.items.find( - (item) => item.name === PREBUILT_RULES_PACKAGE_NAME - ); - if (rulesPackage && 'result' in rulesPackage && rulesPackage.result.status === 'installed') { - // The rules package was installed/updated, so invalidate the pre-packaged rules status query + if ( + response?.packages.find((pkg) => pkg.name === PREBUILT_RULES_PACKAGE_NAME)?.status === + 'installed' + ) { + // Invalidate other pre-packaged rules related queries. We need to do + // that only in case the prebuilt rules package was installed indicating + // that there might be new rules to install. invalidatePrePackagedRulesStatus(); invalidatePrebuiltRulesInstallReview(); invalidatePrebuiltRulesUpdateReview(); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_install_fleet_package_mutation.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_install_fleet_package_mutation.ts deleted file mode 100644 index e4f88d8b9c3ef..0000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_install_fleet_package_mutation.ts +++ /dev/null @@ -1,47 +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 { EPM_API_ROUTES } from '@kbn/fleet-plugin/common'; -import type { InstallPackageResponse } from '@kbn/fleet-plugin/common/types'; -import type { UseMutationOptions } from '@tanstack/react-query'; -import { useMutation } from '@tanstack/react-query'; -import { PREBUILT_RULES_PACKAGE_NAME } from '../../../../../common/detection_engine/constants'; -import type { InstallFleetPackageProps } from '../api'; -import { installFleetPackage } from '../api'; -import { useInvalidateFetchPrebuiltRulesInstallReviewQuery } from './prebuilt_rules/use_fetch_prebuilt_rules_install_review_query'; -import { useInvalidateFetchPrebuiltRulesStatusQuery } from './prebuilt_rules/use_fetch_prebuilt_rules_status_query'; -import { useInvalidateFetchPrebuiltRulesUpgradeReviewQuery } from './prebuilt_rules/use_fetch_prebuilt_rules_upgrade_review_query'; - -export const INSTALL_FLEET_PACKAGE_MUTATION_KEY = [ - 'POST', - EPM_API_ROUTES.INSTALL_FROM_REGISTRY_PATTERN, -]; - -export const useInstallFleetPackageMutation = ( - options?: UseMutationOptions -) => { - const invalidatePrePackagedRulesStatus = useInvalidateFetchPrebuiltRulesStatusQuery(); - const invalidatePrebuiltRulesInstallReview = useInvalidateFetchPrebuiltRulesInstallReviewQuery(); - const invalidatePrebuiltRulesUpdateReview = useInvalidateFetchPrebuiltRulesUpgradeReviewQuery(); - - return useMutation((props: InstallFleetPackageProps) => installFleetPackage(props), { - ...options, - mutationKey: INSTALL_FLEET_PACKAGE_MUTATION_KEY, - onSettled: (...args) => { - const { packageName } = args[2]; - if (packageName === PREBUILT_RULES_PACKAGE_NAME) { - // Invalidate the pre-packaged rules status query as there might be new rules to install - invalidatePrePackagedRulesStatus(); - invalidatePrebuiltRulesInstallReview(); - invalidatePrebuiltRulesUpdateReview(); - } - - if (options?.onSettled) { - options.onSettled(...args); - } - }, - }); -}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_upgrade_secuirty_packages.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_upgrade_secuirty_packages.test.tsx deleted file mode 100644 index 9454a1c4dfb16..0000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_upgrade_secuirty_packages.test.tsx +++ /dev/null @@ -1,198 +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 { epmRouteService } from '@kbn/fleet-plugin/common'; -import { renderHook } from '@testing-library/react-hooks'; -import { useKibana, KibanaServices } from '../../../common/lib/kibana'; -import { TestProviders } from '../../../common/mock'; -import { useUpgradeSecurityPackages } from './use_upgrade_security_packages'; - -jest.mock('../../../common/components/user_privileges', () => { - return { - useUserPrivileges: jest.fn().mockReturnValue({ - endpointPrivileges: { - canAccessFleet: true, - }, - }), - }; -}); -jest.mock('../../../common/lib/kibana'); - -const mockGetPrebuiltRulesPackageVersion = - KibanaServices.getPrebuiltRulesPackageVersion as jest.Mock; -const mockGetKibanaVersion = KibanaServices.getKibanaVersion as jest.Mock; -const mockGetKibanaBranch = KibanaServices.getKibanaBranch as jest.Mock; -const mockBuildFlavor = KibanaServices.getBuildFlavor as jest.Mock; -const useKibanaMock = useKibana as jest.MockedFunction; - -describe('When using the `useUpgradeSecurityPackages()` hook', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('should call fleet setup first via `isInitialized()` and then send upgrade request', async () => { - const { waitFor } = renderHook(() => useUpgradeSecurityPackages(), { - wrapper: TestProviders, - }); - - expect(useKibanaMock().services.fleet?.isInitialized).toHaveBeenCalled(); - expect(useKibanaMock().services.http.post).not.toHaveBeenCalled(); - - await waitFor(() => (useKibanaMock().services.http.post as jest.Mock).mock.calls.length > 0); - - expect(useKibanaMock().services.http.post).toHaveBeenCalledWith( - `${epmRouteService.getBulkInstallPath()}`, - expect.objectContaining({ - body: '{"packages":["endpoint","security_detection_engine"]}', - }) - ); - }); - - it('should send upgrade request with prerelease:false if in serverless', async () => { - mockGetKibanaVersion.mockReturnValue('8.0.0'); - mockGetKibanaBranch.mockReturnValue('main'); - mockBuildFlavor.mockReturnValue('serverless'); - - const { waitFor } = renderHook(() => useUpgradeSecurityPackages(), { - wrapper: TestProviders, - }); - - await waitFor(() => (useKibanaMock().services.http.post as jest.Mock).mock.calls.length > 0); - - expect(useKibanaMock().services.http.post).toHaveBeenCalledWith( - `${epmRouteService.getBulkInstallPath()}`, - expect.objectContaining({ - body: '{"packages":["endpoint","security_detection_engine"]}', - query: expect.objectContaining({ prerelease: false }), - }) - ); - }); - - it('should send upgrade request with prerelease:false if in serverless SNAPSHOT', async () => { - mockGetKibanaVersion.mockReturnValue('8.0.0-SNAPSHOT'); - mockGetKibanaBranch.mockReturnValue('main'); - mockBuildFlavor.mockReturnValue('serverless'); - - const { waitFor } = renderHook(() => useUpgradeSecurityPackages(), { - wrapper: TestProviders, - }); - - await waitFor(() => (useKibanaMock().services.http.post as jest.Mock).mock.calls.length > 0); - - expect(useKibanaMock().services.http.post).toHaveBeenCalledWith( - `${epmRouteService.getBulkInstallPath()}`, - expect.objectContaining({ - body: '{"packages":["endpoint","security_detection_engine"]}', - query: expect.objectContaining({ prerelease: false }), - }) - ); - }); - - it('should send upgrade request with prerelease:false if build does not include `-SNAPSHOT`', async () => { - mockGetKibanaVersion.mockReturnValue('8.0.0'); - mockGetKibanaBranch.mockReturnValue('release'); - mockBuildFlavor.mockReturnValue('traditional'); - - const { waitFor } = renderHook(() => useUpgradeSecurityPackages(), { - wrapper: TestProviders, - }); - - await waitFor(() => (useKibanaMock().services.http.post as jest.Mock).mock.calls.length > 0); - - expect(useKibanaMock().services.http.post).toHaveBeenCalledWith( - `${epmRouteService.getBulkInstallPath()}`, - expect.objectContaining({ - body: '{"packages":["endpoint","security_detection_engine"]}', - query: expect.objectContaining({ prerelease: false }), - }) - ); - }); - - it('should send upgrade request with prerelease:true if not serverless and branch is `main` AND build includes `-SNAPSHOT`', async () => { - mockGetKibanaVersion.mockReturnValue('8.0.0-SNAPSHOT'); - mockGetKibanaBranch.mockReturnValue('main'); - mockBuildFlavor.mockReturnValue('traditional'); - - const { waitFor } = renderHook(() => useUpgradeSecurityPackages(), { - wrapper: TestProviders, - }); - - await waitFor(() => (useKibanaMock().services.http.post as jest.Mock).mock.calls.length > 0); - - expect(useKibanaMock().services.http.post).toHaveBeenCalledWith( - `${epmRouteService.getBulkInstallPath()}`, - expect.objectContaining({ - body: '{"packages":["endpoint","security_detection_engine"]}', - query: expect.objectContaining({ prerelease: true }), - }) - ); - }); - - it('should send upgrade request with prerelease:true if branch is `release` and build includes `-SNAPSHOT`', async () => { - mockGetKibanaVersion.mockReturnValue('8.0.0-SNAPSHOT'); - mockGetKibanaBranch.mockReturnValue('release'); - mockBuildFlavor.mockReturnValue('traditional'); - - const { waitFor } = renderHook(() => useUpgradeSecurityPackages(), { - wrapper: TestProviders, - }); - - await waitFor(() => (useKibanaMock().services.http.post as jest.Mock).mock.calls.length > 0); - - expect(useKibanaMock().services.http.post).toHaveBeenCalledWith( - `${epmRouteService.getBulkInstallPath()}`, - expect.objectContaining({ - body: '{"packages":["endpoint","security_detection_engine"]}', - query: expect.objectContaining({ prerelease: true }), - }) - ); - }); - - it('should send upgrade request with prerelease:true if branch is `main` and build does not include `-SNAPSHOT`', async () => { - mockGetKibanaVersion.mockReturnValue('8.0.0'); - mockGetKibanaBranch.mockReturnValue('main'); - mockBuildFlavor.mockReturnValue('traditional'); - - const { waitFor } = renderHook(() => useUpgradeSecurityPackages(), { - wrapper: TestProviders, - }); - - await waitFor(() => (useKibanaMock().services.http.post as jest.Mock).mock.calls.length > 0); - - expect(useKibanaMock().services.http.post).toHaveBeenCalledWith( - `${epmRouteService.getBulkInstallPath()}`, - expect.objectContaining({ - body: '{"packages":["endpoint","security_detection_engine"]}', - query: expect.objectContaining({ prerelease: true }), - }) - ); - }); - - it('should send separate upgrade requests if prebuiltRulesPackageVersion is provided', async () => { - mockGetPrebuiltRulesPackageVersion.mockReturnValue('8.2.1'); - - const { waitFor } = renderHook(() => useUpgradeSecurityPackages(), { - wrapper: TestProviders, - }); - - await waitFor(() => (useKibanaMock().services.http.post as jest.Mock).mock.calls.length > 0); - - expect(useKibanaMock().services.http.post).toHaveBeenNthCalledWith( - 1, - `${epmRouteService.getInstallPath('security_detection_engine', '8.2.1')}`, - expect.objectContaining({ query: { prerelease: true } }) - ); - expect(useKibanaMock().services.http.post).toHaveBeenNthCalledWith( - 2, - `${epmRouteService.getBulkInstallPath()}`, - expect.objectContaining({ - body: expect.stringContaining('endpoint'), - query: expect.objectContaining({ prerelease: true }), - }) - ); - }); -}); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_upgrade_security_packages.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_upgrade_security_packages.ts index 8ad266169231d..e19cf2bcacf94 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_upgrade_security_packages.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_upgrade_security_packages.ts @@ -7,88 +7,29 @@ import { useIsMutating } from '@tanstack/react-query'; import { useEffect } from 'react'; -import { PREBUILT_RULES_PACKAGE_NAME } from '../../../../common/detection_engine/constants'; -import { useUserPrivileges } from '../../../common/components/user_privileges'; -import { KibanaServices, useKibana } from '../../../common/lib/kibana'; -import type { BulkInstallFleetPackagesProps, InstallFleetPackageProps } from '../api/api'; import { - BULK_INSTALL_FLEET_PACKAGES_MUTATION_KEY, - useBulkInstallFleetPackagesMutation, -} from '../api/hooks/use_bulk_install_fleet_packages_mutation'; -import { - INSTALL_FLEET_PACKAGE_MUTATION_KEY, - useInstallFleetPackageMutation, -} from '../api/hooks/use_install_fleet_package_mutation'; + BOOTSTRAP_PREBUILT_RULES_KEY, + useBootstrapPrebuiltRulesMutation, +} from '../api/hooks/use_bootstrap_prebuilt_rules'; /** * Install or upgrade the security packages (endpoint and prebuilt rules) */ export const useUpgradeSecurityPackages = () => { - const context = useKibana(); - const canAccessFleet = useUserPrivileges().endpointPrivileges.canAccessFleet; - const { mutate: bulkInstallFleetPackages } = useBulkInstallFleetPackagesMutation(); - const { mutate: installFleetPackage } = useInstallFleetPackageMutation(); + const { mutate: bootstrapPrebuiltRules } = useBootstrapPrebuiltRulesMutation(); useEffect(() => { - if (!canAccessFleet) { - return; - } - - (async () => { - // Make sure fleet is initialized first - await context.services.fleet?.isInitialized(); - - // Install the latest prerelease if in non-production non-serverless environments - const prerelease = - KibanaServices.getBuildFlavor() === 'traditional' && - (KibanaServices.getKibanaVersion().includes('-SNAPSHOT') || - KibanaServices.getKibanaBranch() === 'main'); - - const prebuiltRulesPackageVersion = KibanaServices.getPrebuiltRulesPackageVersion(); - // ignore the response for now since we aren't notifying the user - const packages = ['endpoint', PREBUILT_RULES_PACKAGE_NAME]; - - // If `prebuiltRulesPackageVersion` is provided, try to install that version - // Must be done as two separate requests as bulk API doesn't support versions - if (prebuiltRulesPackageVersion != null) { - installFleetPackage({ - packageName: PREBUILT_RULES_PACKAGE_NAME, - packageVersion: prebuiltRulesPackageVersion, - prerelease, - force: true, - }); - packages.splice(packages.indexOf(PREBUILT_RULES_PACKAGE_NAME), 1); - } - - // Note: if `prerelease:true` option is provided, endpoint package will also be installed as prerelease - bulkInstallFleetPackages({ - packages, - prerelease, - }); - })(); - }, [bulkInstallFleetPackages, canAccessFleet, context.services.fleet, installFleetPackage]); + bootstrapPrebuiltRules(); + }, [bootstrapPrebuiltRules]); }; /** * @returns true if the security packages are being installed or upgraded */ export const useIsUpgradingSecurityPackages = () => { - const isInstallingPackages = useIsMutating({ - predicate: ({ options: { mutationKey }, state: { variables } }) => { - // The mutation is bulk Fleet packages installation. Check if the packages include the prebuilt rules package - if (mutationKey === BULK_INSTALL_FLEET_PACKAGES_MUTATION_KEY) { - return (variables as BulkInstallFleetPackagesProps).packages.includes( - PREBUILT_RULES_PACKAGE_NAME - ); - } - - // The mutation is single Fleet package installation. Check if the package is the prebuilt rules package - if (mutationKey === INSTALL_FLEET_PACKAGE_MUTATION_KEY) { - return (variables as InstallFleetPackageProps).packageName === PREBUILT_RULES_PACKAGE_NAME; - } - return false; - }, + const bootstrappingRules = useIsMutating({ + mutationKey: BOOTSTRAP_PREBUILT_RULES_KEY, }); - return isInstallingPackages > 0; + return bootstrappingRules > 0; }; diff --git a/x-pack/plugins/security_solution/server/client/client.test.ts b/x-pack/plugins/security_solution/server/client/client.test.ts index 4c0c4361b3a72..59be6609b3374 100644 --- a/x-pack/plugins/security_solution/server/client/client.test.ts +++ b/x-pack/plugins/security_solution/server/client/client.test.ts @@ -17,7 +17,7 @@ describe('SiemClient', () => { [SIGNALS_INDEX_KEY]: 'mockSignalsIndex', }; const spaceId = 'fooSpace'; - const client = new AppClient(spaceId, mockConfig, '8.7', 'main'); + const client = new AppClient(spaceId, mockConfig, '8.7', 'main', 'traditional'); expect(client.getSignalsIndex()).toEqual('mockSignalsIndex-fooSpace'); }); diff --git a/x-pack/plugins/security_solution/server/client/client.ts b/x-pack/plugins/security_solution/server/client/client.ts index c7e63769ac1e0..b2418e0263699 100644 --- a/x-pack/plugins/security_solution/server/client/client.ts +++ b/x-pack/plugins/security_solution/server/client/client.ts @@ -5,6 +5,7 @@ * 2.0. */ +import type { BuildFlavor } from '@kbn/config'; import type { ConfigType } from '../config'; import { DEFAULT_ALERTS_INDEX, @@ -20,8 +21,15 @@ export class AppClient { private readonly sourcererDataViewId: string; private readonly kibanaVersion: string; private readonly kibanaBranch: string; + private readonly buildFlavor: BuildFlavor; - constructor(spaceId: string, config: ConfigType, kibanaVersion: string, kibanaBranch: string) { + constructor( + spaceId: string, + config: ConfigType, + kibanaVersion: string, + kibanaBranch: string, + buildFlavor: BuildFlavor + ) { const configuredSignalsIndex = config.signalsIndex; this.alertsIndex = `${DEFAULT_ALERTS_INDEX}-${spaceId}`; @@ -31,6 +39,7 @@ export class AppClient { this.spaceId = spaceId; this.kibanaVersion = kibanaVersion; this.kibanaBranch = kibanaBranch; + this.buildFlavor = buildFlavor; } public getAlertsIndex = (): string => this.alertsIndex; @@ -40,4 +49,5 @@ export class AppClient { public getSpaceId = (): string => this.spaceId; public getKibanaVersion = (): string => this.kibanaVersion; public getKibanaBranch = (): string => this.kibanaBranch; + public getBuildFlavor = (): BuildFlavor => this.buildFlavor; } diff --git a/x-pack/plugins/security_solution/server/client/factory.test.ts b/x-pack/plugins/security_solution/server/client/factory.test.ts index 681daf88fc0d9..81f9f73d759a7 100644 --- a/x-pack/plugins/security_solution/server/client/factory.test.ts +++ b/x-pack/plugins/security_solution/server/client/factory.test.ts @@ -23,6 +23,7 @@ describe('AppClientFactory', () => { config: createMockConfig(), kibanaVersion: '8.7', kibanaBranch: 'main', + buildFlavor: 'traditional', }); factory.create(mockRequest); @@ -30,6 +31,7 @@ describe('AppClientFactory', () => { 'mockSpace', expect.anything(), expect.anything(), + expect.anything(), expect.anything() ); }); @@ -42,6 +44,7 @@ describe('AppClientFactory', () => { config: createMockConfig(), kibanaVersion: '8.7', kibanaBranch: 'main', + buildFlavor: 'traditional', }); factory.create(mockRequest); @@ -49,6 +52,7 @@ describe('AppClientFactory', () => { 'default', expect.anything(), expect.anything(), + expect.anything(), expect.anything() ); }); diff --git a/x-pack/plugins/security_solution/server/client/factory.ts b/x-pack/plugins/security_solution/server/client/factory.ts index 3dc8925c0c6e3..d36b076e55531 100644 --- a/x-pack/plugins/security_solution/server/client/factory.ts +++ b/x-pack/plugins/security_solution/server/client/factory.ts @@ -6,6 +6,7 @@ */ import type { KibanaRequest } from '@kbn/core/server'; +import type { BuildFlavor } from '@kbn/config'; import { AppClient } from './client'; import type { ConfigType } from '../config'; import { invariant } from '../../common/utils/invariant'; @@ -15,6 +16,7 @@ interface SetupDependencies { config: ConfigType; kibanaVersion: string; kibanaBranch: string; + buildFlavor: BuildFlavor; } export class AppClientFactory { @@ -22,12 +24,20 @@ export class AppClientFactory { private config?: SetupDependencies['config']; private kibanaVersion?: string; private kibanaBranch?: string; + private buildFlavor?: BuildFlavor; - public setup({ getSpaceId, config, kibanaBranch, kibanaVersion }: SetupDependencies) { + public setup({ + getSpaceId, + config, + kibanaBranch, + kibanaVersion, + buildFlavor, + }: SetupDependencies) { this.getSpaceId = getSpaceId; this.config = config; this.kibanaVersion = kibanaVersion; this.kibanaBranch = kibanaBranch; + this.buildFlavor = buildFlavor; } public create(request: KibanaRequest): AppClient { @@ -43,8 +53,18 @@ export class AppClientFactory { this.kibanaBranch != null, 'Cannot create AppClient as kibanaBranch is not present. Did you forget to call setup()?' ); + invariant( + this.buildFlavor != null, + 'Cannot create AppClient as buildFlavor is not present. Did you forget to call setup()?' + ); const spaceId = this.getSpaceId?.(request) ?? 'default'; - return new AppClient(spaceId, this.config, this.kibanaVersion, this.kibanaBranch); + return new AppClient( + spaceId, + this.config, + this.kibanaVersion, + this.kibanaBranch, + this.buildFlavor + ); } } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.test.ts new file mode 100644 index 0000000000000..c2982d4b16092 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.test.ts @@ -0,0 +1,141 @@ +/* + * 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 { bootstrapPrebuiltRulesRoute } from './bootstrap_prebuilt_rules'; + +import type { Installation, RegistryPackage } from '@kbn/fleet-plugin/common'; +import { requestContextMock, serverMock } from '../../../routes/__mocks__'; +import { getBootstrapRulesRequest } from '../../../routes/__mocks__/request_responses'; + +const packageMock: RegistryPackage = { + name: 'detection_engine', + version: '1.0.0', + format_version: '1.0.0', + title: 'Test package', + description: 'Test package', + owner: { github: 'elastic' }, + download: '', + path: '', +}; + +const installationMock: Installation = { + name: 'detection_engine', + version: '1.0.0', + installed_kibana: [], + installed_es: [], + es_index_patterns: {}, + install_status: 'installed', + verification_status: 'verified', + install_version: '1.0.0', + install_source: 'registry', + install_started_at: '2021-08-02T15:00:00.000Z', +}; + +describe('bootstrap_prebuilt_rules_route', () => { + let server: ReturnType; + let { clients, context } = requestContextMock.createTools(); + + beforeEach(() => { + jest.clearAllMocks(); + server = serverMock.create(); + ({ clients, context } = requestContextMock.createTools()); + + bootstrapPrebuiltRulesRoute(server.router); + }); + + it('returns information about installed packages', async () => { + clients.internalFleetServices.packages.fetchFindLatestPackage.mockResolvedValue(packageMock); + clients.internalFleetServices.packages.ensureInstalledPackage.mockResolvedValue({ + status: 'installed', + package: installationMock, + }); + const response = await server.inject( + getBootstrapRulesRequest(), + requestContextMock.convertContext(context) + ); + expect(response.status).toEqual(200); + expect(response.body).toEqual({ + packages: expect.arrayContaining([ + expect.objectContaining({ + name: 'detection_engine', + version: '1.0.0', + status: 'installed', + }), + ]), + }); + }); + + it('installs pre-release packages in dev mode', async () => { + // Mock the package installation + clients.internalFleetServices.packages.fetchFindLatestPackage.mockResolvedValue(packageMock); + clients.internalFleetServices.packages.ensureInstalledPackage.mockResolvedValue({ + status: 'installed', + package: installationMock, + }); + + // Mock Kibana build and branch + clients.appClient.getBuildFlavor.mockReturnValue('traditional'); + clients.appClient.getKibanaBranch.mockReturnValue('main'); + + const response = await server.inject( + getBootstrapRulesRequest(), + requestContextMock.convertContext(context) + ); + expect(response.status).toEqual(200); + expect(clients.internalFleetServices.packages.fetchFindLatestPackage).toHaveBeenCalledWith( + 'security_detection_engine', + { prerelease: true } + ); + }); + + it('installs pre-release packages for release candidates', async () => { + // Mock the package installation + clients.internalFleetServices.packages.fetchFindLatestPackage.mockResolvedValue(packageMock); + clients.internalFleetServices.packages.ensureInstalledPackage.mockResolvedValue({ + status: 'installed', + package: installationMock, + }); + + // Mock Kibana build and branch + clients.appClient.getBuildFlavor.mockReturnValue('traditional'); + clients.appClient.getKibanaBranch.mockReturnValue('8.16.0'); + clients.appClient.getKibanaVersion.mockReturnValue('8.16.0-SNAPSHOT'); + + const response = await server.inject( + getBootstrapRulesRequest(), + requestContextMock.convertContext(context) + ); + expect(response.status).toEqual(200); + expect(clients.internalFleetServices.packages.fetchFindLatestPackage).toHaveBeenCalledWith( + 'security_detection_engine', + { prerelease: true } + ); + }); + + it('installs release packages for Serverless', async () => { + // Mock the package installation + clients.internalFleetServices.packages.fetchFindLatestPackage.mockResolvedValue(packageMock); + clients.internalFleetServices.packages.ensureInstalledPackage.mockResolvedValue({ + status: 'installed', + package: installationMock, + }); + + // Mock Kibana build and branch + clients.appClient.getBuildFlavor.mockReturnValue('serverless'); + clients.appClient.getKibanaBranch.mockReturnValue('main'); + + const response = await server.inject( + getBootstrapRulesRequest(), + requestContextMock.convertContext(context) + ); + expect(response.status).toEqual(200); + expect(clients.internalFleetServices.packages.fetchFindLatestPackage).toHaveBeenCalledWith( + 'security_detection_engine', + { prerelease: false } + ); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.ts new file mode 100644 index 0000000000000..d17435a543320 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.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 { transformError } from '@kbn/securitysolution-es-utils'; +import type { IKibanaResponse } from '@kbn/core/server'; +import { BOOTSTRAP_PREBUILT_RULES_URL } from '../../../../../../common/api/detection_engine/prebuilt_rules'; +import type { BootstrapPrebuiltRulesResponse } from '../../../../../../common/api/detection_engine/prebuilt_rules/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.gen'; +import type { SecuritySolutionPluginRouter } from '../../../../../types'; +import { buildSiemResponse } from '../../../routes/utils'; +import { + installEndpointPackage, + installPrebuiltRulesPackage, +} from '../install_prebuilt_rules_and_timelines/install_prebuilt_rules_package'; + +export const bootstrapPrebuiltRulesRoute = (router: SecuritySolutionPluginRouter) => { + router.versioned + .post({ + access: 'internal', + path: BOOTSTRAP_PREBUILT_RULES_URL, + options: { + tags: ['access:securitySolution'], + }, + }) + .addVersion( + { + version: '1', + validate: {}, + }, + async (context, _, response): Promise> => { + const siemResponse = buildSiemResponse(response); + + try { + const ctx = await context.resolve(['securitySolution']); + const securityContext = ctx.securitySolution; + const config = securityContext.getConfig(); + + const results = await Promise.all([ + installPrebuiltRulesPackage(config, securityContext), + installEndpointPackage(config, securityContext), + ]); + + const responseBody: BootstrapPrebuiltRulesResponse = { + packages: results.map((result) => ({ + name: result.package.name, + version: result.package.version, + status: result.status, + })), + }; + + return response.ok({ + body: responseBody, + }); + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } + } + ); +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/install_prebuilt_rules_package.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/install_prebuilt_rules_package.ts index 4bbc4c3561674..23bcc654eb780 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/install_prebuilt_rules_package.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/install_prebuilt_rules_package.ts @@ -6,7 +6,10 @@ */ import type { SecuritySolutionApiRequestHandlerContext } from '../../../../../types'; -import { PREBUILT_RULES_PACKAGE_NAME } from '../../../../../../common/detection_engine/constants'; +import { + ENDPOINT_PACKAGE_NAME, + PREBUILT_RULES_PACKAGE_NAME, +} from '../../../../../../common/detection_engine/constants'; import type { ConfigType } from '../../../../../config'; /** @@ -19,24 +22,45 @@ export async function installPrebuiltRulesPackage( config: ConfigType, context: SecuritySolutionApiRequestHandlerContext ) { - // Get package version from the config + const pkgVersion = await findLatestPackageVersion(config, context, PREBUILT_RULES_PACKAGE_NAME); + + return context + .getInternalFleetServices() + .packages.ensureInstalledPackage({ pkgName: PREBUILT_RULES_PACKAGE_NAME, pkgVersion }); +} + +export async function installEndpointPackage( + config: ConfigType, + context: SecuritySolutionApiRequestHandlerContext +) { + const pkgVersion = await findLatestPackageVersion(config, context, ENDPOINT_PACKAGE_NAME); + + return context.getInternalFleetServices().packages.ensureInstalledPackage({ + pkgName: ENDPOINT_PACKAGE_NAME, + pkgVersion, + }); +} + +async function findLatestPackageVersion( + config: ConfigType, + context: SecuritySolutionApiRequestHandlerContext, + packageName: string +) { let pkgVersion = config.prebuiltRulesPackageVersion; // Find latest package if the version isn't specified in the config if (!pkgVersion) { + const securityAppClient = context.getAppClient(); // Use prerelease versions in dev environment const isPrerelease = - context.getAppClient().getKibanaVersion().includes('-SNAPSHOT') || - context.getAppClient().getKibanaBranch() === 'main'; + securityAppClient.getBuildFlavor() === 'traditional' && + (securityAppClient.getKibanaVersion().includes('-SNAPSHOT') || + securityAppClient.getKibanaBranch() === 'main'); const result = await context .getInternalFleetServices() - .packages.fetchFindLatestPackage(PREBUILT_RULES_PACKAGE_NAME, { prerelease: isPrerelease }); + .packages.fetchFindLatestPackage(packageName, { prerelease: isPrerelease }); pkgVersion = result.version; } - - // Install the package - await context - .getInternalFleetServices() - .packages.ensureInstalledPackage({ pkgName: PREBUILT_RULES_PACKAGE_NAME, pkgVersion }); + return pkgVersion; } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/register_routes.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/register_routes.ts index 71740086e3fa5..c9871f86a43e2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/register_routes.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/register_routes.ts @@ -14,6 +14,7 @@ import { reviewRuleInstallationRoute } from './review_rule_installation/review_r import { reviewRuleUpgradeRoute } from './review_rule_upgrade/review_rule_upgrade_route'; import { performRuleInstallationRoute } from './perform_rule_installation/perform_rule_installation_route'; import { performRuleUpgradeRoute } from './perform_rule_upgrade/perform_rule_upgrade_route'; +import { bootstrapPrebuiltRulesRoute } from './bootstrap_prebuilt_rules/bootstrap_prebuilt_rules'; export const registerPrebuiltRulesRoutes = (router: SecuritySolutionPluginRouter) => { // Legacy endpoints that we're going to deprecate @@ -26,4 +27,5 @@ export const registerPrebuiltRulesRoutes = (router: SecuritySolutionPluginRouter performRuleUpgradeRoute(router); reviewRuleInstallationRoute(router); reviewRuleUpgradeRoute(router); + bootstrapPrebuiltRulesRoute(router); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts index fa9fcc1fa5e22..b39cba0cf4952 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts @@ -39,6 +39,8 @@ import { riskScoreDataClientMock } from '../../../entity_analytics/risk_score/ri import { assetCriticalityDataClientMock } from '../../../entity_analytics/asset_criticality/asset_criticality_data_client.mock'; import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks'; import { detectionRulesClientMock } from '../../rule_management/logic/detection_rules_client/__mocks__/detection_rules_client'; +import { packageServiceMock } from '@kbn/fleet-plugin/server/services/epm/package_service.mock'; +import type { EndpointInternalFleetServicesInterface } from '../../../../endpoint/services/fleet'; export const createMockClients = () => { const core = coreMock.createRequestHandlerContext(); @@ -70,6 +72,10 @@ export const createMockClients = () => { riskEngineDataClient: riskEngineDataClientMock.create(), riskScoreDataClient: riskScoreDataClientMock.create(), assetCriticalityDataClient: assetCriticalityDataClientMock.create(), + + internalFleetServices: { + packages: packageServiceMock.createClient(), + }, }; }; @@ -146,10 +152,9 @@ const createSecuritySolutionRequestContextMock = ( getDetectionEngineHealthClient: jest.fn(() => clients.detectionEngineHealthClient), getRuleExecutionLog: jest.fn(() => clients.ruleExecutionLog), getExceptionListClient: jest.fn(() => clients.lists.exceptionListClient), - getInternalFleetServices: jest.fn(() => { - // TODO: Mock EndpointInternalFleetServicesInterface and return the mocked object. - throw new Error('Not implemented'); - }), + getInternalFleetServices: jest.fn( + () => clients.internalFleetServices as unknown as EndpointInternalFleetServicesInterface + ), getRiskEngineDataClient: jest.fn(() => clients.riskEngineDataClient), getRiskScoreDataClient: jest.fn(() => clients.riskScoreDataClient), getAssetCriticalityDataClient: jest.fn(() => clients.assetCriticalityDataClient), diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts index 2bd7efdbbdcf2..3a48bb449c55d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -33,6 +33,7 @@ import { import { RULE_MANAGEMENT_FILTERS_URL } from '../../../../../common/api/detection_engine/rule_management/urls'; import { + BOOTSTRAP_PREBUILT_RULES_URL, PREBUILT_RULES_STATUS_URL, PREBUILT_RULES_URL, } from '../../../../../common/api/detection_engine/prebuilt_rules'; @@ -203,6 +204,12 @@ export const getRuleManagementFiltersRequest = () => path: RULE_MANAGEMENT_FILTERS_URL, }); +export const getBootstrapRulesRequest = () => + requestMock.create({ + method: 'post', + path: BOOTSTRAP_PREBUILT_RULES_URL, + }); + export interface FindHit { page: number; perPage: number; diff --git a/x-pack/plugins/security_solution/server/mocks.ts b/x-pack/plugins/security_solution/server/mocks.ts index 7e71246fd1c2c..1d838476a8325 100644 --- a/x-pack/plugins/security_solution/server/mocks.ts +++ b/x-pack/plugins/security_solution/server/mocks.ts @@ -18,6 +18,9 @@ const createAppClientMock = (): AppClientMock => getAlertsIndex: jest.fn(), getSignalsIndex: jest.fn(), getSourcererDataViewId: jest.fn().mockReturnValue('security-solution'), + getKibanaVersion: jest.fn().mockReturnValue('8.0.0'), + getKibanaBranch: jest.fn().mockReturnValue('main'), + getBuildFlavor: jest.fn().mockReturnValue('traditional'), } as unknown as AppClientMock); export const siemMock = { diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index 0fb3f0355fbf5..c24e70baa5db8 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -224,6 +224,7 @@ export class Plugin implements ISecuritySolutionPlugin { ruleMonitoringService: this.ruleMonitoringService, kibanaVersion: pluginContext.env.packageInfo.version, kibanaBranch: pluginContext.env.packageInfo.branch, + buildFlavor: pluginContext.env.packageInfo.buildFlavor, }); productFeaturesService.registerApiAccessControl(core.http); @@ -417,6 +418,7 @@ export class Plugin implements ISecuritySolutionPlugin { config, kibanaVersion: pluginContext.env.packageInfo.version, kibanaBranch: pluginContext.env.packageInfo.branch, + buildFlavor: pluginContext.env.packageInfo.buildFlavor, }); const endpointFieldsStrategy = endpointFieldsProvider( diff --git a/x-pack/plugins/security_solution/server/request_context_factory.ts b/x-pack/plugins/security_solution/server/request_context_factory.ts index 780b69681caf4..4bda7e0338aa8 100644 --- a/x-pack/plugins/security_solution/server/request_context_factory.ts +++ b/x-pack/plugins/security_solution/server/request_context_factory.ts @@ -9,6 +9,7 @@ import { memoize } from 'lodash'; import type { Logger, KibanaRequest, RequestHandlerContext } from '@kbn/core/server'; +import type { BuildFlavor } from '@kbn/config'; import { DEFAULT_SPACE_ID } from '../common/constants'; import { AppClientFactory } from './client'; import type { ConfigType } from './config'; @@ -47,6 +48,7 @@ interface ConstructorOptions { ruleMonitoringService: IRuleMonitoringService; kibanaVersion: string; kibanaBranch: string; + buildFlavor: BuildFlavor; } export class RequestContextFactory implements IRequestContextFactory { @@ -79,6 +81,7 @@ export class RequestContextFactory implements IRequestContextFactory { config, kibanaVersion: options.kibanaVersion, kibanaBranch: options.kibanaBranch, + buildFlavor: options.buildFlavor, }); const getAuditLogger = () => security?.audit.asScoped(request); diff --git a/x-pack/plugins/security_solution/tsconfig.json b/x-pack/plugins/security_solution/tsconfig.json index ba89ca2864d74..3258167eb50b7 100644 --- a/x-pack/plugins/security_solution/tsconfig.json +++ b/x-pack/plugins/security_solution/tsconfig.json @@ -208,5 +208,6 @@ "@kbn/core-theme-browser", "@kbn/integration-assistant-plugin", "@kbn/avc-banner", + "@kbn/config", ] } diff --git a/x-pack/test/api_integration/services/security_solution_api.gen.ts b/x-pack/test/api_integration/services/security_solution_api.gen.ts index 676fea3fd5364..582b7feea4d55 100644 --- a/x-pack/test/api_integration/services/security_solution_api.gen.ts +++ b/x-pack/test/api_integration/services/security_solution_api.gen.ts @@ -129,6 +129,16 @@ after 30 days. It also deletes other artifacts specific to the migration impleme .set(ELASTIC_HTTP_VERSION_HEADER, '1') .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); }, + /** + * Ensures that the packages needed for prebuilt detection rules to work are installed and up to date + */ + bootstrapPrebuiltRules() { + return supertest + .post('/internal/detection_engine/prebuilt_rules/_bootstrap') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); + }, /** * Create new detection rules in bulk. */ diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/bootstrap_prebuilt_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/bootstrap_prebuilt_rules.ts new file mode 100644 index 0000000000000..97287ef0cdb93 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/bootstrap_prebuilt_rules.ts @@ -0,0 +1,64 @@ +/* + * 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 { + ENDPOINT_PACKAGE_NAME, + PREBUILT_RULES_PACKAGE_NAME, +} from '@kbn/security-solution-plugin/common/detection_engine/constants'; +import expect from 'expect'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { deletePrebuiltRulesFleetPackage } from '../../../../utils'; +import { deleteEndpointFleetPackage } from '../../../../utils/rules/prebuilt_rules/delete_endpoint_fleet_package'; + +export default ({ getService }: FtrProviderContext): void => { + const supertest = getService('supertest'); + const securitySolutionApi = getService('securitySolutionApi'); + + describe('@ess @serverless @skipInServerlessMKI Bootstrap Prebuilt Rules', () => { + beforeEach(async () => { + await deletePrebuiltRulesFleetPackage(supertest); + await deleteEndpointFleetPackage(supertest); + }); + + it('should install fleet packages required for detection engine to function', async () => { + const { body } = await securitySolutionApi.bootstrapPrebuiltRules().expect(200); + + expect(body).toMatchObject({ + packages: expect.arrayContaining([ + expect.objectContaining({ + name: PREBUILT_RULES_PACKAGE_NAME, + status: 'installed', + }), + expect.objectContaining({ + name: ENDPOINT_PACKAGE_NAME, + status: 'installed', + }), + ]), + }); + }); + + it('should skip installing fleet packages if they are already installed', async () => { + // Install the packages + await securitySolutionApi.bootstrapPrebuiltRules().expect(200); + // Try to install the packages again + const { body } = await securitySolutionApi.bootstrapPrebuiltRules().expect(200); + + expect(body).toMatchObject({ + packages: expect.arrayContaining([ + expect.objectContaining({ + name: PREBUILT_RULES_PACKAGE_NAME, + status: 'already_installed', + }), + expect.objectContaining({ + name: ENDPOINT_PACKAGE_NAME, + status: 'already_installed', + }), + ]), + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/index.ts index 4d17ef3f63f72..8266fba86a099 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/index.ts @@ -9,6 +9,7 @@ import { FtrProviderContext } from '../../../../../../ftr_provider_context'; export default ({ loadTestFile }: FtrProviderContext): void => { describe('Rules Management - Prebuilt Rules - Prebuilt Rules Management', function () { + loadTestFile(require.resolve('./bootstrap_prebuilt_rules')); loadTestFile(require.resolve('./get_prebuilt_rules_status')); loadTestFile(require.resolve('./get_prebuilt_timelines_status')); loadTestFile(require.resolve('./install_prebuilt_rules')); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/delete_endpoint_fleet_package.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/delete_endpoint_fleet_package.ts new file mode 100644 index 0000000000000..e53e24f98de3b --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/delete_endpoint_fleet_package.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 { epmRouteService } from '@kbn/fleet-plugin/common'; +import { ENDPOINT_PACKAGE_NAME } from '@kbn/security-solution-plugin/common/detection_engine/constants'; +import type SuperTest from 'supertest'; + +/** + * Delete the endpoint package using fleet API. + * + * @param supertest Supertest instance + */ +export async function deleteEndpointFleetPackage(supertest: SuperTest.Agent) { + const resp = await supertest + .get(epmRouteService.getInfoPath(ENDPOINT_PACKAGE_NAME)) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send(); + + if (resp.status === 200 && resp.body.response.status === 'installed') { + await supertest + .delete(epmRouteService.getRemovePath(ENDPOINT_PACKAGE_NAME, resp.body.response.version)) + .set('kbn-xsrf', 'true') + .send({ force: true }); + } +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/delete_prebuilt_rules_fleet_package.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/delete_prebuilt_rules_fleet_package.ts index a3468ccb32b5a..930c9d39757f4 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/delete_prebuilt_rules_fleet_package.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/delete_prebuilt_rules_fleet_package.ts @@ -6,6 +6,7 @@ */ import { epmRouteService } from '@kbn/fleet-plugin/common'; +import { PREBUILT_RULES_PACKAGE_NAME } from '@kbn/security-solution-plugin/common/detection_engine/constants'; import type SuperTest from 'supertest'; /** @@ -15,7 +16,7 @@ import type SuperTest from 'supertest'; */ export async function deletePrebuiltRulesFleetPackage(supertest: SuperTest.Agent) { const resp = await supertest - .get(epmRouteService.getInfoPath('security_detection_engine')) + .get(epmRouteService.getInfoPath(PREBUILT_RULES_PACKAGE_NAME)) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .send(); @@ -23,7 +24,7 @@ export async function deletePrebuiltRulesFleetPackage(supertest: SuperTest.Agent if (resp.status === 200 && resp.body.response.status === 'installed') { await supertest .delete( - epmRouteService.getRemovePath('security_detection_engine', resp.body.response.version) + epmRouteService.getRemovePath(PREBUILT_RULES_PACKAGE_NAME, resp.body.response.version) ) .set('kbn-xsrf', 'true') .send({ force: true }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/sourcerer/sourcerer_timeline.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/sourcerer/sourcerer_timeline.cy.ts index fbb434c4d7263..bb4010a6db4f6 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/sourcerer/sourcerer_timeline.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/sourcerer/sourcerer_timeline.cy.ts @@ -31,7 +31,7 @@ import { saveSourcerer, } from '../../../tasks/sourcerer'; import { openTimelineUsingToggle } from '../../../tasks/security_main'; -import { waitForFleetSetup } from '../../../tasks/fleet_integrations'; +import { waitForRulesBootstrap } from '../../../tasks/fleet_integrations'; import { SOURCERER } from '../../../screens/sourcerer'; import { createTimeline, deleteTimelines } from '../../../tasks/api_calls/timelines'; import { getTimelineModifiedSourcerer } from '../../../objects/timeline'; @@ -42,7 +42,7 @@ const dataViews = ['logs-*', 'metrics-*', '.kibana-event-log-*']; describe('Timeline scope', { tags: ['@ess', '@serverless', '@skipInServerless'] }, () => { before(() => { - waitForFleetSetup(); + waitForRulesBootstrap(); }); beforeEach(() => { diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/prebuilt_rules.ts b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/prebuilt_rules.ts index 273491ebd2efe..f4273cad22299 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/prebuilt_rules.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/prebuilt_rules.ts @@ -8,6 +8,7 @@ import { PerformRuleInstallationResponseBody, PERFORM_RULE_INSTALLATION_URL, + BOOTSTRAP_PREBUILT_RULES_URL, } from '@kbn/security-solution-plugin/common/api/detection_engine'; import { ELASTIC_SECURITY_RULE_ID } from '@kbn/security-solution-plugin/common/detection_engine/constants'; import type { PrePackagedRulesStatusResponse } from '@kbn/security-solution-plugin/public/detection_engine/rule_management/logic/types'; @@ -203,8 +204,7 @@ export const getRuleAssets = (index: string | undefined = '.kibana_security_solu /* during e2e tests, and allow for manual installation of mock rules instead. */ export const preventPrebuiltRulesPackageInstallation = () => { cy.log('Prevent prebuilt rules package installation'); - cy.intercept('POST', '/api/fleet/epm/packages/_bulk*', {}); - cy.intercept('POST', '/api/fleet/epm/packages/security_detection_engine/*', {}); + cy.intercept('POST', BOOTSTRAP_PREBUILT_RULES_URL, {}); }; /** diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/fleet_integrations.ts b/x-pack/test/security_solution_cypress/cypress/tasks/fleet_integrations.ts index 7105ebc4df70f..f0b59a39b8478 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/fleet_integrations.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/fleet_integrations.ts @@ -6,6 +6,7 @@ */ import { + BOOTSTRAP_PREBUILT_RULES_URL, GET_ALL_INTEGRATIONS_URL, Integration, } from '@kbn/security-solution-plugin/common/api/detection_engine'; @@ -21,10 +22,10 @@ export const mockFleetIntegrations = (integrations: Integration[] = []) => { }).as('integrations'); }; -export const waitForFleetSetup = () => { - cy.intercept('POST', '/api/fleet/epm/packages/_bulk?prerelease=true').as('fleetSetup'); +export const waitForRulesBootstrap = () => { + cy.intercept('POST', BOOTSTRAP_PREBUILT_RULES_URL).as('rulesBootstrap'); cy.clearLocalStorage(); login(); visitGetStartedPage(); - cy.wait('@fleetSetup'); + cy.wait('@rulesBootstrap'); }; From a76390722ea22fdc5e5764c319754d66f0defbb1 Mon Sep 17 00:00:00 2001 From: Janki Salvi <117571355+js-jankisalvi@users.noreply.github.com> Date: Wed, 7 Aug 2024 13:50:45 +0100 Subject: [PATCH 09/44] [ResponseOps][Cases] Fix AllCasesList test (#190032) ## Summary Fixes https://github.com/elastic/kibana/issues/148095 I updated whole test file to use `findBy` instead of `getBy` and removed unnecessary `await waitFor`. --- .../all_cases/all_cases_list.test.tsx | 405 ++++++++---------- 1 file changed, 176 insertions(+), 229 deletions(-) diff --git a/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx b/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx index 242fc3260b7e2..5324a5a793067 100644 --- a/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx @@ -184,31 +184,28 @@ describe('AllCasesListGeneric', () => { useLicenseMock.mockReturnValue({ isAtLeastPlatinum: () => true }); appMockRenderer.render(); - await waitFor(() => { - expect(screen.getAllByTestId('case-details-link')[0]).toHaveAttribute( - 'href', - '/app/security/cases/test' - ); - expect(screen.getAllByTestId('case-details-link')[0]).toHaveTextContent( - useGetCasesMockState.data.cases[0].title - ); - expect( - screen.getAllByTestId('case-user-profile-avatar-damaged_raccoon')[0] - ).toHaveTextContent('DR'); - expect(screen.getAllByTestId('case-table-column-tags-coke')[0]).toHaveAttribute( - 'title', - useGetCasesMockState.data.cases[0].tags[0] - ); - expect( - screen.getAllByTestId('case-table-column-createdAt')[0].querySelector('.euiToolTipAnchor') - ).toHaveTextContent(removeMsFromDate(useGetCasesMockState.data.cases[0].createdAt)); - expect(screen.getByTestId('case-table-case-count')).toHaveTextContent( - `Showing 10 of ${useGetCasesMockState.data.total} cases` - ); + const caseDetailsLinks = await screen.findAllByTestId('case-details-link'); + + expect(caseDetailsLinks[0]).toHaveAttribute('href', '/app/security/cases/test'); + expect(caseDetailsLinks[0]).toHaveTextContent(useGetCasesMockState.data.cases[0].title); + expect( + (await screen.findAllByTestId('case-user-profile-avatar-damaged_raccoon'))[0] + ).toHaveTextContent('DR'); + expect((await screen.findAllByTestId('case-table-column-tags-coke'))[0]).toHaveAttribute( + 'title', + useGetCasesMockState.data.cases[0].tags[0] + ); + expect( + (await screen.findAllByTestId('case-table-column-createdAt'))[0].querySelector( + '.euiToolTipAnchor' + ) + ).toHaveTextContent(removeMsFromDate(useGetCasesMockState.data.cases[0].createdAt)); + expect(await screen.findByTestId('case-table-case-count')).toHaveTextContent( + `Showing 10 of ${useGetCasesMockState.data.total} cases` + ); - expect(screen.queryByTestId('all-cases-maximum-limit-warning')).not.toBeInTheDocument(); - expect(screen.queryByTestId('all-cases-clear-filters-link-icon')).not.toBeInTheDocument(); - }); + expect(screen.queryByTestId('all-cases-maximum-limit-warning')).not.toBeInTheDocument(); + expect(screen.queryByTestId('all-cases-clear-filters-link-icon')).not.toBeInTheDocument(); }); it("should show a tooltip with the assignee's email when hover over the assignee avatar", async () => { @@ -216,21 +213,17 @@ describe('AllCasesListGeneric', () => { appMockRenderer.render(); - userEvent.hover(screen.queryAllByTestId('case-user-profile-avatar-damaged_raccoon')[0]); + userEvent.hover((await screen.findAllByTestId('case-user-profile-avatar-damaged_raccoon'))[0]); - await waitFor(() => { - expect(screen.getByText('damaged_raccoon@elastic.co')).toBeInTheDocument(); - }); + expect(await screen.findByText('damaged_raccoon@elastic.co')).toBeInTheDocument(); }); it('should show a tooltip with all tags when hovered', async () => { appMockRenderer.render(); - userEvent.hover(screen.queryAllByTestId('case-table-column-tags')[0]); + userEvent.hover((await screen.findAllByTestId('case-table-column-tags'))[0]); - await waitFor(() => { - expect(screen.getByTestId('case-table-column-tags-tooltip')).toBeTruthy(); - }); + expect(await screen.findByTestId('case-table-column-tags-tooltip')).toBeTruthy(); }); it('should render empty fields', async () => { @@ -259,8 +252,10 @@ describe('AllCasesListGeneric', () => { appMockRenderer.render(); - const checkIt = (columnName: string, key: number) => { - const column = screen.getByTestId('cases-table').querySelectorAll('tbody .euiTableRowCell'); + const checkIt = async (columnName: string, key: number) => { + const column = (await screen.findByTestId('cases-table')).querySelectorAll( + 'tbody .euiTableRowCell' + ); expect(column[key].querySelector('.euiTableRowCell--hideForDesktop')).toHaveTextContent( columnName ); @@ -290,7 +285,7 @@ describe('AllCasesListGeneric', () => { }, }); appMockRenderer.render(); - userEvent.click(screen.getByTestId('cases-table-add-case')); + userEvent.click(await screen.findByTestId('cases-table-add-case')); await waitFor(() => { expect(onRowClick).not.toHaveBeenCalled(); }); @@ -299,7 +294,7 @@ describe('AllCasesListGeneric', () => { it('should tableHeaderSortButton AllCasesList', async () => { appMockRenderer.render(); - userEvent.click(screen.getAllByTestId('tableHeaderSortButton')[0]); + userEvent.click((await screen.findAllByTestId('tableHeaderSortButton'))[0]); await waitFor(() => { expect(useGetCasesMock).toBeCalledWith( @@ -315,28 +310,26 @@ describe('AllCasesListGeneric', () => { it('renders the columns correctly', async () => { appMockRenderer.render(); - const casesTable = within(screen.getByTestId('cases-table')); - - expect(casesTable.getByTitle('Name')).toBeInTheDocument(); - expect(casesTable.getByTitle('Category')).toBeInTheDocument(); - expect(casesTable.getByTitle('Created on')).toBeInTheDocument(); - expect(casesTable.getByTitle('Updated on')).toBeInTheDocument(); - expect(casesTable.getByTitle('Status')).toBeInTheDocument(); - expect(casesTable.getByTitle('Severity')).toBeInTheDocument(); - expect(casesTable.getByTitle('Tags')).toBeInTheDocument(); - expect(casesTable.getByTitle('Alerts')).toBeInTheDocument(); - expect(casesTable.getByTitle('Comments')).toBeInTheDocument(); - expect(casesTable.getByTitle('External incident')).toBeInTheDocument(); - expect(casesTable.getByTitle('Actions')).toBeInTheDocument(); + const casesTable = within(await screen.findByTestId('cases-table')); + + expect(await casesTable.findByTitle('Name')).toBeInTheDocument(); + expect(await casesTable.findByTitle('Category')).toBeInTheDocument(); + expect(await casesTable.findByTitle('Created on')).toBeInTheDocument(); + expect(await casesTable.findByTitle('Updated on')).toBeInTheDocument(); + expect(await casesTable.findByTitle('Status')).toBeInTheDocument(); + expect(await casesTable.findByTitle('Severity')).toBeInTheDocument(); + expect(await casesTable.findByTitle('Tags')).toBeInTheDocument(); + expect(await casesTable.findByTitle('Alerts')).toBeInTheDocument(); + expect(await casesTable.findByTitle('Comments')).toBeInTheDocument(); + expect(await casesTable.findByTitle('External incident')).toBeInTheDocument(); + expect(await casesTable.findByTitle('Actions')).toBeInTheDocument(); }); it('should not render table utility bar when isSelectorView=true', async () => { appMockRenderer.render(); - await waitFor(() => { - expect(screen.queryByTestId('case-table-selected-case-count')).not.toBeInTheDocument(); - expect(screen.queryByTestId('case-table-bulk-actions')).not.toBeInTheDocument(); - }); + expect(screen.queryByTestId('case-table-selected-case-count')).not.toBeInTheDocument(); + expect(screen.queryByTestId('case-table-bulk-actions')).not.toBeInTheDocument(); }); it('should not render table utility bar when the user does not have permissions to delete', async () => { @@ -346,31 +339,28 @@ describe('AllCasesListGeneric', () => { ); - await waitFor(() => { - expect(screen.queryByTestId('case-table-selected-case-count')).not.toBeInTheDocument(); - expect(screen.queryByTestId('case-table-bulk-actions')).not.toBeInTheDocument(); - }); + expect(screen.queryByTestId('case-table-selected-case-count')).not.toBeInTheDocument(); + expect(screen.queryByTestId('case-table-bulk-actions')).not.toBeInTheDocument(); }); it('should render metrics when isSelectorView=false', async () => { appMockRenderer.render(); - await waitFor(() => { - expect(screen.getByTestId('cases-metrics-stats')).toBeInTheDocument(); - }); + + expect(await screen.findByTestId('cases-metrics-stats')).toBeInTheDocument(); }); it('should not render metrics when isSelectorView=true', async () => { appMockRenderer.render(); - await waitFor(() => { - expect(screen.queryByTestId('case-table-selected-case-count')).not.toBeInTheDocument(); - expect(screen.queryByTestId('cases-metrics-stats')).not.toBeInTheDocument(); - }); + + expect(screen.queryByTestId('case-table-selected-case-count')).not.toBeInTheDocument(); + expect(screen.queryByTestId('cases-metrics-stats')).not.toBeInTheDocument(); }); it('should call onRowClick with no cases and isSelectorView=true when create case is clicked', async () => { appMockRenderer.render(); - userEvent.click(screen.getByTestId('cases-table-add-case-filter-bar')); + userEvent.click(await screen.findByTestId('cases-table-add-case-filter-bar')); const isCreateCase = true; + await waitFor(() => { expect(onRowClick).toHaveBeenCalled(); expect(onRowClick).toBeCalledWith(undefined, isCreateCase); @@ -382,7 +372,7 @@ describe('AllCasesListGeneric', () => { appMockRenderer.render(); - userEvent.click(screen.getByTestId(`cases-table-row-select-${theCase.id}`)); + userEvent.click(await screen.findByTestId(`cases-table-row-select-${theCase.id}`)); await waitFor(() => { expect(onRowClick).toHaveBeenCalledWith(theCase); @@ -392,7 +382,7 @@ describe('AllCasesListGeneric', () => { it('should NOT call onRowClick when clicking a case with modal=true', async () => { appMockRenderer.render(); - userEvent.click(screen.getByTestId('cases-table-row-1')); + userEvent.click(await screen.findByTestId('cases-table-row-1')); await waitFor(() => { expect(onRowClick).not.toHaveBeenCalled(); @@ -403,7 +393,7 @@ describe('AllCasesListGeneric', () => { appMockRenderer.render(); // 0 is the status filter button label - userEvent.click(screen.getAllByTitle('Status')[1]); + userEvent.click((await screen.findAllByTitle('Status'))[1]); await waitFor(() => { expect(useGetCasesMock).toHaveBeenLastCalledWith( @@ -420,20 +410,19 @@ describe('AllCasesListGeneric', () => { it('should render Name, Category, CreatedOn and Severity columns when isSelectorView=true', async () => { appMockRenderer.render(); - await waitFor(() => { - expect(screen.getByTitle('Name')).toBeInTheDocument(); - expect(screen.getByTitle('Category')).toBeInTheDocument(); - expect(screen.getByTitle('Created on')).toBeInTheDocument(); - // 0 is the severity filter button label - expect(screen.getAllByTitle('Severity')[1]).toBeInTheDocument(); - }); + + expect(await screen.findByTitle('Name')).toBeInTheDocument(); + expect(await screen.findByTitle('Category')).toBeInTheDocument(); + expect(await screen.findByTitle('Created on')).toBeInTheDocument(); + // 0 is the severity filter button label + expect((await screen.findAllByTitle('Severity'))[1]).toBeInTheDocument(); }); it('should sort by severity', async () => { appMockRenderer.render(); // 0 is the severity filter button label - userEvent.click(screen.getAllByTitle('Severity')[1]); + userEvent.click((await screen.findAllByTitle('Severity'))[1]); await waitFor(() => { expect(useGetCasesMock).toHaveBeenLastCalledWith( @@ -451,7 +440,7 @@ describe('AllCasesListGeneric', () => { it('should sort by title', async () => { appMockRenderer.render(); - userEvent.click(screen.getByTitle('Name')); + userEvent.click(await screen.findByTitle('Name')); await waitFor(() => { expect(useGetCasesMock).toHaveBeenLastCalledWith( @@ -469,7 +458,7 @@ describe('AllCasesListGeneric', () => { it('should sort by updatedOn', async () => { appMockRenderer.render(); - userEvent.click(screen.getByTitle('Updated on')); + userEvent.click(await screen.findByTitle('Updated on')); await waitFor(() => { expect(useGetCasesMock).toHaveBeenLastCalledWith( @@ -487,7 +476,7 @@ describe('AllCasesListGeneric', () => { it('should sort by category', async () => { appMockRenderer.render(); - userEvent.click(screen.getByTitle('Category')); + userEvent.click(await screen.findByTitle('Category')); await waitFor(() => { expect(useGetCasesMock).toHaveBeenLastCalledWith( @@ -505,9 +494,9 @@ describe('AllCasesListGeneric', () => { it('should filter by category', async () => { appMockRenderer.render(); - userEvent.click(screen.getByTestId('options-filter-popover-button-category')); + userEvent.click(await screen.findByTestId('options-filter-popover-button-category')); await waitForEuiPopoverOpen(); - userEvent.click(screen.getByTestId('options-filter-popover-item-twix')); + userEvent.click(await screen.findByTestId('options-filter-popover-item-twix')); await waitFor(() => { expect(useGetCasesMock).toHaveBeenLastCalledWith({ @@ -524,17 +513,17 @@ describe('AllCasesListGeneric', () => { it('should show the correct count on stats', async () => { appMockRenderer.render(); - userEvent.click(screen.getByTestId('options-filter-popover-button-status')); + userEvent.click(await screen.findByTestId('options-filter-popover-button-status')); - await waitFor(() => { - expect(screen.getByTestId('options-filter-popover-item-open')).toHaveTextContent('Open (20)'); - expect(screen.getByTestId('options-filter-popover-item-in-progress')).toHaveTextContent( - 'In progress (40)' - ); - expect(screen.getByTestId('options-filter-popover-item-closed')).toHaveTextContent( - 'Closed (130)' - ); - }); + expect(await screen.findByTestId('options-filter-popover-item-open')).toHaveTextContent( + 'Open (20)' + ); + expect(await screen.findByTestId('options-filter-popover-item-in-progress')).toHaveTextContent( + 'In progress (40)' + ); + expect(await screen.findByTestId('options-filter-popover-item-closed')).toHaveTextContent( + 'Closed (130)' + ); }); it('shows Solution column if there are no set owners', async () => { @@ -544,17 +533,13 @@ describe('AllCasesListGeneric', () => { ); - await waitFor(() => { - expect(screen.getAllByText('Solution')[0]).toBeInTheDocument(); - }); + expect((await screen.findAllByText('Solution'))[0]).toBeInTheDocument(); }); it('hides Solution column if there is a set owner', async () => { appMockRenderer.render(); - await waitFor(() => { - expect(screen.queryByText('Solution')).not.toBeInTheDocument(); - }); + expect(screen.queryByText('Solution')).not.toBeInTheDocument(); }); it('should deselect cases when refreshing', async () => { @@ -568,7 +553,7 @@ describe('AllCasesListGeneric', () => { expect(checkbox).toBeChecked(); } - userEvent.click(screen.getByText('Refresh')); + userEvent.click(await screen.findByText('Refresh')); for (const checkbox of checkboxes) { expect(checkbox).not.toBeChecked(); } @@ -593,9 +578,9 @@ describe('AllCasesListGeneric', () => { expect(checkbox).toBeChecked(); } - userEvent.click(screen.getByTestId('options-filter-popover-button-status')); + userEvent.click(await screen.findByTestId('options-filter-popover-button-status')); await waitForEuiPopoverOpen(); - userEvent.click(screen.getByTestId('options-filter-popover-item-open')); + userEvent.click(await screen.findByTestId('options-filter-popover-item-open')); for (const checkbox of checkboxes) { expect(checkbox).not.toBeChecked(); @@ -611,10 +596,8 @@ describe('AllCasesListGeneric', () => { ); - await waitFor(() => { - expect(screen.getByTestId('cases-table')).toBeTruthy(); - expect(screen.queryAllByTestId('case-table-column-alertsCount').length).toBe(0); - }); + expect(await screen.findByTestId('cases-table')).toBeInTheDocument(); + expect(screen.queryAllByTestId('case-table-column-alertsCount').length).toBe(0); }); it('should show the alerts column if the alert feature is enabled', async () => { @@ -659,13 +642,13 @@ describe('AllCasesListGeneric', () => { describe('Solutions', () => { it('should hide the solutions filter if the owner is provided', async () => { - const { queryByTestId } = render( + render( ); - expect(queryByTestId('options-filter-popover-button-owner')).toBeFalsy(); + expect(screen.queryByTestId('options-filter-popover-button-owner')).not.toBeInTheDocument(); }); }); @@ -677,16 +660,13 @@ describe('AllCasesListGeneric', () => { it('Renders bulk action', async () => { appMockRenderer.render(); - await waitFor(() => { - expect(screen.getByTestId('cases-table')).toBeInTheDocument(); - }); + expect(await screen.findByTestId('cases-table')).toBeInTheDocument(); - userEvent.click(screen.getByTestId('checkboxSelectAll')); - expect(screen.getByText('Bulk actions')).toBeInTheDocument(); - userEvent.click(screen.getByText('Bulk actions')); + userEvent.click(await screen.findByTestId('checkboxSelectAll')); + userEvent.click(await screen.findByText('Bulk actions')); - expect(screen.getByTestId('case-bulk-action-status')).toBeInTheDocument(); - expect(screen.getByTestId('cases-bulk-action-delete')).toBeInTheDocument(); + expect(await screen.findByTestId('case-bulk-action-status')).toBeInTheDocument(); + expect(await screen.findByTestId('cases-bulk-action-delete')).toBeInTheDocument(); }); it.each([[CaseStatuses.open], [CaseStatuses['in-progress']], [CaseStatuses.closed]])( @@ -694,25 +674,21 @@ describe('AllCasesListGeneric', () => { async (status) => { appMockRenderer.render(); - await waitFor(() => { - expect(screen.getByTestId('cases-table')).toBeInTheDocument(); - }); - - userEvent.click(screen.getByTestId('checkboxSelectAll')); + expect(await screen.findByTestId('cases-table')).toBeInTheDocument(); - expect(screen.getByText('Bulk actions')).toBeInTheDocument(); + userEvent.click(await screen.findByTestId('checkboxSelectAll')); - userEvent.click(screen.getByText('Bulk actions')); + userEvent.click(await screen.findByText('Bulk actions')); - userEvent.click(screen.getByTestId('case-bulk-action-status'), undefined, { + userEvent.click(await screen.findByTestId('case-bulk-action-status'), undefined, { skipPointerEventsCheck: true, }); - await waitFor(() => { - expect(screen.getByTestId(`cases-bulk-action-status-${status}`)).toBeInTheDocument(); - }); + expect( + await screen.findByTestId(`cases-bulk-action-status-${status}`) + ).toBeInTheDocument(); - userEvent.click(screen.getByTestId(`cases-bulk-action-status-${status}`)); + userEvent.click(await screen.findByTestId(`cases-bulk-action-status-${status}`)); await waitFor(() => { expect(updateCasesSpy).toBeCalledWith({ @@ -734,25 +710,21 @@ describe('AllCasesListGeneric', () => { ])('Bulk update severity: %s', async (severity) => { appMockRenderer.render(); - await waitFor(() => { - expect(screen.getByTestId('cases-table')).toBeInTheDocument(); - }); + expect(await screen.findByTestId('cases-table')).toBeInTheDocument(); - userEvent.click(screen.getByTestId('checkboxSelectAll')); + userEvent.click(await screen.findByTestId('checkboxSelectAll')); - expect(screen.getByText('Bulk actions')).toBeInTheDocument(); + userEvent.click(await screen.findByText('Bulk actions')); - userEvent.click(screen.getByText('Bulk actions')); - - userEvent.click(screen.getByTestId('case-bulk-action-severity'), undefined, { + userEvent.click(await screen.findByTestId('case-bulk-action-severity'), undefined, { skipPointerEventsCheck: true, }); - await waitFor(() => { - expect(screen.getByTestId(`cases-bulk-action-severity-${severity}`)).toBeInTheDocument(); - }); + expect( + await screen.findByTestId(`cases-bulk-action-severity-${severity}`) + ).toBeInTheDocument(); - userEvent.click(screen.getByTestId(`cases-bulk-action-severity-${severity}`)); + userEvent.click(await screen.findByTestId(`cases-bulk-action-severity-${severity}`)); await waitFor(() => { expect(updateCasesSpy).toBeCalledWith({ @@ -768,23 +740,19 @@ describe('AllCasesListGeneric', () => { it('Bulk delete', async () => { appMockRenderer.render(); - await waitFor(() => { - expect(screen.getByTestId('cases-table')).toBeInTheDocument(); - }); - - userEvent.click(screen.getByTestId('checkboxSelectAll')); + expect(await screen.findByTestId('cases-table')).toBeInTheDocument(); - expect(screen.getByText('Bulk actions')).toBeInTheDocument(); + userEvent.click(await screen.findByTestId('checkboxSelectAll')); - userEvent.click(screen.getByText('Bulk actions')); + userEvent.click(await screen.findByText('Bulk actions')); - userEvent.click(screen.getByTestId('cases-bulk-action-delete'), undefined, { + userEvent.click(await screen.findByTestId('cases-bulk-action-delete'), undefined, { skipPointerEventsCheck: true, }); - expect(screen.getByTestId('confirm-delete-case-modal')).toBeInTheDocument(); + expect(await screen.findByTestId('confirm-delete-case-modal')).toBeInTheDocument(); - userEvent.click(screen.getByTestId('confirmModalConfirmButton')); + userEvent.click(await screen.findByTestId('confirmModalConfirmButton')); await waitFor(() => { expect(deleteCasesSpy).toHaveBeenCalledWith({ @@ -806,18 +774,15 @@ describe('AllCasesListGeneric', () => { appMockRenderer = createAppMockRenderer({ permissions: readCasesPermissions() }); appMockRenderer.render(); - expect(screen.getByTestId('checkboxSelectAll')).toBeDisabled(); + expect(await screen.findByTestId('checkboxSelectAll')).toBeDisabled(); for (const theCase of defaultGetCases.data.cases) { - await waitFor(() => { - expect(screen.getByTestId(`checkboxSelectRow-${theCase.id}`)).toBeDisabled(); - }); + expect(await screen.findByTestId(`checkboxSelectRow-${theCase.id}`)).toBeDisabled(); } }); }); - // FLAKY: https://github.com/elastic/kibana/issues/148095 - describe.skip('Row actions', () => { + describe('Row actions', () => { const statusTests = [ [CaseStatuses.open], [CaseStatuses['in-progress']], @@ -835,11 +800,9 @@ describe('AllCasesListGeneric', () => { appMockRenderer.render(); for (const theCase of defaultGetCases.data.cases) { - await waitFor(() => { - expect( - screen.getByTestId(`case-action-popover-button-${theCase.id}`) - ).toBeInTheDocument(); - }); + expect( + await screen.findByTestId(`case-action-popover-button-${theCase.id}`) + ).toBeInTheDocument(); } }); @@ -849,21 +812,17 @@ describe('AllCasesListGeneric', () => { const inProgressCase = useGetCasesMockState.data.cases[1]; const theCase = status === CaseStatuses.open ? inProgressCase : openCase; - expect(screen.getByTestId(`case-action-popover-button-${theCase.id}`)).toBeInTheDocument(); - - userEvent.click(screen.getByTestId(`case-action-popover-button-${theCase.id}`)); + userEvent.click(await screen.findByTestId(`case-action-popover-button-${theCase.id}`)); - expect(screen.getByTestId(`case-action-status-panel-${theCase.id}`)).toBeInTheDocument(); - - userEvent.click(screen.getByTestId(`case-action-status-panel-${theCase.id}`), undefined, { - skipPointerEventsCheck: true, - }); - - await waitFor(() => { - expect(screen.getByTestId(`cases-bulk-action-status-${status}`)).toBeInTheDocument(); - }); + userEvent.click( + await screen.findByTestId(`case-action-status-panel-${theCase.id}`), + undefined, + { + skipPointerEventsCheck: true, + } + ); - userEvent.click(screen.getByTestId(`cases-bulk-action-status-${status}`)); + userEvent.click(await screen.findByTestId(`cases-bulk-action-status-${status}`)); await waitFor(() => { expect(updateCasesSpy).toHaveBeenCalledWith({ @@ -878,21 +837,17 @@ describe('AllCasesListGeneric', () => { const mediumCase = useGetCasesMockState.data.cases[1]; const theCase = severity === CaseSeverity.LOW ? mediumCase : lowCase; - expect(screen.getByTestId(`case-action-popover-button-${theCase.id}`)).toBeInTheDocument(); + userEvent.click(await screen.findByTestId(`case-action-popover-button-${theCase.id}`)); - userEvent.click(screen.getByTestId(`case-action-popover-button-${theCase.id}`)); - - expect(screen.getByTestId(`case-action-severity-panel-${theCase.id}`)).toBeInTheDocument(); - - userEvent.click(screen.getByTestId(`case-action-severity-panel-${theCase.id}`), undefined, { - skipPointerEventsCheck: true, - }); - - await waitFor(() => { - expect(screen.getByTestId(`cases-bulk-action-severity-${severity}`)).toBeInTheDocument(); - }); + userEvent.click( + await screen.findByTestId(`case-action-severity-panel-${theCase.id}`), + undefined, + { + skipPointerEventsCheck: true, + } + ); - userEvent.click(screen.getByTestId(`cases-bulk-action-severity-${severity}`)); + userEvent.click(await screen.findByTestId(`cases-bulk-action-severity-${severity}`)); await waitFor(() => { expect(updateCasesSpy).toHaveBeenCalledWith({ @@ -905,19 +860,15 @@ describe('AllCasesListGeneric', () => { appMockRenderer.render(); const theCase = defaultGetCases.data.cases[0]; - expect(screen.getByTestId(`case-action-popover-button-${theCase.id}`)).toBeInTheDocument(); - - userEvent.click(screen.getByTestId(`case-action-popover-button-${theCase.id}`)); + userEvent.click(await screen.findByTestId(`case-action-popover-button-${theCase.id}`)); - expect(screen.getByTestId('cases-bulk-action-delete')).toBeInTheDocument(); - - userEvent.click(screen.getByTestId('cases-bulk-action-delete'), undefined, { + userEvent.click(await screen.findByTestId('cases-bulk-action-delete'), undefined, { skipPointerEventsCheck: true, }); - expect(screen.getByTestId('confirm-delete-case-modal')).toBeInTheDocument(); + expect(await screen.findByTestId('confirm-delete-case-modal')).toBeInTheDocument(); - userEvent.click(screen.getByTestId('confirmModalConfirmButton')); + userEvent.click(await screen.findByTestId('confirmModalConfirmButton')); await waitFor(() => { expect(deleteCasesSpy).toHaveBeenCalledWith({ caseIds: ['basic-case-id'] }); @@ -927,12 +878,12 @@ describe('AllCasesListGeneric', () => { it('should disable row actions when bulk selecting all cases', async () => { appMockRenderer.render(); - userEvent.click(screen.getByTestId('checkboxSelectAll')); + userEvent.click(await screen.findByTestId('checkboxSelectAll')); for (const theCase of defaultGetCases.data.cases) { - await waitFor(() => { - expect(screen.getByTestId(`case-action-popover-button-${theCase.id}`)).toBeDisabled(); - }); + expect( + await screen.findByTestId(`case-action-popover-button-${theCase.id}`) + ).toBeDisabled(); } }); @@ -940,12 +891,12 @@ describe('AllCasesListGeneric', () => { appMockRenderer.render(); const caseToSelect = defaultGetCases.data.cases[0]; - userEvent.click(screen.getByTestId(`checkboxSelectRow-${caseToSelect.id}`)); + userEvent.click(await screen.findByTestId(`checkboxSelectRow-${caseToSelect.id}`)); for (const theCase of defaultGetCases.data.cases) { - await waitFor(() => { - expect(screen.getByTestId(`case-action-popover-button-${theCase.id}`)).toBeDisabled(); - }); + expect( + await screen.findByTestId(`case-action-popover-button-${theCase.id}`) + ).toBeDisabled(); } }); }); @@ -956,10 +907,8 @@ describe('AllCasesListGeneric', () => { appMockRenderer.render(); - await waitFor(() => { - expect(screen.getByTestId('cases-table')).toBeTruthy(); - expect(screen.queryAllByTestId('case-table-column-assignee').length).toBe(0); - }); + expect(await screen.findByTestId('cases-table')).toBeTruthy(); + expect(screen.queryAllByTestId('case-table-column-assignee').length).toBe(0); }); it('should show the assignees column on platinum license', async () => { @@ -967,10 +916,8 @@ describe('AllCasesListGeneric', () => { appMockRenderer.render(); - await waitFor(() => { - expect(screen.getByTestId('cases-table')).toBeTruthy(); - expect(screen.queryAllByTestId('case-table-column-assignee').length).toBeGreaterThan(0); - }); + expect(await screen.findByTestId('cases-table')).toBeTruthy(); + expect(screen.queryAllByTestId('case-table-column-assignee').length).toBeGreaterThan(0); }); it('should hide the assignees filters on basic license', async () => { @@ -978,10 +925,8 @@ describe('AllCasesListGeneric', () => { appMockRenderer.render(); - await waitFor(() => { - expect(screen.getByTestId('cases-table')).toBeTruthy(); - expect(screen.queryAllByTestId('options-filter-popover-button-assignees').length).toBe(0); - }); + expect(await screen.findByTestId('cases-table')).toBeTruthy(); + expect(screen.queryAllByTestId('options-filter-popover-button-assignees').length).toBe(0); }); it('should show the assignees filters on platinum license', async () => { @@ -989,12 +934,10 @@ describe('AllCasesListGeneric', () => { appMockRenderer.render(); - await waitFor(() => { - expect(screen.getByTestId('cases-table')).toBeTruthy(); - expect( - screen.queryAllByTestId('options-filter-popover-button-assignees').length - ).toBeGreaterThan(0); - }); + expect(await screen.findByTestId('cases-table')).toBeTruthy(); + expect( + screen.queryAllByTestId('options-filter-popover-button-assignees').length + ).toBeGreaterThan(0); }); it('should reset the assignees when deactivating the filter', async () => { @@ -1003,15 +946,19 @@ describe('AllCasesListGeneric', () => { appMockRenderer.render(); // Opens assignees filter and checks an option - const assigneesButton = screen.getByTestId('options-filter-popover-button-assignees'); + const assigneesButton = await screen.findByTestId( + 'options-filter-popover-button-assignees' + ); userEvent.click(assigneesButton); - userEvent.click(screen.getByText('Damaged Raccoon')); - expect(within(assigneesButton).getByLabelText('1 active filters')).toBeInTheDocument(); + userEvent.click(await screen.findByText('Damaged Raccoon')); + expect( + await within(assigneesButton).findByLabelText('1 active filters') + ).toBeInTheDocument(); // Deactivates assignees filter - userEvent.click(screen.getByRole('button', { name: 'More' })); + userEvent.click(await screen.findByRole('button', { name: 'More' })); await waitForEuiPopoverOpen(); - userEvent.click(screen.getByRole('option', { name: 'Assignees' })); + userEvent.click(await screen.findByRole('option', { name: 'Assignees' })); expect(useGetCasesMock).toHaveBeenLastCalledWith({ filterOptions: { @@ -1022,14 +969,14 @@ describe('AllCasesListGeneric', () => { }); // Reopens assignees filter - userEvent.click(screen.getByRole('option', { name: 'Assignees' })); + userEvent.click(await screen.findByRole('option', { name: 'Assignees' })); // Opens the assignees popup userEvent.click(assigneesButton); - expect(screen.getByLabelText('click to filter assignees')).toBeInTheDocument(); + expect(await screen.findByLabelText('click to filter assignees')).toBeInTheDocument(); expect( - within(screen.getByTestId('options-filter-popover-button-assignees')).queryByLabelText( - '1 active filters' - ) + within( + await screen.findByTestId('options-filter-popover-button-assignees') + ).queryByLabelText('1 active filters') ).not.toBeInTheDocument(); }); }); From 358e104ebcc2bfcfeec3457b2e913d071d27745d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Zaffarano?= Date: Wed, 7 Aug 2024 15:13:13 +0200 Subject: [PATCH 10/44] [Telemetry][Security Solution] Fix flaky tests (#190044) ## Summary Fixes: https://github.com/elastic/kibana/issues/188234 https://github.com/elastic/kibana/issues/187719 and https://github.com/elastic/kibana/issues/178918 The flakiness was while calculating the Detection Rules task invocations. It could have two different RCs: 1) The code didn't retry in case the task wasn't executed yet, which makes sense in a CI environment, which is slower than a dev environment; 2) The timestamp to filter out requests was calculated after the task was triggered, and if the task is executed fast enough, it could lead to empty responses because of that. --- .../integration_tests/telemetry.test.ts | 103 ++++++++---------- 1 file changed, 45 insertions(+), 58 deletions(-) diff --git a/x-pack/plugins/security_solution/server/integration_tests/telemetry.test.ts b/x-pack/plugins/security_solution/server/integration_tests/telemetry.test.ts index d45e59b2fe295..558f7e7ade2f6 100644 --- a/x-pack/plugins/security_solution/server/integration_tests/telemetry.test.ts +++ b/x-pack/plugins/security_solution/server/integration_tests/telemetry.test.ts @@ -148,8 +148,7 @@ describe('telemetry tasks', () => { }); }); - // FLAKY: https://github.com/elastic/kibana/issues/187719 - describe.skip('detection-rules', () => { + describe('detection-rules', () => { it('should execute when scheduled', async () => { await mockAndScheduleDetectionRulesTask(); @@ -169,8 +168,7 @@ describe('telemetry tasks', () => { }); it('should send task metrics', async () => { - const task = await mockAndScheduleDetectionRulesTask(); - const started = performance.now(); + const [task, started] = await mockAndScheduleDetectionRulesTask(); const requests = await getTaskMetricsRequests(task, started); @@ -181,13 +179,10 @@ describe('telemetry tasks', () => { }); }); - // FLAKY: https://github.com/elastic/kibana/issues/178918 - // FLAKY: https://github.com/elastic/kibana/issues/187720 - describe.skip('sender configuration', () => { + describe('sender configuration', () => { it('should use legacy sender by default', async () => { // launch a random task and verify it uses the new configuration - const task = await mockAndScheduleDetectionRulesTask(); - const started = performance.now(); + const [task, started] = await mockAndScheduleDetectionRulesTask(); const requests = await getTaskMetricsRequests(task, started); expect(requests.length).toBeGreaterThan(0); @@ -216,8 +211,7 @@ describe('telemetry tasks', () => { expect(found).toBeFalsy(); }); - const task = await mockAndScheduleDetectionRulesTask(); - const started = performance.now(); + const [task, started] = await mockAndScheduleDetectionRulesTask(); const requests = await getTaskMetricsRequests(task, started); expect(requests.length).toBeGreaterThan(0); @@ -258,8 +252,7 @@ describe('telemetry tasks', () => { }); }); - // FLAKY: https://github.com/elastic/kibana/issues/189192 - describe.skip('endpoint-diagnostics', () => { + describe('endpoint-diagnostics', () => { it('should execute when scheduled', async () => { await mockAndScheduleEndpointDiagnosticsTask(); @@ -298,8 +291,7 @@ describe('telemetry tasks', () => { }); }); - // FLAKY: https://github.com/elastic/kibana/issues/189330 - describe.skip('endpoint-meta-telemetry', () => { + describe('endpoint-meta-telemetry', () => { beforeEach(async () => { await initEndpointIndices(esClient); }); @@ -335,8 +327,7 @@ describe('telemetry tasks', () => { Promise.reject(Error(errorMessage)) ); - const task = await mockAndScheduleEndpointTask(); - const started = performance.now(); + const [task, started] = await mockAndScheduleEndpointTask(); const requests = await getTaskMetricsRequests(task, started); @@ -361,8 +352,7 @@ describe('telemetry tasks', () => { agentClient.listAgents = jest.fn((_) => Promise.reject(Error(errorMessage))); - const task = await mockAndScheduleEndpointTask(); - const started = performance.now(); + const [task, started] = await mockAndScheduleEndpointTask(); const endpointMetaRequests = await getEndpointMetaRequests(); @@ -401,8 +391,7 @@ describe('telemetry tasks', () => { }) ); - const task = await mockAndScheduleEndpointTask(); - const started = performance.now(); + const [task, started] = await mockAndScheduleEndpointTask(); const endpointMetaRequests = await getEndpointMetaRequests(); @@ -434,8 +423,7 @@ describe('telemetry tasks', () => { telemetryReceiver.fetchPolicyConfigs = jest.fn((_) => Promise.reject(Error(errorMessage))); - const task = await mockAndScheduleEndpointTask(); - const started = performance.now(); + const [task, started] = await mockAndScheduleEndpointTask(); const endpointMetaRequests = await getEndpointMetaRequests(); @@ -478,8 +466,7 @@ describe('telemetry tasks', () => { } as unknown as AgentPolicy); }); - const task = await mockAndScheduleEndpointTask(); - const started = performance.now(); + const [task, started] = await mockAndScheduleEndpointTask(); const endpointMetaRequests = await getEndpointMetaRequests(); @@ -512,8 +499,7 @@ describe('telemetry tasks', () => { return Promise.reject(Error(errorMessage)); }); - const task = await mockAndScheduleEndpointTask(); - const started = performance.now(); + const [task, started] = await mockAndScheduleEndpointTask(); const endpointMetaRequests = await getEndpointMetaRequests(); @@ -545,8 +531,7 @@ describe('telemetry tasks', () => { return Promise.resolve(new Map()); }); - const task = await mockAndScheduleEndpointTask(); - const started = performance.now(); + const [task, started] = await mockAndScheduleEndpointTask(); const endpointMetaRequests = await getEndpointMetaRequests(); @@ -579,8 +564,7 @@ describe('telemetry tasks', () => { return Promise.reject(Error(errorMessage)); }); - const task = await mockAndScheduleEndpointTask(); - const started = performance.now(); + const [task, started] = await mockAndScheduleEndpointTask(); const endpointMetaRequests = await getEndpointMetaRequests(); @@ -615,8 +599,7 @@ describe('telemetry tasks', () => { return Promise.resolve(new Map()); }); - const task = await mockAndScheduleEndpointTask(); - const started = performance.now(); + const [task, started] = await mockAndScheduleEndpointTask(); const endpointMetaRequests = await getEndpointMetaRequests(); @@ -663,8 +646,7 @@ describe('telemetry tasks', () => { return esClient.search(query); }); - const task = await mockAndScheduleEndpointTask(); - const started = performance.now(); + const [task, started] = await mockAndScheduleEndpointTask(); const endpointMetaRequests = await getEndpointMetaRequests(); @@ -688,8 +670,7 @@ describe('telemetry tasks', () => { }); }); - // FLAKY: https://github.com/elastic/kibana/issues/188234 - describe.skip('telemetry-prebuilt-rule-alerts', () => { + describe('telemetry-prebuilt-rule-alerts', () => { it('should execute when scheduled', async () => { await mockAndSchedulePrebuiltRulesTask(); @@ -722,8 +703,7 @@ describe('telemetry tasks', () => { telemetryReceiver.fetchPrebuiltRuleAlertsBatch = mockedGenerator; - const task = await mockAndSchedulePrebuiltRulesTask(); - const started = performance.now(); + const [task, started] = await mockAndSchedulePrebuiltRulesTask(); const requests = await getTaskMetricsRequests(task, started); @@ -781,7 +761,7 @@ describe('telemetry tasks', () => { }); } - async function mockAndScheduleDetectionRulesTask(): Promise { + async function mockAndScheduleDetectionRulesTask(): Promise<[SecurityTelemetryTask, number]> { const task = getTelemetryTask(tasks, 'security:telemetry-detection-rules'); // create some data @@ -797,50 +777,52 @@ describe('telemetry tasks', () => { exceptionsListItem.push(exceptionListItem); // schedule task to run ASAP - await eventually(async () => { + return eventually(async () => { + const started = performance.now(); await taskManagerPlugin.runSoon(task.getTaskId()); + return [task, started]; }); - - return task; } - async function mockAndScheduleEndpointTask(): Promise { + async function mockAndScheduleEndpointTask(): Promise<[SecurityTelemetryTask, number]> { const task = getTelemetryTask(tasks, 'security:endpoint-meta-telemetry'); await mockEndpointData(esClient, kibanaServer.coreStart.savedObjects); // schedule task to run ASAP - await eventually(async () => { + return eventually(async () => { + const started = performance.now(); await taskManagerPlugin.runSoon(task.getTaskId()); + return [task, started]; }); - - return task; } - async function mockAndSchedulePrebuiltRulesTask(): Promise { + async function mockAndSchedulePrebuiltRulesTask(): Promise<[SecurityTelemetryTask, number]> { const task = getTelemetryTask(tasks, 'security:telemetry-prebuilt-rule-alerts'); await mockPrebuiltRulesData(esClient); // schedule task to run ASAP - await eventually(async () => { + return eventually(async () => { + const started = performance.now(); await taskManagerPlugin.runSoon(task.getTaskId()); + return [task, started]; }); - - return task; } - async function mockAndScheduleEndpointDiagnosticsTask(): Promise { + async function mockAndScheduleEndpointDiagnosticsTask(): Promise< + [SecurityTelemetryTask, number] + > { const task = getTelemetryTask(tasks, 'security:endpoint-diagnostics'); await createMockedEndpointAlert(kibanaServer.coreStart.elasticsearch.client.asInternalUser); // schedule task to run ASAP - await eventually(async () => { + return eventually(async () => { + const started = performance.now(); await taskManagerPlugin.runSoon(task.getTaskId()); + return [task, started]; }); - - return task; } function mockAxiosGet(bufferConfig: unknown = fakeBufferAndSizesConfigAsyncDisabled) { @@ -877,6 +859,7 @@ describe('telemetry tasks', () => { requestConfig: AxiosRequestConfig | undefined; }> > { + const taskType = getTelemetryTaskType(task); return eventually(async () => { const calls = mockedAxiosPost.mock.calls.flatMap(([url, data, config]) => { return (data as string).split('\n').map((body) => { @@ -886,20 +869,24 @@ describe('telemetry tasks', () => { const requests = calls.filter(({ url, body }) => { return ( - body.indexOf(getTelemetryTaskType(task)) !== -1 && + body.indexOf(taskType) !== -1 && url.startsWith(ENDPOINT_STAGING) && url.endsWith('task-metrics') ); }); expect(requests.length).toBeGreaterThan(0); - return requests + const filtered = requests .map((r) => { return { taskMetric: JSON.parse(r.body) as TaskMetric, requestConfig: r.config, }; }) - .filter((t) => t.taskMetric.start_time >= olderThan); + .filter((t) => { + return t.taskMetric.start_time >= olderThan; + }); + expect(filtered.length).toBeGreaterThan(0); + return filtered; }); } }); From 44c5f8ca806edfe124023618d2845d5ba98c4d67 Mon Sep 17 00:00:00 2001 From: Drew Tate Date: Wed, 7 Aug 2024 07:31:18 -0600 Subject: [PATCH 11/44] [ES|QL] open the suggestion menu automatically in more places (#189585) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Closes https://github.com/elastic/kibana/issues/189662 ### Before https://github.com/user-attachments/assets/9f4e0fc6-1399-4b17-a151-753178e5ec7f ### After https://github.com/user-attachments/assets/45d59b63-3f18-457e-b4b9-1d98fa0ad8b1 | | Before | After | |------------------------------|------------------------------------------|-----------------------| | Source command | ✅ | ✅ | | Pipe command | ✅ | ✅ | | Function argument (when the minimum args aren't met) | ❌ | ✅ | | Pipe `\|` | ❌ | ✅ | | Assignment `var0 =` | ❌ | ✅ | | FROM source | ❌ | ✅ | | FROM source METADATA | ✅ | ✅ | | FROM source METADATA field | ❌ | ❌ | | FROM source METADATA field, | ❌ | ❌ | | EVAL argument | ❌ | ❌ | | DISSECT field | ❌ | ❌ | | DISSECT field pattern | ❌ | ❌ | | DISSECT field pattern APPEND_SEPARATOR | ✅ | ✅ | | DISSECT field pattern APPEND_SEPARATOR = separator | ❌ | ❌ | | DROP field | ❌ | ❌ | | DROP field1, field2 | ❌ | ❌ | | ENRICH policy | ❌ | ❌ | | ENRICH policy ON | ✅ | ✅ | | ENRICH policy ON field | ❌ | ❌ | | ENRICH policy WITH | ✅ | ✅ | | ENRICH policy WITH field | ❌ | ❌ | | GROK field | ❌ | ❌ | | GROK field pattern | ❌ | ❌ | | KEEP field | ❌ | ❌ | | KEEP field1, field2 | ❌ | ❌ | | LIMIT number | ❌ | ✅ | | MV_EXPAND field | ❌ | ❌ | | RENAME field | ❌ | ❌ | | RENAME field AS | ✅ |✅ | | RENAME field AS var0 | ❌ | ❌ | | SORT field | ❌ | ✅ | | SORT field order | ❌ | ✅ | | SORT field order nulls-order | ❌ | ✅ | | STATS argument | ✅ | ✅ | | STATS argument BY | ✅ | ✅ | | STATS argument BY expression | ❌ | ✅ | | WHERE argument | ❌ | ✅ | | WHERE argument comparison | ✅ | ✅ | | WHERE argument comparison argument | ❌ | ❌ | Also made a couple of improvements - Added support for Invoke completion triggers for subsequent function arguments (e.g. `date_diff("day", )`) - When you select a date in the date picker, your cursor is now advanced past the inserted date and the editor is refocused. ### 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 --- .../autocomplete.command.from.test.ts | 39 +- .../autocomplete.command.stats.test.ts | 39 +- .../src/autocomplete/__tests__/helpers.ts | 1 + .../src/autocomplete/autocomplete.test.ts | 509 +++++++++++++----- .../src/autocomplete/autocomplete.ts | 195 ++++--- .../src/autocomplete/complete_items.ts | 13 +- .../src/autocomplete/factories.ts | 83 ++- .../src/shared/esql_types.ts | 29 +- .../src/shared/helpers.ts | 5 + .../src/text_based_languages_editor.tsx | 10 + 10 files changed, 654 insertions(+), 269 deletions(-) diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.command.from.test.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.command.from.test.ts index 3752fad6c580f..911f8760f60b7 100644 --- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.command.from.test.ts +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.command.from.test.ts @@ -14,6 +14,9 @@ const visibleIndices = indexes .map(({ name, suggestedAs }) => suggestedAs || name) .sort(); +const addTrailingSpace = (strings: string[], predicate: (s: string) => boolean = (_s) => true) => + strings.map((string) => (predicate(string) ? `${string} ` : string)); + const metadataFields = [...METADATA_FIELDS].sort(); describe('autocomplete.suggest', () => { @@ -33,17 +36,17 @@ describe('autocomplete.suggest', () => { test('suggests visible indices on space', async () => { const { assertSuggestions } = await setup(); - await assertSuggestions('from /', visibleIndices); - await assertSuggestions('FROM /', visibleIndices); - await assertSuggestions('from /index', visibleIndices); + await assertSuggestions('from /', addTrailingSpace(visibleIndices)); + await assertSuggestions('FROM /', addTrailingSpace(visibleIndices)); + await assertSuggestions('from /index', addTrailingSpace(visibleIndices)); }); test('suggests visible indices on comma', async () => { const { assertSuggestions } = await setup(); - await assertSuggestions('FROM a,/', visibleIndices); - await assertSuggestions('FROM a, /', visibleIndices); - await assertSuggestions('from *,/', visibleIndices); + await assertSuggestions('FROM a,/', addTrailingSpace(visibleIndices)); + await assertSuggestions('FROM a, /', addTrailingSpace(visibleIndices)); + await assertSuggestions('from *,/', addTrailingSpace(visibleIndices)); }); test('can suggest integration data sources', async () => { @@ -52,17 +55,21 @@ describe('autocomplete.suggest', () => { .filter(({ hidden }) => !hidden) .map(({ name, suggestedAs }) => suggestedAs || name) .sort(); + const expectedSuggestions = addTrailingSpace( + visibleDataSources, + (s) => !integrations.find(({ name }) => name === s) + ); const { assertSuggestions, callbacks } = await setup(); const cb = { ...callbacks, getSources: jest.fn().mockResolvedValue(dataSources), }; - assertSuggestions('from /', visibleDataSources, { callbacks: cb }); - assertSuggestions('FROM /', visibleDataSources, { callbacks: cb }); - assertSuggestions('FROM a,/', visibleDataSources, { callbacks: cb }); - assertSuggestions('from a, /', visibleDataSources, { callbacks: cb }); - assertSuggestions('from *,/', visibleDataSources, { callbacks: cb }); + await assertSuggestions('from /', expectedSuggestions, { callbacks: cb }); + await assertSuggestions('FROM /', expectedSuggestions, { callbacks: cb }); + await assertSuggestions('FROM a,/', expectedSuggestions, { callbacks: cb }); + await assertSuggestions('from a, /', expectedSuggestions, { callbacks: cb }); + await assertSuggestions('from *,/', expectedSuggestions, { callbacks: cb }); }); }); @@ -71,7 +78,7 @@ describe('autocomplete.suggest', () => { test('on SPACE without comma ",", suggests adding metadata', async () => { const { assertSuggestions } = await setup(); - const expected = ['METADATA $0', ',', '|'].sort(); + const expected = ['METADATA $0', ',', '| '].sort(); await assertSuggestions('from a, b /', expected); }); @@ -86,10 +93,10 @@ describe('autocomplete.suggest', () => { test('on SPACE after "METADATA" column suggests command and pipe operators', async () => { const { assertSuggestions } = await setup(); - await assertSuggestions('from a, b [metadata _index /]', [',', '|']); - await assertSuggestions('from a, b metadata _index /', [',', '|']); - await assertSuggestions('from a, b metadata _index, _source /', [',', '|']); - await assertSuggestions(`from a, b metadata ${METADATA_FIELDS.join(', ')} /`, ['|']); + await assertSuggestions('from a, b [metadata _index /]', [',', '| ']); + await assertSuggestions('from a, b metadata _index /', [',', '| ']); + await assertSuggestions('from a, b metadata _index, _source /', [',', '| ']); + await assertSuggestions(`from a, b metadata ${METADATA_FIELDS.join(', ')} /`, ['| ']); }); test('filters out already used metadata fields', async () => { diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.command.stats.test.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.command.stats.test.ts index 88fd654a83453..a94fb0b8bead9 100644 --- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.command.stats.test.ts +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.command.stats.test.ts @@ -45,7 +45,7 @@ describe('autocomplete.suggest', () => { describe('... ...', () => { test('lists possible aggregations on space after command', async () => { const { assertSuggestions } = await setup(); - const expected = ['var0 =', ...allAggFunctions, ...allEvaFunctions]; + const expected = ['var0 = ', ...allAggFunctions, ...allEvaFunctions]; await assertSuggestions('from a | stats /', expected); await assertSuggestions('FROM a | STATS /', expected); @@ -60,14 +60,14 @@ describe('autocomplete.suggest', () => { test('on space after aggregate field', async () => { const { assertSuggestions } = await setup(); - await assertSuggestions('from a | stats a=min(b) /', ['BY $0', ',', '|']); + await assertSuggestions('from a | stats a=min(b) /', ['BY $0', ',', '| ']); }); test('on space after aggregate field with comma', async () => { const { assertSuggestions } = await setup(); await assertSuggestions('from a | stats a=max(b), /', [ - 'var0 =', + 'var0 = ', ...allAggFunctions, ...allEvaFunctions, ]); @@ -78,7 +78,7 @@ describe('autocomplete.suggest', () => { await assertSuggestions('from a | stats by bucket(/', [ ...getFieldNamesByType([...ESQL_COMMON_NUMERIC_TYPES, 'date']).map( - (field) => `${field},` + (field) => `${field}, ` ), ...getFunctionSignaturesByReturnType('eval', ['date', ...ESQL_COMMON_NUMERIC_TYPES], { scalar: true, @@ -172,21 +172,21 @@ describe('autocomplete.suggest', () => { test('when typing right paren', async () => { const { assertSuggestions } = await setup(); - await assertSuggestions('from a | stats a = min(b)/ | sort b', ['BY $0', ',', '|']); + await assertSuggestions('from a | stats a = min(b)/ | sort b', ['BY $0', ',', '| ']); }); test('increments suggested variable name counter', async () => { const { assertSuggestions } = await setup(); await assertSuggestions('from a | eval var0=round(b), var1=round(c) | stats /', [ - 'var2 =', + 'var2 = ', ...allAggFunctions, 'var0', 'var1', ...allEvaFunctions, ]); await assertSuggestions('from a | stats var0=min(b),var1=c,/', [ - 'var2 =', + 'var2 = ', ...allAggFunctions, ...allEvaFunctions, ]); @@ -197,8 +197,8 @@ describe('autocomplete.suggest', () => { test('on space after "BY" keyword', async () => { const { assertSuggestions } = await setup(); const expected = [ - 'var0 =', - ...getFieldNamesByType('any'), + 'var0 = ', + ...getFieldNamesByType('any').map((field) => `${field} `), ...allEvaFunctions, ...allGroupingFunctions, ]; @@ -211,26 +211,27 @@ describe('autocomplete.suggest', () => { test('on space after grouping field', async () => { const { assertSuggestions } = await setup(); - await assertSuggestions('from a | stats a=c by d /', [',', '|']); + await assertSuggestions('from a | stats a=c by d /', [',', '| ']); }); test('after comma "," in grouping fields', async () => { const { assertSuggestions } = await setup(); + const fields = getFieldNamesByType('any').map((field) => `${field} `); await assertSuggestions('from a | stats a=c by d, /', [ - 'var0 =', - ...getFieldNamesByType('any'), + 'var0 = ', + ...fields, ...allEvaFunctions, ...allGroupingFunctions, ]); await assertSuggestions('from a | stats a=min(b),/', [ - 'var0 =', + 'var0 = ', ...allAggFunctions, ...allEvaFunctions, ]); await assertSuggestions('from a | stats avg(b) by c, /', [ - 'var0 =', - ...getFieldNamesByType('any'), + 'var0 = ', + ...fields, ...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }), ...allGroupingFunctions, ]); @@ -251,12 +252,12 @@ describe('autocomplete.suggest', () => { ...allGroupingFunctions, ]); await assertSuggestions('from a | stats avg(b) by var0 = /', [ - ...getFieldNamesByType('any'), + ...getFieldNamesByType('any').map((field) => `${field} `), ...allEvaFunctions, ...allGroupingFunctions, ]); await assertSuggestions('from a | stats avg(b) by c, var0 = /', [ - ...getFieldNamesByType('any'), + ...getFieldNamesByType('any').map((field) => `${field} `), ...allEvaFunctions, ...allGroupingFunctions, ]); @@ -265,11 +266,11 @@ describe('autocomplete.suggest', () => { test('on space after expression right hand side operand', async () => { const { assertSuggestions } = await setup(); - await assertSuggestions('from a | stats avg(b) by doubleField % 2 /', [',', '|']); + await assertSuggestions('from a | stats avg(b) by doubleField % 2 /', [',', '| ']); await assertSuggestions( 'from a | stats var0 = AVG(doubleField) BY var1 = BUCKET(dateField, 1 day)/', - [',', '|', '+ $0', '- $0'] + [',', '| ', '+ $0', '- $0'] ); }); }); diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/helpers.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/helpers.ts index 6600ffdbaf1d8..464239d3ae960 100644 --- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/helpers.ts +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/helpers.ts @@ -41,6 +41,7 @@ export const triggerCharacters = [',', '(', '=', ' ']; export const fields: Array<{ name: string; type: string; suggestedAs?: string }> = [ ...[ 'string', + 'keyword', 'double', 'date', 'boolean', diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts index d9c958b5bd4f7..a6c2756914ce2 100644 --- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts @@ -10,7 +10,12 @@ import { suggest } from './autocomplete'; import { evalFunctionDefinitions } from '../definitions/functions'; import { timeUnitsToSuggest } from '../definitions/literals'; import { commandDefinitions } from '../definitions/commands'; -import { getSafeInsertText, getUnitDuration, TRIGGER_SUGGESTION_COMMAND } from './factories'; +import { + getSafeInsertText, + getUnitDuration, + TIME_SYSTEM_PARAMS, + TRIGGER_SUGGESTION_COMMAND, +} from './factories'; import { camelCase, partition } from 'lodash'; import { getAstAndSyntaxErrors } from '@kbn/esql-ast'; import { FunctionParameter, FunctionReturnType } from '../definitions/types'; @@ -26,6 +31,7 @@ import { createCompletionContext, getPolicyFields, PartialSuggestionWithText, + TIME_PICKER_SUGGESTION, } from './__tests__/helpers'; import { METADATA_FIELDS } from '../shared/constants'; import { @@ -143,7 +149,7 @@ describe('autocomplete', () => { describe('show', () => { testSuggestions('show ', ['INFO']); for (const fn of ['info']) { - testSuggestions(`show ${fn} `, ['|']); + testSuggestions(`show ${fn} `, ['| ']); } }); @@ -151,9 +157,12 @@ describe('autocomplete', () => { const allEvalFns = getFunctionSignaturesByReturnType('where', 'any', { scalar: true, }); - testSuggestions('from a | where ', [...getFieldNamesByType('any'), ...allEvalFns]); + testSuggestions('from a | where ', [ + ...getFieldNamesByType('any').map((field) => `${field} `), + ...allEvalFns, + ]); testSuggestions('from a | eval var0 = 1 | where ', [ - ...getFieldNamesByType('any'), + ...getFieldNamesByType('any').map((name) => `${name} `), 'var0', ...allEvalFns, ]); @@ -172,6 +181,14 @@ describe('autocomplete', () => { ...getFieldNamesByType('any'), ...getFunctionSignaturesByReturnType('where', ['any'], { scalar: true }), ]); + + testSuggestions('from a | where dateField >= ', [ + TIME_PICKER_SUGGESTION, + ...TIME_SYSTEM_PARAMS, + ...getFieldNamesByType('date'), + ...getFunctionSignaturesByReturnType('where', ['date'], { scalar: true }), + ]); + // Skip these tests until the insensitive case equality gets restored back testSuggestions.skip('from a | where stringField =~ ', [ ...getFieldNamesByType('string'), @@ -182,7 +199,7 @@ describe('autocomplete', () => { ...getFunctionSignaturesByReturnType('where', 'any', { scalar: true }), ]); testSuggestions.skip('from a | where stringField =~ stringField ', [ - '|', + '| ', ...getFunctionSignaturesByReturnType( 'where', 'boolean', @@ -307,7 +324,7 @@ describe('autocomplete', () => { for (const subExpression of subExpressions) { testSuggestions(`from a | ${subExpression} grok `, getFieldNamesByType('string')); testSuggestions(`from a | ${subExpression} grok stringField `, [constantPattern], ' '); - testSuggestions(`from a | ${subExpression} grok stringField ${constantPattern} `, ['|']); + testSuggestions(`from a | ${subExpression} grok stringField ${constantPattern} `, ['| ']); } }); @@ -324,7 +341,7 @@ describe('autocomplete', () => { testSuggestions(`from a | ${subExpression} dissect stringField `, [constantPattern], ' '); testSuggestions( `from a | ${subExpression} dissect stringField ${constantPattern} `, - ['APPEND_SEPARATOR = $0', '|'], + ['APPEND_SEPARATOR = $0', '| '], ' ' ); testSuggestions( @@ -333,30 +350,30 @@ describe('autocomplete', () => { ); testSuggestions( `from a | ${subExpression} dissect stringField ${constantPattern} append_separator = ":" `, - ['|'] + ['| '] ); } }); describe('sort', () => { testSuggestions('from a | sort ', [ - ...getFieldNamesByType('any'), + ...getFieldNamesByType('any').map((name) => `${name} `), ...getFunctionSignaturesByReturnType('sort', 'any', { scalar: true }), ]); - testSuggestions('from a | sort stringField ', ['ASC', 'DESC', ',', '|']); - testSuggestions('from a | sort stringField desc ', ['NULLS FIRST', 'NULLS LAST', ',', '|']); + testSuggestions('from a | sort stringField ', ['ASC ', 'DESC ', ',', '| ']); + testSuggestions('from a | sort stringField desc ', ['NULLS FIRST ', 'NULLS LAST ', ',', '| ']); // @TODO: improve here // testSuggestions('from a | sort stringField desc ', ['first', 'last']); }); describe('limit', () => { - testSuggestions('from a | limit ', ['10', '100', '1000']); - testSuggestions('from a | limit 4 ', ['|']); + testSuggestions('from a | limit ', ['10 ', '100 ', '1000 ']); + testSuggestions('from a | limit 4 ', ['| ']); }); describe('mv_expand', () => { testSuggestions('from a | mv_expand ', getFieldNamesByType('any')); - testSuggestions('from a | mv_expand a ', ['|']); + testSuggestions('from a | mv_expand a ', ['| ']); }); describe('rename', () => { @@ -413,8 +430,9 @@ describe('autocomplete', () => { testSuggestions(`from a ${prevCommand}| enrich _${mode.toUpperCase()}:`, policyNames, ':'); testSuggestions(`from a ${prevCommand}| enrich _${camelCase(mode)}:`, policyNames, ':'); } - testSuggestions(`from a ${prevCommand}| enrich policy `, ['ON $0', 'WITH $0', '|']); + testSuggestions(`from a ${prevCommand}| enrich policy `, ['ON $0', 'WITH $0', '| ']); testSuggestions(`from a ${prevCommand}| enrich policy on `, [ + 'keywordField', 'stringField', 'doubleField', 'dateField', @@ -427,28 +445,28 @@ describe('autocomplete', () => { '`any#Char$Field`', 'kubernetes.something.something', ]); - testSuggestions(`from a ${prevCommand}| enrich policy on b `, ['WITH $0', ',', '|']); + testSuggestions(`from a ${prevCommand}| enrich policy on b `, ['WITH $0', ',', '| ']); testSuggestions( `from a ${prevCommand}| enrich policy on b with `, - ['var0 =', ...getPolicyFields('policy')], + ['var0 = ', ...getPolicyFields('policy')], ' ' ); - testSuggestions(`from a ${prevCommand}| enrich policy on b with var0 `, ['= $0', ',', '|']); + testSuggestions(`from a ${prevCommand}| enrich policy on b with var0 `, ['= $0', ',', '| ']); testSuggestions(`from a ${prevCommand}| enrich policy on b with var0 = `, [ ...getPolicyFields('policy'), ]); testSuggestions(`from a ${prevCommand}| enrich policy on b with var0 = stringField `, [ ',', - '|', + '| ', ]); testSuggestions(`from a ${prevCommand}| enrich policy on b with var0 = stringField, `, [ - 'var1 =', + 'var1 = ', ...getPolicyFields('policy'), ]); testSuggestions(`from a ${prevCommand}| enrich policy on b with var0 = stringField, var1 `, [ '= $0', ',', - '|', + '| ', ]); testSuggestions( `from a ${prevCommand}| enrich policy on b with var0 = stringField, var1 = `, @@ -456,16 +474,20 @@ describe('autocomplete', () => { ); testSuggestions( `from a ${prevCommand}| enrich policy with `, - ['var0 =', ...getPolicyFields('policy')], + ['var0 = ', ...getPolicyFields('policy')], ' ' ); - testSuggestions(`from a ${prevCommand}| enrich policy with stringField `, ['= $0', ',', '|']); + testSuggestions(`from a ${prevCommand}| enrich policy with stringField `, [ + '= $0', + ',', + '| ', + ]); } }); describe('eval', () => { testSuggestions('from a | eval ', [ - 'var0 =', + 'var0 = ', ...getFieldNamesByType('any'), ...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }), ]); @@ -474,7 +496,7 @@ describe('autocomplete', () => { 'double', ]), ',', - '|', + '| ', ]); testSuggestions('from index | EVAL stringField not ', ['LIKE $0', 'RLIKE $0', 'IN $0']); testSuggestions('from index | EVAL stringField NOT ', ['LIKE $0', 'RLIKE $0', 'IN $0']); @@ -499,7 +521,7 @@ describe('autocomplete', () => { ...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }), ]); testSuggestions('from a | eval a=doubleField, ', [ - 'var0 =', + 'var0 = ', ...getFieldNamesByType('any'), 'a', ...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }), @@ -548,7 +570,7 @@ describe('autocomplete', () => { ); testSuggestions('from a | eval a=round(doubleField) ', [ ',', - '|', + '| ', ...getFunctionSignaturesByReturnType('eval', 'any', { builtin: true, skipAssign: true }, [ 'double', ]), @@ -573,7 +595,7 @@ describe('autocomplete', () => { ' ' ); testSuggestions('from a | eval a=round(doubleField),', [ - 'var0 =', + 'var0 = ', ...getFieldNamesByType('any'), 'a', ...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }), @@ -597,7 +619,7 @@ describe('autocomplete', () => { testSuggestions( 'from a | stats avg(doubleField) by stringField | eval ', [ - 'var0 =', + 'var0 = ', '`avg(doubleField)`', ...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }), ], @@ -609,7 +631,7 @@ describe('autocomplete', () => { testSuggestions( 'from a | eval abs(doubleField) + 1 | eval ', [ - 'var0 =', + 'var0 = ', ...getFieldNamesByType('any'), '`abs(doubleField) + 1`', ...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }), @@ -619,7 +641,7 @@ describe('autocomplete', () => { testSuggestions( 'from a | stats avg(doubleField) by stringField | eval ', [ - 'var0 =', + 'var0 = ', '`avg(doubleField)`', ...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }), ], @@ -631,7 +653,7 @@ describe('autocomplete', () => { testSuggestions( 'from a | stats avg(doubleField), avg(kubernetes.something.something) by stringField | eval ', [ - 'var0 =', + 'var0 = ', '`avg(doubleField)`', '`avg(kubernetes.something.something)`', ...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }), @@ -664,7 +686,7 @@ describe('autocomplete', () => { ); // test that comma is correctly added to the suggestions if minParams is not reached yet testSuggestions('from a | eval a=concat( ', [ - ...getFieldNamesByType(['text', 'keyword']).map((v) => `${v},`), + ...getFieldNamesByType(['text', 'keyword']).map((v) => `${v}, `), ...getFunctionSignaturesByReturnType( 'eval', ['text', 'keyword'], @@ -691,7 +713,7 @@ describe('autocomplete', () => { testSuggestions( 'from a | eval a=cidr_match(ipField, textField, ', [ - ...getFieldNamesByType('text'), + ...getFieldNamesByType('keyword'), ...getFunctionSignaturesByReturnType( 'eval', ['text', 'keyword'], @@ -704,7 +726,7 @@ describe('autocomplete', () => { ); // test that comma is correctly added to the suggestions if minParams is not reached yet testSuggestions('from a | eval a=cidr_match( ', [ - ...getFieldNamesByType('ip').map((v) => `${v},`), + ...getFieldNamesByType('ip').map((v) => `${v}, `), ...getFunctionSignaturesByReturnType('eval', 'ip', { scalar: true }, undefined, [ 'cidr_match', ]).map((v) => ({ ...v, text: `${v.text},` })), @@ -749,7 +771,7 @@ describe('autocomplete', () => { 'from a | eval var0 = abs(doubleField) | eval abs(var0)', [ ',', - '|', + '| ', ...getFunctionSignaturesByReturnType('eval', 'any', { builtin: true, skipAssign: true }, [ 'double', ]), @@ -807,13 +829,13 @@ describe('autocomplete', () => { if (!requiresMoreArgs || s === '' || (typeof s === 'object' && s.text === '')) { return s; } - return typeof s === 'string' ? `${s},` : { ...s, text: `${s.text},` }; + return typeof s === 'string' ? `${s}, ` : { ...s, text: `${s.text},` }; }; testSuggestions( `from a | eval ${fn.name}(${Array(i).fill('field').join(', ')}${i ? ',' : ''} )`, suggestedConstants?.length - ? suggestedConstants.map((option) => `"${option}"${requiresMoreArgs ? ',' : ''}`) + ? suggestedConstants.map((option) => `"${option}"${requiresMoreArgs ? ', ' : ''}`) : [ ...getDateLiteralsByFieldType(getTypesFromParamDefs(acceptsFieldParamDefs)), ...getFieldNamesByType(getTypesFromParamDefs(acceptsFieldParamDefs)), @@ -833,7 +855,7 @@ describe('autocomplete', () => { i ? ',' : '' } )`, suggestedConstants?.length - ? suggestedConstants.map((option) => `"${option}"${requiresMoreArgs ? ',' : ''}`) + ? suggestedConstants.map((option) => `"${option}"${requiresMoreArgs ? ', ' : ''}`) : [ ...getDateLiteralsByFieldType(getTypesFromParamDefs(acceptsFieldParamDefs)), ...getFieldNamesByType(getTypesFromParamDefs(acceptsFieldParamDefs)), @@ -865,7 +887,7 @@ describe('autocomplete', () => { testSuggestions( `from a | eval ${fn.name}(`, suggestedConstants?.length - ? [...suggestedConstants.map((option) => `"${option}"${requiresMoreArgs ? ',' : ''}`)] + ? [...suggestedConstants.map((option) => `"${option}"${requiresMoreArgs ? ', ' : ''}`)] : [] ); } @@ -881,7 +903,7 @@ describe('autocomplete', () => { [ ...dateSuggestions, ',', - '|', + '| ', ...getFunctionSignaturesByReturnType('eval', 'any', { builtin: true, skipAssign: true }, [ 'integer', ]), @@ -890,12 +912,12 @@ describe('autocomplete', () => { ); testSuggestions('from a | eval a = 1 year ', [ ',', - '|', + '| ', ...getFunctionSignaturesByReturnType('eval', 'any', { builtin: true, skipAssign: true }, [ 'time_interval', ]), ]); - testSuggestions('from a | eval a = 1 day + 2 ', [',', '|']); + testSuggestions('from a | eval a = 1 day + 2 ', [',', '| ']); testSuggestions( 'from a | eval 1 day + 2 ', [ @@ -908,19 +930,19 @@ describe('autocomplete', () => { ); testSuggestions( 'from a | eval var0=date_trunc()', - [...getLiteralsByType('time_literal').map((t) => `${t},`)], + getLiteralsByType('time_literal').map((t) => `${t}, `), '(' ); testSuggestions( 'from a | eval var0=date_trunc(2 )', - [...dateSuggestions.map((t) => `${t},`), ','], + [...dateSuggestions.map((t) => `${t}, `), ','], ' ' ); }); }); describe('values suggestions', () => { - testSuggestions('FROM "a"', ['a', 'b'], undefined, 7, [ + testSuggestions('FROM "a"', ['a ', 'b '], undefined, 7, [ , [ { name: 'a', hidden: false }, @@ -975,50 +997,6 @@ describe('autocomplete', () => { }); }); - describe('auto triggers', () => { - function getSuggestionsFor(statement: string) { - const callbackMocks = createCustomCallbackMocks(undefined, undefined, undefined); - const triggerOffset = statement.lastIndexOf(' ') + 1; // drop - const context = createCompletionContext(statement[triggerOffset]); - return suggest( - statement, - triggerOffset + 1, - context, - async (text) => (text ? getAstAndSyntaxErrors(text) : { ast: [], errors: [] }), - callbackMocks - ); - } - it('should trigger further suggestions for functions', async () => { - const suggestions = await getSuggestionsFor('from a | eval '); - // test that all functions will retrigger suggestions - expect( - suggestions - .filter(({ kind }) => kind === 'Function') - .every(({ command }) => command === TRIGGER_SUGGESTION_COMMAND) - ).toBeTruthy(); - // now test that non-function won't retrigger - expect( - suggestions - .filter(({ kind }) => kind !== 'Function') - .every(({ command }) => command == null) - ).toBeTruthy(); - }); - it('should trigger further suggestions for commands', async () => { - const suggestions = await getSuggestionsFor('from a | '); - // test that all commands will retrigger suggestions - expect( - suggestions.every(({ command }) => command === TRIGGER_SUGGESTION_COMMAND) - ).toBeTruthy(); - }); - it('should trigger further suggestions after enrich mode', async () => { - const suggestions = await getSuggestionsFor('from a | enrich _any:'); - // test that all commands will retrigger suggestions - expect( - suggestions.every(({ command }) => command === TRIGGER_SUGGESTION_COMMAND) - ).toBeTruthy(); - }); - }); - /** * Monaco asks for suggestions in at least two different scenarios. * 1. When the user types a non-whitespace character (e.g. 'FROM k') - this is the Invoke trigger kind @@ -1049,24 +1027,47 @@ describe('autocomplete', () => { 10 ); - // function argument - testSuggestions( - 'FROM kibana_sample_data_logs | EVAL TRIM(e)', - [ - ...getFunctionSignaturesByReturnType( - 'eval', - ['text', 'keyword'], - { scalar: true }, - undefined, - ['trim'] - ), - ], - undefined, - 42 - ); + describe('function arguments', () => { + // function argument + testSuggestions( + 'FROM kibana_sample_data_logs | EVAL TRIM(e)', + [ + ...getFieldNamesByType(['text', 'keyword']), + ...getFunctionSignaturesByReturnType( + 'eval', + ['text', 'keyword'], + { scalar: true }, + undefined, + ['trim'] + ), + ], + undefined, + 42 + ); + + // subsequent function argument + const expectedDateDiff2ndArgSuggestions = [ + TIME_PICKER_SUGGESTION, + ...TIME_SYSTEM_PARAMS.map((t) => `${t}, `), + ...getFieldNamesByType('date').map((name) => `${name}, `), + ...getFunctionSignaturesByReturnType('eval', 'date', { scalar: true }).map((s) => ({ + ...s, + text: `${s.text},`, + })), + ]; + testSuggestions( + 'FROM a | EVAL DATE_DIFF("day", )', + expectedDateDiff2ndArgSuggestions, + undefined, + 31 + ); + + // trigger character case for comparison + testSuggestions('FROM a | EVAL DATE_DIFF("day", )', expectedDateDiff2ndArgSuggestions, ' '); + }); // FROM source - testSuggestions('FROM k', ['index1', 'index2'], undefined, 6, [ + testSuggestions('FROM k', ['index1 ', 'index2 '], undefined, 6, [ , [ { name: 'index1', hidden: false }, @@ -1075,7 +1076,7 @@ describe('autocomplete', () => { ]); // FROM source METADATA - testSuggestions('FROM index1 M', [',', 'METADATA $0', '|'], undefined, 13); + testSuggestions('FROM index1 M', [',', 'METADATA $0', '| '], undefined, 13); // FROM source METADATA field testSuggestions('FROM index1 METADATA _', METADATA_FIELDS, undefined, 22); @@ -1084,7 +1085,7 @@ describe('autocomplete', () => { testSuggestions( 'FROM index1 | EVAL b', [ - 'var0 =', + 'var0 = ', ...getFieldNamesByType('any'), ...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }), ], @@ -1117,7 +1118,7 @@ describe('autocomplete', () => { ); // ENRICH policy ON - testSuggestions('FROM index1 | ENRICH policy O', ['ON $0', 'WITH $0', '|'], undefined, 29); + testSuggestions('FROM index1 | ENRICH policy O', ['ON $0', 'WITH $0', '| '], undefined, 29); // ENRICH policy ON field testSuggestions('FROM index1 | ENRICH policy ON f', getFieldNamesByType('any'), undefined, 32); @@ -1125,14 +1126,14 @@ describe('autocomplete', () => { // ENRICH policy WITH policyfield testSuggestions( 'FROM index1 | ENRICH policy WITH v', - ['var0 =', ...getPolicyFields('policy')], + ['var0 = ', ...getPolicyFields('policy')], undefined, 34 ); testSuggestions( 'FROM index1 | ENRICH policy WITH \tv', - ['var0 =', ...getPolicyFields('policy')], + ['var0 = ', ...getPolicyFields('policy')], undefined, 34 ); @@ -1154,7 +1155,7 @@ describe('autocomplete', () => { // LIMIT argument // Here we actually test that the invoke trigger kind does not work // because it isn't very useful to see literal suggestions when typing a number - testSuggestions('FROM a | LIMIT 1', ['|'], undefined, 16); + testSuggestions('FROM a | LIMIT 1', ['| '], undefined, 16); // MV_EXPAND field testSuggestions('FROM index1 | MV_EXPAND f', getFieldNamesByType('any'), undefined, 25); @@ -1173,19 +1174,24 @@ describe('autocomplete', () => { 'FROM index1 | SORT f', [ ...getFunctionSignaturesByReturnType('sort', 'any', { scalar: true }), - ...getFieldNamesByType('any'), + ...getFieldNamesByType('any').map((field) => `${field} `), ], undefined, 20 ); // SORT field order - testSuggestions('FROM index1 | SORT stringField a', ['ASC', 'DESC', ',', '|'], undefined, 32); + testSuggestions( + 'FROM index1 | SORT stringField a', + ['ASC ', 'DESC ', ',', '| '], + undefined, + 32 + ); // SORT field order nulls testSuggestions( 'FROM index1 | SORT stringField ASC n', - ['NULLS FIRST', 'NULLS LAST', ',', '|'], + ['NULLS FIRST ', 'NULLS LAST ', ',', '| '], undefined, 36 ); @@ -1193,21 +1199,24 @@ describe('autocomplete', () => { // STATS argument testSuggestions( 'FROM index1 | STATS f', - ['var0 =', ...getFunctionSignaturesByReturnType('stats', 'any', { scalar: true, agg: true })], + [ + 'var0 = ', + ...getFunctionSignaturesByReturnType('stats', 'any', { scalar: true, agg: true }), + ], undefined, 21 ); // STATS argument BY - testSuggestions('FROM index1 | STATS AVG(booleanField) B', ['BY $0', ',', '|'], undefined, 39); + testSuggestions('FROM index1 | STATS AVG(booleanField) B', ['BY $0', ',', '| '], undefined, 39); // STATS argument BY expression testSuggestions( 'FROM index1 | STATS field BY f', [ - 'var0 =', + 'var0 = ', ...getFunctionSignaturesByReturnType('stats', 'any', { grouping: true, scalar: true }), - ...getFieldNamesByType('any'), + ...getFieldNamesByType('any').map((field) => `${field} `), ], undefined, 30 @@ -1217,7 +1226,7 @@ describe('autocomplete', () => { testSuggestions( 'FROM index1 | WHERE f', [ - ...getFieldNamesByType('any'), + ...getFieldNamesByType('any').map((field) => `${field} `), ...getFunctionSignaturesByReturnType('where', 'any', { scalar: true }), ], undefined, @@ -1239,4 +1248,264 @@ describe('autocomplete', () => { 33 ); }); + + describe('advancing the cursor and opening the suggestion menu automatically ✨', () => { + const attachTriggerCommand = ( + s: string | PartialSuggestionWithText + ): PartialSuggestionWithText => + typeof s === 'string' + ? { + text: s, + command: TRIGGER_SUGGESTION_COMMAND, + } + : { ...s, command: TRIGGER_SUGGESTION_COMMAND }; + + const attachAsSnippet = (s: PartialSuggestionWithText): PartialSuggestionWithText => ({ + ...s, + asSnippet: true, + }); + + // Source command + testSuggestions( + 'F', + ['FROM $0', 'ROW $0', 'SHOW $0'].map(attachTriggerCommand).map(attachAsSnippet), + undefined, + 1 + ); + + // Pipe command + testSuggestions( + 'FROM a | E', + commandDefinitions + .filter(({ name }) => !sourceCommands.includes(name)) + .map(({ name }) => attachTriggerCommand(name.toUpperCase() + ' $0')) + .map(attachAsSnippet), // TODO consider making this check more fundamental + undefined, + 10 + ); + + describe('function arguments', () => { + // literalSuggestions parameter + const dateDiffFirstParamSuggestions = + evalFunctionDefinitions.find(({ name }) => name === 'date_diff')?.signatures[0].params?.[0] + .literalSuggestions ?? []; + testSuggestions( + 'FROM a | EVAL DATE_DIFF()', + dateDiffFirstParamSuggestions.map((s) => `"${s}", `).map(attachTriggerCommand), + undefined, + 24 + ); + + // field parameter + + const expectedStringSuggestionsWhenMoreArgsAreNeeded = [ + ...getFieldNamesByType('keyword') + .map((field) => `${field}, `) + .map(attachTriggerCommand), + ...getFunctionSignaturesByReturnType('eval', 'keyword', { scalar: true }, undefined, [ + 'replace', + ]).map((s) => ({ + ...s, + text: `${s.text},`, + })), + ]; + + testSuggestions( + 'FROM a | EVAL REPLACE()', + expectedStringSuggestionsWhenMoreArgsAreNeeded, + undefined, + 22 + ); + + // subsequent parameter + testSuggestions( + 'FROM a | EVAL REPLACE(stringField, )', + expectedStringSuggestionsWhenMoreArgsAreNeeded, + undefined, + 35 + ); + + // final parameter — should not advance! + testSuggestions( + 'FROM a | EVAL REPLACE(stringField, stringField, )', + [ + ...getFieldNamesByType('keyword').map((field) => ({ text: field, command: undefined })), + ...getFunctionSignaturesByReturnType('eval', 'keyword', { scalar: true }, undefined, [ + 'replace', + ]), + ], + undefined, + 48 + ); + + // Trigger character because this is how it will actually be... the user will press + // space-bar... this may change if we fix the tokenization of timespan literals + // such that "2 days" is a single monaco token + testSuggestions( + 'FROM a | EVAL DATE_TRUNC(2 )', + [...timeUnitsToSuggest.map((s) => `${s.name}, `).map(attachTriggerCommand), ','], + ' ' + ); + }); + + // PIPE (|) + testSuggestions( + 'FROM a ', + [attachTriggerCommand('| '), ',', attachAsSnippet(attachTriggerCommand('METADATA $0'))], + undefined, + 7 + ); + + // Assignment + testSuggestions(`FROM a | ENRICH policy on b with `, [ + attachTriggerCommand('var0 = '), + ...getPolicyFields('policy'), + ]); + + // FROM source + // + // Using an Invoke trigger kind here because that's what Monaco uses when the show suggestions + // action is triggered (e.g. accepting the "FROM" suggestion) + testSuggestions( + 'FROM ', + [ + { text: 'index1 ', command: TRIGGER_SUGGESTION_COMMAND }, + { text: 'index2 ', command: TRIGGER_SUGGESTION_COMMAND }, + ], + undefined, + 5, + [ + , + [ + { name: 'index1', hidden: false }, + { name: 'index2', hidden: false }, + ], + ] + ); + + // FROM source METADATA + testSuggestions( + 'FROM index1 M', + [',', attachAsSnippet(attachTriggerCommand('METADATA $0')), '| '], + undefined, + 13 + ); + + // LIMIT number + testSuggestions('FROM a | LIMIT ', ['10 ', '100 ', '1000 '].map(attachTriggerCommand)); + + // SORT field + testSuggestions( + 'FROM a | SORT ', + [ + ...getFieldNamesByType('any').map((field) => `${field} `), + ...getFunctionSignaturesByReturnType('sort', 'any', { scalar: true }), + ].map(attachTriggerCommand), + undefined, + 14 + ); + + // SORT field order + testSuggestions( + 'FROM a | SORT field ', + [',', ...['ASC ', 'DESC ', '| '].map(attachTriggerCommand)], + undefined, + 20 + ); + + // SORT field order nulls + testSuggestions( + 'FROM a | SORT field ASC ', + [',', ...['NULLS FIRST ', 'NULLS LAST ', '| '].map(attachTriggerCommand)], + undefined, + 24 + ); + + // STATS argument + testSuggestions( + 'FROM a | STATS ', + [ + 'var0 = ', + ...getFunctionSignaturesByReturnType('stats', 'any', { scalar: true, agg: true }).map( + attachAsSnippet + ), + ].map(attachTriggerCommand), + undefined, + 15 + ); + + // STATS argument BY + testSuggestions( + 'FROM a | STATS AVG(numberField) ', + [',', attachAsSnippet(attachTriggerCommand('BY $0')), attachTriggerCommand('| ')], + undefined, + 32 + ); + + // STATS argument BY field + const allByCompatibleFunctions = getFunctionSignaturesByReturnType( + 'stats', + 'any', + { + scalar: true, + grouping: true, + }, + undefined, + undefined, + 'by' + ); + testSuggestions( + 'FROM a | STATS AVG(numberField) BY ', + [ + attachTriggerCommand('var0 = '), + ...getFieldNamesByType('any') + .map((field) => `${field} `) + .map(attachTriggerCommand), + ...allByCompatibleFunctions, + ], + undefined, + 35 + ); + + // STATS argument BY assignment (checking field suggestions) + testSuggestions( + 'FROM a | STATS AVG(numberField) BY var0 = ', + [ + ...getFieldNamesByType('any') + .map((field) => `${field} `) + .map(attachTriggerCommand), + ...allByCompatibleFunctions, + ], + undefined, + 41 + ); + + // WHERE argument (field suggestions) + testSuggestions( + 'FROM a | WHERE ', + [ + ...getFieldNamesByType('any') + .map((field) => `${field} `) + .map(attachTriggerCommand), + ...getFunctionSignaturesByReturnType('where', 'any', { scalar: true }).map(attachAsSnippet), + ], + undefined, + 15 + ); + + // WHERE argument comparison + testSuggestions( + 'FROM a | WHERE stringField ', + getFunctionSignaturesByReturnType( + 'where', + 'boolean', + { + builtin: true, + }, + ['string'] + ).map((s) => (s.text.toLowerCase().includes('null') ? s : attachTriggerCommand(s))), + undefined, + 27 + ); + }); }); diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.ts index d2d617aac4315..67af737d34da0 100644 --- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.ts +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.ts @@ -13,10 +13,11 @@ import type { ESQLCommand, ESQLCommandOption, ESQLFunction, + ESQLLiteral, ESQLSingleAstItem, } from '@kbn/esql-ast'; import { partition } from 'lodash'; -import { ESQL_NUMBER_TYPES, isNumericType } from '../shared/esql_types'; +import { ESQL_NUMBER_TYPES, compareTypesWithLiterals, isNumericType } from '../shared/esql_types'; import type { EditorContext, SuggestionRawDefinition } from './types'; import { lookupColumn, @@ -44,6 +45,7 @@ import { nonNullable, getColumnExists, findPreviousWord, + noCaseCompare, } from '../shared/helpers'; import { collectVariables, excludeVariablesFromCurrentCommand } from '../shared/variables'; import type { ESQLPolicy, ESQLRealField, ESQLVariable, ReferenceMaps } from '../validation/types'; @@ -93,7 +95,7 @@ import { isAggFunctionUsedAlready, removeQuoteForSuggestedSources, } from './helper'; -import { FunctionParameter } from '../definitions/types'; +import { FunctionParameter, FunctionReturnType, SupportedFieldType } from '../definitions/types'; type GetSourceFn = () => Promise; type GetDataStreamsForIntegrationFn = ( @@ -101,7 +103,8 @@ type GetDataStreamsForIntegrationFn = ( ) => Promise | undefined>; type GetFieldsByTypeFn = ( type: string | string[], - ignored?: string[] + ignored?: string[], + options?: { advanceCursorAndOpenSuggestions?: boolean; addComma?: boolean } ) => Promise; type GetFieldsMapFn = () => Promise>; type GetPoliciesFn = () => Promise; @@ -185,8 +188,8 @@ function correctQuerySyntax(_query: string, context: EditorContext) { (context.triggerCharacter && charThatNeedMarkers.includes(context.triggerCharacter)) || // monaco.editor.CompletionTriggerKind['Invoke'] === 0 (context.triggerKind === 0 && unclosedRoundBrackets === 0) || - (context.triggerCharacter === ' ' && - (isMathFunction(query, query.length) || isComma(query.trimEnd()[query.trimEnd().length - 1]))) + (context.triggerCharacter === ' ' && isMathFunction(query, query.length)) || + isComma(query.trimEnd()[query.trimEnd().length - 1]) ) { query += EDITOR_MARKER; } @@ -308,12 +311,19 @@ export async function suggest( return []; } -function getFieldsByTypeRetriever(queryString: string, resourceRetriever?: ESQLCallbacks) { +function getFieldsByTypeRetriever( + queryString: string, + resourceRetriever?: ESQLCallbacks +): { getFieldsByType: GetFieldsByTypeFn; getFieldsMap: GetFieldsMapFn } { const helpers = getFieldsByTypeHelper(queryString, resourceRetriever); return { - getFieldsByType: async (expectedType: string | string[] = 'any', ignored: string[] = []) => { + getFieldsByType: async ( + expectedType: string | string[] = 'any', + ignored: string[] = [], + options + ) => { const fields = await helpers.getFieldsByType(expectedType, ignored); - return buildFieldsDefinitionsWithMetadata(fields); + return buildFieldsDefinitionsWithMetadata(fields, options); }, getFieldsMap: helpers.getFieldsMap, }; @@ -429,7 +439,13 @@ function areCurrentArgsValid( function extractFinalTypeFromArg( arg: ESQLAstItem, references: Pick -): string | undefined { +): + | ESQLLiteral['literalType'] + | SupportedFieldType + | FunctionReturnType + | 'timeInterval' + | string // @TODO remove this + | undefined { if (Array.isArray(arg)) { return extractFinalTypeFromArg(arg[0], references); } @@ -792,7 +808,11 @@ async function getExpressionSuggestionsByType( // if the definition includes a list of constants, suggest them if (argDef.values) { // ... | ... - suggestions.push(...buildConstantsDefinitions(argDef.values)); + suggestions.push( + ...buildConstantsDefinitions(argDef.values, undefined, undefined, { + advanceCursorAndOpenSuggestions: true, + }) + ); } // If the type is specified try to dig deeper in the definition to suggest the best candidate if ( @@ -815,6 +835,7 @@ async function getExpressionSuggestionsByType( // ... | // In this case start suggesting something not strictly based on type suggestions.push( + ...(await getFieldsByType('any', [], { advanceCursorAndOpenSuggestions: true })), ...(await getFieldsOrFunctionsSuggestions( ['any'], command.name, @@ -822,7 +843,7 @@ async function getExpressionSuggestionsByType( getFieldsByType, { functions: true, - fields: true, + fields: false, variables: anyVariables, } )) @@ -1087,7 +1108,11 @@ async function getFieldsOrFunctionsSuggestions( } = {} ): Promise { const filteredFieldsByType = pushItUpInTheList( - (await (fields ? getFieldsByType(types, ignoreFields) : [])) as SuggestionRawDefinition[], + (await (fields + ? getFieldsByType(types, ignoreFields, { + advanceCursorAndOpenSuggestions: commandName === 'sort', + }) + : [])) as SuggestionRawDefinition[], functions ); @@ -1187,6 +1212,8 @@ async function getFunctionArgsSuggestions( ? refSignature.minParams - 1 > argIndex : false); + const shouldAddComma = hasMoreMandatoryArgs && fnDefinition.type !== 'builtin'; + const suggestedConstants = Array.from( new Set( fnDefinition.signatures.reduce((acc, signature) => { @@ -1207,13 +1234,13 @@ async function getFunctionArgsSuggestions( ); if (suggestedConstants.length) { - return buildValueDefinitions(suggestedConstants).map((suggestion) => ({ - ...suggestion, - text: addCommaIf(hasMoreMandatoryArgs && fnDefinition.type !== 'builtin', suggestion.text), - })); + return buildValueDefinitions(suggestedConstants, { + addComma: hasMoreMandatoryArgs, + advanceCursorAndOpenSuggestions: hasMoreMandatoryArgs, + }); } - const suggestions = []; + const suggestions: SuggestionRawDefinition[] = []; const noArgDefined = !arg; const isUnknownColumn = arg && @@ -1267,7 +1294,9 @@ async function getFunctionArgsSuggestions( // if existing arguments are preset already, use them to filter out incompatible signatures .filter((signature) => { if (existingTypes.length) { - return existingTypes.every((type, index) => signature.params[index].type === type); + return existingTypes.every((type, index) => + compareTypesWithLiterals(signature.params[index].type, type) + ); } return true; }); @@ -1299,28 +1328,51 @@ async function getFunctionArgsSuggestions( return Array.from(new Set(paramDefs.map(({ type }) => type))); }; + // Literals suggestions.push( - ...getCompatibleLiterals(command.name, getTypesFromParamDefs(constantOnlyParamDefs)) + ...getCompatibleLiterals( + command.name, + getTypesFromParamDefs(constantOnlyParamDefs), + undefined, + { addComma: shouldAddComma, advanceCursorAndOpenSuggestions: hasMoreMandatoryArgs } + ) ); + // Fields suggestions.push( - ...(await getFieldsOrFunctionsSuggestions( - getTypesFromParamDefs(paramDefsWhichSupportFields), + ...pushItUpInTheList( + await getFieldsByType(getTypesFromParamDefs(paramDefsWhichSupportFields), [], { + addComma: shouldAddComma, + advanceCursorAndOpenSuggestions: hasMoreMandatoryArgs, + }), + true + ) + ); + + // Functions + suggestions.push( + ...getCompatibleFunctionDefinition( command.name, option?.name, - getFieldsByType, - { - functions: true, - fields: true, - variables: variablesExcludingCurrentCommandOnes, - }, - // do not repropose the same function as arg - // i.e. avoid cases like abs(abs(abs(...))) with suggestions - { - ignoreFn: fnToIgnore, - } - )) + getTypesFromParamDefs(paramDefsWhichSupportFields), + fnToIgnore + ).map((suggestion) => ({ + ...suggestion, + text: addCommaIf(shouldAddComma, suggestion.text), + })) ); + + // could also be in stats (bucket) but our autocomplete is not great yet + if ( + getTypesFromParamDefs(paramDefsWhichSupportFields).includes('date') && + ['where', 'eval'].includes(command.name) + ) + suggestions.push( + ...getDateLiterals({ + addComma: shouldAddComma, + advanceCursorAndOpenSuggestions: hasMoreMandatoryArgs, + }) + ); } // for eval and row commands try also to complete numeric literals with time intervals where possible @@ -1329,18 +1381,10 @@ async function getFunctionArgsSuggestions( if (isLiteralItem(arg) && isNumericType(arg.literalType)) { // ... | EVAL fn(2 ) suggestions.push( - ...(await getFieldsOrFunctionsSuggestions( - ['time_literal_unit'], - command.name, - option?.name, - getFieldsByType, - { - functions: false, - fields: false, - variables: variablesExcludingCurrentCommandOnes, - literals: true, - } - )) + ...getCompatibleLiterals(command.name, ['time_literal_unit'], undefined, { + addComma: shouldAddComma, + advanceCursorAndOpenSuggestions: hasMoreMandatoryArgs, + }) ); } } @@ -1349,24 +1393,9 @@ async function getFunctionArgsSuggestions( // suggest a comma if there's another argument for the function suggestions.push(commaCompleteItem); } - // if there are other arguments in the function, inject automatically a comma after each suggestion - return suggestions.map((suggestion) => - suggestion !== commaCompleteItem - ? { - ...suggestion, - text: - hasMoreMandatoryArgs && fnDefinition.type !== 'builtin' - ? `${suggestion.text},` - : suggestion.text, - } - : suggestion - ); } - return suggestions.map(({ text, ...rest }) => ({ - ...rest, - text: addCommaIf(hasMoreMandatoryArgs && fnDefinition.type !== 'builtin' && text !== '', text), - })); + return suggestions; } async function getListArgsSuggestions( @@ -1521,7 +1550,7 @@ async function getOptionArgsSuggestions( innerText ); - if (isNewExpression || findPreviousWord(innerText) === 'WITH') { + if (isNewExpression || noCaseCompare(findPreviousWord(innerText), 'WITH')) { suggestions.push(buildNewVarDefinition(findNewVariable(anyEnhancedVariables))); } @@ -1595,19 +1624,6 @@ async function getOptionArgsSuggestions( } if (command.name === 'stats') { - suggestions.push( - ...(await getFieldsOrFunctionsSuggestions( - ['column'], - command.name, - option.name, - getFieldsByType, - { - functions: false, - fields: true, - } - )) - ); - const argDef = optionDef?.signature.params[argIndex]; const nodeArgType = extractFinalTypeFromArg(nodeArg, references); @@ -1667,20 +1683,27 @@ async function getOptionArgsSuggestions( }) ); } else if (isNewExpression || (isAssignment(nodeArg) && !isAssignmentComplete(nodeArg))) { - // Otherwise try to complete the expression suggesting some columns suggestions.push( - ...(await getFieldsOrFunctionsSuggestions( - types[0] === 'column' ? ['any'] : types, - command.name, - option.name, - getFieldsByType, - { - functions: option.name === 'by', - fields: true, - } - )) + ...(await getFieldsByType(types[0] === 'column' ? ['any'] : types, [], { + advanceCursorAndOpenSuggestions: true, + })) ); + if (option.name === 'by') { + suggestions.push( + ...(await getFieldsOrFunctionsSuggestions( + types[0] === 'column' ? ['any'] : types, + command.name, + option.name, + getFieldsByType, + { + functions: true, + fields: false, + } + )) + ); + } + if (command.name === 'stats' && isNewExpression) { suggestions.push(buildNewVarDefinition(findNewVariable(anyVariables))); } diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/complete_items.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/complete_items.ts index e0ab600aa1382..88acf1c207f09 100644 --- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/complete_items.ts +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/complete_items.ts @@ -98,13 +98,16 @@ function buildCharCompleteItem( sortText, }; } -export const pipeCompleteItem = buildCharCompleteItem( - '|', - i18n.translate('kbn-esql-validation-autocomplete.esql.autocomplete.pipeDoc', { +export const pipeCompleteItem: SuggestionRawDefinition = { + label: '|', + text: '| ', + kind: 'Keyword', + detail: i18n.translate('kbn-esql-validation-autocomplete.esql.autocomplete.pipeDoc', { defaultMessage: 'Pipe (|)', }), - { sortText: 'C', quoted: false } -); + sortText: 'C', + command: TRIGGER_SUGGESTION_COMMAND, +}; export const commaCompleteItem = buildCharCompleteItem( ',', diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/factories.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/factories.ts index cb56f40a6d6f4..13a3c76b389f0 100644 --- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/factories.ts +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/factories.ts @@ -130,7 +130,8 @@ export function getSuggestionCommandDefinition( } export const buildFieldsDefinitionsWithMetadata = ( - fields: ESQLRealField[] + fields: ESQLRealField[], + options?: { advanceCursorAndOpenSuggestions?: boolean; addComma?: boolean } ): SuggestionRawDefinition[] => { return fields.map((field) => { const description = field.metadata?.description; @@ -138,7 +139,10 @@ export const buildFieldsDefinitionsWithMetadata = ( const titleCaseType = field.type.charAt(0).toUpperCase() + field.type.slice(1); return { label: field.name, - text: getSafeInsertText(field.name), + text: + getSafeInsertText(field.name) + + (options?.addComma ? ',' : '') + + (options?.advanceCursorAndOpenSuggestions ? ' ' : ''), kind: 'Variable', detail: titleCaseType, documentation: description @@ -151,6 +155,7 @@ ${description}`, : undefined, // If there is a description, it is a field from ECS, so it should be sorted to the top sortText: description ? '1D' : 'D', + command: options?.advanceCursorAndOpenSuggestions ? TRIGGER_SUGGESTION_COMMAND : undefined, }; }); }; @@ -185,9 +190,8 @@ export const buildSourcesDefinitions = ( ): SuggestionRawDefinition[] => sources.map(({ name, isIntegration, title }) => ({ label: title ?? name, - text: getSafeInsertSourceText(name), + text: getSafeInsertSourceText(name) + (!isIntegration ? ' ' : ''), isSnippet: isIntegration, - ...(isIntegration && { command: TRIGGER_SUGGESTION_COMMAND }), kind: isIntegration ? 'Class' : 'Issue', detail: isIntegration ? i18n.translate('kbn-esql-validation-autocomplete.esql.autocomplete.integrationDefinition', { @@ -197,16 +201,24 @@ export const buildSourcesDefinitions = ( defaultMessage: `Index`, }), sortText: 'A', + command: TRIGGER_SUGGESTION_COMMAND, })); export const buildConstantsDefinitions = ( userConstants: string[], detail?: string, - sortText?: string + sortText?: string, + /** + * Whether or not to advance the cursor and open the suggestions dialog after inserting the constant. + */ + options?: { advanceCursorAndOpenSuggestions?: boolean; addComma?: boolean } ): SuggestionRawDefinition[] => userConstants.map((label) => ({ label, - text: label, + text: + label + + (options?.addComma ? ',' : '') + + (options?.advanceCursorAndOpenSuggestions ? ' ' : ''), kind: 'Constant', detail: detail ?? @@ -214,32 +226,35 @@ export const buildConstantsDefinitions = ( defaultMessage: `Constant`, }), sortText: sortText ?? 'A', + command: options?.advanceCursorAndOpenSuggestions ? TRIGGER_SUGGESTION_COMMAND : undefined, })); export const buildValueDefinitions = ( values: string[], - detail?: string + options?: { advanceCursorAndOpenSuggestions?: boolean; addComma?: boolean } ): SuggestionRawDefinition[] => values.map((value) => ({ label: `"${value}"`, - text: `"${value}"`, - detail: - detail ?? - i18n.translate('kbn-esql-validation-autocomplete.esql.autocomplete.valueDefinition', { - defaultMessage: 'Literal value', - }), + text: `"${value}"${options?.addComma ? ',' : ''}${ + options?.advanceCursorAndOpenSuggestions ? ' ' : '' + }`, + detail: i18n.translate('kbn-esql-validation-autocomplete.esql.autocomplete.valueDefinition', { + defaultMessage: 'Literal value', + }), kind: 'Value', + command: options?.advanceCursorAndOpenSuggestions ? TRIGGER_SUGGESTION_COMMAND : undefined, })); export const buildNewVarDefinition = (label: string): SuggestionRawDefinition => { return { label, - text: `${label} =`, + text: `${label} = `, kind: 'Variable', detail: i18n.translate('kbn-esql-validation-autocomplete.esql.autocomplete.newVarDoc', { defaultMessage: 'Define a new variable', }), sortText: '1', + command: TRIGGER_SUGGESTION_COMMAND, }; }; @@ -358,21 +373,39 @@ export function getUnitDuration(unit: number = 1) { * "magical" logic. Maybe this is really the same thing as the literalOptions parameter * definition property... */ -export function getCompatibleLiterals(commandName: string, types: string[], names?: string[]) { +export function getCompatibleLiterals( + commandName: string, + types: string[], + names?: string[], + options?: { advanceCursorAndOpenSuggestions?: boolean; addComma?: boolean } +) { const suggestions: SuggestionRawDefinition[] = []; if (types.some(isNumericType)) { if (commandName === 'limit') { // suggest 10/100/1000 for limit - suggestions.push(...buildConstantsDefinitions(['10', '100', '1000'], '')); + suggestions.push( + ...buildConstantsDefinitions(['10', '100', '1000'], '', undefined, { + advanceCursorAndOpenSuggestions: true, + }) + ); } } if (types.includes('time_literal')) { // filter plural for now and suggest only unit + singular - suggestions.push(...buildConstantsDefinitions(getUnitDuration(1))); // i.e. 1 year + suggestions.push( + ...buildConstantsDefinitions(getUnitDuration(1), undefined, undefined, options) + ); // i.e. 1 year } // this is a special type built from the suggestion system, not inherited from the AST if (types.includes('time_literal_unit')) { - suggestions.push(...buildConstantsDefinitions(timeUnitsToSuggest.map(({ name }) => name))); // i.e. year, month, ... + suggestions.push( + ...buildConstantsDefinitions( + timeUnitsToSuggest.map(({ name }) => name), + undefined, + undefined, + options + ) + ); // i.e. year, month, ... } if (types.includes('string')) { if (names) { @@ -383,25 +416,31 @@ export function getCompatibleLiterals(commandName: string, types: string[], name [commandName === 'grok' ? '"%{WORD:firstWord}"' : '"%{firstWord}"'], i18n.translate('kbn-esql-validation-autocomplete.esql.autocomplete.aPatternString', { defaultMessage: 'A pattern string', - }) + }), + undefined, + options ) ); } else { - suggestions.push(...buildConstantsDefinitions(['string'], '')); + suggestions.push(...buildConstantsDefinitions(['string'], '', undefined, options)); } } } return suggestions; } -export function getDateLiterals() { +export function getDateLiterals(options?: { + advanceCursorAndOpenSuggestions?: boolean; + addComma?: boolean; +}) { return [ ...buildConstantsDefinitions( TIME_SYSTEM_PARAMS, i18n.translate('kbn-esql-validation-autocomplete.esql.autocomplete.namedParamDefinition', { defaultMessage: 'Named parameter', }), - '1A' + '1A', + options ), { label: i18n.translate( diff --git a/packages/kbn-esql-validation-autocomplete/src/shared/esql_types.ts b/packages/kbn-esql-validation-autocomplete/src/shared/esql_types.ts index dab8769f8477a..fada6bea88134 100644 --- a/packages/kbn-esql-validation-autocomplete/src/shared/esql_types.ts +++ b/packages/kbn-esql-validation-autocomplete/src/shared/esql_types.ts @@ -6,7 +6,8 @@ * Side Public License, v 1. */ -import { ESQLDecimalLiteral, ESQLNumericLiteralType } from '@kbn/esql-ast/src/types'; +import { ESQLDecimalLiteral, ESQLLiteral, ESQLNumericLiteralType } from '@kbn/esql-ast/src/types'; +import { FunctionParameterType } from '../definitions/types'; export const ESQL_COMMON_NUMERIC_TYPES = ['double', 'long', 'integer'] as const; export const ESQL_NUMERIC_DECIMAL_TYPES = [ @@ -47,3 +48,29 @@ export function isNumericDecimalType(type: unknown): type is ESQLDecimalLiteral ESQL_NUMERIC_DECIMAL_TYPES.includes(type as (typeof ESQL_NUMERIC_DECIMAL_TYPES)[number]) ); } + +/** + * Compares two types, taking into account literal types + * @TODO strengthen typing here (remove `string`) + */ +export const compareTypesWithLiterals = ( + a: ESQLLiteral['literalType'] | FunctionParameterType | string, + b: ESQLLiteral['literalType'] | FunctionParameterType | string +) => { + if (a === b) { + return true; + } + if (a === 'decimal') { + return isNumericDecimalType(b); + } + if (b === 'decimal') { + return isNumericDecimalType(a); + } + if (a === 'string') { + return isStringType(b); + } + if (b === 'string') { + return isStringType(a); + } + return false; +}; diff --git a/packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts b/packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts index e13326c2a9f43..1c9c82f676087 100644 --- a/packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts +++ b/packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts @@ -645,3 +645,8 @@ export const isParam = (x: unknown): x is ESQLParamLiteral => typeof x === 'object' && (x as ESQLParamLiteral).type === 'literal' && (x as ESQLParamLiteral).literalType === 'param'; + +/** + * Compares two strings in a case-insensitive manner + */ +export const noCaseCompare = (a: string, b: string) => a.toLowerCase() === b.toLowerCase(); diff --git a/packages/kbn-text-based-editor/src/text_based_languages_editor.tsx b/packages/kbn-text-based-editor/src/text_based_languages_editor.tsx index 8b92bb213157e..e67ffeac3c177 100644 --- a/packages/kbn-text-based-editor/src/text_based_languages_editor.tsx +++ b/packages/kbn-text-based-editor/src/text_based_languages_editor.tsx @@ -763,8 +763,18 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ forceMoveMarkers: true, }, ]); + setPopoverPosition({}); + datePickerOpenStatusRef.current = false; + + // move the cursor past the date we just inserted + editor1.current?.setPosition({ + lineNumber: currentCursorPosition?.lineNumber ?? 0, + column: (currentCursorPosition?.column ?? 0) + addition.length - 1, + }); + // restore focus to the editor + editor1.current?.focus(); } }} inline From f985bd4a1459308f7e72bfb2e2e7c8f09dcb180d Mon Sep 17 00:00:00 2001 From: Jeramy Soucy Date: Wed, 7 Aug 2024 15:41:59 +0200 Subject: [PATCH 12/44] Unskips security response headers tests for mki (#190012) ## Summary Unskips the security response headers tests for MKI, as skipped by https://github.com/elastic/kibana/pull/188716. The issue was resolved by https://github.com/elastic/kibana/pull/189139. --- .../test_suites/common/platform_security/response_headers.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/x-pack/test_serverless/api_integration/test_suites/common/platform_security/response_headers.ts b/x-pack/test_serverless/api_integration/test_suites/common/platform_security/response_headers.ts index 97afa8213aa4f..562e98d33866a 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/platform_security/response_headers.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/platform_security/response_headers.ts @@ -17,9 +17,6 @@ export default function ({ getService }: FtrProviderContext) { let roleAuthc: RoleCredentials; describe('security/response_headers', function () { - // fails on MKI, see https://github.com/elastic/kibana/issues/188714 - this.tags(['failsOnMKI']); - const baseCSP = `script-src 'report-sample' 'self'; worker-src 'report-sample' 'self' blob:; style-src 'report-sample' 'self' 'unsafe-inline'; frame-ancestors 'self'`; const defaultCOOP = 'same-origin'; const defaultPermissionsPolicy = From 1c4b5c748977463b70b8fe7716498e99ab6863dc Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Wed, 7 Aug 2024 15:51:52 +0200 Subject: [PATCH 13/44] [OneDiscover][Extension] DataTable Row Actions (#188762) - Closes https://github.com/elastic/kibana/issues/186637 - Closes https://github.com/elastic/kibana/issues/186808 ## Summary - [x] Extend UnifiedDataTable with a new `rowAdditionalLeadingControls` prop to render additional leading controls - [x] In case of many actions, collapse the rest of them under "More" button - [x] New OneDiscover extension - [x] Convert from `customControlColumnsConfiguration` to the new prop. Refactor actions format in Log Explorer. - [x] Swap the default "select" and "expand" control columns ~if custom row actions are specified~ - [x] Add to example OneDiscover profile - [x] Add functional and units tests Screenshot 2024-07-26 at 16 00 17 Screenshot 2024-07-26 at 16 00 47 ### Testing For testing the example profile: - add `discover.experimental.enabledProfiles: ['example-root-profile', 'example-data-source-profile', 'example-document-profile']` to kibana.dev.yml - start kibana - make sure to have an index with `my-example-logs` name or create an alias to an existing index: ``` POST _aliases { "actions": [ { "add": { "index": "kibana_sample_data_logs", "alias": "my-example-logs" } } ] } ``` - create a data view for `my-example-logs` index ### Follow up for Security solution The following items would require deprecation/refactoring in components on Security Solution pages to have consistent UX (can result in 500+ lines of code changes): - Convert from `externalControlColumns` to the new prop `rowAdditionalLeadingControls` - Convert from `trailingControlColumns` to a normal column. `trailingControlColumns` is deprecated. - https://github.com/elastic/kibana/issues/189294 - Use `getRowIndicator` prop on UnifiedDataTable instead of `border-left` style - https://github.com/elastic/kibana/issues/189295 ### 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 renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [x] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- packages/kbn-unified-data-table/README.md | 5 +- .../__mocks__/external_control_columns.tsx | 20 ++ packages/kbn-unified-data-table/index.ts | 2 +- ...et_additional_row_control_columns.test.tsx | 51 +++++ .../get_additional_row_control_columns.ts | 25 +++ .../additional_row_control/index.ts | 9 + .../row_control_column.test.tsx | 53 +++++ .../row_control_column.tsx | 76 ++++++++ .../row_menu_control_column.test.tsx | 65 +++++++ .../row_menu_control_column.tsx | 130 +++++++++++++ .../color_indicator_control_column.tsx | 30 +-- .../custom_control_columns/index.ts | 2 + .../src/components/data_table.scss | 13 +- .../src/components/data_table.test.tsx | 31 +-- .../src/components/data_table.tsx | 77 +++++--- .../src/components/data_table_columns.tsx | 19 +- .../data_table_document_selection.tsx | 27 +-- .../components/data_table_expand_button.tsx | 32 +-- .../src/components/data_table_row_control.tsx | 13 +- .../kbn-unified-data-table/src/constants.ts | 2 + .../src/hooks/use_control_column.ts | 41 ++++ packages/kbn-unified-data-table/src/types.ts | 36 ++-- .../layout/discover_documents.test.tsx | 11 +- .../components/layout/discover_documents.tsx | 4 +- .../discover_grid/discover_grid.tsx | 16 +- .../example_data_source_profile/profile.tsx | 26 +++ .../public/context_awareness/types.ts | 13 +- .../data_table_customisation.ts | 4 +- .../_get_row_additional_leading_controls.ts | 64 ++++++ .../apps/discover/context_awareness/index.ts | 1 + .../apps/discover/esql/_esql_view.ts | 2 +- .../dashboard_embed_mode_scrolling.png | Bin 88013 -> 82090 bytes test/functional/services/data_grid.ts | 16 ++ .../public/components/common/translations.tsx | 45 +---- .../actions_column_tooltip.tsx | 95 --------- .../customizations/custom_control_column.tsx | 182 +++++++----------- .../customizations/logs_explorer_profile.tsx | 4 +- .../translations/translations/fr-FR.json | 5 - .../translations/translations/ja-JP.json | 5 - .../translations/translations/zh-CN.json | 5 - .../columns_selection.ts | 24 +-- .../custom_control_columns.ts | 10 +- .../_get_row_additional_leading_controls.ts | 66 +++++++ .../discover/context_awareness/index.ts | 1 + .../common/discover/esql/_esql_view.ts | 2 +- .../columns_selection.ts | 24 +-- .../custom_control_columns.ts | 31 ++- 47 files changed, 942 insertions(+), 473 deletions(-) create mode 100644 packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/get_additional_row_control_columns.test.tsx create mode 100644 packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/get_additional_row_control_columns.ts create mode 100644 packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/index.ts create mode 100644 packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_control_column.test.tsx create mode 100644 packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_control_column.tsx create mode 100644 packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_menu_control_column.test.tsx create mode 100644 packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_menu_control_column.tsx create mode 100644 packages/kbn-unified-data-table/src/hooks/use_control_column.ts create mode 100644 test/functional/apps/discover/context_awareness/extensions/_get_row_additional_leading_controls.ts delete mode 100644 x-pack/plugins/observability_solution/logs_explorer/public/components/virtual_columns/column_tooltips/actions_column_tooltip.tsx create mode 100644 x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/extensions/_get_row_additional_leading_controls.ts diff --git a/packages/kbn-unified-data-table/README.md b/packages/kbn-unified-data-table/README.md index 576a676289d7a..7a2db17781ad1 100644 --- a/packages/kbn-unified-data-table/README.md +++ b/packages/kbn-unified-data-table/README.md @@ -41,13 +41,12 @@ Props description: | **configRowHeight** | (optional)number | Optional value for providing configuration setting for UnifiedDataTable rows height. | | **showMultiFields** | (optional)boolean | Optional value for providing configuration setting for enabling to display the complex fields in the table. Default is true. | | **maxDocFieldsDisplayed** | (optional)number | Optional value for providing configuration setting for maximum number of document fields to display in the table. Default is 50. | -| **externalControlColumns** | (optional)EuiDataGridControlColumn[] | Optional value for providing EuiDataGridControlColumn list of the additional leading control columns. UnifiedDataTable includes two control columns: Open Details and Select. | +| **rowAdditionalLeadingControls** | (optional)RowControlColumn[] | Optional value for providing an list of the additional leading control columns. UnifiedDataTable includes two control columns: Open Details and Select. | | **totalHits** | (optional)number | Number total hits from ES. | | **onFetchMoreRecords** | (optional)() => void | To fetch more. | | **externalAdditionalControls** | (optional)React.ReactNode | Optional value for providing the additional controls available in the UnifiedDataTable toolbar to manage it's records or state. UnifiedDataTable includes Columns, Sorting and Bulk Actions. | | **rowsPerPageOptions** | (optional)number[] | Optional list of number type values to set custom UnifiedDataTable paging options to display the records per page. | | **renderCustomGridBody** | (optional)(args: EuiDataGridCustomBodyProps) => React.ReactNode; | An optional function called to completely customize and control the rendering of EuiDataGrid's body and cell placement. | -| **trailingControlColumns** | (optional)EuiDataGridControlColumn[] | An optional list of the EuiDataGridControlColumn type for setting trailing control columns standard for EuiDataGrid. | | **visibleCellActions** | (optional)number | An optional value for a custom number of the visible cell actions in the table. By default is up to 3. | | **externalCustomRenderers** | (optional)Record React.ReactNode>; | An optional settings for a specified fields rendering like links. Applied only for the listed fields rendering. | | **consumer** | (optional)string | Name of the UnifiedDataTable consumer component or application. | @@ -141,9 +140,7 @@ Usage example: [browserFields, handleOnPanelClosed, runtimeMappings, timelineId] ); } - externalControlColumns={leadingControlColumns} externalAdditionalControls={additionalControls} - trailingControlColumns={trailingControlColumns} renderCustomGridBody={renderCustomGridBody} rowsPerPageOptions={[10, 30, 40, 100]} showFullScreenButton={false} diff --git a/packages/kbn-unified-data-table/__mocks__/external_control_columns.tsx b/packages/kbn-unified-data-table/__mocks__/external_control_columns.tsx index d67afccc01559..dce5e13a3c89d 100644 --- a/packages/kbn-unified-data-table/__mocks__/external_control_columns.tsx +++ b/packages/kbn-unified-data-table/__mocks__/external_control_columns.tsx @@ -17,6 +17,7 @@ import { EuiSpacer, EuiDataGridControlColumn, } from '@elastic/eui'; +import type { RowControlColumn } from '../src/types'; const SelectionHeaderCell = () => { return ( @@ -116,3 +117,22 @@ export const testLeadingControlColumn: EuiDataGridControlColumn = { rowCellRender: SelectionRowCell, width: 100, }; + +export const mockRowAdditionalLeadingControls = ['visBarVerticalStacked', 'heart', 'inspect'].map( + (iconType, index): RowControlColumn => ({ + id: `exampleControl_${iconType}`, + headerAriaLabel: `Example Row Control ${iconType}`, + renderControl: (Control, rowProps) => { + return ( + { + alert(`Example "${iconType}" control clicked. Row index: ${rowProps.rowIndex}`); + }} + /> + ); + }, + }) +); diff --git a/packages/kbn-unified-data-table/index.ts b/packages/kbn-unified-data-table/index.ts index 0929c33208fa0..7dace83c3774e 100644 --- a/packages/kbn-unified-data-table/index.ts +++ b/packages/kbn-unified-data-table/index.ts @@ -25,7 +25,7 @@ export { getRowsPerPageOptions } from './src/utils/rows_per_page'; export { popularizeField } from './src/utils/popularize_field'; export { useColumns } from './src/hooks/use_data_grid_columns'; -export { OPEN_DETAILS, SELECT_ROW } from './src/components/data_table_columns'; +export { OPEN_DETAILS, SELECT_ROW } from './src/components/data_table_columns'; // TODO: deprecate? export { DataTableRowControl } from './src/components/data_table_row_control'; export type { diff --git a/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/get_additional_row_control_columns.test.tsx b/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/get_additional_row_control_columns.test.tsx new file mode 100644 index 0000000000000..044b864213d82 --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/get_additional_row_control_columns.test.tsx @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { getAdditionalRowControlColumns } from './get_additional_row_control_columns'; +import { mockRowAdditionalLeadingControls } from '../../../../__mocks__/external_control_columns'; + +describe('getAdditionalRowControlColumns', () => { + it('should work correctly for 0 controls', () => { + const columns = getAdditionalRowControlColumns([]); + + expect(columns).toHaveLength(0); + }); + + it('should work correctly for 1 control', () => { + const columns = getAdditionalRowControlColumns([mockRowAdditionalLeadingControls[0]]); + + expect(columns.map((column) => column.id)).toEqual([ + `additionalRowControl_${mockRowAdditionalLeadingControls[0].id}`, + ]); + }); + + it('should work correctly for 2 controls', () => { + const columns = getAdditionalRowControlColumns([ + mockRowAdditionalLeadingControls[0], + mockRowAdditionalLeadingControls[1], + ]); + + expect(columns.map((column) => column.id)).toEqual([ + `additionalRowControl_${mockRowAdditionalLeadingControls[0].id}`, + `additionalRowControl_${mockRowAdditionalLeadingControls[1].id}`, + ]); + }); + + it('should work correctly for 3 and more controls', () => { + const columns = getAdditionalRowControlColumns([ + mockRowAdditionalLeadingControls[0], + mockRowAdditionalLeadingControls[1], + mockRowAdditionalLeadingControls[2], + ]); + + expect(columns.map((column) => column.id)).toEqual([ + `additionalRowControl_${mockRowAdditionalLeadingControls[0].id}`, + `additionalRowControl_menuControl`, + ]); + }); +}); diff --git a/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/get_additional_row_control_columns.ts b/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/get_additional_row_control_columns.ts new file mode 100644 index 0000000000000..bd297b37bb5df --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/get_additional_row_control_columns.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { EuiDataGridControlColumn } from '@elastic/eui'; +import type { RowControlColumn } from '../../../types'; +import { getRowControlColumn } from './row_control_column'; +import { getRowMenuControlColumn } from './row_menu_control_column'; + +export const getAdditionalRowControlColumns = ( + rowControlColumns: RowControlColumn[] +): EuiDataGridControlColumn[] => { + if (rowControlColumns.length <= 2) { + return rowControlColumns.map(getRowControlColumn); + } + + return [ + getRowControlColumn(rowControlColumns[0]), + getRowMenuControlColumn(rowControlColumns.slice(1)), + ]; +}; diff --git a/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/index.ts b/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/index.ts new file mode 100644 index 0000000000000..d3a79fc3a0d50 --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { getAdditionalRowControlColumns } from './get_additional_row_control_columns'; diff --git a/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_control_column.test.tsx b/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_control_column.test.tsx new file mode 100644 index 0000000000000..360fa7bc235c4 --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_control_column.test.tsx @@ -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 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 type { EuiDataGridCellValueElementProps } from '@elastic/eui'; +import { render, screen } from '@testing-library/react'; +import { getRowControlColumn } from './row_control_column'; +import { dataTableContextMock } from '../../../../__mocks__/table_context'; +import { UnifiedDataTableContext } from '../../../table_context'; + +describe('getRowControlColumn', () => { + const contextMock = { + ...dataTableContextMock, + }; + + it('should render the component', () => { + const mockClick = jest.fn(); + const props = { + id: 'test_row_control', + headerAriaLabel: 'row control', + renderControl: jest.fn((Control, rowProps) => ( + + )), + }; + const rowControlColumn = getRowControlColumn(props); + const RowControlColumn = + rowControlColumn.rowCellRender as React.FC; + render( + + + + ); + const button = screen.getByTestId('unifiedDataTable_rowControl_test_row_control'); + expect(button).toBeInTheDocument(); + + button.click(); + + expect(mockClick).toHaveBeenCalledWith({ record: contextMock.rows[1], rowIndex: 1 }); + }); +}); diff --git a/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_control_column.tsx b/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_control_column.tsx new file mode 100644 index 0000000000000..f8d3ad063fb2d --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_control_column.tsx @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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, { useMemo } from 'react'; +import { + EuiButtonIcon, + EuiDataGridCellValueElementProps, + EuiDataGridControlColumn, + EuiScreenReaderOnly, + EuiToolTip, +} from '@elastic/eui'; +import { DataTableRowControl, Size } from '../../data_table_row_control'; +import type { RowControlColumn, RowControlProps } from '../../../types'; +import { DEFAULT_CONTROL_COLUMN_WIDTH } from '../../../constants'; +import { useControlColumn } from '../../../hooks/use_control_column'; + +export const RowControlCell = ({ + renderControl, + ...props +}: EuiDataGridCellValueElementProps & { + renderControl: RowControlColumn['renderControl']; +}) => { + const rowProps = useControlColumn(props); + + const Control: React.FC = useMemo( + () => + ({ 'data-test-subj': dataTestSubj, color, disabled, label, iconType, onClick }) => { + return ( + + + { + onClick?.(rowProps); + }} + /> + + + ); + }, + [props.columnId, rowProps] + ); + + return renderControl(Control, rowProps); +}; + +export const getRowControlColumn = ( + rowControlColumn: RowControlColumn +): EuiDataGridControlColumn => { + const { id, headerAriaLabel, headerCellRender, renderControl } = rowControlColumn; + + return { + id: `additionalRowControl_${id}`, + width: DEFAULT_CONTROL_COLUMN_WIDTH, + headerCellRender: + headerCellRender ?? + (() => ( + + {headerAriaLabel} + + )), + rowCellRender: (props) => { + return ; + }, + }; +}; diff --git a/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_menu_control_column.test.tsx b/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_menu_control_column.test.tsx new file mode 100644 index 0000000000000..8e26e3f01d062 --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_menu_control_column.test.tsx @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 type { EuiDataGridCellValueElementProps } from '@elastic/eui'; +import { render, screen } from '@testing-library/react'; +import { getRowMenuControlColumn } from './row_menu_control_column'; +import { dataTableContextMock } from '../../../../__mocks__/table_context'; +import { mockRowAdditionalLeadingControls } from '../../../../__mocks__/external_control_columns'; +import { UnifiedDataTableContext } from '../../../table_context'; + +describe('getRowMenuControlColumn', () => { + const contextMock = { + ...dataTableContextMock, + }; + + it('should render the component', () => { + const mockClick = jest.fn(); + const props = { + id: 'test_row_menu_control', + headerAriaLabel: 'row control', + renderControl: jest.fn((Control, rowProps) => ( + + )), + }; + const rowMenuControlColumn = getRowMenuControlColumn([ + props, + mockRowAdditionalLeadingControls[0], + mockRowAdditionalLeadingControls[1], + ]); + const RowMenuControlColumn = + rowMenuControlColumn.rowCellRender as React.FC; + render( + + + + ); + const menuButton = screen.getByTestId('unifiedDataTable_test_row_menu_control'); + expect(menuButton).toBeInTheDocument(); + + menuButton.click(); + + expect(screen.getByTestId('exampleRowControl-visBarVerticalStacked')).toBeInTheDocument(); + expect(screen.getByTestId('exampleRowControl-heart')).toBeInTheDocument(); + + const button = screen.getByTestId('unifiedDataTable_rowMenu_test_row_menu_control'); + expect(button).toBeInTheDocument(); + + button.click(); + expect(mockClick).toHaveBeenCalledWith({ record: contextMock.rows[1], rowIndex: 1 }); + }); +}); diff --git a/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_menu_control_column.tsx b/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_menu_control_column.tsx new file mode 100644 index 0000000000000..917174618fa37 --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_menu_control_column.tsx @@ -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 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, { Fragment, useCallback, useMemo, useState } from 'react'; +import { + EuiButtonIcon, + EuiContextMenuItem, + EuiContextMenuPanel, + EuiDataGridCellValueElementProps, + EuiDataGridControlColumn, + EuiPopover, + EuiScreenReaderOnly, + EuiToolTip, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { css } from '@emotion/react'; +import { DataTableRowControl, Size } from '../../data_table_row_control'; +import type { RowControlColumn, RowControlProps } from '../../../types'; +import { DEFAULT_CONTROL_COLUMN_WIDTH } from '../../../constants'; +import { useControlColumn } from '../../../hooks/use_control_column'; + +/** + * Menu button under which all other additional row controls would be placed + */ +export const RowMenuControlCell = ({ + rowControlColumns, + ...props +}: EuiDataGridCellValueElementProps & { + rowControlColumns: RowControlColumn[]; +}) => { + const rowProps = useControlColumn(props); + const [isMoreActionsPopoverOpen, setIsMoreActionsPopoverOpen] = useState(false); + + const buttonLabel = i18n.translate('unifiedDataTable.grid.additionalRowActions', { + defaultMessage: 'Additional actions', + }); + + const getControlComponent: (id: string) => React.FC = useCallback( + (id) => + ({ 'data-test-subj': dataTestSubj, color, disabled, label, iconType, onClick }) => { + return ( + { + onClick?.(rowProps); + setIsMoreActionsPopoverOpen(false); + }} + > + {label} + + ); + }, + [rowProps, setIsMoreActionsPopoverOpen] + ); + + const popoverMenuItems = useMemo( + () => + rowControlColumns.map((rowControlColumn) => { + const Control = getControlComponent(rowControlColumn.id); + return ( + + {rowControlColumn.renderControl(Control, rowProps)} + + ); + }), + [rowControlColumns, rowProps, getControlComponent] + ); + + return ( + + + { + setIsMoreActionsPopoverOpen(!isMoreActionsPopoverOpen); + }} + /> + + + } + isOpen={isMoreActionsPopoverOpen} + closePopover={() => setIsMoreActionsPopoverOpen(false)} + panelPaddingSize="none" + anchorPosition="downLeft" + > + + + ); +}; + +export const getRowMenuControlColumn = ( + rowControlColumns: RowControlColumn[] +): EuiDataGridControlColumn => { + return { + id: 'additionalRowControl_menuControl', + width: DEFAULT_CONTROL_COLUMN_WIDTH, + headerCellRender: () => ( + + + {i18n.translate('unifiedDataTable.additionalActionsColumnHeader', { + defaultMessage: 'Additional actions column', + })} + + + ), + rowCellRender: (props) => { + return ; + }, + }; +}; diff --git a/packages/kbn-unified-data-table/src/components/custom_control_columns/color_indicator/color_indicator_control_column.tsx b/packages/kbn-unified-data-table/src/components/custom_control_columns/color_indicator/color_indicator_control_column.tsx index dd9be4ab90d13..902667810e613 100644 --- a/packages/kbn-unified-data-table/src/components/custom_control_columns/color_indicator/color_indicator_control_column.tsx +++ b/packages/kbn-unified-data-table/src/components/custom_control_columns/color_indicator/color_indicator_control_column.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React, { useContext, useEffect } from 'react'; +import React from 'react'; import { css } from '@emotion/react'; import { EuiDataGridControlColumn, @@ -15,7 +15,7 @@ import { EuiDataGridCellValueElementProps, } from '@elastic/eui'; import type { DataTableRecord } from '@kbn/discover-utils'; -import { UnifiedDataTableContext } from '../../../table_context'; +import { useControlColumn } from '../../../hooks/use_control_column'; const COLOR_INDICATOR_WIDTH = 4; @@ -28,32 +28,14 @@ interface ColorIndicatorCellParams { ) => { color: string; label: string } | undefined; } -const ColorIndicatorCell: React.FC = ({ - rowIndex, - setCellProps, - getRowIndicator, -}) => { +const ColorIndicatorCell: React.FC = ({ getRowIndicator, ...props }) => { + const { record } = useControlColumn(props); const { euiTheme } = useEuiTheme(); - const { rows, expanded } = useContext(UnifiedDataTableContext); - const row = rows[rowIndex]; - const configuration = row ? getRowIndicator(row, euiTheme) : undefined; + + const configuration = record ? getRowIndicator(record, euiTheme) : undefined; const color = configuration?.color || 'transparent'; const label = configuration?.label; - useEffect(() => { - if (row.isAnchor) { - setCellProps({ - className: 'unifiedDataTable__cell--highlight', - }); - } else if (expanded && row && expanded.id === row.id) { - setCellProps({ - className: 'unifiedDataTable__cell--expanded', - }); - } else { - setCellProps({ className: '' }); - } - }, [expanded, row, setCellProps]); - return (
{ }); }); - describe('customControlColumnsConfiguration', () => { - const customControlColumnsConfiguration = jest.fn(); - it('should be able to customise the leading control column', async () => { + describe('custom control columns', () => { + it('should be able to customise the leading controls', async () => { const component = await getComponent({ ...getProps(), expandedDoc: { @@ -467,23 +467,19 @@ describe('UnifiedDataTable', () => { setExpandedDoc: jest.fn(), renderDocumentView: jest.fn(), externalControlColumns: [testLeadingControlColumn], - customControlColumnsConfiguration: customControlColumnsConfiguration.mockImplementation( - () => { - return { - leadingControlColumns: [testLeadingControlColumn, testTrailingControlColumns[0]], - trailingControlColumns: [], - }; - } - ), + rowAdditionalLeadingControls: mockRowAdditionalLeadingControls, }); expect(findTestSubject(component, 'test-body-control-column-cell').exists()).toBeTruthy(); expect( - findTestSubject(component, 'test-trailing-column-popover-button').exists() + findTestSubject(component, 'exampleRowControl-visBarVerticalStacked').exists() + ).toBeTruthy(); + expect( + findTestSubject(component, 'unifiedDataTable_additionalRowControl_menuControl').exists() ).toBeTruthy(); }); - it('should be able to customise the trailing control column', async () => { + it('should be able to customise the trailing controls', async () => { const component = await getComponent({ ...getProps(), expandedDoc: { @@ -497,14 +493,7 @@ describe('UnifiedDataTable', () => { setExpandedDoc: jest.fn(), renderDocumentView: jest.fn(), externalControlColumns: [testLeadingControlColumn], - customControlColumnsConfiguration: customControlColumnsConfiguration.mockImplementation( - () => { - return { - leadingControlColumns: [], - trailingControlColumns: [testLeadingControlColumn, testTrailingControlColumns[0]], - }; - } - ), + trailingControlColumns: testTrailingControlColumns, }); expect(findTestSubject(component, 'test-body-control-column-cell').exists()).toBeTruthy(); diff --git a/packages/kbn-unified-data-table/src/components/data_table.tsx b/packages/kbn-unified-data-table/src/components/data_table.tsx index 1a12151c7c18a..243b86b540865 100644 --- a/packages/kbn-unified-data-table/src/components/data_table.tsx +++ b/packages/kbn-unified-data-table/src/components/data_table.tsx @@ -51,18 +51,19 @@ import { DataTableColumnsMeta, CustomCellRenderer, CustomGridColumnsConfiguration, - CustomControlColumnConfiguration, + RowControlColumn, } from '../types'; import { getDisplayedColumns } from '../utils/columns'; import { convertValueToString } from '../utils/convert_value_to_string'; import { getRowsPerPageOptions } from '../utils/rows_per_page'; import { getRenderCellValueFn } from '../utils/get_render_cell_value'; import { - getAllControlColumns, getEuiGridColumns, getLeadControlColumns, getVisibleColumns, canPrependTimeFieldColumn, + SELECT_ROW, + OPEN_DETAILS, } from './data_table_columns'; import { UnifiedDataTableContext } from '../table_context'; import { getSchemaDetectors } from './data_table_schema'; @@ -85,8 +86,11 @@ import { useSelectedDocs } from '../hooks/use_selected_docs'; import { getColorIndicatorControlColumn, type ColorIndicatorControlColumnParams, + getAdditionalRowControlColumns, } from './custom_control_columns'; +const CONTROL_COLUMN_IDS_DEFAULT = [SELECT_ROW, OPEN_DETAILS]; + export type SortOrder = [string, string]; export enum DataLoadingState { @@ -290,9 +294,20 @@ export interface UnifiedDataTableProps { */ maxDocFieldsDisplayed?: number; /** + * @deprecated Use only `rowAdditionalLeadingControls` instead * Optional value for providing EuiDataGridControlColumn list of the additional leading control columns. UnifiedDataTable includes two control columns: Open Details and Select. */ externalControlColumns?: EuiDataGridControlColumn[]; + /** + * An optional list of the EuiDataGridControlColumn type for setting trailing control columns standard for EuiDataGrid. + * We recommend to rather position all controls in the beginning of rows and use `rowAdditionalLeadingControls` for that + * as number of columns can be dynamically changed and we don't want the controls to become hidden due to horizontal scroll. + */ + trailingControlColumns?: EuiDataGridControlColumn[]; + /** + * Optional value to extend the list of default row actions + */ + rowAdditionalLeadingControls?: RowControlColumn[]; /** * Number total hits from ES */ @@ -327,10 +342,6 @@ export interface UnifiedDataTableProps { * @param gridProps */ renderCustomToolbar?: UnifiedDataTableRenderCustomToolbar; - /** - * An optional list of the EuiDataGridControlColumn type for setting trailing control columns standard for EuiDataGrid. - */ - trailingControlColumns?: EuiDataGridControlColumn[]; /** * An optional value for a custom number of the visible cell actions in the table. By default is up to 3. **/ @@ -347,10 +358,6 @@ export interface UnifiedDataTableProps { * An optional settings for customising the column */ customGridColumnsConfiguration?: CustomGridColumnsConfiguration; - /** - * An optional settings to control which columns to render as trailing and leading control columns - */ - customControlColumnsConfiguration?: CustomControlColumnConfiguration; /** * Name of the UnifiedDataTable consumer component or application */ @@ -396,8 +403,6 @@ export interface UnifiedDataTableProps { export const EuiDataGridMemoized = React.memo(EuiDataGrid); -const CONTROL_COLUMN_IDS_DEFAULT = ['openDetails', 'select']; - export const UnifiedDataTable = ({ ariaLabelledBy, columns, @@ -407,6 +412,7 @@ export const UnifiedDataTable = ({ headerRowHeightState, onUpdateHeaderRowHeight, controlColumnIds = CONTROL_COLUMN_IDS_DEFAULT, + rowAdditionalLeadingControls, dataView, loadingState, onFilter, @@ -437,7 +443,8 @@ export const UnifiedDataTable = ({ services, renderCustomGridBody, renderCustomToolbar, - trailingControlColumns, + externalControlColumns, // TODO: deprecate in favor of rowAdditionalLeadingControls + trailingControlColumns, // TODO: deprecate in favor of rowAdditionalLeadingControls totalHits, onFetchMoreRecords, renderDocumentView, @@ -446,7 +453,6 @@ export const UnifiedDataTable = ({ configRowHeight, showMultiFields = true, maxDocFieldsDisplayed = 50, - externalControlColumns, externalAdditionalControls, rowsPerPageOptions, visibleCellActions, @@ -458,7 +464,6 @@ export const UnifiedDataTable = ({ rowLineHeightOverride, cellActionsMetadata, customGridColumnsConfiguration, - customControlColumnsConfiguration, enableComparisonMode, cellContext, renderCellPopover, @@ -847,10 +852,19 @@ export const UnifiedDataTable = ({ const canSetExpandedDoc = Boolean(setExpandedDoc && !!renderDocumentView); const leadingControlColumns: EuiDataGridControlColumn[] = useMemo(() => { - const internalControlColumns = getLeadControlColumns(canSetExpandedDoc).filter(({ id }) => - controlColumnIds.includes(id) - ); - const leadingColumns = externalControlColumns + const defaultControlColumns = getLeadControlColumns(canSetExpandedDoc); + const internalControlColumns = controlColumnIds + ? // reorder the default controls as per controlColumnIds + controlColumnIds.reduce((acc, id) => { + const controlColumn = defaultControlColumns.find((col) => col.id === id); + if (controlColumn) { + acc.push(controlColumn); + } + return acc; + }, [] as EuiDataGridControlColumn[]) + : defaultControlColumns; + + const leadingColumns: EuiDataGridControlColumn[] = externalControlColumns ? [...internalControlColumns, ...externalControlColumns] : internalControlColumns; @@ -861,17 +875,18 @@ export const UnifiedDataTable = ({ leadingColumns.unshift(colorIndicatorControlColumn); } - return leadingColumns; - }, [canSetExpandedDoc, controlColumnIds, externalControlColumns, getRowIndicator]); - - const controlColumnsConfig = customControlColumnsConfiguration?.({ - controlColumns: getAllControlColumns(), - }); + if (rowAdditionalLeadingControls?.length) { + leadingColumns.push(...getAdditionalRowControlColumns(rowAdditionalLeadingControls)); + } - const customLeadingControlColumn = - controlColumnsConfig?.leadingControlColumns ?? leadingControlColumns; - const customTrailingControlColumn = - controlColumnsConfig?.trailingControlColumns ?? trailingControlColumns; + return leadingColumns; + }, [ + canSetExpandedDoc, + controlColumnIds, + externalControlColumns, + getRowIndicator, + rowAdditionalLeadingControls, + ]); const additionalControls = useMemo(() => { if (!externalAdditionalControls && !selectedDocIds.length) { @@ -1082,7 +1097,7 @@ export const UnifiedDataTable = ({ columns={euiGridColumns} columnVisibility={columnsVisibility} data-test-subj="docTable" - leadingControlColumns={customLeadingControlColumn} + leadingControlColumns={leadingControlColumns} onColumnResize={onResize} pagination={paginationObj} renderCellValue={renderCellValue} @@ -1096,7 +1111,7 @@ export const UnifiedDataTable = ({ gridStyle={gridStyleOverride ?? GRID_STYLE} renderCustomGridBody={renderCustomGridBody} renderCustomToolbar={renderCustomToolbarFn} - trailingControlColumns={customTrailingControlColumn} + trailingControlColumns={trailingControlColumns} cellContext={cellContext} renderCellPopover={renderCustomPopover} /> diff --git a/packages/kbn-unified-data-table/src/components/data_table_columns.tsx b/packages/kbn-unified-data-table/src/components/data_table_columns.tsx index 202a593018792..4528e323c2047 100644 --- a/packages/kbn-unified-data-table/src/components/data_table_columns.tsx +++ b/packages/kbn-unified-data-table/src/components/data_table_columns.tsx @@ -17,12 +17,16 @@ import { type DataView, DataViewField } from '@kbn/data-views-plugin/public'; import { ToastsStart, IUiSettingsClient } from '@kbn/core/public'; import { DocViewFilterFn } from '@kbn/unified-doc-viewer/types'; import { ExpandButton } from './data_table_expand_button'; -import { ControlColumns, CustomGridColumnsConfiguration, UnifiedDataTableSettings } from '../types'; +import { CustomGridColumnsConfiguration, UnifiedDataTableSettings } from '../types'; import type { ValueToStringConverter, DataTableColumnsMeta } from '../types'; import { buildCellActions } from './default_cell_actions'; import { getSchemaByKbnType } from './data_table_schema'; import { SelectButton, SelectAllButton } from './data_table_document_selection'; -import { defaultTimeColumnWidth, ROWS_HEIGHT_OPTIONS } from '../constants'; +import { + defaultTimeColumnWidth, + ROWS_HEIGHT_OPTIONS, + DEFAULT_CONTROL_COLUMN_WIDTH, +} from '../constants'; import { buildCopyColumnNameButton, buildCopyColumnValuesButton } from './build_copy_column_button'; import { buildEditFieldButton } from './build_edit_field_button'; import { DataTableColumnHeader, DataTableTimeColumnHeader } from './data_table_column_header'; @@ -53,7 +57,7 @@ export const SELECT_ROW = 'select'; const openDetails = { id: OPEN_DETAILS, - width: 26, + width: DEFAULT_CONTROL_COLUMN_WIDTH, headerCellRender: () => ( @@ -68,18 +72,11 @@ const openDetails = { const select = { id: SELECT_ROW, - width: 24, + width: DEFAULT_CONTROL_COLUMN_WIDTH, rowCellRender: SelectButton, headerCellRender: SelectAllButton, }; -export function getAllControlColumns(): ControlColumns { - return { - [SELECT_ROW]: select, - [OPEN_DETAILS]: openDetails, - }; -} - export function getLeadControlColumns(canSetExpandedDoc: boolean) { if (!canSetExpandedDoc) { return [select]; diff --git a/packages/kbn-unified-data-table/src/components/data_table_document_selection.tsx b/packages/kbn-unified-data-table/src/components/data_table_document_selection.tsx index b844a4bb537ca..2a34c4866cb86 100644 --- a/packages/kbn-unified-data-table/src/components/data_table_document_selection.tsx +++ b/packages/kbn-unified-data-table/src/components/data_table_document_selection.tsx @@ -5,7 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useContext, useMemo, useState } from 'react'; import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; import { ES_FIELD_TYPES, KBN_FIELD_TYPES } from '@kbn/field-types'; import { @@ -28,28 +28,19 @@ import { css } from '@emotion/react'; import type { DataTableRecord } from '@kbn/discover-utils/types'; import type { UseSelectedDocsState } from '../hooks/use_selected_docs'; import { UnifiedDataTableContext } from '../table_context'; +import { useControlColumn } from '../hooks/use_control_column'; -export const SelectButton = ({ rowIndex, setCellProps }: EuiDataGridCellValueElementProps) => { +export const SelectButton = (props: EuiDataGridCellValueElementProps) => { + const { record, rowIndex } = useControlColumn(props); const { euiTheme } = useEuiTheme(); - const { selectedDocsState, expanded, rows, isDarkMode } = useContext(UnifiedDataTableContext); + const { selectedDocsState } = useContext(UnifiedDataTableContext); const { isDocSelected, toggleDocSelection } = selectedDocsState; - const doc = useMemo(() => rows[rowIndex], [rows, rowIndex]); const toggleDocumentSelectionLabel = i18n.translate('unifiedDataTable.grid.selectDoc', { defaultMessage: `Select document ''{rowNumber}''`, values: { rowNumber: rowIndex + 1 }, }); - useEffect(() => { - if (expanded && doc && expanded.id === doc.id) { - setCellProps({ - className: 'unifiedDataTable__cell--selected', - }); - } else { - setCellProps({ className: '' }); - } - }, [expanded, doc, setCellProps, isDarkMode]); - return ( { - toggleDocSelection(doc.id); + toggleDocSelection(record.id); }} /> diff --git a/packages/kbn-unified-data-table/src/components/data_table_expand_button.tsx b/packages/kbn-unified-data-table/src/components/data_table_expand_button.tsx index da3c8a5026ea0..04ae49abec141 100644 --- a/packages/kbn-unified-data-table/src/components/data_table_expand_button.tsx +++ b/packages/kbn-unified-data-table/src/components/data_table_expand_button.tsx @@ -10,39 +10,27 @@ import React, { useContext, useEffect, useRef, useState } from 'react'; import { EuiButtonIcon, EuiDataGridCellValueElementProps, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { UnifiedDataTableContext } from '../table_context'; -import { DataTableRowControl } from './data_table_row_control'; +import { DataTableRowControl, Size } from './data_table_row_control'; +import { useControlColumn } from '../hooks/use_control_column'; /** * Button to expand a given row */ -export const ExpandButton = ({ rowIndex, setCellProps }: EuiDataGridCellValueElementProps) => { +export const ExpandButton = (props: EuiDataGridCellValueElementProps) => { + const { record, rowIndex } = useControlColumn(props); + const toolTipRef = useRef(null); const [pressed, setPressed] = useState(false); - const { expanded, setExpanded, rows, isDarkMode, componentsTourSteps } = - useContext(UnifiedDataTableContext); - const current = rows[rowIndex]; + const { expanded, setExpanded, componentsTourSteps } = useContext(UnifiedDataTableContext); const tourStep = componentsTourSteps ? componentsTourSteps.expandButton : undefined; - useEffect(() => { - if (current.isAnchor) { - setCellProps({ - className: 'unifiedDataTable__cell--highlight', - }); - } else if (expanded && current && expanded.id === current.id) { - setCellProps({ - className: 'unifiedDataTable__cell--expanded', - }); - } else { - setCellProps({ className: '' }); - } - }, [expanded, current, setCellProps, isDarkMode]); - const isCurrentRowExpanded = current === expanded; + const isCurrentRowExpanded = record === expanded; const buttonLabel = i18n.translate('unifiedDataTable.grid.viewDoc', { defaultMessage: 'Toggle dialog with details', }); - const testSubj = current.isAnchor + const testSubj = record.isAnchor ? 'docTableExpandToggleColumnAnchor' : 'docTableExpandToggleColumn'; @@ -60,7 +48,7 @@ export const ExpandButton = ({ rowIndex, setCellProps }: EuiDataGridCellValueEle } return ( - + { - const nextHit = isCurrentRowExpanded ? undefined : current; + const nextHit = isCurrentRowExpanded ? undefined : record; toolTipRef.current?.hideToolTip(); setPressed(Boolean(nextHit)); setExpanded?.(nextHit); diff --git a/packages/kbn-unified-data-table/src/components/data_table_row_control.tsx b/packages/kbn-unified-data-table/src/components/data_table_row_control.tsx index 4ceadea549dce..0ac0bbd4cb2f6 100644 --- a/packages/kbn-unified-data-table/src/components/data_table_row_control.tsx +++ b/packages/kbn-unified-data-table/src/components/data_table_row_control.tsx @@ -7,7 +7,16 @@ */ import React from 'react'; +import classnames from 'classnames'; -export const DataTableRowControl = ({ children }: { children: React.ReactNode }) => { - return {children}; +export enum Size { + normal = 'normal', +} + +export const DataTableRowControl: React.FC<{ size?: Size }> = ({ size, children }) => { + const classes = classnames('unifiedDataTable__rowControl', { + // normalize the size of the control + [`unifiedDataTable__rowControl--size-${size}`]: size, + }); + return {children}; }; diff --git a/packages/kbn-unified-data-table/src/constants.ts b/packages/kbn-unified-data-table/src/constants.ts index c2d5654c602c2..c7cf1793039a5 100644 --- a/packages/kbn-unified-data-table/src/constants.ts +++ b/packages/kbn-unified-data-table/src/constants.ts @@ -7,6 +7,8 @@ */ import { EuiDataGridStyle } from '@elastic/eui'; +export const DEFAULT_CONTROL_COLUMN_WIDTH = 24; + export const DEFAULT_ROWS_PER_PAGE = 100; export const MAX_LOADED_GRID_ROWS = 10000; diff --git a/packages/kbn-unified-data-table/src/hooks/use_control_column.ts b/packages/kbn-unified-data-table/src/hooks/use_control_column.ts new file mode 100644 index 0000000000000..e2bc05f668508 --- /dev/null +++ b/packages/kbn-unified-data-table/src/hooks/use_control_column.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 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 { useContext, useEffect, useMemo } from 'react'; +import type { EuiDataGridCellValueElementProps } from '@elastic/eui'; +import type { DataTableRecord } from '@kbn/discover-utils'; +import { UnifiedDataTableContext } from '../table_context'; + +export const useControlColumn = ({ + rowIndex, + setCellProps, +}: Pick): { + record: DataTableRecord; + rowIndex: number; +} => { + const { expanded, rows } = useContext(UnifiedDataTableContext); + const record = useMemo(() => rows[rowIndex], [rows, rowIndex]); + + useEffect(() => { + if (record.isAnchor) { + setCellProps({ + className: 'unifiedDataTable__cell--highlight', + }); + } else if (expanded && record && expanded.id === record.id) { + setCellProps({ + className: 'unifiedDataTable__cell--expanded', + }); + } else { + setCellProps({ + className: '', + }); + } + }, [expanded, record, setCellProps]); + + return useMemo(() => ({ record, rowIndex }), [record, rowIndex]); +}; diff --git a/packages/kbn-unified-data-table/src/types.ts b/packages/kbn-unified-data-table/src/types.ts index 5914fa03f8827..b08818e1a861a 100644 --- a/packages/kbn-unified-data-table/src/types.ts +++ b/packages/kbn-unified-data-table/src/types.ts @@ -6,8 +6,13 @@ * Side Public License, v 1. */ -import type { ReactElement } from 'react'; -import type { EuiDataGridCellValueElementProps, EuiDataGridColumn } from '@elastic/eui'; +import type { ReactElement, FC } from 'react'; +import type { + EuiDataGridCellValueElementProps, + EuiDataGridColumn, + IconType, + EuiButtonIconProps, +} from '@elastic/eui'; import type { DataTableRecord } from '@kbn/discover-utils/src/types'; import type { DataView } from '@kbn/data-views-plugin/common'; import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; @@ -70,16 +75,25 @@ export type CustomGridColumnsConfiguration = Record< (props: CustomGridColumnProps) => EuiDataGridColumn >; -export interface ControlColumns { - select: EuiDataGridControlColumn; - openDetails: EuiDataGridControlColumn; +export interface RowControlRowProps { + rowIndex: number; + record: DataTableRecord; } -export interface ControlColumnsProps { - controlColumns: ControlColumns; +export interface RowControlProps { + 'data-test-subj'?: string; + color?: EuiButtonIconProps['color']; + disabled?: boolean; + label: string; + iconType: IconType; + onClick: ((props: RowControlRowProps) => void) | undefined; } -export type CustomControlColumnConfiguration = (props: ControlColumnsProps) => { - leadingControlColumns: EuiDataGridControlColumn[]; - trailingControlColumns?: EuiDataGridControlColumn[]; -}; +export type RowControlComponent = FC; + +export interface RowControlColumn { + id: string; + headerAriaLabel: string; + headerCellRender?: EuiDataGridControlColumn['headerCellRender']; + renderControl: (Control: RowControlComponent, props: RowControlRowProps) => ReactElement; +} diff --git a/src/plugins/discover/public/application/main/components/layout/discover_documents.test.tsx b/src/plugins/discover/public/application/main/components/layout/discover_documents.test.tsx index 85c2dd581eecb..458d3dcb6318c 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_documents.test.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_documents.test.tsx @@ -114,15 +114,10 @@ describe('Discover documents layout', () => { }); test('should render customisations', async () => { - const customControlColumnsConfiguration = () => ({ - leadingControlColumns: [], - trailingControlColumns: [], - }); - const customization: DiscoverCustomization = { id: 'data_table', logsEnabled: true, - customControlColumnsConfiguration, + rowAdditionalLeadingControls: [], }; customisationService.set(customization); @@ -130,8 +125,8 @@ describe('Discover documents layout', () => { const discoverGridComponent = component.find(DiscoverGrid); expect(discoverGridComponent.exists()).toBeTruthy(); - expect(discoverGridComponent.prop('customControlColumnsConfiguration')).toEqual( - customControlColumnsConfiguration + expect(discoverGridComponent.prop('rowAdditionalLeadingControls')).toBe( + customization.rowAdditionalLeadingControls ); expect(discoverGridComponent.prop('externalCustomRenderers')).toBeDefined(); expect(discoverGridComponent.prop('customGridColumnsConfiguration')).toBeDefined(); diff --git a/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx b/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx index e6fb6472397f3..e1b5636d010b1 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx @@ -259,7 +259,7 @@ function DiscoverDocumentsComponent({ [dataView, onAddColumn, onAddFilter, onRemoveColumn, query, savedSearch.id, setExpandedDoc] ); - const { customControlColumnsConfiguration } = useDiscoverCustomization('data_table') || {}; + const { rowAdditionalLeadingControls } = useDiscoverCustomization('data_table') || {}; const { customCellRenderer, customGridColumnsConfiguration } = useContextualGridCustomisations() || {}; const additionalFieldGroups = useAdditionalFieldGroups(); @@ -435,7 +435,7 @@ function DiscoverDocumentsComponent({ componentsTourSteps={TOUR_STEPS} externalCustomRenderers={cellRenderers} customGridColumnsConfiguration={customGridColumnsConfiguration} - customControlColumnsConfiguration={customControlColumnsConfiguration} + rowAdditionalLeadingControls={rowAdditionalLeadingControls} additionalFieldGroups={additionalFieldGroups} /> diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid.tsx b/src/plugins/discover/public/components/discover_grid/discover_grid.tsx index 79ca8afd005e3..986f66e9680c4 100644 --- a/src/plugins/discover/public/components/discover_grid/discover_grid.tsx +++ b/src/plugins/discover/public/components/discover_grid/discover_grid.tsx @@ -16,21 +16,33 @@ import { useProfileAccessor } from '../../context_awareness'; /** * Customized version of the UnifiedDataTable - * @param props * @constructor */ -export const DiscoverGrid: React.FC = (props) => { +export const DiscoverGrid: React.FC = ({ + rowAdditionalLeadingControls: customRowAdditionalLeadingControls, + ...props +}) => { const getRowIndicatorProvider = useProfileAccessor('getRowIndicatorProvider'); const getRowIndicator = useMemo(() => { return getRowIndicatorProvider(() => undefined)({ dataView: props.dataView }); }, [getRowIndicatorProvider, props.dataView]); + const getRowAdditionalLeadingControlsAccessor = useProfileAccessor( + 'getRowAdditionalLeadingControls' + ); + const rowAdditionalLeadingControls = useMemo(() => { + return getRowAdditionalLeadingControlsAccessor(() => customRowAdditionalLeadingControls)({ + dataView: props.dataView, + }); + }, [getRowAdditionalLeadingControlsAccessor, props.dataView, customRowAdditionalLeadingControls]); + return ( ); diff --git a/src/plugins/discover/public/context_awareness/profile_providers/example_data_source_profile/profile.tsx b/src/plugins/discover/public/context_awareness/profile_providers/example_data_source_profile/profile.tsx index 911e69e6d0d33..747bada5b0284 100644 --- a/src/plugins/discover/public/context_awareness/profile_providers/example_data_source_profile/profile.tsx +++ b/src/plugins/discover/public/context_awareness/profile_providers/example_data_source_profile/profile.tsx @@ -8,6 +8,7 @@ import { EuiBadge } from '@elastic/eui'; import type { DataTableRecord } from '@kbn/discover-utils'; +import type { RowControlColumn } from '@kbn/unified-data-table'; import { isOfAggregateQueryType } from '@kbn/es-query'; import { getIndexPatternFromESQLQuery } from '@kbn/esql-utils'; import { euiThemeVars } from '@kbn/ui-theme'; @@ -71,6 +72,31 @@ export const exampleDataSourceProfileProvider: DataSourceProfileProvider = { }, }; }, + getRowAdditionalLeadingControls: (prev) => (params) => { + const additionalControls = prev(params) || []; + + return [ + ...additionalControls, + ...['visBarVerticalStacked', 'heart', 'inspect'].map( + (iconType, index): RowControlColumn => ({ + id: `exampleControl_${iconType}`, + headerAriaLabel: `Example Row Control ${iconType}`, + renderControl: (Control, rowProps) => { + return ( + { + alert(`Example "${iconType}" control clicked. Row index: ${rowProps.rowIndex}`); + }} + /> + ); + }, + }) + ), + ]; + }, getDefaultAppState: () => () => ({ columns: [ { diff --git a/src/plugins/discover/public/context_awareness/types.ts b/src/plugins/discover/public/context_awareness/types.ts index 38c7116a765b1..b6e4d4558162d 100644 --- a/src/plugins/discover/public/context_awareness/types.ts +++ b/src/plugins/discover/public/context_awareness/types.ts @@ -38,11 +38,20 @@ export interface DefaultAppStateExtension { rowHeight?: number; } +export interface RowControlsExtensionParams { + dataView: DataView; +} + export interface Profile { - getCellRenderers: () => CustomCellRenderer; - getDocViewer: (params: DocViewerExtensionParams) => DocViewerExtension; getDefaultAppState: (params: DefaultAppStateExtensionParams) => DefaultAppStateExtension; + // Data grid + getCellRenderers: () => CustomCellRenderer; getRowIndicatorProvider: ( params: RowIndicatorExtensionParams ) => UnifiedDataTableProps['getRowIndicator'] | undefined; + getRowAdditionalLeadingControls: ( + params: RowControlsExtensionParams + ) => UnifiedDataTableProps['rowAdditionalLeadingControls'] | undefined; + // Doc viewer + getDocViewer: (params: DocViewerExtensionParams) => DocViewerExtension; } diff --git a/src/plugins/discover/public/customizations/customization_types/data_table_customisation.ts b/src/plugins/discover/public/customizations/customization_types/data_table_customisation.ts index 3e6e510488fbd..d53485911d12c 100644 --- a/src/plugins/discover/public/customizations/customization_types/data_table_customisation.ts +++ b/src/plugins/discover/public/customizations/customization_types/data_table_customisation.ts @@ -6,10 +6,10 @@ * Side Public License, v 1. */ -import { CustomControlColumnConfiguration } from '@kbn/unified-data-table'; +import type { UnifiedDataTableProps } from '@kbn/unified-data-table'; export interface DataTableCustomization { id: 'data_table'; logsEnabled: boolean; // TODO / NOTE: Just temporary until Discover's data type contextual awareness lands. - customControlColumnsConfiguration?: CustomControlColumnConfiguration; + rowAdditionalLeadingControls?: UnifiedDataTableProps['rowAdditionalLeadingControls']; } diff --git a/test/functional/apps/discover/context_awareness/extensions/_get_row_additional_leading_controls.ts b/test/functional/apps/discover/context_awareness/extensions/_get_row_additional_leading_controls.ts new file mode 100644 index 0000000000000..be536fd6cdbe9 --- /dev/null +++ b/test/functional/apps/discover/context_awareness/extensions/_get_row_additional_leading_controls.ts @@ -0,0 +1,64 @@ +/* + * 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 kbnRison from '@kbn/rison'; +import type { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects(['common', 'discover']); + const testSubjects = getService('testSubjects'); + const dataViews = getService('dataViews'); + + describe('extension getRowAdditionalLeadingControls', () => { + describe('ES|QL mode', () => { + it('should render logs controls for logs data source', async () => { + const state = kbnRison.encode({ + dataSource: { type: 'esql' }, + query: { esql: 'from my-example-logs | sort @timestamp desc' }, + }); + await PageObjects.common.navigateToApp('discover', { + hash: `/?_a=${state}`, + }); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await testSubjects.existOrFail('exampleLogsControl_visBarVerticalStacked'); + await testSubjects.existOrFail('unifiedDataTable_additionalRowControl_menuControl'); + }); + + it('should not render logs controls for non-logs data source', async () => { + const state = kbnRison.encode({ + dataSource: { type: 'esql' }, + query: { esql: 'from my-example-metrics | sort @timestamp desc' }, + }); + await PageObjects.common.navigateToApp('discover', { + hash: `/?_a=${state}`, + }); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await testSubjects.missingOrFail('exampleLogsControl_visBarVerticalStacked'); + await testSubjects.missingOrFail('unifiedDataTable_additionalRowControl_menuControl'); + }); + }); + + describe('data view mode', () => { + it('should render logs controls for logs data source', async () => { + await PageObjects.common.navigateToApp('discover'); + await dataViews.switchTo('my-example-logs'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await testSubjects.existOrFail('exampleLogsControl_visBarVerticalStacked'); + await testSubjects.existOrFail('unifiedDataTable_additionalRowControl_menuControl'); + }); + + it('should not render logs controls for non-logs data source', async () => { + await PageObjects.common.navigateToApp('discover'); + await dataViews.switchTo('my-example-metrics'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await testSubjects.missingOrFail('exampleLogsControl_visBarVerticalStacked'); + await testSubjects.missingOrFail('unifiedDataTable_additionalRowControl_menuControl'); + }); + }); + }); +} diff --git a/test/functional/apps/discover/context_awareness/index.ts b/test/functional/apps/discover/context_awareness/index.ts index 82f03e7f54bbc..0bba18a339263 100644 --- a/test/functional/apps/discover/context_awareness/index.ts +++ b/test/functional/apps/discover/context_awareness/index.ts @@ -36,6 +36,7 @@ export default function ({ getService, getPageObjects, loadTestFile }: FtrProvid loadTestFile(require.resolve('./_root_profile')); loadTestFile(require.resolve('./_data_source_profile')); loadTestFile(require.resolve('./extensions/_get_row_indicator_provider')); + loadTestFile(require.resolve('./extensions/_get_row_additional_leading_controls')); loadTestFile(require.resolve('./extensions/_get_doc_viewer')); loadTestFile(require.resolve('./extensions/_get_cell_renderers')); loadTestFile(require.resolve('./extensions/_get_default_app_state')); diff --git a/test/functional/apps/discover/esql/_esql_view.ts b/test/functional/apps/discover/esql/_esql_view.ts index a66471b921528..0ab0e30a45eab 100644 --- a/test/functional/apps/discover/esql/_esql_view.ts +++ b/test/functional/apps/discover/esql/_esql_view.ts @@ -194,8 +194,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const cell = await dataGrid.getCellElementExcludingControlColumns(0, 1); expect(await cell.getVisibleText()).to.be(' - '); expect(await dataGrid.getHeaders()).to.eql([ - 'Control column', 'Select column', + 'Control column', 'Numberbytes', 'machine.ram_range', ]); diff --git a/test/functional/screenshots/baseline/dashboard_embed_mode_scrolling.png b/test/functional/screenshots/baseline/dashboard_embed_mode_scrolling.png index 3fd3025ebb9a18b2f5b94dd38d4dcdb6f01a4a05..94de1a1c3cc4f74f73a410a4f508cd7f81602b7f 100644 GIT binary patch literal 82090 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≈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

f#ad12measUfy{Y3#H;>6 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 literal 88013 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

C~WM5{x$yq|3I2g%Khqdp4G2U)VG0YjBnX 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 b4*udinU5;UTcr*GWWto>)#XZM9zOd&$?om0 diff --git a/test/functional/services/data_grid.ts b/test/functional/services/data_grid.ts index 5014846a67407..70a67d33ffd00 100644 --- a/test/functional/services/data_grid.ts +++ b/test/functional/services/data_grid.ts @@ -139,6 +139,22 @@ export class DataGridService extends FtrService { 'euiDataGridCellExpandButton' ); await actionButton.click(); + await this.retry.waitFor('popover to be opened', async () => { + return await this.testSubjects.exists('euiDataGridExpansionPopover'); + }); + } + + /** + * Clicks grid cell 'expand' action button + * @param rowIndex data row index starting from 0 (0 means 1st row) + * @param columnIndex column index starting from 0 (0 means 1st column) + */ + public async clickCellExpandButtonExcludingControlColumns( + rowIndex: number = 0, + columnIndex: number = 0 + ) { + const controlsCount = await this.getControlColumnsCount(); + await this.clickCellExpandButton(rowIndex, controlsCount + columnIndex); } /** diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/components/common/translations.tsx b/x-pack/plugins/observability_solution/logs_explorer/public/components/common/translations.tsx index 577e068483427..826fcdab65915 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/components/common/translations.tsx +++ b/x-pack/plugins/observability_solution/logs_explorer/public/components/common/translations.tsx @@ -7,7 +7,6 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiCode } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; export const contentLabel = i18n.translate('xpack.logsExplorer.dataTable.header.popover.content', { @@ -21,17 +20,6 @@ export const resourceLabel = i18n.translate( } ); -export const actionsLabel = i18n.translate('xpack.logsExplorer.dataTable.header.popover.actions', { - defaultMessage: 'Actions', -}); - -export const actionsLabelLowerCase = i18n.translate( - 'xpack.logsExplorer.dataTable.header.popover.actions.lowercase', - { - defaultMessage: 'actions', - } -); - export const actionFilterForText = (text: string) => i18n.translate('xpack.logsExplorer.flyoutDetail.value.hover.filterFor', { defaultMessage: 'Filter for this {value}', @@ -109,35 +97,18 @@ export const resourceHeaderTooltipParagraph = i18n.translate( } ); -export const actionsHeaderTooltipParagraph = i18n.translate( - 'xpack.logsExplorer.dataTable.header.actions.tooltip.paragraph', +export const actionsHeaderAriaLabelDegradedAction = i18n.translate( + 'xpack.logsExplorer.dataTable.controlColumnHeader.degradedDocArialLabel', { - defaultMessage: 'Fields that provide actionable information, such as:', + defaultMessage: 'Access to degraded docs', } ); -export const actionsHeaderTooltipExpandAction = i18n.translate( - 'xpack.logsExplorer.dataTable.header.actions.tooltip.expand', - { defaultMessage: 'Expand log details' } -); - -export const actionsHeaderTooltipDegradedAction = ( - - _ignored - - ), - }} - /> -); - -export const actionsHeaderTooltipStacktraceAction = i18n.translate( - 'xpack.logsExplorer.dataTable.header.actions.tooltip.stacktrace', - { defaultMessage: 'Access to available stacktraces based on:' } +export const actionsHeaderAriaLabelStacktraceAction = i18n.translate( + 'xpack.logsExplorer.dataTable.controlColumnHeader.stacktraceArialLabel', + { + defaultMessage: 'Access to available stacktraces', + } ); export const degradedDocButtonLabelWhenPresent = i18n.translate( diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/components/virtual_columns/column_tooltips/actions_column_tooltip.tsx b/x-pack/plugins/observability_solution/logs_explorer/public/components/virtual_columns/column_tooltips/actions_column_tooltip.tsx deleted file mode 100644 index c33c514bf3789..0000000000000 --- a/x-pack/plugins/observability_solution/logs_explorer/public/components/virtual_columns/column_tooltips/actions_column_tooltip.tsx +++ /dev/null @@ -1,95 +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 { css } from '@emotion/react'; -import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText } from '@elastic/eui'; -import { euiThemeVars } from '@kbn/ui-theme'; -import { - actionsHeaderTooltipExpandAction, - actionsHeaderTooltipDegradedAction, - actionsHeaderTooltipParagraph, - actionsHeaderTooltipStacktraceAction, - actionsLabel, - actionsLabelLowerCase, -} from '../../common/translations'; -import { TooltipButton } from './tooltip_button'; -import * as constants from '../../../../common/constants'; -import { FieldWithToken } from './field_with_token'; - -const spacingCSS = css` - margin-bottom: ${euiThemeVars.euiSizeS}; -`; - -export const ActionsColumnTooltip = () => { - return ( - -

- -

{actionsHeaderTooltipParagraph}

-
- - - - - - -

{actionsHeaderTooltipExpandAction}

-
-
-
- - - - - - -

{actionsHeaderTooltipDegradedAction}

-
-
-
- - - - - - -

{actionsHeaderTooltipStacktraceAction}

-
-
-
-
- {[ - constants.ERROR_STACK_TRACE, - constants.ERROR_EXCEPTION_STACKTRACE, - constants.ERROR_LOG_STACKTRACE, - ].map((field) => ( - - ))} -
-
- - ); -}; diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/customizations/custom_control_column.tsx b/x-pack/plugins/observability_solution/logs_explorer/public/customizations/custom_control_column.tsx index 43edee4cf73af..89bc38482c803 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/customizations/custom_control_column.tsx +++ b/x-pack/plugins/observability_solution/logs_explorer/public/customizations/custom_control_column.tsx @@ -5,19 +5,16 @@ * 2.0. */ -import React, { ComponentClass } from 'react'; -import { - OPEN_DETAILS, - SELECT_ROW, - type ControlColumnsProps, - DataTableRowControl, -} from '@kbn/unified-data-table'; -import { EuiButtonIcon, EuiDataGridCellValueElementProps, EuiToolTip } from '@elastic/eui'; -import type { DataTableRecord } from '@kbn/discover-utils/src/types'; -import { useActor } from '@xstate/react'; +import React from 'react'; import { LogDocument } from '@kbn/discover-utils/src'; -import { LogsExplorerControllerStateService } from '../state_machines/logs_explorer_controller'; +import type { + UnifiedDataTableProps, + RowControlComponent, + RowControlRowProps, +} from '@kbn/unified-data-table'; import { + actionsHeaderAriaLabelDegradedAction, + actionsHeaderAriaLabelStacktraceAction, degradedDocButtonLabelWhenNotPresent, degradedDocButtonLabelWhenPresent, stacktraceAvailableControlButton, @@ -25,122 +22,79 @@ import { } from '../components/common/translations'; import * as constants from '../../common/constants'; import { getStacktraceFields } from '../utils/get_stack_trace'; -import { ActionsColumnTooltip } from '../components/virtual_columns/column_tooltips/actions_column_tooltip'; - -const ConnectedDegradedDocs = ({ - rowIndex, - service, -}: { - rowIndex: number; - service: LogsExplorerControllerStateService; -}) => { - const [state] = useActor(service); - if (state.matches('initialized') && state.context.rows[rowIndex]) { - return ; - } - - return null; -}; -const ConnectedStacktraceDocs = ({ - rowIndex, - service, +const DegradedDocs = ({ + Control, + rowProps: { record }, }: { - rowIndex: number; - service: LogsExplorerControllerStateService; + Control: RowControlComponent; + rowProps: RowControlRowProps; }) => { - const [state] = useActor(service); - if (state.matches('initialized') && state.context.rows[rowIndex]) { - return ; - } - - return null; -}; - -const DegradedDocs = ({ row, rowIndex }: { row: DataTableRecord; rowIndex: number }) => { - const isDegradedDocumentExists = constants.DEGRADED_DOCS_FIELD in row.raw; + const isDegradedDocumentExists = constants.DEGRADED_DOCS_FIELD in record.raw; return isDegradedDocumentExists ? ( - - - - - + ) : ( - - - - - + ); }; -const Stacktrace = ({ row, rowIndex }: { row: DataTableRecord; rowIndex: number }) => { - const stacktrace = getStacktraceFields(row as LogDocument); +const Stacktrace = ({ + Control, + rowProps: { record }, +}: { + Control: RowControlComponent; + rowProps: RowControlRowProps; +}) => { + const stacktrace = getStacktraceFields(record as LogDocument); const hasValue = Object.values(stacktrace).some((value) => value); - return ( - - - - - + return hasValue ? ( + + ) : ( + ); }; -export const createCustomControlColumnsConfiguration = - (service: LogsExplorerControllerStateService) => - ({ controlColumns }: ControlColumnsProps) => { - const checkBoxColumn = controlColumns[SELECT_ROW]; - const openDetails = controlColumns[OPEN_DETAILS]; - const ExpandButton = - openDetails.rowCellRender as ComponentClass; - const actionsColumn = { - id: 'actionsColumn', - width: constants.ACTIONS_COLUMN_WIDTH, - headerCellRender: ActionsColumnTooltip, - rowCellRender: ({ rowIndex, setCellProps, ...rest }: EuiDataGridCellValueElementProps) => { - return ( - - - - - - ); +export const getRowAdditionalControlColumns = + (): UnifiedDataTableProps['rowAdditionalLeadingControls'] => { + return [ + { + id: 'connectedDegradedDocs', + headerAriaLabel: actionsHeaderAriaLabelDegradedAction, + renderControl: (Control, rowProps) => { + return ; + }, }, - }; - - return { - leadingControlColumns: [checkBoxColumn, actionsColumn], - }; + { + id: 'connectedStacktraceDocs', + headerAriaLabel: actionsHeaderAriaLabelStacktraceAction, + renderControl: (Control, rowProps) => { + return ; + }, + }, + ]; }; diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/customizations/logs_explorer_profile.tsx b/x-pack/plugins/observability_solution/logs_explorer/public/customizations/logs_explorer_profile.tsx index 7b193f4aafff8..557cbe4dd2728 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/customizations/logs_explorer_profile.tsx +++ b/x-pack/plugins/observability_solution/logs_explorer/public/customizations/logs_explorer_profile.tsx @@ -82,8 +82,8 @@ export const createLogsExplorerProfileCustomizations = customizations.set({ id: 'data_table', logsEnabled: true, - customControlColumnsConfiguration: await import('./custom_control_column').then((module) => - module.createCustomControlColumnsConfiguration(service) + rowAdditionalLeadingControls: await import('./custom_control_column').then((module) => + module.getRowAdditionalControlColumns() ), }); diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 9abcbd4c40cde..d81b810c983b8 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -24325,13 +24325,8 @@ "xpack.lists.services.items.fileUploadFromFileSystem": "Fichier chargé depuis le système de fichiers de {fileName}", "xpack.logsExplorer.dataTable.controlColumn.actions.button.stacktrace.available": "Traces d'appel disponibles", "xpack.logsExplorer.dataTable.controlColumn.actions.button.stacktrace.notAvailable": "Traces d'appel indisponibles", - "xpack.logsExplorer.dataTable.header.actions.tooltip.expand": "Développer les détails du log", - "xpack.logsExplorer.dataTable.header.actions.tooltip.paragraph": "Les champs fournissant des informations exploitables, comme :", - "xpack.logsExplorer.dataTable.header.actions.tooltip.stacktrace": "L'accès aux traces d'appel disponibles est basé sur :", "xpack.logsExplorer.dataTable.header.content.tooltip.paragraph1": "Affiche le {logLevel} du document et les champs {message}.", "xpack.logsExplorer.dataTable.header.content.tooltip.paragraph2": "Lorsque le champ de message est vide, l'une des informations suivantes s'affiche :", - "xpack.logsExplorer.dataTable.header.popover.actions": "Actions", - "xpack.logsExplorer.dataTable.header.popover.actions.lowercase": "actions", "xpack.logsExplorer.dataTable.header.popover.content": "Contenu", "xpack.logsExplorer.dataTable.header.popover.resource": "Ressource", "xpack.logsExplorer.dataTable.header.resource.tooltip.paragraph": "Les champs fournissant des informations sur la source du document, comme :", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index c5be020320a04..267f2bb3c8f2e 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -24250,13 +24250,8 @@ "xpack.lists.services.items.fileUploadFromFileSystem": "ファイルは{fileName}のファイルシステムからアップロードされました", "xpack.logsExplorer.dataTable.controlColumn.actions.button.stacktrace.available": "スタックトレースがあります", "xpack.logsExplorer.dataTable.controlColumn.actions.button.stacktrace.notAvailable": "スタックトレースがありません", - "xpack.logsExplorer.dataTable.header.actions.tooltip.expand": "ログの詳細を展開", - "xpack.logsExplorer.dataTable.header.actions.tooltip.paragraph": "次のようなアクショナブルな情報を提供するフィールド:", - "xpack.logsExplorer.dataTable.header.actions.tooltip.stacktrace": "次に基づいて使用可能なスタックトレースにアクセス:", "xpack.logsExplorer.dataTable.header.content.tooltip.paragraph1": "ドキュメントの{logLevel}と{message}フィールドを表示します。", "xpack.logsExplorer.dataTable.header.content.tooltip.paragraph2": "メッセージフィールドが空のときには、次のいずれかが表示されます。", - "xpack.logsExplorer.dataTable.header.popover.actions": "アクション", - "xpack.logsExplorer.dataTable.header.popover.actions.lowercase": "アクション", "xpack.logsExplorer.dataTable.header.popover.content": "コンテンツ", "xpack.logsExplorer.dataTable.header.popover.resource": "リソース", "xpack.logsExplorer.dataTable.header.resource.tooltip.paragraph": "次のようなドキュメントのソースに関する情報を提供するフィールド:", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 0497f0f96fe31..287dde9aaaaf7 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -24360,13 +24360,8 @@ "xpack.lists.services.items.fileUploadFromFileSystem": "从 {fileName} 的文件系统上传的文件", "xpack.logsExplorer.dataTable.controlColumn.actions.button.stacktrace.available": "堆栈跟踪可用", "xpack.logsExplorer.dataTable.controlColumn.actions.button.stacktrace.notAvailable": "堆栈跟踪不可用", - "xpack.logsExplorer.dataTable.header.actions.tooltip.expand": "展开日志详情", - "xpack.logsExplorer.dataTable.header.actions.tooltip.paragraph": "提供可操作信息的字段,例如:", - "xpack.logsExplorer.dataTable.header.actions.tooltip.stacktrace": "基于以下项访问可用堆栈跟踪:", "xpack.logsExplorer.dataTable.header.content.tooltip.paragraph1": "显示该文档的 {logLevel} 和 {message} 字段。", "xpack.logsExplorer.dataTable.header.content.tooltip.paragraph2": "消息字段为空时,将显示以下项之一:", - "xpack.logsExplorer.dataTable.header.popover.actions": "操作", - "xpack.logsExplorer.dataTable.header.popover.actions.lowercase": "操作", "xpack.logsExplorer.dataTable.header.popover.content": "内容", "xpack.logsExplorer.dataTable.header.popover.resource": "资源", "xpack.logsExplorer.dataTable.header.resource.tooltip.paragraph": "提供有关文档来源信息的字段,例如:", diff --git a/x-pack/test/functional/apps/observability_logs_explorer/columns_selection.ts b/x-pack/test/functional/apps/observability_logs_explorer/columns_selection.ts index b944ebd9b8306..3c87d53031aa4 100644 --- a/x-pack/test/functional/apps/observability_logs_explorer/columns_selection.ts +++ b/x-pack/test/functional/apps/observability_logs_explorer/columns_selection.ts @@ -85,7 +85,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('render content virtual column properly', async () => { it('should render log level and log message when present', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 2); const cellValue = await cellElement.getVisibleText(); expect(cellValue.includes('info')).to.be(true); expect(cellValue.includes('A sample log')).to.be(true); @@ -94,7 +94,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render log message when present and skip log level when missing', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(1, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(1, 2); const cellValue = await cellElement.getVisibleText(); expect(cellValue.includes('info')).to.be(false); expect(cellValue.includes('A sample log')).to.be(true); @@ -103,7 +103,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render message from error object when top level message not present', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(2, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(2, 2); const cellValue = await cellElement.getVisibleText(); expect(cellValue.includes('info')).to.be(true); expect(cellValue.includes('error.message')).to.be(true); @@ -113,7 +113,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render message from event.original when top level message and error.message not present', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(3, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(3, 2); const cellValue = await cellElement.getVisibleText(); expect(cellValue.includes('info')).to.be(true); expect(cellValue.includes('event.original')).to.be(true); @@ -123,7 +123,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render the whole JSON when neither message, error.message and event.original are present', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(4, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(4, 2); const cellValue = await cellElement.getVisibleText(); expect(cellValue.includes('info')).to.be(true); @@ -137,7 +137,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('on cell expansion with no message field should open JSON Viewer', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - await dataGrid.clickCellExpandButton(4, 4); + await dataGrid.clickCellExpandButtonExcludingControlColumns(4, 2); await testSubjects.existOrFail('dataTableExpandCellActionJsonPopover'); }); }); @@ -145,7 +145,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('on cell expansion with message field should open regular popover', async () => { await navigateToLogsExplorer(); await retry.tryForTime(TEST_TIMEOUT, async () => { - await dataGrid.clickCellExpandButton(3, 4); + await dataGrid.clickCellExpandButtonExcludingControlColumns(3, 2); await testSubjects.existOrFail('euiDataGridExpansionPopover'); }); }); @@ -154,7 +154,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('render resource virtual column properly', async () => { it('should render service name and host name when present', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 3); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 1); const cellValue = await cellElement.getVisibleText(); expect(cellValue.includes('synth-service')).to.be(true); expect(cellValue.includes('synth-host')).to.be(true); @@ -168,7 +168,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should render a popover with cell actions when a chip on content column is clicked', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 2); const logLevelChip = await cellElement.findByTestSubject('*logLevelBadge-'); await logLevelChip.click(); // Check Filter In button is present @@ -182,7 +182,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render the table filtered where log.level value is info when filter in action is clicked', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 2); const logLevelChip = await cellElement.findByTestSubject('*logLevelBadge-'); const actionSelector = 'dataTableCellAction_addToFilterAction_log.level'; @@ -203,7 +203,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render the table filtered where log.level value is not info when filter out action is clicked', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 2); const logLevelChip = await cellElement.findByTestSubject('*logLevelBadge-'); const actionSelector = 'dataTableCellAction_removeFromFilterAction_log.level'; @@ -222,7 +222,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render the table filtered where service.name value is selected', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 3); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 1); const serviceNameChip = await cellElement.findByTestSubject( 'dataTablePopoverChip_service.name' ); diff --git a/x-pack/test/functional/apps/observability_logs_explorer/custom_control_columns.ts b/x-pack/test/functional/apps/observability_logs_explorer/custom_control_columns.ts index 21dd8a6772bb7..58b123d08cdaf 100644 --- a/x-pack/test/functional/apps/observability_logs_explorer/custom_control_columns.ts +++ b/x-pack/test/functional/apps/observability_logs_explorer/custom_control_columns.ts @@ -47,7 +47,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render control column with proper header', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { // First control column has no title, so empty string, leading control column has title - expect(await dataGrid.getControlColumnHeaderFields()).to.eql(['', 'actions']); + expect(await dataGrid.getControlColumnHeaderFields()).to.eql(['', '', '', '']); }); }); @@ -61,7 +61,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render the degraded icon in the leading control column if degraded doc exists', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(1, 1); + const cellElement = await dataGrid.getCellElement(1, 2); const degradedButton = await cellElement.findByTestSubject('docTableDegradedDocExist'); expect(degradedButton).to.not.be.empty(); }); @@ -69,7 +69,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render the disabled degraded icon in the leading control column when degraded doc does not exists', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 1); + const cellElement = await dataGrid.getCellElement(0, 2); const degradedDisableButton = await cellElement.findByTestSubject( 'docTableDegradedDocDoesNotExist' ); @@ -79,7 +79,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render the stacktrace icon in the leading control column when stacktrace exists', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(4, 1); + const cellElement = await dataGrid.getCellElement(4, 3); const stacktraceButton = await cellElement.findByTestSubject('docTableStacktraceExist'); expect(stacktraceButton).to.not.be.empty(); }); @@ -87,7 +87,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render the stacktrace icon disabled in the leading control column when stacktrace does not exists', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(1, 1); + const cellElement = await dataGrid.getCellElement(1, 3); const stacktraceButton = await cellElement.findByTestSubject( 'docTableStacktraceDoesNotExist' ); diff --git a/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/extensions/_get_row_additional_leading_controls.ts b/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/extensions/_get_row_additional_leading_controls.ts new file mode 100644 index 0000000000000..c91dae10bc4ea --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/extensions/_get_row_additional_leading_controls.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 kbnRison from '@kbn/rison'; +import type { FtrProviderContext } from '../../../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects(['common', 'discover', 'svlCommonPage']); + const testSubjects = getService('testSubjects'); + const dataViews = getService('dataViews'); + + describe('extension getRowAdditionalLeadingControls', () => { + before(async () => { + await PageObjects.svlCommonPage.loginAsAdmin(); + }); + describe('ES|QL mode', () => { + it('should render logs controls for logs data source', async () => { + const state = kbnRison.encode({ + dataSource: { type: 'esql' }, + query: { esql: 'from my-example-logs | sort @timestamp desc' }, + }); + await PageObjects.common.navigateToApp('discover', { + hash: `/?_a=${state}`, + }); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await testSubjects.existOrFail('exampleLogsControl_visBarVerticalStacked'); + await testSubjects.existOrFail('unifiedDataTable_additionalRowControl_menuControl'); + }); + + it('should not render logs controls for non-logs data source', async () => { + const state = kbnRison.encode({ + dataSource: { type: 'esql' }, + query: { esql: 'from my-example-metrics | sort @timestamp desc' }, + }); + await PageObjects.common.navigateToApp('discover', { + hash: `/?_a=${state}`, + }); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await testSubjects.missingOrFail('exampleLogsControl_visBarVerticalStacked'); + await testSubjects.missingOrFail('unifiedDataTable_additionalRowControl_menuControl'); + }); + }); + + describe('data view mode', () => { + it('should render logs controls for logs data source', async () => { + await PageObjects.common.navigateToApp('discover'); + await dataViews.switchTo('my-example-logs'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await testSubjects.existOrFail('exampleLogsControl_visBarVerticalStacked'); + await testSubjects.existOrFail('unifiedDataTable_additionalRowControl_menuControl'); + }); + + it('should not render logs controls for non-logs data source', async () => { + await PageObjects.common.navigateToApp('discover'); + await dataViews.switchTo('my-example-metrics'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await testSubjects.missingOrFail('exampleLogsControl_visBarVerticalStacked'); + await testSubjects.missingOrFail('unifiedDataTable_additionalRowControl_menuControl'); + }); + }); + }); +} diff --git a/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/index.ts b/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/index.ts index e8c8f1234aab5..d0e23c825870b 100644 --- a/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/index.ts +++ b/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/index.ts @@ -38,6 +38,7 @@ export default function ({ getService, getPageObjects, loadTestFile }: FtrProvid loadTestFile(require.resolve('./_root_profile')); loadTestFile(require.resolve('./_data_source_profile')); loadTestFile(require.resolve('./extensions/_get_row_indicator_provider')); + loadTestFile(require.resolve('./extensions/_get_row_additional_leading_controls')); loadTestFile(require.resolve('./extensions/_get_doc_viewer')); loadTestFile(require.resolve('./extensions/_get_cell_renderers')); loadTestFile(require.resolve('./extensions/_get_default_app_state')); diff --git a/x-pack/test_serverless/functional/test_suites/common/discover/esql/_esql_view.ts b/x-pack/test_serverless/functional/test_suites/common/discover/esql/_esql_view.ts index 80fff5ef76014..0d1e2b3de6a02 100644 --- a/x-pack/test_serverless/functional/test_suites/common/discover/esql/_esql_view.ts +++ b/x-pack/test_serverless/functional/test_suites/common/discover/esql/_esql_view.ts @@ -198,8 +198,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const cell = await dataGrid.getCellElementExcludingControlColumns(0, 1); expect(await cell.getVisibleText()).to.be(' - '); expect(await dataGrid.getHeaders()).to.eql([ - 'Control column', 'Select column', + 'Control column', 'Numberbytes', 'machine.ram_range', ]); diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/columns_selection.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/columns_selection.ts index 8e7294d35f1b8..6fcfea151db12 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/columns_selection.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/columns_selection.ts @@ -86,7 +86,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('render content virtual column properly', async () => { it('should render log level and log message when present', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 2); const cellValue = await cellElement.getVisibleText(); expect(cellValue.includes('info')).to.be(true); expect(cellValue.includes('A sample log')).to.be(true); @@ -95,7 +95,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render log message when present and skip log level when missing', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(1, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(1, 2); const cellValue = await cellElement.getVisibleText(); expect(cellValue.includes('info')).to.be(false); expect(cellValue.includes('A sample log')).to.be(true); @@ -104,7 +104,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render message from error object when top level message not present', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(2, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(2, 2); const cellValue = await cellElement.getVisibleText(); expect(cellValue.includes('info')).to.be(true); expect(cellValue.includes('error.message')).to.be(true); @@ -114,7 +114,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render message from event.original when top level message and error.message not present', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(3, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(3, 2); const cellValue = await cellElement.getVisibleText(); expect(cellValue.includes('info')).to.be(true); expect(cellValue.includes('event.original')).to.be(true); @@ -124,7 +124,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render the whole JSON when neither message, error.message and event.original are present', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(4, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(4, 2); const cellValue = await cellElement.getVisibleText(); expect(cellValue.includes('info')).to.be(true); @@ -138,7 +138,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('on cell expansion with no message field should open JSON Viewer', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - await dataGrid.clickCellExpandButton(4, 4); + await dataGrid.clickCellExpandButtonExcludingControlColumns(4, 2); await testSubjects.existOrFail('dataTableExpandCellActionJsonPopover'); }); }); @@ -146,7 +146,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('on cell expansion with message field should open regular popover', async () => { await navigateToLogsExplorer(); await retry.tryForTime(TEST_TIMEOUT, async () => { - await dataGrid.clickCellExpandButton(3, 4); + await dataGrid.clickCellExpandButtonExcludingControlColumns(3, 2); await testSubjects.existOrFail('euiDataGridExpansionPopover'); }); }); @@ -155,7 +155,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('render resource virtual column properly', async () => { it('should render service name and host name when present', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 3); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 1); const cellValue = await cellElement.getVisibleText(); expect(cellValue.includes('synth-service')).to.be(true); expect(cellValue.includes('synth-host')).to.be(true); @@ -169,7 +169,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should render a popover with cell actions when a chip on content column is clicked', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 2); const logLevelChip = await cellElement.findByTestSubject('*logLevelBadge-'); await logLevelChip.click(); // Check Filter In button is present @@ -183,7 +183,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render the table filtered where log.level value is info when filter in action is clicked', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 2); const logLevelChip = await cellElement.findByTestSubject('*logLevelBadge-'); const actionSelector = 'dataTableCellAction_addToFilterAction_log.level'; @@ -204,7 +204,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render the table filtered where log.level value is not info when filter out action is clicked', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 2); const logLevelChip = await cellElement.findByTestSubject('*logLevelBadge-'); const actionSelector = 'dataTableCellAction_removeFromFilterAction_log.level'; @@ -223,7 +223,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render the table filtered where service.name value is selected', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 3); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 1); const serviceNameChip = await cellElement.findByTestSubject( 'dataTablePopoverChip_service.name' ); diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/custom_control_columns.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/custom_control_columns.ts index fea974fd0096e..c1f4692f19fdf 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/custom_control_columns.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/custom_control_columns.ts @@ -48,7 +48,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render control column with proper header', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { // First control column has no title, so empty string, leading control column has title - expect(await dataGrid.getControlColumnHeaderFields()).to.eql(['', 'actions']); + expect(await dataGrid.getControlColumnHeaderFields()).to.eql(['', '', '', '']); }); }); @@ -60,27 +60,27 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); - it('should render the malformed icon in the leading control column if malformed doc exists', async () => { + it('should render the degraded icon in the leading control column if degraded doc exists', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(1, 1); - const malformedButton = await cellElement.findByTestSubject('docTableDegradedDocExist'); - expect(malformedButton).to.not.be.empty(); + const cellElement = await dataGrid.getCellElement(1, 2); + const degradedButton = await cellElement.findByTestSubject('docTableDegradedDocExist'); + expect(degradedButton).to.not.be.empty(); }); }); - it('should render the disabled malformed icon in the leading control column when malformed doc does not exists', async () => { + it('should render the disabled degraded icon in the leading control column when degraded doc does not exists', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 1); - const malformedDisableButton = await cellElement.findByTestSubject( + const cellElement = await dataGrid.getCellElement(0, 2); + const degradedDisableButton = await cellElement.findByTestSubject( 'docTableDegradedDocDoesNotExist' ); - expect(malformedDisableButton).to.not.be.empty(); + expect(degradedDisableButton).to.not.be.empty(); }); }); it('should render the stacktrace icon in the leading control column when stacktrace exists', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(4, 1); + const cellElement = await dataGrid.getCellElement(4, 3); const stacktraceButton = await cellElement.findByTestSubject('docTableStacktraceExist'); expect(stacktraceButton).to.not.be.empty(); }); @@ -88,7 +88,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render the stacktrace icon disabled in the leading control column when stacktrace does not exists', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(1, 1); + const cellElement = await dataGrid.getCellElement(1, 3); const stacktraceButton = await cellElement.findByTestSubject( 'docTableStacktraceDoesNotExist' ); @@ -116,10 +116,7 @@ function generateLogsData({ to, count = 1 }: { to: string; count?: number }) { }) ); - const malformedDocs = timerange( - moment(to).subtract(2, 'second'), - moment(to).subtract(1, 'second') - ) + const degradedDocs = timerange(moment(to).subtract(2, 'second'), moment(to).subtract(1, 'second')) .interval('1m') .rate(1) .generator((timestamp) => @@ -128,7 +125,7 @@ function generateLogsData({ to, count = 1 }: { to: string; count?: number }) { .map(() => { return log .create() - .message('A malformed doc') + .message('A degraded doc') .logLevel(MORE_THAN_1024_CHARS) .timestamp(timestamp) .defaults({ 'service.name': 'synth-service' }); @@ -186,5 +183,5 @@ function generateLogsData({ to, count = 1 }: { to: string; count?: number }) { }) ); - return [logs, malformedDocs, logsWithErrorMessage, logsWithErrorException, logsWithErrorInLog]; + return [logs, degradedDocs, logsWithErrorMessage, logsWithErrorException, logsWithErrorInLog]; } From a74e5a0b4ebf1767b92e6a996900d55b1ae0ce87 Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Wed, 7 Aug 2024 16:20:20 +0200 Subject: [PATCH 14/44] [Discover][SavedSearch] Fix default rowsPerPage for Dashboard panels (#189717) - Follow up for https://github.com/elastic/kibana/pull/180536 ## Summary This PR fixes an issue with `rowsPerPage` Advanced Setting: after the refactoring it was ignored. To test: Change `rowsPerPage` on Advanced Setting page, navigate to Dashboard and add a saved search panel. It should use the configured value by default. The default value can be overwritten by custom panel settings or saved search own settings. ### 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 --- .../components/saved_search_grid.tsx | 4 +-- .../search_embeddable_grid_component.tsx | 11 +++++-- .../get_search_embeddable_defaults.ts | 30 +++++++++++++++++++ .../initialize_search_embeddable_api.tsx | 23 +++++++------- .../_data_grid_pagination.ts | 18 +++++++++++ 5 files changed, 67 insertions(+), 19 deletions(-) create mode 100644 src/plugins/discover/public/embeddable/get_search_embeddable_defaults.ts diff --git a/src/plugins/discover/public/embeddable/components/saved_search_grid.tsx b/src/plugins/discover/public/embeddable/components/saved_search_grid.tsx index 2c67595b846b6..77d6f44de126b 100644 --- a/src/plugins/discover/public/embeddable/components/saved_search_grid.tsx +++ b/src/plugins/discover/public/embeddable/components/saved_search_grid.tsx @@ -9,7 +9,7 @@ import React, { useCallback, useMemo, useState } from 'react'; import type { DataTableRecord } from '@kbn/discover-utils/types'; import { AggregateQuery, Query } from '@kbn/es-query'; import type { SearchResponseWarning } from '@kbn/search-response-warnings'; -import { MAX_DOC_FIELDS_DISPLAYED, ROW_HEIGHT_OPTION, SHOW_MULTIFIELDS } from '@kbn/discover-utils'; +import { MAX_DOC_FIELDS_DISPLAYED, SHOW_MULTIFIELDS } from '@kbn/discover-utils'; import { type UnifiedDataTableProps, type DataTableColumnsMeta, @@ -108,7 +108,6 @@ export function DiscoverGridEmbeddable(props: DiscoverGridEmbeddableProps) { totalHits={props.totalHitCount} setExpandedDoc={setExpandedDoc} expandedDoc={expandedDoc} - configRowHeight={props.services.uiSettings.get(ROW_HEIGHT_OPTION)} showMultiFields={props.services.uiSettings.get(SHOW_MULTIFIELDS)} maxDocFieldsDisplayed={props.services.uiSettings.get(MAX_DOC_FIELDS_DISPLAYED)} renderDocumentView={renderDocumentView} @@ -116,7 +115,6 @@ export function DiscoverGridEmbeddable(props: DiscoverGridEmbeddableProps) { externalCustomRenderers={cellRenderers} enableComparisonMode showColumnTokens - configHeaderRowHeight={3} showFullScreenButton={false} className="unifiedDataTable" /> diff --git a/src/plugins/discover/public/embeddable/components/search_embeddable_grid_component.tsx b/src/plugins/discover/public/embeddable/components/search_embeddable_grid_component.tsx index fe511f5887dd5..55ed59d30f1ae 100644 --- a/src/plugins/discover/public/embeddable/components/search_embeddable_grid_component.tsx +++ b/src/plugins/discover/public/embeddable/components/search_embeddable_grid_component.tsx @@ -12,8 +12,8 @@ import { BehaviorSubject } from 'rxjs'; import type { DataView } from '@kbn/data-views-plugin/common'; import { DOC_HIDE_TIME_COLUMN_SETTING, - isLegacyTableEnabled, SEARCH_FIELDS_FROM_SOURCE, + isLegacyTableEnabled, } from '@kbn/discover-utils'; import { Filter } from '@kbn/es-query'; import { @@ -33,6 +33,7 @@ import { SEARCH_EMBEDDABLE_CELL_ACTIONS_TRIGGER_ID } from '../constants'; import { isEsqlMode } from '../initialize_fetch'; import type { SearchEmbeddableApi, SearchEmbeddableStateManager } from '../types'; import { DiscoverGridEmbeddable } from './saved_search_grid'; +import { getSearchEmbeddableDefaults } from '../get_search_embeddable_defaults'; interface SavedSearchEmbeddableComponentProps { api: SearchEmbeddableApi & { fetchWarnings$: BehaviorSubject }; @@ -144,13 +145,15 @@ export function SearchEmbeddableGridComponent({ return getAllowedSampleSize(savedSearch.sampleSize, discoverServices.uiSettings); }, [savedSearch.sampleSize, discoverServices]); + const defaults = getSearchEmbeddableDefaults(discoverServices.uiSettings); + const sharedProps = { columns: savedSearch.columns ?? [], dataView, interceptedWarnings, onFilter: onAddFilter, rows, - rowsPerPageState: savedSearch.rowsPerPage, + rowsPerPageState: savedSearch.rowsPerPage ?? defaults.rowsPerPage, sampleSizeState: fetchedSampleSize, searchDescription: panelDescription || savedSearchDescription, sort, @@ -179,12 +182,14 @@ export function SearchEmbeddableGridComponent({ ariaLabelledBy={'documentsAriaLabel'} cellActionsTriggerId={SEARCH_EMBEDDABLE_CELL_ACTIONS_TRIGGER_ID} columnsMeta={columnsMeta} + configHeaderRowHeight={defaults.headerRowHeight} + configRowHeight={defaults.rowHeight} headerRowHeightState={savedSearch.headerRowHeight} + rowHeightState={savedSearch.rowHeight} isPlainRecord={isEsql} loadingState={Boolean(loading) ? DataLoadingState.loading : DataLoadingState.loaded} maxAllowedSampleSize={getMaxAllowedSampleSize(discoverServices.uiSettings)} query={savedSearch.searchSource.getField('query')} - rowHeightState={savedSearch.rowHeight} savedSearchId={savedSearchId} searchTitle={panelTitle || savedSearchTitle} services={discoverServices} diff --git a/src/plugins/discover/public/embeddable/get_search_embeddable_defaults.ts b/src/plugins/discover/public/embeddable/get_search_embeddable_defaults.ts new file mode 100644 index 0000000000000..e820f2ec38e21 --- /dev/null +++ b/src/plugins/discover/public/embeddable/get_search_embeddable_defaults.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { IUiSettingsClient } from '@kbn/core/public'; +import { ROW_HEIGHT_OPTION, SAMPLE_SIZE_SETTING } from '@kbn/discover-utils'; +import { getDefaultRowsPerPage } from '../../common/constants'; +import { DEFAULT_HEADER_ROW_HEIGHT_LINES } from './constants'; + +export interface SearchEmbeddableDefaults { + rowHeight: number | undefined; + headerRowHeight: number | undefined; + rowsPerPage: number | undefined; + sampleSize: number | undefined; +} + +export const getSearchEmbeddableDefaults = ( + uiSettings: IUiSettingsClient +): SearchEmbeddableDefaults => { + return { + rowHeight: uiSettings.get(ROW_HEIGHT_OPTION), + headerRowHeight: DEFAULT_HEADER_ROW_HEIGHT_LINES, + rowsPerPage: getDefaultRowsPerPage(uiSettings), + sampleSize: uiSettings.get(SAMPLE_SIZE_SETTING), + }; +}; diff --git a/src/plugins/discover/public/embeddable/initialize_search_embeddable_api.tsx b/src/plugins/discover/public/embeddable/initialize_search_embeddable_api.tsx index 02f64d34fac2c..cc6711e3f5015 100644 --- a/src/plugins/discover/public/embeddable/initialize_search_embeddable_api.tsx +++ b/src/plugins/discover/public/embeddable/initialize_search_embeddable_api.tsx @@ -12,7 +12,6 @@ import { BehaviorSubject, combineLatest, map, Observable, skip } from 'rxjs'; import { ISearchSource, SerializedSearchSourceFields } from '@kbn/data-plugin/common'; import { DataView } from '@kbn/data-views-plugin/common'; -import { ROW_HEIGHT_OPTION, SAMPLE_SIZE_SETTING } from '@kbn/discover-utils'; import { DataTableRecord } from '@kbn/discover-utils/types'; import type { PublishesDataViews, @@ -24,9 +23,9 @@ import { SortOrder, VIEW_MODE } from '@kbn/saved-search-plugin/public'; import { DataTableColumnsMeta } from '@kbn/unified-data-table'; import { AggregateQuery, Filter, Query } from '@kbn/es-query'; -import { getDefaultRowsPerPage } from '../../common/constants'; import { DiscoverServices } from '../build_services'; -import { DEFAULT_HEADER_ROW_HEIGHT_LINES, EDITABLE_SAVED_SEARCH_KEYS } from './constants'; +import { EDITABLE_SAVED_SEARCH_KEYS } from './constants'; +import { getSearchEmbeddableDefaults } from './get_search_embeddable_defaults'; import { PublishesSavedSearch, SearchEmbeddableRuntimeState, @@ -85,14 +84,16 @@ export const initializeSearchEmbeddableApi = async ( const searchSource$ = new BehaviorSubject(searchSource); const dataViews = new BehaviorSubject(dataView ? [dataView] : undefined); + const defaults = getSearchEmbeddableDefaults(discoverServices.uiSettings); + /** This is the state that can be initialized from the saved initial state */ const columns$ = new BehaviorSubject(initialState.columns); const grid$ = new BehaviorSubject(initialState.grid); + const headerRowHeight$ = new BehaviorSubject(initialState.headerRowHeight); const rowHeight$ = new BehaviorSubject(initialState.rowHeight); const rowsPerPage$ = new BehaviorSubject(initialState.rowsPerPage); - const headerRowHeight$ = new BehaviorSubject(initialState.headerRowHeight); - const sort$ = new BehaviorSubject(initialState.sort); const sampleSize$ = new BehaviorSubject(initialState.sampleSize); + const sort$ = new BehaviorSubject(initialState.sort); const savedSearchViewMode$ = new BehaviorSubject(initialState.viewMode); /** @@ -112,10 +113,6 @@ export const initializeSearchEmbeddableApi = async ( const columnsMeta$ = new BehaviorSubject(undefined); const totalHitCount$ = new BehaviorSubject(undefined); - const defaultRowHeight = discoverServices.uiSettings.get(ROW_HEIGHT_OPTION); - const defaultRowsPerPage = getDefaultRowsPerPage(discoverServices.uiSettings); - const defaultSampleSize = discoverServices.uiSettings.get(SAMPLE_SIZE_SETTING); - /** * The state manager is used to modify the state of the saved search - this should never be * treated as the source of truth @@ -175,22 +172,22 @@ export const initializeSearchEmbeddableApi = async ( sampleSize: [ sampleSize$, (value) => sampleSize$.next(value), - (a, b) => (a ?? defaultSampleSize) === (b ?? defaultSampleSize), + (a, b) => (a ?? defaults.sampleSize) === (b ?? defaults.sampleSize), ], rowsPerPage: [ rowsPerPage$, (value) => rowsPerPage$.next(value), - (a, b) => (a ?? defaultRowsPerPage) === (b ?? defaultRowsPerPage), + (a, b) => (a ?? defaults.rowsPerPage) === (b ?? defaults.rowsPerPage), ], rowHeight: [ rowHeight$, (value) => rowHeight$.next(value), - (a, b) => (a ?? defaultRowHeight) === (b ?? defaultRowHeight), + (a, b) => (a ?? defaults.rowHeight) === (b ?? defaults.rowHeight), ], headerRowHeight: [ headerRowHeight$, (value) => headerRowHeight$.next(value), - (a, b) => (a ?? DEFAULT_HEADER_ROW_HEIGHT_LINES) === (b ?? DEFAULT_HEADER_ROW_HEIGHT_LINES), + (a, b) => (a ?? defaults.headerRowHeight) === (b ?? defaults.headerRowHeight), ], /** The following can't currently be changed from the dashboard */ diff --git a/test/functional/apps/discover/group2_data_grid3/_data_grid_pagination.ts b/test/functional/apps/discover/group2_data_grid3/_data_grid_pagination.ts index 193d3f4607987..55747eec93119 100644 --- a/test/functional/apps/discover/group2_data_grid3/_data_grid_pagination.ts +++ b/test/functional/apps/discover/group2_data_grid3/_data_grid_pagination.ts @@ -30,6 +30,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const retry = getService('retry'); const security = getService('security'); const dashboardAddPanel = getService('dashboardAddPanel'); + const dashboardPanelActions = getService('dashboardPanelActions'); describe('discover data grid pagination', function describeIndexTests() { before(async () => { @@ -126,6 +127,23 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.waitUntilSearchingHasFinished(); expect((await dataGrid.getDocTableRows()).length).to.be(10); // as in the saved search await dataGrid.checkCurrentRowsPerPageToBe(10); + + // should use "rowsPerPage" form the saved search on dashboard + await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.clickNewDashboard(); + await PageObjects.timePicker.setDefaultAbsoluteRange(); + await dashboardAddPanel.clickOpenAddPanel(); + await dashboardAddPanel.addSavedSearch(savedSearchTitle); + await PageObjects.header.waitUntilLoadingHasFinished(); + expect((await dataGrid.getDocTableRows()).length).to.be(10); // as in the saved search + await dataGrid.checkCurrentRowsPerPageToBe(10); + + // should use "rowsPerPage" form settings by default on dashboard + await dashboardPanelActions.removePanelByTitle(savedSearchTitle); + await dashboardAddPanel.addSavedSearch('A Saved Search'); + await PageObjects.header.waitUntilLoadingHasFinished(); + expect((await dataGrid.getDocTableRows()).length).to.be(6); // as in settings + await dataGrid.checkCurrentRowsPerPageToBe(6); }); it('should not split ES|QL results into pages', async () => { From 018cb1ddd60138b88adb2739977b126b58c585cf Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 7 Aug 2024 15:29:46 +0100 Subject: [PATCH 15/44] skip flaky suites (#169888) --- x-pack/plugins/osquery/cypress/e2e/all/live_query_packs.cy.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/osquery/cypress/e2e/all/live_query_packs.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/live_query_packs.cy.ts index 85ae94dd3266d..a8461dd9742a8 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/live_query_packs.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/live_query_packs.cy.ts @@ -18,7 +18,8 @@ import { LIVE_QUERY_EDITOR } from '../../screens/live_query'; import { loadPack, cleanupPack, cleanupCase, loadCase } from '../../tasks/api_fixtures'; import { ServerlessRoleName } from '../../support/roles'; -describe('ALL - Live Query Packs', { tags: ['@ess', '@serverless'] }, () => { +// FLAKY: https://github.com/elastic/kibana/issues/169888 +describe.skip('ALL - Live Query Packs', { tags: ['@ess', '@serverless'] }, () => { let packName: string; let packId: string; let caseId: string; From 3774941636b068f29b8a1f969ce53fe55c66d938 Mon Sep 17 00:00:00 2001 From: "Devin W. Hurley" Date: Wed, 7 Aug 2024 10:45:46 -0400 Subject: [PATCH 16/44] [Security Solution] [Detections] Adds FTR test for lucene query rule (#189829) ## Summary This test case covers the bug where certain lucene rules were not running successfully due to a faulty parsing error that lead to a commit revert and fix here: https://github.com/elastic/kibana-team/issues/971 Resolves: https://github.com/elastic/security-team/issues/9900 and https://github.com/elastic/kibana-team/issues/971 --- .../rules/get_rule_for_alert_testing.ts | 15 ++++++++++++++ .../es_archives/auditbeat/hosts/data.json.gz | Bin 186535 -> 183944 bytes .../serverless/auditbeat/hosts/data.json.gz | Bin 186535 -> 183940 bytes .../execution_logic/custom_query.ts | 19 +++++++++++++++++- .../execution_logic/threshold.ts | 2 +- 5 files changed, 34 insertions(+), 2 deletions(-) diff --git a/x-pack/test/common/utils/security_solution/detections_response/rules/get_rule_for_alert_testing.ts b/x-pack/test/common/utils/security_solution/detections_response/rules/get_rule_for_alert_testing.ts index 5649031185feb..5c0500f89ef51 100644 --- a/x-pack/test/common/utils/security_solution/detections_response/rules/get_rule_for_alert_testing.ts +++ b/x-pack/test/common/utils/security_solution/detections_response/rules/get_rule_for_alert_testing.ts @@ -30,3 +30,18 @@ export const getRuleForAlertTesting = ( query: '*:*', from: '1900-01-01T00:00:00.000Z', }); + +export const getLuceneRuleForTesting = (): QueryRuleCreateProps => ({ + rule_id: 'lucene-rule-1', + enabled: true, + name: 'Incident 496 test rule', + description: 'Ensures lucene rules generate alerts', + risk_score: 1, + severity: 'high', + type: 'query', + index: ['auditbeat-*'], + query: + '((event.category: (network OR network_traffic) AND type: (tls OR http)) OR event.dataset: (network_traffic.tls OR network_traffic.http)) AND destination.domain:/[a-z]{3}.stage.[0-9]{8}..*/', + language: 'lucene', + from: '1900-01-01T00:00:00.000Z', +}); diff --git a/x-pack/test/functional/es_archives/auditbeat/hosts/data.json.gz b/x-pack/test/functional/es_archives/auditbeat/hosts/data.json.gz index 00c6963b16937c6c0d0397f4d082a03a781f6536..b400db7f4540dc193a4bc9e2723f866fcb101e98 100644 GIT binary patch literal 183944 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
1w04 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 (XdO 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�HRm|QVZjFeN-{e49_J1#b(yDdnbxqA5Ow$M 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 Sr-pg!!~K-L+qKFG_x}Jz6Wh}O literal 186535 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

    SfP1v5 zIx@nxDiW1h-vKfTVYP)BNf!AbTQET0g|mAkhk!xezU#hH&UMWg!-tn+#P+R^&J?FV zJM*#ksvswKj^6&?m{?{Gj{MA;2a~8Lm5!M2Z*_SZHF(xiup%|tafsD9t|D&JM+$#~{$xI#sPfI` zwmNGHW4RdBG!Y?V=SO|3Xhl{u*Vf&i14dR3tA*nL6%^uKsgRWCqJb*1*p)_7^I8w2 zdNQE~X=wi7u++<+Dk4~{MuoMZEPr?Wd26&hol_O-JkxxSOI7^c5|eGa87GVAzdzcn z@>RB2PP>dj^(h#(wC3lP{pN0{$22}SW0oSwjG@J-dxw94`JmRn^!bT^UO6N>+(pZj z!Mv2NvdR?uw5T4Sqws+PPeeG1W}6c)dyb=MCr{f5c3U0k*$tq;t{b*~2;@-2i#xGdJii#(u4v zN6M@a4gV=~j+s9&nu4ge7ty`5S_k*DsWys@5|<*`Uxa7Gr6~=JKwVcpbDW<{ttNlX z6k*!YUM$w@jCZ$Eh5qx!JM(n8K(K}g&WZAp`0QAeNYeKT9WP@VeHpH4llzsVHB&FduAt>NDK)Xtxhw;Fu&zb8^arpPDZ7 zd{&+O4;4j+Mnm}>2ff;-mG#!2{>_Z8HBV>NlJi$6ID1=79{FE>?d>F#Zi(cu%?l&A zW_TR@YAlI!bk#`Iekl$f_JsR5tk12`Me*o~0o&Uj@YYPJ7C?`)UutD5}_{#OfDuB9*? zC0oPp+#b`Z&uC}}hi}j>wffvXBl+IN<-I>DxR&9l}=c>p1jc@NQMQ76m(wD}>kIXwKnLYIMUx)k;*yCngM^IE= zCtqqmr>@j*FPB{$h!RRa%uCQ4sx8UI>|^|bkA1*555h`G10UCqmTK>TvR0|{W{hr< zGsjXx)2+Ef&K%+TIG<*`<;i+a;>hNBo5}L`c}->FvgWjYM40=zxHxjZJ$D+mV}BIl zwZ(1S7=ZeX!0|~6Vv4+3ufZ(Mc>8E9#e-=B5LAhY3&8ju)X3yCZGJf7>{Ypa{ixB_^?}gc?3$qXCP)X5gy$A zC-OqT>xGo0cf#fHUwpVUN4cs%w4~LzNw0uO7ebJ318u5ZhsslZwLjRZPe` z^t>th$U2`T>)Sg=<@ajaeJPKQf-;Lz$Iy}LOrZ6W=N(ja9bau2E9{B!k#?@O^%%Xz+8UV<@dC)D3pE>`+h-H@_vrY6!Nj zBpoTKjbi}Bfl9X^uAf!FzYf+^JnN}vc=$<&T^9~CC#n#lRL3dU&Xo*druAZ{+4H0G zjktB}mCI;Zq!%}3)4DD0x3F&Zq()x&)Q`F(`Ook;K-xG0SBeF5lHrFZaUn60sHsQY z4-voPAgr(!+A%K_d=%nd=5$#+9({l+ned6qz>KlKT3)zSK==V2x2;bpF(d|AzIFcdWqI{YS3# zfMx{V)g=s~A-UFR9LiAN!}klrH_l9M$fW6X2`=AdpV13nt>G>&n_;%~WD|ewXwFvK zjbW>I#yf;@pX~wWoNb|M4@FzUYS6&u?c3FbziBp|g_!jaS(Em=eBMwOxt;@W-!Rom ze|a-j)?Jf?H*RI5qIYY|UH~i6fU9BjePV8(-5qJLe65T>o{w)0WP$ zGkMLmj4W=&Zy{y7@{2cIs<#S@&hqwUDPW`9`R@i-dOLIVJALbqnE|v`_x#0#n(q&^ z7$_D8T0q8*c?IW64nkJ?EZv>v4~#y3COm5}^5U>UJ<;L`#Phc1mrXo)!;UFc5uLQ0 zKSvkKwLKguDa|j_Mdl1!_sw2=zSU6z5#Ah~IduVwCe1GG568d{+vM`Gk)7L2 z-m^FjoM{|57)=tO365P=ztNA}a*7;-vyUIFG=`6Z*Fb#V`UYinhtlTtMU4(Jl0BTU zhDGC~^~qWeJOyr#uR-XIk3E9*%H0Q|2w!vK+)T7};I#6i&ba#Rb(37yt!=;VJF7Ln zPcK%L4aBghOoynk84ToO)cNz>pW{ixzI*B5o=8|_i*uRi)gYyNt^TT>soIXn=5qe~ zs2sX1y6+;_n;reuZE18u{ZHgCaekRs=d&Zr(yb6QFAE7HTRgT~^V0eyB)L-#&v0tF z_Kth%F^?biUlY9Cm*Q@p_y>Y1{i1<& z66sxEV@cm567CnURO&eWVzq9@hMyPeaBapR{jM2NG^7%96&O(oHba~TFv%r#aypua z_PKJeSq9Q9UGy?IuL)i!cPlVBQ^+1B@G6YB-#YJb7>Xe8hw1k?3HRK1--#5|aU|V`1`&2Lh(MB`hXahgWiiNj=sGxx=*(T2 zmH7Sn(16t=NB-qa_K!Jt&Xf%L)EDBK-`w_`Q}O`mKMLddTyx0d`A?I-RVfzy7v3S= zzkDzl*2_0h3C^+usJ(2!czo|VAdTEE{@h|CW+msL^q3_2p2dUpTlWU8AUx413T2B{ zLyIA`=-eP`BFyA{!UyNMWa>$^3SA;Xx*CHM$`Q(GNXPiOTQ&TX7B|Q#$Z0Qge_(j= zAed6mE0P`Q-b`oq#4f~SpPhfBp*`*_7BbhF6w|AS9hvkjc96nkAZ3&kj0*Aj_bd;P z+FCtG1M4&vvT+DOO9M>0ry&HVZyTUJH-z9evgbU*11yWgkHiOk;1=R`0O2UiQ*j0% zxQ(nY6;)EcL#RWDEFKC03V|6Nn+bqz#l8<9ix1}s;t3KVgu?;g7_c$<`H|}x9jA&8 zLz7uC@5`!&H)z;7Rw6Ei!%UqE$uoDi9r=XY;hLn&PW(6>$ zMZcEfQP-yRdn(D7!?yP9_}zzo)m+iZc*fV>W}dp^iWwx}da`=*e4$8jzL*`A&_f2$ zod2fMIU)J^y;^;pJEo!iwO8XHVivy574^7U1MjI`r;Vb}R4#%*p!TNyVL;{f>GS;j zqX~+-oWG{|eQ9=AoMvNl;LG)UOw4vU)?oYj;RIyNa(kSX{~M)AsZ`DX!ys8p|M)ki z%;VMBCf%L_-no^e%;T`Ql7ZYvaIv@4IYzf}7-q zf4^%``FaI+f92vkVJAs1sUrU0MuB<%bwS3mwI-d26ZDWg(5ZA1@NP#~`RZAYl9zmE zgwT=+=wp-z*q@u*_})+ln}hppQ@j1hIOx$ll`8Ku%JtzVa;!bmlDd!Vt=A~6=C|p6 zyDZP&{{8TdM1B}?@|gCa*SC|REN{7X>7K44{^yqp&5~Kwfg-!CWk{k16j2(2oKIcv zR1Ui_1*KH~Pz@C~sIUOmZt57>;ZUfvoNv&9ai`}hx~+l7;P?gWiC}wt?sU6FQp99E zBA1%I&a%}xnZmrW2Q8_C<=TQKO7UhkdX8*={aN1*OV!EXAG3x`Zm+yvS4)}wI1~ng zJ$oGgnomWOM1cnapSK|ekYoHPvB~~Nr$v(B2+rWeBB8`?zmXY@JLYik{>-6CCbLQh zVW|iKPKGF0lz2I2Kkjg;Y$Xo`VgRN0aUA`MQ)f0`TU_MJy*)V$stKyeCl-_6_XHw{ z|05RKM9qC_5W|DdFbpR_5sxYKq6Ow+?jqV%13-+o%9e%*fYQS<<(*u>vgd?!L=)G- zOw?L3S`JEW^cs?%L(MJ#hr)-#{{<1}`SWBW5Rg! z$)0)>8RAUn(fa~)Gn~K5v4>bT zhjqwh5Imr^u@XTNiHndxik>4~`iGXzGVbGK2mfzP9YPZ?zXc_7YMSJk8E2=FC`9~P z#hEF%v*6nmoQ6C&C*fhBQ^q-FiaHw&Q;&d?vgvK4ZdJn$BnJ=89O>K-sJM^xeGb6? zz%F2oO=(u{8N1g~dV9_=Rs~1Xg8`Sq2Vg|4m8421I(=%EAtKWbqLw-Zla{K44jdb{ zB?K%GhU2ZGbim<#B@H1xC?pFp;Q+AxLxYS$ZwfH@S-|*kzbQ7;HaB{LOT%Cv}1+IjR05T`i;Ar5d)$Ph)v84;>W=W%FlLb4GM6?H9+NiwHiSdCezzk zU35#9|JRC>|JRBIBd~(;IF(=eNRj?LmlX19Ds>?7IpO3y$aMpftF0PhAXocKcD;lj z#`;}bgbO<0s2TvY!;mQ148+a|R!$H|5OJ_DaUTU8h@iKBiG#Kp>OVATP}_aGrKNBI z!xBdnmnt+FU*k#7!GaH6#txO8fSJv3<&^-N{_dszLVjQq$MULoU8 z=N0nv)aAP&uoPt2rXg)384$vBND|Vv--qnPoWgniu33hx@*EHhHfd;aa6&=o-UO4g z0hs@xG5d9w^tY+oirRZdNI0KJ7h=w|cnCcCBxPu-0`xFOdw*2uAsUsZcp={MGa7;O zGdo$DG96Skd~-xTo3_sk<5(ep7r;x5%lm2A0@dXo4E8x37|&g)=V&g0O=hXH(y}xu z`)fmEwNh)a*Lq5cW-J=iWjr2jOyjaGF0?O22(Eg~kf!Yawc}OFM~WR7CS6_WiE9lgEuCnw9= zJ8@Qe2Hc>UIKqa2aL~}#U4VjCUj0!;~ z%_w!!XetP#d&+l0u;3X$9EgP-i4_7}_5YA1plSTV`F%d?xRQMrPzg#~JnVB+gM&vF zlAh?^#!5gfhKQ_pOs<2uFwnCPpqPSaiWn2YBKi?TrGt~u0f+zn@4!He&jAz(0bTxI zOglx+mp^eq#wS8@_&UaEbP{vj3d4+aPW4k>*F;Udsv+Wr^X#?2Q&(uY#^KC(0|tQJ zR!~5JP3(?cC>$sp6eJwXG-0QIG@}3U&Vzm4=QJUt0IwM9<2j2?9;XFMi<8njD`Fe` zNC$?X;oZQR&i};q$4qj)*=FVNyV|{(U88USMis3(m?4{28ME-j9zVYEaR_!J_Tf8mh=s*{ud+NE47dMME?LtwWq`9bce8+5X z!Zn%7b3`!Nq`5C=`TLW)s3D-f{J#MQJF<~S-6b?OpX`J|1f5Cuz?B72vT4bn6O~LW z5xn3lN7BNuk<3Ctvoe>k{7a_OcM=I4-PzPln#J-4C~PR~Y@;L~ddTzb#E;DI z-1iWxt2BrXnn6Et*eFrLfF{+yj6iUz1Q>?D7+RA2;iQS9pnPNK_c}9)CI@78a%+zq z_SThdHO9n!6{a_@fj*$5%rl#zlYq7Yr-I~(<-ktpfaV|s_1zKC5SU?8RI7h5P+|Q# z@Jf0;x&Mse9BP(NMTJ+)$;S)XFKHONswc_V!6<^wVTZ0QdI(kt)F7=^l5JwsraT-# zk)sp%G9W*N`fL1=me3f+cm#W@AOjK91^*Izq?b?fdzY`cgJ%GI#mJsTNBd$HT@QYq zG8CS}LfXZFPNTyr43<~~K8O|#3-?-fNkhW#eaqmj>7PluF4~p`F={tzN1BS8m#@jYI+dN z(5WySVyuC#e7gQdp(8(C9fafG2rb-&#)QWjp?QHaA&LJmwN{73>JaS9xz5d?$l*py zjws2PP==xDkfPmBuqriU)1sz0N%qE~FbRioKsq$SIsxZ}6b!H`Y*~YJ5DD*`t;FDn zgu;Wu%T5$V!~gr{FS2;-`vtySePe_49DE_wytagUhj94yMc;prGl-dIssvfXjUPp@h1LAw`^jr-{E{kFiD< zUupO8WRKcrP!J028OdX6z*EAVWHj^)ou*(BN_rlD&vjQYJMM^2_T~uu4oR90b>hzF zW_<7p*KRbjHJb-Yun~zg5X+dM3^WO0`G+YI`-tXI7W)Mna=sa4n0I|YWo57h1o{}M z7b3Eg)`8S2M24x379@NIJR5epNsVc6lppMj6|pl02Lr|b-%vjNh$wJ^7!djxtCpG; zCELOfj&LO|90@QmQ}6NM5`K_o7ejJ{ptN+SMgMRrOC3t<+VFwd3C7C-{hNbA)(~>e zzccrLvv-Z9o+}I?nbaU#Vae3|M3GoaPVu90C72G`Rjn+~YTS%6nK~sso)#4H_av}! zO!Xn6Rrar^?%P<&3hO0y&Y&?-)S=?ViLjRPlcGv16g zPLLUUH~=sdlORQdyF|BSqQt{Xsgtrq+2Ysso?z@Sz78VDiSI;rus1g8COLRAW+hhh9Y{s~C%Ms8Vr{C>nn& z<>yzoY_Gj3|C?as4s-*%L4@cT(w={4?TYsZaf}T%)9(y=9+xij2#O>t<>yB0@Hvek z^*7eUC{75Ot-noiG_S8^B(EadTT2Xv7L#_Exy-5DK-v!0e?Fyl-DTf_ z_?ZzwbCLRSfX@}L^dVMV;EJZgXQ~>{rdyVS0ne}BKxs?PT&yCGL)Q#mnH&Z-G79WH zQ5k|f;QX5BLf`-#KpKdP7(t5O_`ln;J2K~)@(S8~SO>010)PID2EPt5lXG=2r$guG zh6QJma%>K_3RPRj&DWAvDOW0iq7SmnbmfcXu=u;Ef3EQiN8FnKyS6t)FjLBb5&p*r z1X}@Vqv==W^gzK9tngGxmjW^2zC;FOCS5f`&4BR}%Q8 z2#p96(zZw3m%4{hqrt2-R7XpID!WB4IOcm;v^d{*XjD!!ur+3!2p7NWsbHgE5Dj#< zKe4#E(b|L*{KJ%1s09z9@|{1cIqzGJXoPRaC8poyVH1IZ#g1FkQ{36|w3kYci0GKu z9fW+UJP$Yzw*+a)SPr7llr|L^9(fe)S_gXRquy8%^rEri*ZFtPHv7=p01Jff4#sZw z-yRnxd?IMwsNHtPIPl4IQk{0Z)u z?4Go+3^pd5pf1%jj5oI@Rao+?DH;{^sLYB1E>_N0wyvqQyhGydtBbCXwjR!5RJ$VI zhA2udGOo1|YD``cyMHvLQu=@W*vD6hcS4u2$S*G1DocaydZalK3*iUMK`*+r2%n)X!_`kJd2HHAk@n~g85}I@Y zxUC$wrEr7OhG%Xtj8T!p8vKp}-Cv=Gxd*Le>BG8^-eY?3FgYhRn>)h^j$>@N-6IG_`X6!P=#6eiv-``09DyW-r@BDlf^n9v=Y6-85j+d;r&v+3 zs>m_CAA3{OI34D+SzQtP>P}N>`$Iv}LuDu(&}@M_kZc^wMr30@VDPU0-)Spd>Q*Kp?6x9J+Yphef#;>b zR&khy&5Di3!47c|bYhRw-gff+@3pj-!D)rL`HlP+c^*IK&_Nq(X2vBQ$?{($iHZFzH2aW#0lyZ@ z=CIG2xs{1cr!=t&M^Tv*Wh6ilDhA2I>=H?4$AN*D5m%!f&3&R+^=y-*vCLl!{`@NN z!*mT{h!BFrkS5u*7f+_g?zu zmw!@VI!t{!C3tsJz}|glFGQ1d*vmxzb2ko&VP8opX(Q0C7B7KrKIl6V$@p?6MxpWyZ3Jg%LbE92fD=fi$JN0+*03+2_qN{0Aceo z`NWekwUh>qK-Xp%_xS6edC@3V3Qd1 z=MwjxZDyJJ`njU%V5W}W^74o_!03K6Q@Wtov9<7F`ydYK^?foWQWV?q`d0w%=l0=r zf>mMzo(jT;zywG-Oa{>ck^gAwY;jo=91UW(#6BSQw>N(cYbV2QDS@jA(Q0Xx5Es&5 z6~|4Fab2Mssf`YzJwY;+fNS90&+Z>eg=dCks++p9o&7m+Ok)8w-Sk%qzuR2Ao8Yo3 z9K$KX{*SNFttBjYttqpb?=St-rs)dGvjLET4`4Bj?&79rZ!0T$>&?cUPgRQ6k%qTl zrZe{3-ro(r)ks*(7>HW|wG&cWYjkc}6FX|h5` z=wa3D1~;mu9FYq+pNTPz9{@a(T}eV>+E~n$A|14#*SE}8MXg6S*pr)n3s9OHnBBy6 z^1g~~l0A>uwX;{_&No!Q|pVvsU$MGfH2k zI*#%24p+)hh_rkt(KNIMa#3LDc z>;WV(*X-=gwxiZJ3ibu(C*l%QAZ5f#Y#SO<*dgltKNun{0^0_qaQH-D>rX!r{qZ#dZr%G3ZA77%%ut>@hprDDU7pLPvR{F!}v6sLafP z(47qBIc`iIEnu7%lFp^l3!ni*NrP0#Vj6OPi%rkpEO~xvf8cUC&J_hq@LOQ$Akr(+ zs||J5!Y+Y)pX@(gOrpvqx(!T`KPh0kyvOs0lCi^q6Cwnq`M`hT&y2gtgNCI$G?Hc2 zvqX;4oxVLr(;3#&*C6@A0r*eQ*Brnj^bQtY`AF7c?eDYe-n?M=3-0KeW|E zGs5diY*m!$&unjMBTUj>xNC1G?Ay{a=)^Gtabr*ie>zaS+c{Etb_PQ8FwbGJCgE9Ix8tZS_e@v2DxCuyno^&wZb2&
    g!GZ!-ND|S`#H(We0b_u6*heq0#fc(|o*4q(6-78nE zi3MX?9iz2y+G~L7|D%r8eBu7US>il>J*EG}37J8Ug!jhWZ7<@*?9#z+qL{J1RT3w~ z`RA7(JSPwRo}>1515Xvc+xO}1+Fg4Jw7I6n20SBy^!1n~$di0zklM;@3JpW@so`)I zWT%vJ`&B#+>V2MKQ|p8F!en*VK_c;J@M!S%MTsxR`GU;FbAsEGehiqw7G^T25rwEI z3wY=BZ=~5$I9?l)m=t zZjtb<8s*>fbAotMp2%%JOT!1*zc}UUN(u})nMszf{w#{;Ie)w}AW%u^+Jyj`dUAM%M`E(nv`_UC#sWo@iJ-()Hs7CpBR6Vxi zZNsA6b|c-NIU=o8Gr&}@_+)7*m9wV0=rkRaBX3Sr?qNM- zBh!+!Z%G9&9*dMuiz_&GO^o>Uc+U`VTsvg>wr76v%nBipQi%|Z?-G3L-R$fuH2mh@ zdd~i?GS}iM(aFWG`?h4pLeQTa#8R;lD^2#mhWwf9rAL<{pZ(=2<1i#3kX1}zHo>+bLD{&RyDu0Si^-F@ip$cbf^(0MdJNga(h@U+i|99h7YVX(_T~snla(z zk`&$XT%}|1+YdmHR;<>A1oj)ar18}rkDYvk&iSK>PY&I!`uR!wm$%9~5~L^WVv$=* z!&X0LduGYU*Y;!4*e|Ut>GxG(^rE(1S**TYlP|sY&6@vdq_o=WOt-pl)%>F)w1qOK zH`H0R)~Zyee5!50T3GdPEM!y)3!GZhnX&b5=O-K|AJN$c>BBK;c)OG6Y%NmZFcX}lBRkqoEbd}OY>MWJI5+%>pBI+p&< zfhb?wTJ!EFTw+NB!Q`Ig9%82Yntopid|43PWsS|9w{oExeJq7X5H$xAUYF*z>_fpS z+7fE3r@ZU_L}|#V_HL{~Uwe3@gdFCs8H6HxX$E zz}u)AIe#}8e+h1CQ4y51Z0j6nlpTExNNDxAKoT-SlLLfHhpgj&I-SZv(6IDF%hgUb zDotSk_e-1)j6ycV7NLCCGynSJDs6Gym@jMcmu_4BUwG1We%{I1xx1TfCK&9nr*njmN`Pb78(AaJSVuTP8p`lx#YubVHc3}W*rN;ilR+inb#;b zG(Gg!B-(a9&B0uLft$hkl?=~Ar5TUufBLyhzH;{>yDAq(B#p~4xeRDiBNCB}x8`g= zQvJN0<1!9zPd`fQ6}Y1PtWHe%Bs#jAnNe~INkMHbYlbY@J$<8@DwP}!kP{N=XXk>s zF6DA~JfmKmKI@h1;Huj_`?AkE6Ll@9E*l8J;FUr9)_~n}PK{-QD$9ze7=o>;Un)el zj|9ne;fC6VHSi1>gkgJmSSi{v@tL@-!uH%$_)-<5_%crm)_%3rygpXF5hsBwU1RKA6SaStVccNL*DEp1S9suj#4r_M0|lO8 zSP3ck(LAfRW?h_b$?ROxj?E4B`O|N!@}?fR9AD%- zRusKM1~(|pjz(mE?^%^4fpmrpm#^CYA5UlD*W~;DZ3RUnrAtIWx&&ziq`SM7W^|1X z>CVy3$k8ym1f;tL4C!vAYw+;V@9)|17uYzeA+kfFi(M-^5zNe zW&P7XR?XtBK{Y>V8aEQ$lL+;)9K9C+jILbua=^g;ub=t9y~>qBBvkCOr~kb03@AJQ z);RrY{*_2_0D2KWhw?6K200I$xIiw9LH_o`DYhZmZSi>)w_< zU5h{Sej1r;$bAibm%d$;PMBKXYHMiTSKiTqP;3$~L!wJFY)*U#U4~lBb-J$h6GaP- zY`8md8D_npZ`<+k1|re`T1x4O9&}nhoWH=fJs#(K`?ZUD%iJDZX^qI?Cp&B%UJ_3I zL0-zPx30EgccG1!a~qzOx^X+6dgJxJ#~2nbofe=Bu=}#%g|bGy%Hu29t9xd{>t&A) zu-H&6o`_qHEudpK-bS}h(DtR%hdJ`Wy=v*BEzNX{Vi`stZ<2tpxG#;&BK5W3LD#of zs<@|dVsWIwa|T?9G5OfrW_zQE1x#z!{sfDLnx{yX*iY@N9rwJ$Fw6Ohl+t%YUc#Td zBD1}TH&&?=?#pQ%AsGb^em;&ho!m8abJddQ~fJf(E|d7-SZ* z-6uV3LS;#S8R}VAV^j4uyTIN9;c7;M&q<7FYI}-&2fWrYNgbRTq6+V^{eyN;y924C zK|4B(<9NqI4jM^PPP#5seqr~N357cc*x!*K9BOCuqg|}3SP{Z2pG#R^FFx;)Ng1G~ ziVZ8+krAYt$_87Hx6(za_dMnzLrJBCaBjpu$dAM6oK%H!S=VL_BUlm3?Ve~lE$;AV z0GDujwY>`D5*;;VTUUtZGAG(6Yqp+bRBN_$F}gx=J6C1ysw|N*O}DaoTRSH{8^^5* z&0-*R$Mm9BRZvtA%fvcyhGZm23W=b6ocfh|ng&Muu? zG|t>^O4hH&HGx0VL0#fwRZ@QtUjyvqv9BYqIdToUjAx*SWGTw6Ttd(H4?8;k)TiuN zlr-xqN(pMWwR!iM|01{dl_^`Y)hL?)Ji_*8jMEZT$^_?vOIOVgb&5wp z&b4#*=sQI@et_FpMa1G^W3jMevai|gP?^P>lg=5|W>C856(Xc7J?FhaUGd_%^A66b zE0b^k^o~M$I|I4!O2#iBQ>^PRU!8cz^}-z(bL=|Xy$8-R%N=^>ScSWjHmcfTB#$w@JDIrGYXrRzR>&gv+hx61iYiDerstju(;s!Jd!PK zfV~`^=%Q!ZMClpI5+C7@9U|41n`#^@6PF?4`}ibNTHWmevjy|#LL}r3B#n!{$SgMP zS#w{7#&_k$lB;c4I@@aJXUXfA5tq|S_P)0dAsS4=pD8Nnx>C1$a(sLC!O*!(Hq%^S z>xI!{{pHW2OA0=0`19zIeX>lZuZLCLviH+gecWvuMleH9VU(|nY7XW2I%8w(tE5m3 zR;##tRN5#L+UHCGbk(^9Nt5(_U~xP(=CjG+s0K9%RF%dTQ?aqJFMsH~PeAQWui`?% zbkg*Sf^J5DtN^FrX}JYx`ioUTzGgB-OB~wXx_ndw8jOCZ3QEOCtO8m4w$v%>Ki_ z0%aK?335Z-C}nHx%2K~xxiaTr<&DwqSKFhy{vVYp789NKU;#yESp2q#PO)9Q0kG$_ zuN;qWHyhwAae>E9RDnzn=;Vw3_4Uizml=_bgWsn{%A)Fi($ht7_JyueIkWeEz-Zd! z+el!@;k4kVoI8A;ytwq+mB-G*TJq!6ue7h?q0`2gI3t7sdw%LGzR2hSH#?882i_MO zioumO6`VLDDRLi$9@ez*l34LZwp;5~Q-0{gW!8J3=Cjm&I(onVCJduCQlxn|>}%Lp zESl6kck4WT?0XF_eqPC=iKBYMGpv*OhTYvggCAN+Cihz(@6JnS<)?>-3tODsqKcQJ zJ}kTw_>@CJ*1{31aiL|!qV7uW!KnrRK=NsWQtw36R6aEb8KbsFbLJaulS3G9mnSC| z-^cq5f=N;>SvUctX2G4TCW)5?@V15fz+OMpp~KUQk59M?WA@gQqoai9T?PiUf}(iN zb}iSk{b{41+3Fh4_G%#cTEcgJ6ntT?$F5!G<1WU$6^M4UN`|ZZKj7ARQ>ZM4E6~5z%QAck|Mf!W zl-1P$P(xk|;aZ_C8?9;-FVoB=CZuq-{#Df$IA@Lhz`Mef9eK8L&A%k8UZmaTFB?(WTTl!>d>OB0K$AO+dSBTf-M42mC5Y`RHtcwPxF5t1H*$O ztWDn9Qskk<-_6Xwor+QiU|*(}atZh34b6c5Jb7SGZe$T9yusLN1mN**p10F_2WKfNX@!Uz%$KND_~=HR*Cd0q?TZl636u2;_dVpq3mbAm4NYxhJ)zyybXum+uZ zr>byBT$mZQ>E^4b^D`ahz7G(isCADAD~obq8P=eVNw5SCjOEqTDF==3dPU#|I@?*C zE8Bv}`AbumSU@X}=X%o}ezsdhX+CWALk*EE7dF#)QW_HQp${7W(08O`6Yc{Z8W~)8 zX}r^sf-W#mZ2A?goSimu2TN=*{h~1p5}S+?D*OQAVcrc!pCnO6tLy;nu z`zkJ851G28vgXUN~?#9Swq1kn5z_l zHhk^FcbdN#pxQKQ-B!%C1oAA&n7KUYT<65+84Z@7cCGon?w#6rB$l-#m&_Lr(M{J` z;{$J#(%Sq<@H&VhwmVBX3C$1OOBi7HK<41HFo|H6IT0J8s%B37 z`TWDal5zk8UR$)j&OKdOsM_U`(-^6B3K8Sw)7++!y`l@&XQ1gx8$s0+jbhfV{e6I= ztK1?>8?fUL8&c4a-ga;o>j`_{FABr58c!4l{l!H>JY#J!2Jy`foMn9gv$h;ti%owk zV@pE2G<;6-)-Zh6KcA9`OA1M8#DT0{g|uIdee8Coa|v3tV3*Pa72ELNe_p4NG0jO#c9(1 zVGzH7F3kMxbSLM?E|W7UA3HzMM-haL<0cqCSBNMORENP{im z=wk5_P_e_#$d9#HH0||}^qcQYI4ndm zIxL*i%*d@uA~;ZhNKm8k}2pfE|lOkHDP((Dvi*<7A$n1VEH=R*b^orB{?3|Zy^kP`4_Xl{ELGLC1hv%=~ z6KojZ%KLxXjWov^+&%;=;PmTpyaP!m zR1`Y*uKH7aav%d^Ep0C==KSvNGA`$YeT%nTjc*YdkTL^z^{hfnjVYbKEF@vh^4@b2 zZ+y(e;DtN$N|3cfWv)mvWs=(5_2eC+81|q%SjM8A4b`Yo9yxE7RMw1ix(0(_ta%9tP`&eOIaJ2Dha;T(dja{O8Mmm!u5;L_ANPx&qFsfTaT--R9x-HNShYENfBtk zwP;lSVSqDRmaemk4f7o3ZxLjo>lO+;{LcQJP{w2Yh1_#qce>{YHHTi5QP2jZ6k+>s z)*|LY7WziZ{Y=ql7SEjIv2>fzs1`&qAU$=trS#T>2kQ>(OVVik2>AGOth7Zp)Sn_& zw@S;*HK(iv?kF{@4mbV(-@PSZKX%A6x#ZPJJA&`6N^mz?BMw0K!&t>QW3aMu>8uea zYAg_oxnmrc4Yw94D{YH`*Z~ePP;Id+H7M)wj!jmCiGlzZ=d{CL+XiZG`eQqxop(1W zu$9gSSB9@vP#)uOipFOnsX%p^FaOX&|M64ij-!#S`|}*~L0IQ1{(8D|jM(v3PWJOm zhAvd)keyv;*SdCBN*W^Vzt^HT+bk6pI zlxKmDGdWRuei_+mH@8O_nu+!f6ZyBbQ&qAB8OGb23VFd5JUc!{TSl|t{IF&?~df_Q^C1_sc>0zdDA1Z8%|pEbLA`;Db+OEtK}x zdITeD0D24)v|0eWRB_QdWe&1n+Xnj+OffgpF`AdI`xEM@s>k!LOy>X$2GAYlJ$07; z!b%PinHKCQSA^Jfp7(Il`r~&4s#SAiccag~Zw*AR>XQ+*Lnx(&K4qoK(w2@u*(6D3 zWHW}w&X&L1*cc0!2QCA&LUDE&Wk+z2R zk1bsmFi!TmSc`kmA$F+7*K0cbMIibxiHlrK4#4*S|lP zi}A2-TCA%Uux~KnV7%#Yx#n930T5HMew%+vDo?hba#puvk0syOUxkQwSo;9wWwIth z7-Sg#vR_GElC$G;Pe!98xN0lh_U1=Sk1%oR7q%f`Zk{HGU{k~OOWs8 zD3IP$QL!!49i@t*CpcgX;`+pKFxVx6Sihr}ezZ>_q(L43kslxn#+w5gk={$x9ZjhM zBf&*WWnE^KIL)rD=MyOhvO4ugXx@c#a-(7xY5XJTc-UwoS>TXXt| z5ZON(NP!5m3;C_HDk@*GYaC|=(s99k3*|ux!pWU;mEkwI<1%43`E&1_oTh%iH-Jrr znKm<)H2dy|OwTsJ>-`{CIJB>eAD{*lN|ASSaB*kxtyNV=k{t=^j_34JppVPC;WRQ<$_A5e~0p$rg2_ zVXLehPiW8+J46B{wui^UZVyUyZ)2dK|8cG>Cq>b+laF$_PC<^9BXUel;qC<2r2y6F zkQ_&|1qc|J#;rchYY-!+z4iU*+>=?GoI)BLbdr%JP*|4?r_7a26||7C=*ywj7SBBNyzPtmCjA2YC`jGyAl&!S}UZ*9;d>f<>lOZ-! z1L!M$qcWxDz{_*!69i;A(Bx78oN!h0Juq>g(+j+mSAEBddbSqf4!$b`ON}ep-e=o8 zM%g~SsaH@j*_MB_Ow0o606*fb0{4G^+%qgnIf+FXc$2vy5^O3`q95^FRJ`iBc07sk z3zB;7WTvW$%3x#F&8gzuuFb3+xki@yF_)Rc@Oi{T@k-H=zFagmVwO`t?9vsQuXlU2 zxT4$hc{UMd3I)96#+-CgB3Banh%6(Ka=khIj=O7;0ro)+n6-}Fx`qVf4M{kd*^JC!_3 zm8b^AZo|9SXVy^fmi@~KbqoYlb?^MWRomfu9TZ++U)=6I{b%xGOaRg`#m%tC^p^us zBS&{b8T>HAz3jOmKn%-iZm0kH3eQ^~nbM+MHbcfYSQ4<*{EW9!LM<5l7DdkqxILL@ zcy<#QtD-O!W$-l*+>)6L?CK3(%0(}-lufT>jzC>}JGSiIXJN6OzDu46L*M$wT(~Y9 zxrT(58c6*gE~WfdXPotDGHTf~Kto>+YB0$dQGX!D$bc!IV$vI2xgmEAsFa-M1Q3qv zNWAc)y^qI0P4_KAe%4qmu63KmrT~;Ug;OCp+sOlAI30O4Xv$_rSSkUzo;gSes=v#Dm|!|h{&u@`jw8G8t@EdmW$s- z&24ZmzZsb2fYVxkZeY9h?P0D!4{u9RqJGi(=DefE>#i1WRA9TrQzj$3Amtxq%}!)< zbtdq8zjV1y_x4R%)7Q^~Kfd<=AO=+-$o9e5jYPxxOjrHJzzH(&!@;5SkT^x(qBO0O z(%{%&KaVBlpkp`N2+B5Tr{8e9=9X!6^ibWm3iCeea1$yOJmcS5r%Ty5U~Q2Ih9)U~ zE^6@2Z&#Vp{0of6$i+YKW1||yqn5*0K zDYFl)q8L5GF-LHy6!tm7Sj86{#;O-zn_^z9)>6L&q@7tx30gaeR^w;d3ZUH<8*9Jt zwMzE7+lVYm)}8)vN91LZ<)-dYSfQ4n^_RJ%2{1OVvGyI6Ta_xyxiSCsGr#{zPLdC% zUo@iMB%*yqHvPUK<*w=QW&xzeqP&mS6Q1P6X^5Va1dH&f4rki>GcG71%{1Cj7m)so zrR*`a{`xU1loI<6tl7#%ItP^u{PvD{AHqdva%s8eK|#Npgk2$|0{fQf-%r^YTEqPr{M6sFqy(+8K~XV|;)6kKl+JXBfTtwdQ|V$^rP z6kg6GXV|8c?O&Q!K6?43w(hjUR_NOMjG@aTXD}X>cKeJft8mSqA@--QxMdFQH89MV zL#2|m;Wjkk|1kKYjTpExy-=5^cQYsCzD+x9*#;RzP&pf>D$*O^YSb(kyDq@_)FU^1nC*4b{GmjmHLl;>MlAM%&p3E`K>H<2i4c(%0C>r2L$^E6DNE<6p zHTqz}2#RGsa zOMDo2s)=ByRuGIEQpXVinf@5C@u$Pld4|_CJQWai9g@OjgnL`<#p${X5Kud-67W+a*-8`Q*`kc&WN3;sglF7v`rr_YR z^*B&5x9g7Od^CPf;8{p?W5T4M%zbHAzp|SLmZh`JN^to{rF*W{LmOGKPTY0re%OI$ z%HgNqA|5jwM0jcDLRQMjHF0QjX|v!@jArJO3>VEGzCT(pfHrH*bwwgZB-{I6wc9ls zgdcSs3HX!V?Fim~P4sAN&@MC5oqf-_qntrqS}|B+m+~-Iww>{Cpi#!tjK+&Pp`&o| zOwmu7I_Z)Ra68; za?OhEp$hUjy}|X?-EtE&CUBNIuI6801j0bxru|D>>ilaXXYN2h6cV5MXPTMGaMH?z z8VU6UW|{&JRP|++M!XZD=FcCI(vzs*!r5kbymk|>&+}H1NBk0XRc(8%jlvDf_n(Ec zJkOH%06A|pSgd+LGYNlj%1hp9ULl7be8J$w*Q=Ve(V@LY=Vf*lGkk@|RR_0tXFX`I z0kU}cHj!1*EP5tBlxpHaeNsw#(Cnq`UAJJa?rrU-g;hY{k+QsPZ+2j@{c;2A9YUBk zP*VX-1&=3}Iz!J@$#sU*=-SbmBfS#EFX!%}r*5ENt|k_Y^vLme>7xT8B==B{F3i%H z(*Dc*?7ZQ<@Ty?(eFu3Sj!qRu*q%X(R~{!Y8-IUw zb6HX`OMWUO&Htfg%I&kN(C6I)Km1hU4S|aFSW*(=OXsQjQ{m~#(&YJ9+<`e9&66gE z@{c76YU6Q5MN(jTo%UTsdH}6j?>Y-Vm*WU-!~GG}Z)6)k-bDjGJ0wk>JRlC-{HlU2 z>2~J#U5S5zME6?yV@mTfxs_zZ{y{{_pI-2Ae$VjxIkb1j$(xp+qHq%yQ2i`Qhuro_ z*ilf9B>|_x9AYNw%Aeg}U?0yfV|ZWAz+G%0tGj4DcSC~g1@m7~WWg65)@U>zp-=$q zO%IlApO6Dr+}O@M?C>vIGTnXbU3zq8k`x6XsZ-00;S7J-6P0fT`?d<*m6#9n9QWFn znja{$A`dfe>-R*9YFeJuDgW9m2~y~_X0u&?|9+?hP}+DHC@X|&e#VN^ZdaT|U#ua6hw77)gB?R9pdY>UJ@GaF=RA^&S0 zL%?9HXc9=ypfPsD@aX}e)X7!*GcWXBr&JEfq-@xxo_51 z{h5b!(NiJJri%Mk8|5G~xFo2K)NCqnie2|4T3&S{H$hUmPemorStPCL3N-B6bEI38 zWZY<1Y5J{62%ZqCwUpgyV{mN<7^-Z4cycgN`4TJ0zJslTMf`Y|8-rRG!lg8H;Mg=t z|1eNlDB9KXiVIDtGhzFEipQgE;ti4r+oZ>4$57enXBLh%%Cp+!U?_P6gvCOUt76m)^{>;9WC}F3XCfAl0&G6VUPU zQe`W*-Yy&)FK5gy&X|IN&Iz851#jLe=&V1Y5ltj)#;n@(7{^M&~76#341y_SZZ|du?;3IaJG0AGx zLjh4TE$=hybs@@Vq~r!_AE-vQ<898uyF!jb(S)h8z3xt`_U;6hlIYI#_ibV}7=69^ z^+Bon6aTW0aO=*zpry0p=nFs9N0Q`PdvX-g<&nZUNYWafuwkp!GRH(XCxcJ!c4Amd9l)QL~HQeo0#19($T^Izd--WG>$`>qyS?IPkI>HHsi8~Cv%9% zAv>~bJ1&z@NJ2%SIt(JqsT|45@j^vBY;rD0M0QTyxt>XgFv%IM4>Nm5h!o&}1(!yg zUEbLnBHZt)R(u{Ii=yd?3wKf#QXWrcqA=^`!$J3d{vZpp@g+M=(-i;{!-vRHzU3ct zbK^tC>f%YFwfvqVhA)haFD6p}atsWiRDhu&GZ6n4^Dde94QzSDV9?RBwKQvqx%yA*rW2W9Ic{j(m(kDnFgEi9p&EJIL~OAWvHGL-ov zB~QtLX%KX+n|p3lV8`dkg_(oj&r+-RVa)ioti5WufFShc{TkoAzzURVzM`4GrBKZ(8<0A!$^$TiL7G5PwGY3p_6bi&$v@ zBOgp1O7|JIDr(76->fWJ8p$H+9zlyQCYIdivBK~0)as_x_{4otrvvcM03Iq@_`Nh+ zMnLtdlF1G`jE;A&246MuNm=+F`#7uUK4~7U2BH6XLdPw260-J>LwqwJALBh4jUuAn z1do@OiF~gtF|G4YwN~V6i8HRA-mT$u#e3W_Wp^p`R}o`}?(Jd&;LI9nJb6vS9(%d65dB!Xma#l!7+GE!dq zv{8mRfyqi^BwroX zZ0>zry5kmlxn`vQ66XI~iY;{Z+J5X| z(Fbpr))*zXG<$JaoDNB)kb$!xKK}Y(D3DhbFBF4C&JyT5qD3UZm?A+^8l^7D zNPQ7B(^&UveOJpiL)PSCv-j<3U%)$&c_gYS%|C-=dW^l3$}SF!k*Wcly)?lJBTBEq zmRa8$;5X^rUjecxWU^8|#40M+2jyNDKIm%NEi$2zcZ3IM3!P#dKbin5&R6=N4E^_J zf6de*qjl;~w^Kb(%iKu@WYA<*q0N7hSrK>K>5qihZ(>~ch)ifgM0kT_sWlh0hf0#p z_=9(mIw1UoN?zjgx2>b6mQBy&JO!vWt`K$|Sqhh-txIb%24q;WI(q(OHP9Nc2r5}f7GCKiU zMz3`gJLv|RW`=FD==QEleyj*LX&HttU1VGqvQ{nIn{o=IL#c9ZmzU-r3=m?GWQpy} zDhAVFKfTJ8^U(IX-i8YLfRiWX7E`+ZXWcJ?yodR`KAM8{!t9D9#ZhW~dyo3BmwIjF z;@j*7{5za!%q*lmRkHfGj_qPvVhbC2B*G>L=>SUD~f{Z`R)wPJJN(ygbUC zib+t(#i2}!;$r|8xCS~mdVw(=XDsde`$K*2wo(wKCpJP5#Fa{pK7Q&6aVmy93N3Qt z_nSk9My|x}eCFhHLSW?7(xFkep$Bbx=6K-{?i;z+uU|;70V;8#2`3{JB>41f>Rf1L1haf( z<>fClaHwc9C~1tu76B6K-`L%Mh{X7gm?FP%1=w)Im*-zKzO%?X(vrMFIMp0xee#T1 zY;#cwa&iRW4569@afpuJdYsujn71#@r+7vj)O4woeHB>@qMBWw^u*`De;rN_$$t_w z5w$0AY~nsche09+xq{+jmce@=pxC~jKqHn0-ltq*Iv3$lw+uV1$W{aA*By&f-L^{- zOQRxpyODm|Cd)ruRkj)bW@ZaI5@;?C+6iRXr~j_Z2st3aE@(nsFyDw85;6xh^f9^= zJwCI|OOJX5#y|z92LxixUaLCe5IQA0h2_VFkhb7&NKH-jjL(|8O#8jt#cX6)f{Mt^ zWy_p&fKh>sA)%9sOWV1TxQXasd3jn|yu@%1p$|tDHcd9;nfi|ks`kduX18-hLQ2{R z7>DpbNMM6%AWJn?QtA^kclb~#}gx@DP{!xMiTwVB3#HzWQw7~H&debGqHzAUs00m zJp_yT_3o9=x(A1q)GECGvkUf&&YgxH3v`e>O8NYk0g)Mlmc-vJEkoToGm&k1W+M9C zFbf+cIYA%eo^^sw-k-r>{j5>KB#b=q3Jn<y@4E{6jC5CF!aE`>psmvAZmg>e6Po z+VQtKG4=`@j$9LwM@}Nzl`MDaWiv8;KF(4W7Q(gnCg@5bA6!VoC5YC_lq86^pyPoH zyMyLp#jPf94c=L2VcXhVnFvf4EaI6}=?OD7dTkd*;)2lg<&|mwQ>=PY(Zh6fSsSt4 z4vr<^;oi?*s%+_hlvwbm8dOej>&wF-4h+I4D9WvB?T4a8b{CRSn7q=cyP=- z>6mp|hI50Q2U z#fWI=hy?$-Watvl#1(I?27>DijBC`~E+D75u9bbpwY@|6AT1##Bjsr+C{PnFj~@Pj zU!cUL^;(uJuYgrkhRfa1t9mLIxDs}MgM76WE!R}z5AP7+L67bnGACntIn*3 zN$WvairxXq?WG}{xBtlxz0e8xvd?do+a8`~d_o+Un3C7cO2!~ZFN z(=zFt9)xmeJm$vCwm|urhisUNSa>y+k5RCF-2~2~901k)8xzr3VyC#l!g3rgP}s8? zc)7%wxVpHW?cFGL%*t|xZAk^Jm%Vgx=d7OMC9Y(wnefwsahe#J@qe$d>BG=HqQ|MR zJ;z3GlaDda{?AeaLw|p1_5h;)-sLfWZpAG_t`bGeQFk7cSAd3<6uV$XL8;OpyJr#6 zOPpa^+Xt3ukwp_)E_siV#&0u}2VRQr%ZGI>b{Stz>=Zptji$J{H64?M8zK&3mwKN5 z<}0?ZV}!W(F4<-;3=fRF)h!=P6x-=To7IQjnqz$mP3uugk6IQgIhMSj9BX~uj_6U| zIj~>CROP2Zt?tmquN9A52FQL>knST(DMXqc`@Tobz>wn#itwe0>w1!8n~M{Y9nO=t zlH7VW%(ovHvYl9lkF|EP?zFzTj~Zs7_%kX|I_RqQH?V$i=Ck!^mhI__mHO^1mzpf( ze&9e!=V2@f_V@IM2iz9dms3L`I<79-&*_6Nav8f=`9urlSksg_X==Y$Lo8=s#>I8% z${ITs=v|EjiKR>U7Fb+iBY3C_iyBvejdzPIRl+{4WjM6xa<_zI03OW{S~dZLuteaSfxV}%Jx#@D*0w*eR(Ma1PXTsVNq zXod&!5iU+Xmx92n6+6HCJlDf9&DNVx$XSk$()ngVWbK)SIo)3?t0_>(YzM&S_i0h_ z)`2q~Z&FNM8_nm=jS@?u@qmH`U=6_+R(YYTrK(9`5Qbl3Js=~kUh|jxfjq*3i{uJTgCXLI}!edJ-ha$$qKtrYBwwevf9^{jU{H3d7(Ie63-}H01Whbz>`OXA0sf}@*aSFT>A!=J%HRtwfqA@Oud=(C=TE%8AT;D&E)Of;$7gRZs3)}y66GuvhGZ%hAZQ!*mf9T0@sG|P;g#vPkWi-_Y|-Prx%x71ol z;-3&F~7P>+p>2)OjLL(sjXbe`vR=&a{ua>7ij628J? zRpWb~o(nZ|mbzq};c`Ja?Hz$?5F$*bt8obv;Rs;39uy*J@tqX0mCI|&GgIYeEHpjm z2t-b+wN6p4zaUJ~hybcGbNi>f|1L_|+hwygUtM1uLN@fZUv>V3-eDxA%Q{Nxv2@d}!S@6u1z!Ym^ryOCu^2>10&v=JyXdt*Q+>%@Aq9zpAskwi zQuW>$HA?RhH^nDX2SrLgPAd9NYaqwLrvF@PN==_{i(4nvXey29KAI(oagwT5_wt|5 ziaJS1TV;j7O!0B@xX!lAT@s}E7>mMtzHq4F{cL*y82GcGDI;#9$?jr%J;tp!sbz4s7Oyn6=Y`!%4$Jbdu07}&V` zqu|M4W|NR;__M2(Rf(M$Gi~s`Q*%1*Kt&J|l092^|@?YgUGKDAi)D2vm z+yKP1myAW<%PRDMqYjdk*pdus&iPGpt7R7ZzS}EE>gIlI0qg{cZRvAA(=1CaH$U$X zm9D={MUl&;(6$YOz@szassk;0$Ox{{8n5;5CN$7?$*17@9pUw~>ti*0gM^|L$Gt?W zASu!X|WL|>BWLiTAwkZ`gb zS3YkQ(8W zMil5}f_b^_yU)FM_+zNS8a8~>w&Z=YHh0n~^h=sFrx^HqOd@{JRYmfzsA<4^%Dr{b zhR%(qK1ywxbfwYVmoKN&hZk*V&`*Ex`b6pNVL+?|p~eIt-%5=R((#kH7UiJaZ8AV+ zSsG6w#8|X}UgIesjmjiBa9TQ+_U*G$T9U0mw-L*iKh%|ijs|t8;t(tGCY^?Y%=$}qh(OuY4#SEbY z1yD$OCa8qrW)*sw{(zO+R@Y3w_bQ1vo5T*aSQ2~VJf$dKz&a0Uduc7ymZc5qVAY?M zIX$QNO+d~k@x-#|_NQA)&;do0`u{Zu>6+~P>?7mC7)?_Z#%3KWKl3S&PU27`RqEo) zTnEKPe_}7yEid||5r!|PE<`Y-S}(U65#M1+(stp5TIQ9AS*?yPIAt?~I*I7e|GLtp zw~Fuvx^-sRtlA3f9SJ`vRfG$7r0!i*!kps&t!^CRN^i1Q%ol-h>n-#q9yB4#NbcYd z5sZug>yNXlR6~}oGPU!jW{P(GEe-V#{pM`ziooSAD>$rPm4xVmRI!H6Px_D4(2*unk3@% z=3?sV?x-o(KvZ3lk!)&oz(X;oa_Xe?10)2d)7Rh+AgZqk)NWdyftch|5jAS?1>?(S zXTF4c=bQi+2xAKum1cvv4wII$;C+T>WWNe~vFCxfm08U#_h4cv8DhDwa@DkL%i^#8krp2-p2=9F~b zoMP)jMLnjXbKpINJt^-~n1Y>Q7N9(0X_ z5xoTM&;2fDsE)O=iLdE7}v{8hMYnOiOhha^c&Haf$s&=Y-cBmg*NtXi5h29Vi|$oAji)3V z%QM)xJp{o-!~$bJ+vSn9AUW{U!4yLsNXOH7@9E;w;NmNeDW?uA-lTw9a>uqLKlPZ_ zhjVok4H*Ve%W8(%@=v@U8?>1eDs{Edprq}?U8v{yR^L0m6dq=&Cc{?WXpCdG0*^$G06wAqr;I zQXy%Aso}{9yWYe7f?S^7&y!5KBURz z|MgfWSb0be8^C{vLVbS$w~Ox=@>XW+rUM}hWc2y`8_(MVGN0uEhNvnA`$8`5ql#u_ z*fq5nrNQ+H{Ncuu^UXZJloixtG5GH;GW4`v5@i~xkJaHqg>vrLPhV(K%sbiFJ14ew z!|9(zly8ioB3IOgt@N7B{QpkMNXjgWr-Y&ha7V{DQ{JdQTjz9Co-1OShXmGR9$0B z4bn`(zVsEwp8&P177!}Ux=V;(ae2Dwm6Fra_-Qxy?jlsn?_lWkJGY+O@A1vt%Ei6C z);}Ao+r5$bmpXec940dy0FwGB_~W3?0nm@|Z4pE!o5E$+a6qLRNQ%ai1yP-B-YS8K zZ*cPeZZNA8)vJh~($-XnHdw-Q7aY_c~cLI-0QE9;Myq^I4WWYN227 z%~Ja1@duG%L-YpQMt!$oc&y|BEY9&vr@x27d6S0kZlCOb)rZ+wHr!pyZL=F5`#U>* z`rF0@`dN{sM7VY&CrRY1sBX1qSRqu9F9tq(G6do6s?y=~wOsEl3ssbpX0Z z^)Z8d(2(V)Jvrg&-Lc)P15q6-!^#?k@1=FEOQa1si#vCDt*mHMIZvl+swfPBAdy>C zRat*p+qm=SOxZ_wgFiz`f#A|wQYKZ=BynX4!1wN^P0aYGAr&5gwc;27XjS2l$~Myf zBkC*MqS)WJ5otj|8UzH1rD2iohNYJUq>*lDtkSrb(4NM-+BNGHenYxf#wrR0#wk9w-MuFp zOR1whJw+QWml=b}Q=)ttVqRJa>wJHdy+7B!JQzN+=F21TUupDUJ7u>2z0ED%-v@sA zuV@KW&5%y$drqaZajgUF4{KH?1U(yXn zn+iHsdUQJuOg{S=d8<9$9;-@GKd2Wnt)WFI-tL5D+DLQ)wAxeQa#n>vt&B33!jh8? z=TC=9_gQI8(_W1}?SW%hgv>q;!NnVe9}4}W+3}DRF+(b#iAnapRVjHMldcfcb0+Ld= z!&jORQ^f>(LC=3x%we~sWnBTDC3L$S{=~{CF1W3U>nZnr>uBswzNd7zW7VnSA#u&T zJ!h_^%)IO8OCK_E$ z`O5D1jmIf(^yb2bv`>#;eN*u7(-jd(6wz!}%e9 z*A&7D#fk0T-=XVJ@lfNv>Gv&#HL^xgg}6e^tnn4(_A92wLk%jPDv*%viUCSzLtCKWn zj$L_9oOuOt31W8+!SCO3_tlr(-oAL2%lg@P`62#*<}01@0b+etfh_y5n{oiTy;PAL zl(gcjPh{!Xd)cKjI$}qqk=>Ro?t)8lP^Z6!q3!g;x<&KBR$hPq398&B;FbQPArN>P3EJM$NeT4ztpXDXJ+iwbg0+9x;Nf zw5+~)sPpRL6-a~-D@U@iHo_>5%#~zx#RY~>Y#Sdf8i>X&=LI2qBJ6{ltR;Q+G-YUd zJ&|Ad(u(&SYEppw8JVhTC3}?od{vOFxsnpNx=BB!3)cWs-piMfMvT_W5RQCx&e!rgE5Md3?1sx;k6P_k@u8lp) zC=yhmLn-4XZu(H4;Vw%PAa`E8C5nj(oLOrt??n?Qy#Jop!?Vt8&$36$gnx>{7^l^BKMpkfY#(;?bbe&5&|_rI z3-gHJ`o>UqWaFK&_k|+wy6{PLa{m3k`rD(m_gXD(eb6Piqg4ygFt5>5mu%7?esyQr z7*uik?&HF7c@+1-tjrxisP-EWQfJT(=!#~0gV(5UmUO6X>y%2xaJ1-| z_L#&p;O6rJC~|V`*RLTLQEG;zyIg>X3rmNfqT8n<-h^Z>Jaw*szbjGIvG$kfPuXg< zG(d%I)jfFHG%Vkdex7P`xRy#S9r@X6BHG9U9&9TJDGBN!lt>gveVZhe^;5+~U}8G; z^SFJwv#se{DFi+Jb}{605ZHg!)wtF=5<%){6ZK;@@4K^Lgy&6!cP0OAS@Us^#45lk z`r2;c3ntyPB1vE#`qgHa3h>I0D;B7oQ9cT>J|rf(Q?nb2%nGoAjFe|bmM`^o1wIOS z%d^0NphJ6UI}ZWZy>YD170P7KpU}a4MH-!2NoD$2NX1ZHo+XTutU+?oiHDQsXf2H3 z_Q=E6aA723Ty;#abwkZ$qUC4Q^8-E>W0FE6&VPYF{#0qyf%%PdgDG{^aHDrn`qLSS z*Zf<^yH6ZM+SuGHUE~Ik0s_7iB~~^}F=Tp5x#td8q!Okl4`y@7PLqZn(?5AJuBTWn z^!kGuyJ+Eu-yPyjE8NvjH=GtGX-o>Zt7ubYTZ5WEJ?yBaS+9H&u=BpV*T~CccRxRn z#~tO@Vdf!D#~sCx5T!ENY#HA(G}71`5nT!&i4?d#!u*P>pzkmZEdxtbypW@J{9^A$ zmJ%#G3$1}$@-yamBnn$YHRW)^$ub07Rgn*?wz6p!3O|vbzdxc8kH&c@5l8Ty4vG2h z7b12l*ou;;h7pz?xs$gm_m1*Uvjr*rDoa3O(MLw&LY^&5C5LL!yv)zgD{Ax=V-!Gm z4tM|uOw&Fzxz~L~V={%M zRdvLB=wfqBe0+b6x%L1iPz1l;SMOxh2-=KhyMX%qAvz>n&hBf++<5glZDLYiXbCKn zt)|@E`o1!X#DwY*xnsDv{}_K$kX^gvyKP-2gJiH`HHeG^O(n4d*qQgQN|FAki5!6} zr?oz!5*)cTJbpO29-`~`Y$0*yJi0E8GSQ@g|1;6ZO`vDpNf76R3q)NYSwfEN3(;9a zHF%|58}fNK)#6MB*?=ONn9qH)2osa$c4S}r>Pkc9dCP1z@cC!`8OK;&Q)NDVuvZ_k z?wsHox)=#z=YlJ6xdEv&;zkv$zv(A7o(hyL!={9;xc9Sl3u~c31pgTD2|j_Rb@+%PBAByScYeSoWYp-WsPr(`#K9)$#RR zNXk^b+&7)@H{CHHH1lCyhuM;fo!9i}$*u~P7s{?h?x@x1IR^L_9#`*i{A~X@xpNuH z^Yi+?yHlxxex=Ce`bQwdhhQD*Gl-0I4!>ZzQ9c%Rx=L#bDozGZ`*tEjAw-JCHB%Om zS@P)Tl}2qxpzU(S*u?{1)sbN5Mv#lhRb`3urB7U&4a%ry+>QbFu<8IBXVkEC z^!Oz+J5gR+D0_A?xial{<$-ZImW#pSgM}4nsAl3Fh#XP1R89^S%#&A#@NJBoMY5g( z31`mprK7$VSGKhU+9s3=pL~AL-(zl+T4gCABfJd_a!v#gV;c`Y#>i<-ibOhBs&6cm zC9m=JEtK;*4pE%D0qg%b_gq*R_2TGUQLdD7T&Y{dFUjJBWYMjVcQTj69n8MJdrbbp zXeL1^-LlFeBr(fMRzNg^w?$EGU^5-wpA`7jUB#@MAa--YhFP@F?jT9?@q!WVSHZy> zJ*H;@YbOh>wVleXnP;b``K);T&VCz#(Vx-IizGged2hs$>q~Bp#sJfgYDUehdHB*P z{Utn{T642b2Q{2*ADsd8_iJdICN*fQncfrg`*H?U*MPQiUM;tzG9h0)(iPUH?=03H z>#+=x5k_WHQ1{F`3drg4Cyg*TuFphg9}6t*-o~zlzkg$l#ju*>HP|!alG4EXnphioGsYSAo4KWy&{vUXGnvz1RVBvDVxncIxie{(G!j z^jSYlG6&gadB&|`rocTa5YJQy1`O6v@kW~u)=dd|H~yiIu%JUX{OT1rgyn)DK^MfY zy8LH~G(L8|`TS4(;Z5k;H0;LsZpW5oW+=x~tHfv}Q+?^J^zRMTXRU3+aD` zMg311-IT-6PlW^i?Y{YfE6oKZHa-yMc{gr9YJ&05Z(jVW)Ft;6c z4;H&anGTYoN#-&V8d%A~W|d59>4%}+V38LLCuKf^_BG%3 z)l=fRZaKNUY8h?cRi}5a4Fk6ORSK^++N0rRR)5E3x^X9!Cj6C1fjeN$k|mz8xul9w z8-$qcDDE+J@)v{3R??&fEc+zp_8k!C8(36&-&dl<_2l|+t{S99+}kYi%U3p8-FYsz z&({3i{@8<4wEkxg_UFoP%IH2yIkt-N(fc%<>QcWG;7On`&9-b)#uR=TF#fgDl^YzYqWXb>7r+6?#hTLAUXHHnh^F z%(MK%7`=mC6W4GVn{g^k?qCXov{8vw3-8d;6&hT~vz|G7`i0O}>r>t`ZJYo=SZKba zQ?m13rPsMb@o8o&po^qnudtE+HWK(Ll@7s!&^ zdcLW~1HIzKBPuTaK-Yx<7htCdi8Mh{SYbU*nulHqM`s%me~ti77pxfCBYewnUVZgn z9BXZyd%^l-Kaxb^CNb>l&qHIdQ*#4If`uCLdrXm!iyyRU2fsxbS)OntOkQ zUrg#~V9x2wLJGO`6AZ)TC@pM#xgY&nXcx+RJB{yj6sYBuhMAl!)#d>j5$#_a7yH;9;9U}}7HtM$$Z6}3NyI{cM zl#g(fQziEUvaXoi9;V63+DD}A4o&S09Al^=n$l(WB$;Q`ii5yec|Z&h(-6qqkW3y2 z%8@`{3Q6K6lff0n7a+T0rmhG_CKm9n?7Hnm>N<#2?8$*kYNUf59 z${EF~(~ZPycU^(K%UokSv^LMe+=@zCMa9bsOn>9nCXB%S}54R?XEygN9vkjJMa)fy=v@61u z?E7s#vD}*hSc}KJy5pJi9xn!!EETsJ30SQIT{2onSBPCk%=zpXYLZ*XaNA|D==le7 z*v}B24IpYTe&1csLD|m=z5xO5XcFGHIJ`EGE9R4=(##*f+2UY3K=>UJ>Q5Z=zn>Nb z9_>b~blW<+oGmd-cz1&Xe%EiLJvsJELR32cK6kpoQrto!DpkOjD0{c20nhO$$49t=W~RuDJyn3j`Bfi3U~=2O=XjRxM``}7t2p9Zs3HF ziP3x|XwXx1cIul&XPp}tGmMa7@oAW&6Q5pwJ|=GcQc-e_sbSqGTA|9 zz3VH_tZI>AWRE`2-oFKRB6N?DunkpV=-`_66FJ!Msf80DhY-s zMub%7@Ul3!+#3tTJZ47rY7U+SsGRae_eZP<(xwhF%`mh~ zH#eD%s*lsU6cB^=_ALeGWlgUb?`I~e2d)bU)HaOB&ZW{>P51gO&Amq{RpjVqW z>H)mI!@H~Q#3N#Mh**-4PgIKt%a%$cCl&uvGM?p_0EdAZ>I zzk7q@-@lt4nN_2;j#-i=o`Ll?WzheAPdPVptZ7J2x>NReGA~XmkuZa?x{xZe8c6bp zncPs#lb1*ewbzYT58uS!d}n0%ovc)xg@M8bU2M-16CTD|!}Z+EXWKMkj>v|c;w+0m z5ckbMnZPFTgz@w0p!@JCxUE$a(15a9!=ZHPs{G(UV0-S{tTKy#{(Tat9xh}WT!#*e zd}KsG@q($wAnnkp&IA*NTS}XxrMqxA#MQGSEi6UZMK=NJJ$dSF+H>f9RzRS>(n>w{ z&cA1bc)YHMn|4$Yw=&jW#q2^lAOP-r@v$>JD|c_?#VoUO_m`RrX2=X zdCXM=Nzx@{%BfNM1|27jaBE*bV1hW~^tx&6ypvry z1>TdNG!MJ2d3aH)=j|3=`hJ_6j&D0JjNMIQ{cDA{8kES81>J>`fwK0iSL-w6d(m1H z;ub06m%+!NQa2W2}G%U+kk<|AaKlCDoCpvz_~vg}M9d5$++bVgJoa74u8U zHt_!kTF$37&yvTEou$h)J8chdXgIX*VNI7R#G%>$BOT?s(ph2ZV6iK#I$wh~)54fU zvPm^RZ5u!Ar~%DD^@WpIav;;n{IjpoJrI2BY7Kl5F>|NlsT(prrgI_ns*B zx;y1R_WEh0O{EhVb||fb_d<}Q4k=MED#!Pjsn$Jd4wTHPWM|Y={o*J(WYcZBjCD8$ zI&4kSK6orknXG9iJ;xUb#6?{t9#pT2w5TxH`TSrGLbQK9nu(!~g575tY=o@@+t zuU|r(Dg?kvmnBLBxbp`DnPL>=In;xt^woIF$(&>qN>ydBfiLA3ZC~n`SK~@xkiY)G zFPu_eSvqFJc3+|x(BL1_w^e|IL8_z)63i!zqi*MI z*QmUmf?s_0VoL)v$0yy|yF18mM_I)9w5JIg{L` zbiz~8!|xphgbB2TUAcE5f|drNI26U?OxQ&p^~*=V2fSCD-Jex@(O{8#SL@tZUqC$0i3QDa zkw;0d?>CIN{|kWHAa9amk4~z_JE)@#p{J@=>1FJa7gy_Vv>VmRmni_gi*jajOOeM7 zUbwQF%~Z3QZgbItQ|fqM)?(YSkQ*$}vv?GY&Wa|oD}Io>IloskIp7H+*yF>)jjL>J zt-YIYL3XA55~(~}84B_VSZU^LM*Y-WEFdB0J0mf{sVb93Ciw>=mjj>Gc>2+)!Aq)vK(PyhiZduk&#mL-SDC*S`b&=mfFk zlj`v+H=yC{ZZYT3d|vUrmj3;+yUtq%90PtfGLXt67*_$3sv-2bRUB9Gj``fCRQ@OC zFlay`+``rN$c*z!wvwRUqgc5-;0*c5ARY0N(|g4vsvzi`<77EP@OP!*w->B3ng+{v zCAD>dyvt>mU%bh!*wY{x)uX!B?2T9D(^D-vSIla$-L5HxYssl;X*|9q?@i6eMTP<0 zt^)0BO<((Ur9~^)s&%fViQMSFT0+Q{ALEf$ab1S-RQY){tWHYTd3|eKLE^rSp{?V| zyYFE+kuc1mQ4gnf_1pJXz&_{OH-29G``N-9%Gy}HLH2J~K5?EOd#@Jkt1{WXyBVZ9 zLX<|zEOVM`N|tTI=2FqvVq)c)tQtsk-!pk0IT!w;_$2oEa#9Xt*}r0Zc~k&!&Xd&k za31mdCv4^HD{bLj`L-`f?tV=>K)Br&zGLYI6#ip_-@|?%67kJMJY{=U*!OBoSqJ-( zK69lN2_*=AnU`;IUSd@;w}(i=oBIgjw|Ylp-17WDc6~v5_bzz=>#Qi8$U5*&JiyOe zQ6bBftxKc3@Jxte=~F`iN0O|C8!u13CH?##uy2EhSAMDZ^ly z!s*2Em;mFb==|wQkj;^7q2RKAYug>Z-Fp1{{lfDd8P?CqTf^s;?{oVu#KlH$+@6}V zl|HyqU0rR6UG-x_^L3yfET;Bd*_%MjL|Re#C>6Y z{RTj-GY)vG{mvlPZZ*iPt*?DEkRmTgV(}Mun|hmxbBFSRWb?|gZso4;9VKJHVRg(l z)S&FKeESa^c~@$-3Y?km^k+_#^kOX9$Bcbq66@9V^K>diC@VdhQ5A;8oSHJE z3gs_3tW{P$D4f131B|nORQ%Kop)o(e)}_Wc_}ZNI`NyL7em`<2vgS9wW0pGBamz@A z#x;3P=W(IZE*|Uu9%Ep>Pw!wLnqm5(P}D+DuRNPw*gKgc!Uhdmrec8XBfTOBp`H9& z)p=|tJDLG=;x~^FFEs_C>>&HOs{CbxqSK{!-?}^`w|v)^PFHq6M*}kU7Vl%d#npeG zJ2x{gG&Qa*L>vOFDw@!70Czp>Q{|3+y~fXBQS3#3eW*|^-|%U{EQ|sj`K#H7i=*#> zNyW>c^Co_1Y>Aptkt2k4qaMk0HoA~aXcFf8Elh(ooN&k>)>nn#ihM~( zb-GurIx;|Zs&!-ILGY>DGl_NpQ#&IZY6$`|Wh7~MJR;93Nok}91(KE~_5Ud7@X8MH zxzA}|#*U#1P2x9`r;a9+rl6NX<d{b(rRV3iTQLvP2b9v|vvK&p?+AR@xN8 zhGAor0Vkg{Dvoh)=r9kiU=Bq`cPDEAReE})hty8VF*SUbTjH>%CR^Wri?{s6b}%z0 z^9qZ-;MEH!rdh#8CvMHl3vLuY=G0=j88H@QQ@k8G!3EN-i2&+nRH^DRA5rR)w^cXZ zk}V|np1o`B-JNE1t@$n1`i2AQEt7&yj8|AJzbrOLaaIACi`2w3AX051QEzlDha_;} zaKmzXKMTM4CXffTJcWA|z#cz{(ayh-umaEAP6xX~LRhI%bBpV&v=dUg1C1j@%7B!s! z%k~fO1goz=OUj0cxq~3nns*`&jY{@U_64mgvYZ{3$M$xS+B)%|H+4^gy(M6oUCL%8 zkzjj%X&Fu#5J(482^ll3UR2vOVM)N{_Y10Y-+qN<=sU(^X-d=UbCF?xW%INHMS_O@ zhs`y3qK|HOUZzUaYwvbsUEj?p8zJG`P=(|sH~w8hCMC3T9W8#=Qg#C+=At@<5WyB4 z8pmP&uK?pDfhe@$XTVF@83OfRv@Xg9HLgLdd*@X|UJ~wO^0&ib;Jp9wAmeVk1-X9`V^DXjupg)})=xtgRgzGlAU!k~k`(-BGOOIbEo z#j@guO4ElY4`YRSD%K=iXLejh{)r{foaFwIV&W}&1Xr#k+%DS@hHoZPJJr&@lC=S{ zw1mS;YdRTqp6F?!AeBxDoA%YPz+P0$ z^Q4hHRV8Ld;w0SL(1gm)VHX#HG7R(Z(7mipQ*)yi^z0RoCn3Wa z7k$w^(%?NH-~JU-eUHICvHh!&m@6+g>nCowW&Nu9p<62;bogQG8omuP;t#dZV+V^tzN@sEibwc+>`qT4qlv+NZO#^A-3-bqBW# zmxK4u0>R6unzLo6w>z9(xB@p&_v__$3#;*veJ)&`9VD}CN&#Hcsw_9(*A#^Z5zgDQ zncO3Ln3mboa-^_Sbj#@yE#g>(6Hcp~{^iWqPK+8mmvd!oVY=(uh{w;Er?!LXGfz3M ze($Gybs8oVtbBLOa9VF7TD??~*?Y)^-yfg$@Jk9V+yU*N=E#*;7z zFFU1PFGVKw_nFJ0x3V=(}&Gvbq-VANIT7}+2Z+X-%iwC-$ZGzTSB}DVR&YI zAXZMWZ!{Jg4Uxq#M47?^#B2tTvV>SDa< zK8xu1<^SID#?x7T#(7~u?O@=+{d^#o<5STY7hIPs7ijnwiypC-otZ|W@hTcbC$Akk z6Ewv$x3AsyCYqj@D#ti*!`L8m!T9M(wc`lK3a2;)13%+vxpJLUSO)?YmTiy6W|M;X zQ8&FT;-#o{PeDZmt&^O=>)IE&3pBZc20Q^CQ%w2>?biQSun^Jf%_P1!U&5}Y-PexP z-T=i6M#^c8V4Z1$h0TG=xcgG`jt&WM#X5Q>M&G?R#7@lR=ALuQ917|Ie%b^)(|6xT zI4`S>SI*0&-MUw`G;LYXhm=>b&rlG%yYpsJzF?SCJN#cFQcRl9>g$b=-M0>byGLAa z3w;mfj$DMCg*P& zeE5r)8lS7M;nl_XDx`vZGTY%*=rQyeybNt1I^XFjKO=iWswR*u$px?NsrxAX0;Ibk zvxcpMbEy8Kji2GYH`1MaWMC(Y&eVJ3x7-uAq_mxltGk*M!F{!&!n~!%i(V1^{t~@VT?km0f+!jM5FE1Aa};FrDdqixdw1Q zM|kKqOljvYC{NN!3UFPTwe%6K3jEUZb8m(EJ-kmC3W&=fySO$|`< z!J)?gPquJWZ|~?aZnzh{I*Lm4B53vGV?85J#&pj5d28bFP{zQ*-4*{84*--`em^#B zL!hh7T?`y1)5Mb^;V#tUUtx^4#q0R8MW{ukh@e@WqT$`tq@McvPiku@!1GJE9{d&SEZ8oKki#65aSy@XI1&T_N=JcawOksQbHWhUHqu#GH`Z&_sKz_ zdU-g+_~{$SC|7xz7_4%b#iM2Z#Y>#D2A1t*aB_k&BR9VhHR` zxFF~Bxp5&0(e?1r74SZci$~@PrB_wGlN{N#XqYeR9EWSx;_+Lgm9}?| z5%-EN4-^0P*Lk{&vz7+i_YZa%k^ z&Lb9kH*OiSuc(Oh^n1JJ0C#YqV>?<`^Sq5kw7;Ku5dT%|k>x~R_~{fa@8?OCQh<@E zG3GF8P5^=_%YS#+u~xN`v>Jjy^`}C2w?BL`X)0_<28PWmycDci0$NOT^dmE;=jQVu zE0@k$n(Yv`>Xe^H;FrIi{_t?AEy%3k|NL5<4RboiF>0Ta!@82D-M^hyL!7Q-H@mIt zHu`^56J^@XiBgsa6yFxFlcc!|hrvjR45=CgVbxHae9Vpk;>O~zG8q^%i!w#W6(H?HN`BTnF3IQH+xpIp9YSC}E6$hHNglD>=+?F8 zvz^eG^dnq(xY!c>Cj{hO>?qn)?JVGfYhw~UZ^GmdU9GOTYorV+LMzQtxRj2X&ov~P z!b(%)K?A|rQobexP(hff)>BDe$&ZUgjD^3rIEvr+-9E>cRd4ext3gpCn>5C#%aQ{~ zMi~=35YbsL=+aq9w%ISI`fMpW>oji@Q+2t979vGn$g8eXHC1$p-tZ$G7E{QNJ;m@D zU8Y#T=JjnbUSfY-7Eky3^?lDXvlYloQ zeG^qpbA9EhF3ha-2AU}~EHtkN@p$#V0#(rDifGac&7(4bi-kqA^8~bd!n~^qm#E&) z6uO`j-J>O!gbnY7&UxO~2M;vbH;P?pY{>H>Nio=uuckIAcqH{7Jfbb@MvkTY>Q`?T zfe&?uTCnwJaPYz?k}iQ5j}j8hDQxw!PvrM`_l?*Lwkv)#V@%>wse6X=v<;v-i;B4= zrx`B!_I9s=18dWe4&jHX&3Oyc#Svy_;rCsWzkjZA8cqBN`c-3i>`Z*??=ijfsy8R4 z)JXW>Oy1~Bsl8m?#s^2-igfViaxa=>b6Oyj3Bfq|gi~AuN4KC{lhA_`OhI25ryB$D zNKkKcY^+97*EI|5;=LZf@)$~OIWCD7k*)X;B2O~MvoMOY+{?4Dh-X>+h`6<@sJX*-4qvRPFlGNal% z81P{wX#GbZyYf^K5Ys=DZ$s)ruw~zhz7a^VSiW?B9}l}50eb#?df~j0&;&3JcIvUq zl+O_3`!vp?F2XK?`iG<3fItz-oEt<}*wU|eRFJ>|!`qYXc1r&gdP>!fuNY>Qa{HD# zTGVKDGWu5e9ZC)WQHHA5=qgDKZ|IB?dA^K^Y7=ZGn}2`*@p66M+eZ3M>@wZ`x|gS; zU_}&BHM-LP;7iUJ9<{WVK=1{(x=?W)cxsH5qCD4j1>Lc@tIXQ-<)}?DTEY5He7B}5 zIh^M7v9fX+b*!>A8t|<#e>R_b zx0Ez-*L0!I>J81;2=LO)cGLM+9|5YgvEzGRobb<r zI-6a_1>ub3QDu~;=0|QaUx%BQAwgUtp>Fjc-^h!B4}f4)K4yD#VSLvM^rbMAp?E9QyjK1Y9AroLw^11P=rR z>jvG#E`>`HI=yFwhlTgun3I!-|MYoC5Ps3|oN>$7oyexTXt;c5H%Gw^V`Eye%_n@N zgH9Q}kokeKY7+K=OCmTCi~rtETauNSAJ37Nw$JVLGNED$m2gJzS<9h2Ql+(b)Wp%& zB_*ESUBYWdpArsJ|Lf3!<9FL2$mRpsom9D%8z)o_X!7IB0AMVT;A4}LYn%NN4wKMwCqJ0!P7wtU+U<}KfC553)J8D>=NScoX+ zoH*N+__3^C51-QC1`|M}+B$G>?y0(_cKOp+5jxhjy@B`QlpZZP zk2oi644w0Q_p&#;cFh4H$$i6{mhWgS&YrxlaVm6xfA$#L0z6>Y0fimu9W>Zt%pMwFtcL3Gv%9*f|BK zTdz-hwh$LyBNTdhBBxbmDB|#cN_*~ytGygjS92CYp-xVovzlMf^?qrJ)9qn{DeF9X z%i6dGWpnzrJ##4oB1`*kEm6e{lP*jy;3N#QY87;8Zx2v>!d2o{_|x`uqW*_1vy0CA zb>$ly-%00-DZ{R3Ii9+iZcEVr<>56nI)B9#&NkJiN0%41dEes1MS;~=2$+ww;b5C@ zGJt^*8Sm!7kc444hOS}8qv&f19jyl5-1R0J(H{-1^9ixPz80iqEnSW6)QTcBdKjZM znq;EVIi*kTye@utH8VfcI`mbwU>UMMv>mwIY;J6TI{fJ${S)&L^AC?r#s-D`1 zRlM#PQfeCNr$Ff4d5m~y#0Gx>a{=H0A6e`5H?;^rc4Bj{qd&2cJX z)l0pCs}0pd{;v?mLV0N3Xpp6=09&9n`4pTug!7@yUYdZdc$3s}%MplWOw5gLV1S-o z)hKA$_gB)2g19oOoNx*>byv>vfP=oA9O1Jc2NuqfJcq#~>5x7_@4YN;5`)G; z>NU_m0)?tN^Zw(lUZWLTCwh;cHa5Hd5h z9&>cOiS>e4znFR9N*0d`3G2;FQjukdI&V;&7GvqV=%t94+*VMslF z5k+>({THvtQ2tlb3Y8Y_P`DvG;I?hSvmR}}u$k?$~jKOU25=|Tw&90JCwKdU1un5cm$tlx5 zhB@;h3V2mk@Az)k)kEO;uSlo6in+86h$JE@S|OjQNJ=OrCQ|+GLvBwqx?@dv(r87j z`>@JZRZ~CsCA9gy+}Rwe)3banoVkEfT!8QL;q?K*TaR^L&)z$2{jg{IMfObw905Gg~BAGP2Br7GT}s+*5X9oR36;L0Sks( z?2s^#A+zOc=jY6LHiX)4Qz=zdWVBVav~lxNda0bOzKLo{z)w@x>w&UvU0SjD>UZJW zL;md@u!YXrPuE|Z8oD-!f+e?=4n9nb!*y#4{-aXL0o>L2@(})^9!+r_@`>r4hUtDx zI;&}E7@}+`F|i$8pGL2ckZ=^Me;xp3(uPP@Aku3nMgnvmc^BS>+xo zZ>3T`e8*T#0NYXRwIdEUEA)7ke(c0ARP2|Y6qMS~nd1Z;->>^;*I9OB_sY#JTM0?D zDAuu`{nf}d0Qx^>fR5gMJ!!-)JJUo!`=|9_Z96{r*>)|%_ zZ=M7wqA}TCG*Gv^?JLRVbjXbS!GB;Eg>xotU0zkntwZ)EhL;Ts=Z&fW$x#EQc4#D~ zYVRRo$?fXA18*{5A>qS0%5;ouin=!>{bnt<>})*f(~xi?7~${z^Y zA%q`gScO8?zW^$0xy(7hY~k$Ra!{t?tAy{CfBp4qtg2~y52B67hw`L4insfF;5jr-D(@Incyxe;_>*cf zmGt5W*jP|T)?o?uWY({g)Jz~&bmra1KY5#P*Bxa&$Ay5UF!J;>Jb@Wc)H$h@)vRIU zuI~1bqVnhLVnIO^AC2*&E3PSdiL6VKOh@a5Cmc?n1rEz+^Vp&Th?|4>-?DM{ZkB7k z@+DF&Cza8~w9FbKfW9R=B1lCqEY8-6yrgc%(^UUj$bt8mDTW>zIQc2$MAmDk3hphb zt+0IGkv>Q1vt7CCCTfZv)-uuY865%fzPfYi&GWxKq_+7a`KxM0a`)%_X+(~%E{=OW zlzx=Z8iy}5kL}TO^z~z1>axWNP}VVQ$K)S?3#m+9Jq(7WecoqNt!>w=MThZGN5{1L z(?Hbos4rZ5V4aFV>oc=9wVU!atVoBGuU%87DA)gO-c3+1vS7t=K`nG^EBmbIVdH0P zQ&HnI714Vl7 z9;41`o%S#P{g-YaW0iAXdLh|xw(c^M5AXEJ!_11g`*Dw|&`L2Abz{t(yrw~fOMW)V zF)|joM2=Mbb|`b~TdCc#MS=79UN6&DEpC!}H_4{EJjx7*nra}BG9y-1VNaRCU?K9v z&8wQ1Rdu)i*y}cIHc}>rLl&o5ZC72oxr#5+$bV)gtYnZ7ir@S`s!&rbW#A`+G<_z? zm=cz+P17Sc1>xz`OU|`h@(%aX4v~d7a(U}`G`J?=Z%i1haDWT4 zBjDQk()^eg+>3Tn_#75uRt2^-4?8q2D+|CGAgk)nENsROd0u>-i9_wK1hgVWn6Sq4 zU(u@azh|_z6uV)6^V%=y7CBVz*gR<*q!q9_HPvk~4hNx{T-h^HMnTrQxa=-lzNsvz zNN#76>0)`0!wm_ylnrfUkaU>+3#kgIhQO9#TsjYKESY6!@uU?KTXm&vbiKhBhL;7T zpjfM1Z!hZSZ*g2hbDX5S7dYlfSGSQ~EsGk2;(OVhYDXN%_>HTHA9TG(WSuK=Px3qA zBP*n9tseKB0nQ@JLwAcX8+k5rMauSABL%KkH?EJ*LKz;8$@ZLT7iL6%-ih>vvf7JG zihP%6g>}ot$VY9uB~VHESUJ50YO4~>eb9kfQYT+xiX#!RSi#A#D*Ej?Cl)?N3D55td2|H@7&<;YFPL-W>- z+)>z@*M$`aYeI=nG}i;H)4lP=ZL(6hxjsA@oW%n-NTkgKRD@ z?kk;2#JtWa4s(XX$8$ya*4<~DFi;tT$eg2THIZl?l+VK{Z&<>VU;JuXp9t=$t?+LN zrufnm6GvSK_jEBNX7|H8@;q({J_3QL1bM6sLe_mqqD;m+so`>h29acycMay>75HzF zn~lYbimfGw=3}o!`@D6jISLhSHZlZ}ubfS7-e{$E7`f1o)!*HE6yG4s(ur?!NNbF2 z|1Af{U%$g^EVA>21d3aQ6d>Htcb>$=>O59?|WRZ zpdS%xPAo>^c%stuBb!}4BtKm=y()Ln<^ALIN%5pcE>#g>ZaexPN33+QxE`l{9Qj9l*m?xnk~d^sGNo?v)Lhypex99^ zv&}v(b^uX0HE(GYoFHYL*flFN!<2fUCx?*(snfxo^<_+UBBNF{R}6>$0x7Ia8p;9@ zWDCs!&Bk7Lb29-K7nP@Gb83Pa9ue1@Q{7wIc{#}?hFrD(9XJQyXK&wHygvkNxTbNx zj)aNCBf!O&kjm6`cDgX`b`oN3rIs&L6*};bN%Isq{EOxlL~M~jFMy{+1NE_DNASv= zl7ue+@MiyKO(%MXxxmV&o{})pUTVRat4eI!gHM8NSEzGGF7Nn}h6x|4V+LO<9MS9X zk+jt_2MEA2vv(G60oe`7!e6YvwiPfkuSoi5Ys1CSu z)6+i&V)7QZxUk3I)3fEp)tPlQY-STQix@UJ-z&;@CTWul;O)n-c}AwQ3{shw&?)S3 z3of3BZ*;Fy|1wMAHA<2lR~}x|Q&lcu)J|d@hISS0I3}~~GS~hg0dL1sG7igg5cf~o z{LS$4zyv1$kEpW@X!8BvwjhmkNQj8kMhX%F0@5(LySqCC3F*$!AUV1jLt0Yl?v@;> zFhD@ynf|{1pEtY*cJI2c&*wakLwbl2mz_2dEURZ(MfmQT@gTW!gaZd8i5oN)=Hy+| zN=j;I7}C?0aKs07WR<>j?7Hrq7xlz!Y(^vTTHbM79%7{KI!!A#)UMvbB451{^;8+k zTH&vVM@+a|Gaim3;{vQHse?ln9rX4$%MOqmE_-zux>NLL;V3lFsD7xPSzynoQ(etA>VG>PZnVegTw!M&)r5t(>5<)jWSn*NKTzZT(?sn6ix~ zEOEkl`SR}bq0wSzwny7>Y=AQ#(g!=HZ5_s}Elm08@0{nnd-vE$izy;)d``G{M?S33+ZxwpZZ^$U3uZ=#H%L$tNhjsLWXm@Pmxom@9O%J z5dp5V+OZ6_Qv`nc5;drnY!~OLf{c3>hVVqPuqL?!w1BOOk2R{LDg2;oiSe~RAK34@ zPXm5WSFU>p23+*YI6etnaI_9%O*f{jd7_PEbzh(u@Y^Qa5P504g6M{0Ui5_7+=!$= z)%3Db(sk`4i0rXdzrB3-D`?g#qBH8+48+~`a(Q3FHxI_%U>02OT1WfKrXD40DJgFA zzePq^%M|I)E<3M0B&y6QWxkQ@r)Ts2mh}1_kN7lmjUcOZ%eQ|S3}%6Y(X$(NGp^NC zrx1A#5jx&@tRg(nDnnef6m{{FPKzi6f4@Q;i)4yH+MGae2_F@Wn(GX>1t9_0M`FubGwUkk1M z>mF*eMcp8BSOW!i+^GKe3TNOfP%eMR1!4?)X?5%md?{7i9*Gb7LfqgwWb0U%0BS3% z)Zj&z)N=vBH^sJ)n8pp;XR7aRb`JAI9`2D!Z!Z`fZmsjWw=;QT^wxJ7=1u|Dw=E(= z$jBV`Tgp$&Eqfx^ONCkc7|Oz_Zf@6%s!xa-T~u`*;W#nMYT8hKtQM<$#)Tt6rx->0 zEQ~XOOv;m@0`7?>BlVb;!r{mM$T#Q@XIow`dxq~-`cFAIYT!3;+#+L6XKlsgE6=W? zO=$7%sI1aGvB{&DEwG63K0yZUvQ8x5;^p>3`dR+@>Fqr+g8lH(Q$*}+`Ye3I%T|* z(<`soxmG#S&nLg{m46+RoANfm{8vj$`R(5M5gn3eTCpESi`ux9C)Mtg4|>eF$iPaa zkrbdmHOqh=*7~fQmGfgA(7!t|h@&`oE$h*w1sgCTl)lBr8CLL^!>jnw&EuZz`ML~8 zYe$nMizzX0c@0RFhf=c#`ZBuAHGEc#!?r4OUqVTURlViy{|dL6lm+Mw4c(0Op*dJg|7k8j7nin)jggF1T}zGl4ZD=Mo{eW!*FLgu2acQhrgd6* z8VJ9SJPbYDDG_41I5~tgGFi_wMVxf{-T9vFD6f0hzFJqoZ==lX@%rA;c$QjOg4|iN zo~K^lyx{cj>d@stY;$Jsn56)3-l_Auv7Z@!J`*R>#FQP-SJ6bbhSU(}PB`voMPi#1 zk}kdTJH)D9L{Bw9F;>Acj(I);%F}UhNgIhgx=W9#+_?<@Jv*vb=|sX8OwOGpefn^H zqb%xGOEC7!3eR&HUPGom${f0qce8hjK&+Ncsh;+@f4>k;SNj*=eO|lFYmxKpdw@4% z$tJ2K!G-jbtwnAnCu7}@~#D=jg^fC`R&Mfd$H1?+xbLk1mAV12)sTsVn7 z-tkR5LuK`7ne+GKbPR<1A~EIlXffM52VYzj?njNmZ+qgB6ys^8lU!2hUo zYv<=QM=*Xz3kCg6$5g|6SG88AXxuqLI{#W#TKZ5xYDu2O@jE8%Y8kYY3Ej%nstOez zMX6=9kl>&WNO+k5H`(uTNBm!keo^o9f=*Z=TOr2rYj0R-ksqhOKAp^j(h5ZOwU&~l zF-300oBH%X9iE^#4@=p+raE0Fd6dz}oak8Dk9{F%XsVU!<&w8>yW=Bl@Ocn zt2q^R54Miq=ZCZba6lrW+F&~>&FFbTywYrpWYP3IGx2gYp=)j2#*nsT1gQqu+++JmC*!P$@VL6J#w#suBZ+ zmY`Zpkj0FOkT6z3+O`h{rsiY@sGms)a$oG!3Nhrvlveb0MznX^Y&KX*n7*(OSu_vmeP6H`gBTv~F}$YR7tQ;N01NIO9-PV$-CgJH;MYk-{Rx=Ew;RYl-NP2h%8qkeVT;qlies1Jz*<3k1<-?-f~TSg45PC^>XK7aDo z_NrLXP+C!?-#A;eYbdaxZh$nI2TMv6%&4-_uk4D~@pkIbV8goUh+tT@<`ZH7M(DJg z%4Wf$eHqT-07g)6*3?2E^Xq%s?2}T@7y`=O)l(#t+p7JgGi)ktGvR^36uNn+1@13tZB~ZPMI9tQDd7q5ZD6bNd z_#!n;Eb=?Ap(Wr2Jf{#sE14)qt}$izLx2*i+^4X?%d1_`&w9AvfQ_-+O7e8my49~) zV9uHr1Q0GCjUUf?KM-dv#W`bU9Fv)5OPtm`|d4of2&R;n)vkb8t#@ z?~iG#4x^eDpW0s;EhX->oszfZbsp}uvOh)Oe>Gh;@Yc$AV@&(oyXID*9q}w00Y1oE z)Cn?fHB?j#5XZzZ>A_>%$awA4n=k<{_Gvc(_3RKUg*lrWAeXL`z#DkAu1ncQMby?f zq`Cu-?|9?zg38JHHl=pl8TTQDvC_cx55owVN1OA58ST9-Ad?#Y0r|Ap%~G8rB1TwZ zh5@f?kcv?Nvg|Yx`j-O-r>LTfT>7*2V?7piftp{sJ4^hA@hq#|Eck!5)pp%iEw7o( zD;Vtaq@waEH3+BWB4s~%s} zA6U*3CU5HT4N#E^z#Yg!`vwk^E&_h$&ABOTbnOnt?=w7B6|1A8P#raqpYC>%Kvgs^ zb)Rx;IX2;1rtP{$Vl-=OmnUToA}1xIV0Uw^PCLufVRMN#L$s z;^Zb|FBY(7ILWUr`W{$r$328;-|-Vfky?}tZVMg^D+3C@SVisEahbx-)X^&%L1P z-@U*C|C!-C+xX^`i+jY^)GQWI6w~(%!bMzKqb;|~iqBbwa|>fT7WN*us+#o_j)iVa z@F}Ivk{2=k8V+3Ciy%=gluy|>-gaFNV-}ycJ~RutZXP$Z6)o-M;Jp}FETA$rAQ~#=exDAwts*qm* znw{*?JqF8nj@@Hz>BS!VF)(qVJpVAl_8}C9+5THLXpPMWPXTGxhSEnm{!NJh>uTb9 zzH%lCV53Og>LW8U#%}r^g@;SfMX3(8vZ#3fk8 zTB49G^KN$_7l4vzq;StqSptCv52?tmDcPy+W)cu^mmWttb~@g4Qe6wt>MOf7?aX&q?w0jAM1wr?4$RALJxR(>Z;g{?>h!ZbOp7uFFD3+gh zzFwc$txGe98qO?wUa?QYZYAt7oi1j#V&_-Gxt9^0w+FYhU6iaa{5Qu{4m+)^jsLr) zihQ&5&c3p}cH-6_f!Tkq&c;|M5f##unQR|swohbSlq5pdr=A&j;de(TLnq3md0GXL zVXKcUdOWL4D9=M!(L+JtpZFyW332>ymOQr|4g)-HKD2ysat}FwFGFP7d@#M#{Z*Lm zkls0$tlS{-e*wSPnG}zURrVIPVWZH%3O07=s)G?n7nZ$&$3{oe;J!*xW!juFn9!{P zHbkKrxoC^0$^`d!)5&3J2oGh#F_cl82^kEBgT#6;eGr23Zw>|#yYIZ0L7cN3rG^Nh6;6$X5>lXzc#<5TG5 zJQtbL{G;=b7N*VB=e# zmJR}!*pQ%Nh0OJ%l|U+z3UkodV`pWrD`Q&U9CyqN>Y#)GZ5yW^vBMX`U0YfhaRcUN zy$ni5)~_6n64(5Uu~w_B4*g3fl^ffi9dG=J-|D!Lf&bR6xY=ke@=k{s#+N6*mZWh4 zm}1iTc*y|4zTiiykrk=6CKZK2yTT?yJSkF9M%0Y?vWV~iPJ0h}^aMqO=w6(t&cNE{!F)^NQq(;8@faV(F*|=H@88rj2_P0Nt-nNXK-ky7WuEwo;@B)< zZ>ym+FKwNx3$C~L{v;=;zjzHmb7_%x(!0#`V>}h<)*zns+s!wO7nzSC1kXH`KIqj% zaf?vq#VQZ%^=9oONqO9DkXq)h(K-L0$94})B?C%vT|VF?3>7*$R4J~pt2`Ni{&p<|M;_aC!(B8E_Y!_kr>^+cdodY`n+Z{C zM!?hmAuRzU2r<-~c^U_&1FNZq!h2|0=0Vi5XqOL{`2dO;b$EsRv@u~1fSwJ>;8RQv zrhcQFQVj?pdL%1aCZbsj;H}0h4KW{39_9Bi@^BzO;Pm*)m&u;u+PuJAR;Op0SGxAz zx;AcR+5Y#B{fHcItpT@VFj8%2IlygBtC02HU3k!MHrMM3lhRH-q4%|;jGl&hqw3Nm zDk#gW3{TpeM~&zW%|2;#S!&OxD0H`!H-)(D!M0-j>D|?9e%W{sh^ge(J~{d5kN5A} z?DmWYEJa?cI92h=UN6tUI?P-|p)BYED`9Iu&4l-p8F z?%b6a3wJEGb?~+>FEVX?z{fQ>!PkZBh}=xb?e&%V>9KVPaEcTm9(X?_8y6+YDY-rI zP_!c@iR}AatT_1&1s&o;o)-Z?ZAQvfKQ5Ohv4Czgm^6w@iTcJYR?sXo5oeRq&q+pfJt%Umw&7{tYp*_M^p#t|mUPK(b*>-2e~McR)~ z+` zS-Q2pg!c>_?wG3boD1tVUgf3Xr!R}1Oi!Jod{Z^c(Cn@PVnYWp@wmzJif|Y);4-2t zal*g5_Voz^+#ou7v-EK;3n%{qv^f8}2h*vhsK`sTqdf;ufU(&l&=O%+GgrGIg+ zQSE&Vw=S(GFp!8rU|TI`=?deqE1xXXL?`^pff)zhe!l7c;_}U06pA`OytA5nA0b-t zdxTb#n#V(a_gG zjrYHt!ZW>pLC>bsYdd9CzPThQ6N}u>oEiU`2tK?B+@p_q_!?;O-iPwe`FDO#p87q1 zI&|)ECrh;Ghc)Sa>{;zk${o3_zQBO-IFgk&-G6@ktYxEV9aT)Q&twkexcM!>e>~BW zTLzEG#My~cdjTB>Y9dS&hK#VuCia6BN!W#2YhH+pXTqIvG0f`bqMo4lXE0rnY~gvT z|4RBKS>>7Lhmu?J497D0V@+GXXLI1uq{JQpjpvvM&I8%>zu*+@6vFF5$x|CS4fCWG zTDiaHhU$}U66T0wlR*$;(mYzBiDqCb$r0YUvg8Uf$Jj^?CT(ydnR?saf96W!)< z5?|lxQ~ez8W}h~Lu2X=W<5i1K#3>A_2d_j%mzbUsf(F8MKYY`-wuF!W7nvGj^Zzz= z(?_nRA1ZRc(om{ zX1zc$k;|nQ)sUEs74gHbVS-x01xM#4T*&O=*?0n_qQc1=*5^q5+kc8&W}o&>L%E)! zn;zBOLJ@XP_d8T+kwvYJVsx+@o}APp#Y0)$9d%yaaze!>Bu~;t3>{Dqy%CF;)SJNu zKijl$u0v7M6G9!*Mn1AIJ@ZezGF}q|Mv`W`G8<?GKQI) zciPN99QLVdt=Ec~0*}V(<%h?1pbR z_+rK1!(N9_H@@#d+N!dk)2=jnl_(4|Ag{z^XC)2)8kh0ff@pU zA^a;KK0e=P8`?O9u)RJ{P*5)cA|H*LT7!FoTBm6zKF$K#c-egZ>a$cbC?8Vi| zpUCVP(d@YTcn(SSPgW%QEv5cXgKo{V5sEE|9%!%F#6G2X?3(%*B5bJny6SPt`jbiZwuZW|?guM}KR1PJ0l|3WNG(zo3VS zKS(nbP?#T7lKN5He()QP_ z?w;+@kXw7cFtM`DX~qCP#KL^gxze++8pKJZJb3le!C~$k7P^Y5gVxNZ7Rz4F6$Qe} z_4q6EwGa8NZ=H$#Q}CoJ5(GCEZ?~!2vGg7Yh^aSCkp0Sy+ftVBw`unBtQ) z#S6(J8r${6T9zUl?s`M&pSGe8iot{hJX#7g%>+<72hvjXNB2ZDC^k`N$w<`%;J9z-6Vx(+5<3~tSsU| zogbxy7@zw#R2Ujv51s5fq z=qd@cyP%G6D(Bg6*b{e#SHdv==b+QlD=Fmp^K(Go&&bIQ{y*j}H~(o%i$PKnAAd@? z7|u302uXk(j1o8@ta$n=`ALlZ*~vy^qRC`F7#uDRSzr-ID;Iki?NuWMxG5)Z;F%!@ zn}xEoDe*7;FWlis{a<(!VJ zp4=|xy#dWL$G>H^e@H>@EcE`0*!-5F+SpSroEme#m7fs6Z1fIqC2sS^_PSQrnm+%& z6K5$KuN+x^Ca#JuF;ys?Ef3`Kl@HX;L`A#NXGu~VQ+j{;PTu)d8CsIon%p=4smj}l z3<+za&}1)3n~x`JO*%Rz{u;U&aqbto>A9(mNSewRw{>RY$Rf$u<*;{c*E?4yDki8$ zR(mU4(~~2wqC}^T@4aES0NkFBqawo-=s|;SD0zY6uPI{9$c3Hm*#|PIffo6V4JmT8aP$A& zN$tWnGk4CixHHLW?AHa&Z%BAZc!iG`4~De`)l^GgdU#Dm%{74yN9I80acapmFJzPN z?Mp54L5Se9X)ydN$Gn+|Lg8lAPYfNZtw_DzRV`;@+q;`mQFL&l=G|X#ob0H z-ckl~avH56W9yEL4Mq1shO3MD!;`d?hLNp3$LBs5Is12KuIQJw>#uzO4uVg+czjI* zI*v*~%2`jkw^vI(TC627^OE@eTnS5Lm;6E;N;L3>6Ii0DY2i{|Qmmj@jbrcVS<{E6 z$LjnV{q4wrkf@f^UEL1GhL9JUb)X~P&j?VuGbiRp`(l&NaKLW6)Z6C{9XIEPowv41 zi3?_ww4HM+-dbPoM}q#1_x#{%JI~1HNdzEdFx@?)J0peBVS9Tu$}a%wrAaMJmi=u! z07?&K?j&;}ORAcu6eU>OU*8q9?@-QHL8G@ z=lQ!hjLZ2A?l=uK+{{5}q$ds+z}r}*sV42*A!##iQUX^C%wOe{n5E$f4iERxobj_; z(J|QS=Eqjfz?%RM`>T+6L1yi)?`Hq1T%Rr)1Tdsq$296){SWtZwZU|5YrA7&yuxe#>p{%-$H%$8i6g>B>i)= z%+|Z6ws}k6yJJpK!(x_cZ3A`7d+xx@u90=Wdk(;fv&(BaC}CS=fYn98v1Qk^&u0HD zKmk0r1E!Z!<_Xoom8LFw$%K<%aFCOW=b_KD|H9`c55FY*Q7hW((A5Yl>WykI`UqWk zz;=T_H|}{2Wo+{D5;SBm+x&_DL17~|SiWlHKmKh4nL%>yLC1Gue=a1^=>pOJq#>%` zq^Cl&1dG{`wB=1PLPJKQtcpVloBbIVIjUNKzI&aVUZ#1EMc;z!oKP>Gsz$6MUSQZE z|C_H@tIi0(DnHcjcAQIeg~s`=>p2$t-8#L7{{hrp?ZTd;?k~ejv*ZBNXcq z&7&3ERGvNyvTZYnE&YZKB{r5ISmC3_uTY~H7Ob?Mc?S6(1O2g`s%gPaq<~MUJa3!6 z6FTlZ1i?0cX5MEq;&tunv5}-l4fk1oUn99f_IR)jStlw9zLk4?eVzFIdqk!Xh~Y6Q zPt#J+^ZHO5bDpuG>wk*{gO<2F|x`hyQ;!{@PlL*7vZAZKst5Sq1si*8gNt`##w!Cas1=bduAmHWf1-#!wJdy-VBJX}%*@K$DuH%O?}3bj}@-3tPe;^J#_ROen*b%a$_S^VAe)@ni5rnpP($ z&Xe#~M5|qX%GLnnl}$@`&lqJH<;C%X)w7jXJ<|;MV}qP-@;gV^*F{&)w-A;1dXm zPeq6bvdXkD{ab^So?FA38{k4JAF_seIlkipqOG1t-Mpw8SrUJuiOE{TC^(lW7Lye` zDb8%|vAm-Qn#Y;FF~JFI<614Ys-rWACNffc**f4h4_+3G>ioT<>Fqhye>+S?o*jL7 zLRS54G88dmYRw3Is_+kBTuyvX?6piC{j=JJ1-adV8@9^H@5QQWO7J|_RARF67?6X$ zEGw=KUC~J1Pe;`PDzJc~F<8M#YLR=;9|@OKRLfS*G5q{w>U>|fb;5TQugVUU)3v_ceg5eWNBsEah;?vTuJuFC%J-nA zu?IX{`b0e(*b6rG6&%EycNfyEDSuK?f_50se1EaSy3HD?I?3H*6P1RpDefGdJjght zSx~61t7S>&7s*sGikkacl}b}8&OBuqxdSRJpZR&McA$)SLF+2G8-vA^-`b2Y#73eD|?&SI6K71~+5cK;?_&FY1YE=WdDPeqH@xypy&@v6u{=Jt?&hrgPLnSjQQT!hiYwlaD=s*u z|KV^i9xl<^+3$XbJbJ80IvJgXi<1&OB?64jT5CFaiE0i62Mtw7@B&NNpd{#x4fWEe z_LSngQ5_fwpMRNJ-M1E2J|8Uf#LGVRoj2C^rzJ83d3p)o1=e_PmyGkZAs%3N%L%}+ ztaCe*fMHFQzecp77af?k;TIPTZN%z|^1qclPfl-{KmX<~E3s!pv14V}$&zcavykFp z0h%$Rx67s{4B9jhFEJJP7qF4Z13X5_L;=_f*^WU)t{$yR<87lbq4vfQ*rX1`{R^A?k^3znkehs65f@!Q)p4jubrQbLe94tIb5K~RrhSZW-MZ-On ziIw6>xzS8=sk4Nf-o3V^SD+_bXYQ?Ado*ky@5Ysy-L}IB-yrlKUM&>zJhaIB@xrz* z^S`UO`RhAJm>LOAPW8f~($nym^#(FRTw-i_Elr-0N(fW(4l(6%|avKEBh69v zuddQfPEzf6oA34QdmG$aAO!`KMX{6WO@*^KbIl%d2nm_lzN5SqB~IgpGE|T?O>vI9 zDi-i2R^a+OQr+vzsNRHbn|GDena`=Lrby;QQnFt&lE4!mVee~0kkoF)04J~^yxskQy zCPl0lUI^LNMBvKXyg@cHz|=_0ndX=?B;_!TN{mX*zU& zr()zH!CfoVjTcNbgk>4ZEl#^z*mmsDnW^Fnc12&#J^=-a)ep-JEysV<>v)9ii{lL9 z>AwCRoNr&j7lwA+AS)Gw%Q)?r^+*qg#Ra&^vjD#+iO9AKK?(O- zydaozbgt9ixIA{dD>5f`GM!^B3ChV7@kfG&Jk zNaM_9=F3&8_lcyCV9a(sbSDm7wOX9;(V}(&uU>gO>Z2XEu#Dt>HWCf{cRMArNY9UC z`kIzRTRT10ow0G}%_e@TF;zGu3KlOWei3oOE( zBP|Uq0wdLsJ90&L(2+Lnz#LvdTX(kJu%f1l?{v6d_6=T`3;^<_!vsT7TYYbmpd6Zq zMcN+f+1wQlHs>AwlLG!L#goWe+)kyIkif{YD1xBzsc!xZcn&qq4{x>i*JLm8{^j_^ z5$RON)!5JiwH4d{ahP+{oJ|Z}mJQ*2FK*5v+F!8PL`YUfmsQijSg?s=Vp@3F-_ym` zipy|Hl_;cf8_-&K&gPdSX6I4odcB%gq124e;ap|68chKw!Y~WoiCOfcS9i^&NG8Z> zFT33(oloJ939?G9bNvq;4^T(kC&Il9`AJ$o)bK|fX-t9q=(gJJ#eowRY~nZhfW%2D zi2_oz0yVW77E%VD9)V~!K|%GL3)D(Er5V(aQpHn8zQ{?g_yGTqYsK46*Ym@j1Am#p zy{=lg_+h7Gl$~>7&8)rk@=qg(dPFWs$$DV_|KU=EGMv(~d27-RMJPSLj*)W-zwPaR z%*y~xeWBY}%+myjzT+l-?G6xoRQ+n^#m8WI*sS%YTa+{|Cq&i6@_ALs+M?r+l~_vn ziGSQ(GxrSyod#JG*=^KV5BImW_w5cB$DP$BY^|eaTGP>paw=UCGmL7&y4}>1=x|nOyho z(SR7H@vr97Z>aB33o8JA$6=IiW&>K4zzSJui~g-|kv>~#QxMjCS?^~q2=!~kre6Ew zf(jVXGe}9sqYszeOL`ipY-JU4Vsuhk07?rY#`HOcCn9sjn47+4O?2(S6Z9(g($+XDB3O!Hn!#MBXinMYJu|o6;sKil&X8y5uJl&CPFDNuPEw?4@<%*;!PIImJUD zx8(G`>U_iRm+u=EE?e=Of11}4{gk?D+vE7dAk!K8k3mMDAAf0GNT&g}R%fr~Wu8#g zrUZIeoT}^pAVwG0m}NodP&aYMP@EK;(%-3n38~-}8WAY}N|k^wO>I_AO+>QNOyXG~ zkLXnMSr~qa_x6Xvl+lCtj5Lw}E1j7;qGZL~c>XEZ-GPgV0F zu+f}KGEhJp_tP4!IiV#H2Z&k0mWxxx2_#?*){$U@p`w+Kyx{e7$EzcjmPB&~x;^7B zaZIO~vR}V@H-2Drf8*$VQ8Rje;`low=nzr;)}JxJc;A` zq6T4%Z6e=+wcd_4VE=GxHU9o}P6ui*wvN0^7cZ~$@uqRAk$u>x{{tA!{|6YIs=9SO zcS0I-m^)q>u}1hB^Ld)}XhiR+kxG4JnxUvDn>}P9wu*uUKQ0q|Pvoy9eOvZLXUDRd z4;0;)50QaSZTM!mZ{gL=#*chyhsV9T9<~tiyM;7OMBKL^RnRa0si3b2?|vlEi;R+% zkk)(>W1)(La+RoXTc)c~l~JZJM+R4-)902}*hLR%>;a6?t0x3g6Ga3HwY(xTW%Thz zV|!#@PZ~d*7GLu@ox+m%x(hkGcXN$728Tr5(80QcRTf&@tm7xn&A&{AGLz`n6}3Kp zYK}4B=kkw;_CH(EXtfI&`Uuo{=R-2l|FU}KqY*tpOYoUoWHDfZrPKhC4_d}3s!p_$ zB>bV?hDYRqmVbQu-M0-6sB4!o$dRRRT$(*`nC!S8Ng4k>m0QUXt;P}duP`VKYJOwP zGbqZH%Kqi8zbX1-&@G6v#I+uqq?}wl+n1;VsF(3QRcYd`d8c7iMDmWmr zSA4iM&r+NtbQoNT>;@IY05+rs`C2QY%Za4Oq)~U$OsctF%Fui`oaE(Zp%~<*&2YlX z5l;Y)omSt=7%&@f@~;-(*6?ngWQU5xzeCI@K1JS4`2O8YVvE7oGm9T$4hPr0KljRY zdmr~`DVUxiX2Yw*n6cBEY$PHE1alt`S#bB8(LkoP^dRQZ6*?*dZTm%-9)#J&wBNC$ zMa`R060?$UmrsGdNYtPHO%1`J{?f=^@7Li;*5x(b^iVjb!L`MItg47s?7(dHd-8&& zC0NmAAG2r?E^+Y;3qu}A&g8>X*30rz2~zrgRr!x3VQ0ZV4(QAY7qLuZ#Y1{TO4952 z=S<4ow@<=6evz0k%!FIM84u6%Gf&leh!)JJ0@i8hS!N(xw3Li76kx#Xz>^bU4? zR0!A^V*Y!qT7pF!Uix;$S7st&Bxs3py@XtqjT8$iHgX~}ic)!ZUcA-Ga~OLLMkA6} zcsUHit_H+VP58=+@8e9(WgVKcOImADt7%i{t_$m{thGemHmw}v9q_eDZkn~xhjQ3o zvHB-DcU~*suoU%Ab<}bUsUf2#@_)Ul@RoekwE^_Idr7S|#r)H=(9%v>o+UFbY;Db1 zP=y9#=uuX%aq5tD6$X?OxUFXi$$_FJFzFpTrYhZVNrkyr;(g?at$HV<8h^^^wf?&f zdDO7)HU6$gCOW!)-w}I;k<|XdrwjaleEOX1`n_9=+2sK`ROHYhndI$q2UEZ@N3;zGDRf6+)iMM{*_~}q@UEv;&Bc3Eqe`+ z6GG}OL;i<2CDe1o>vXT#Uq~`4I{Ih10K%y(nURFzQ*qq?47hAj|PK(M?>i#GD%~4<^)?iSz;KuU(U08J$o`( zlwy=y)=-Wvslg;q$G($+?Fr}s7`m$!B4s%7imJ`f07wwI15~R=ryhay7*n@IVBfgG zGFFqD0+;d#V2@9~ErZTmr{Zsp$~6213(rLqL7F4vzq0Z+sq-g29Ch`*{=7xTp=uG6tfmgEwm+6Czp0ua(`|RU z{Va7hNij2vVF)R19u3xvMs@aa_=cn+ijBl8WIktKV={4BE-{AjFVJXtj&FVUo&W8w zSvRoy!Ovr+KJsQ3%V6BVN43WdI~994)Ut=$RJkT_*x}rZ^F#5cj{*6WBG-@T7Qwha zhDqEyvV=fGUOGxy6e+aam^wj~GdneG$||k27tgjg6rEpBKa$DSrpJ@14Lr7U-U^UR zH4?uu=51XthKjS%AS+IlZW+HMKog9VA$w!gT8xVL(G-wt( z&Uny;!LF3&=praeRp*e3f_G6-|BuCcFJ1NOTA9!LE$1*6) zzP%jCw|~WFryEu-xtOmYR;kIMDb9_ck^?yb13gvws{MZ08pygh*A?h*yi(&AX2E!# z<@rEnTlB!x@B?PI&ZM3HyzqadySx`VEeWwtn0ysU4c z3RN5$r9?J?TWr~eIX5O{X+VT+$eV>4H!rdBR>#4IM>?U3dG%o@;MuOpmyo#=-`MoL z&$~#KQxEXJHOQL8xtBaf7P|<{*EYy{B30i5%FBckuM4ED#&B$BS)|k0;k^rtB6ma( z8DK=36w09hUtjb{Q&fSUU>@oI}^f)zkaj45YFT3(&(?(;Pk7c(Lu;VaRMObBW1k1be)7zQs4FiufE)XZfR zhD-_eQMp6Dg{!@xh4fJDxWt{?lo!MmxfRo0O|Z=X8pUrc>yL*%$aNhr4K&Rb@HfjH z!W130Hs1b%7jAF+{)2pdvM0Y_2fjLn!}o6=_5_EE+OORc@8mZOxHldLeIYg=#ws(5 zuLy?8&Etr_bJ-AM&5MzWD8Nu{Iv*6^FER0eu0B#uy`-mVb-wH$kx-tf?|bh+*DCm? zz36KouhHIzeP~Q@D(k0zq1KnkX=0x;F-`Buka9NP5#1YPK8>Aw%*L2z=2`W8zZh7F z6;f|UX$YHw04AnTVw6htLOAAbo=w~^h@AfriV8z+B@FkX>-#x4jXtc(lQ!`n`p>+O zTxz2mGr56ddKvH8w(TG138okBN^Jm5BozL?lj=%?y|Q`!c92|f6BF+4g8^jm2?JT9 zTA>u7+B`*1O6Jy#Sks`d;O53gpYnl=*OB%HmwHzK;O)^8uN;xnSZYrtVZk@8f@y`P zGSUQAlzB!1=VOfJ*R%%DE$#kRAx9hK*L2rLMRg_(b-BXKvrBz31OpM_#rp>3KrOmP zMXDG)IdxPj3+(u{=TG1MN_?5bySUwy&!a)2D;$|rxf;?R-ghru;~8k=053xb;<&Cc z7Yw8O&u4eOy`gSNir>T%9Y<>5z1%*6|4V_;;;^nbG_w|>B1S)}|1vDkJ{(lnR#OoK z%f4qiAok$nwPLRLmVupBR+#{#c8;x;LzBT`$FY~j0!K>I6!|I0ZQ4M)Q*G!eZJ3;D zeul^IAJKRP{<@f-Up2yn*SuSTqBMWruWzj_sQ9V1D)iUYLi6us!(H)9zf~};*6@WE zh%PJNDFY;+8A0ezA(L4YLka4Jq$0=qsPlHJGdL`xjH+f7!WqYQm8fihE@}g zTTDrOw(KhcW!&M-JKdoAU^u~@@z*K;hXX}#8`&F6f(6yE)p4Q2LX#cTe+06%5Z+qR z8kh%$ccokwdn@a&636K9Tf)01UTvQZVxJ0@S23W&Uc{lx=vLwDRl-NkQ{Wd6_$V1H;pv=;(v>+VmQ8P+b9fFIpkeB(!#4*EiSUSgq3rRz-JGvK{8=6 z&wm{y>)gH8R{5J6W--5bUg_Cp_q1zDac25-g?(*74X87l1?0q23(tb^a;Sw8XXN%( z#DqQO3WA$4C8lI@0+jMMVBVE(W$BII{%N*6#kh^g(uQ2p5 zZ@z!Z%i9@t3*js}FA70E9(pZG|8!$lHjW2^*`w{W?=zzhv5&gW+&9VzK>aLsmx#F; zHFGyUJ?jZoHkh6ThqL6h!3mD%2h=7Uu`EwdzcxSglt=g4U=K6Q9 z={sUkwdYI7kigiD{)$(9Tf5QMS-kfi&AFrSuatSA0^Unf?}v6&cY%Km*TRzp%1B^a zp%lU^*B-bdfVNj!OQ8dBc&{!#iv5Vr9&A$-%~uAi6QZo!NM<)0*iQXwQT4P_kFtr3 zsYHzKMkoc(vdpVE=4xK+&|!JPX$vWlO&TA>Mzk-i)hjj|cy!(U*cvy?V!!d^=&-m8 zn7pz=>|V`yrWYSj*Tnx@UA9DS{N6ftYwno5y{7PVFmjEk>?>XODz2&-Z1OTp6j7lV zmD^)7$cu??33?5LzA4pc$a=12#xpg6n_h&#dQP~w2&>m#U{4lfz9lt#-Ox$5%l}P&w5LlDAyyJBun3B0sN;{!+(FCQdN^*9v z0UreR>e?VB_-(6QJb6KZF7VE9mxA|=&v`l#DNLsI%e z#CNSmYnRc%r(D7%z8(h|F{ml_rB4(vm>#Eme4m!e#{x@=L0u;iF2i*-l2AnolnD98 zM<9k+vyjgjH$b|PmR3@_Mt6623o1CeHiQu(IZ|POfWU8$KF{a-+wtfAyI=Qxz0T`AkMn&f zuE9$G#$v#J^hDb3TYIu)C$v`|9DC0i$G&{wW`(m}9F>Ma6;|DSfL}uJNqDoAL#P|fl%V8yV_g^o(&5M zg`#sIeIwsE7x6Ativd_Mx&j<#Xqr6&>s*(OEwMw~JU2DX7a88Gaosh`Wg6*i50?su z^V`V+zqiKBoX$#CT)TY!H&FaG`zlH-I6ox;uHb3lfPWpd!)|t`ma(G3B3Y1?Y&sNB z%B3!gvIx)4j?ou}=qxT+-7#>} zXxqQ^AVW=gr!-S84HsTY96`t%f^Gip;u+%Xht`%>TZ%Vrl z@2Q=ts?ro;lVE`R__pe#(uM?!9p9tshTea3`thVjRl#74RJV{QVwf@|4|%XkBIeh~ z61uex=fiF~5E8E}F6NFORke*dM2gp!HDwQfKCQbrbm*^c(KU;k*FBH#;c#%JZjCio z;w?_oxw_Np`1>k8#6aeryw<$3k%3^~?s9jcx(ilMXAy$)f;L5?Y={X`IuCxVIfA`fCt|R*{?Az&dx5fPtOHTKQ zsvZL_N{z;`#qWY)FZXNV6h;vBXlWhxa4|cGb)2Xg`|H<3Ei{gIs=U>|+r{i57nmlx zk;?(knahF-R790vx~^c-q~NvhT>n}*^T-WkHoEKyR4l64zE81UPHrS_e)`T&UAzZqBwM`KHy( z&YFvRc`sQ`();_Uos}+gg82Yi7$BO|pQOBPBS&D48UK9q!T3SRm_u&OYbhDrJ$zB$ z-Skd9+Kp$leN0|za}f!_*Bdc^7cZvG{GiM>;a=* z^iIlz5*|MWvZ4GKkcgA)RfSKyXy9l)!uE~_hE0UlL1{nY7J7u5;^sdUV9)-c1ICyz zh-6HWMM}4Z2}RSU-uh^M@X&w2bS;V)zfQ0q!LZ3oaZ#;a{&^bCFak{`M1fwCv=U?U z>~QlFsv67@hO@v;N22d;2uMQCEwRgucakVN2i2k zHR|cRNQjy47wYtJDW7>(*~@(JzE$F%dEZR)-eOl?sZnN>*s|QfghpQ9&WnL*TpeNk z?Zod&a9|ZNu;}-!ZpJ;66fPscc8!4HqMS&&8giQn2y)vicj=v@;3W=LyC>nmru$ zos=Ub)-yu(nJwF$yL6YR)XKcC<@u%2J3wBj_ZBpTN73QSkRQeiLi2Eu7~ow|sFGd# zz&3h*@R_1aQ>s0WTJFcGZzC$)huJa#SKADIGTGNP9>1p`ihc$dv{lgR;k#~?N7ug+ zbm7JIL&GLn-F(qnb(#O|hS5lm9*Xh4=DzX?AbKeTViPe5gM;Lm<8PUWhHUT+h*mUP&MM0tz%zCq~sH5>27o@_i4Z32vO>K06 zZhrjKjN{zLb6ODwga2nV>S4F-EWOuZB0BB2Ff4I7aq32u?suGNXV*FfOu6^Hq;`>3 zg)3Nn^Z3489UxW(s@SPu&xj#T9o(>x)U5=BgL#L_dOvQ02K z+|0e8n7zX*1J^8`>&S#&@5A%pH$N}W`)_{j?bT(TlXUiPVZsSgMhjy#mHwH=`|1;Z zmQ)+H=8ojLe$-JZy89l{0ZzGV0KCiE@c0!)aQFN^a{rb6m2mwzTLp;?ZDttGSbAI#JdHrcyVe;cuYlSDh zkqZH{5SZ)SoI0mDy@)~nJ6e~9#6s%(>Lc>1Vw@JGV&C^VU|V<&URAGusM zU&)+9i{>^^RN}nQ-P(b-aNUWoA{1blspAo^;VXK2j={<~j^21_zjS65-R5&mlz`ZM zKi4{02)1a`=ly#AU=)>GyZ-{!`9Z5~b_ZrmwLYRSu1uxKP9;K<`{&V}ys|y;Qf+sB z5R=;ld(}CdvTY0PQ<9IeR)_Ca<;+gQ>1sh!RT;H4D2}pcviU_l6lGKF0Fs^5!RQ3O zWf!q4@iOUPzf+U>n&p+7zWAPn131NzPq4Sc`Xl*_xAvv1DSZ$>pRjsKS zM9O26t4CbfQ6YNTdZV=W-pINVRd8w3-pRMngjyIs^|XP0qfj)kV1JeyKyqghJ`i6} zT#3_AIsRVgFrit(+t*huYTHRZb}4Ypy0+Iz=j7I1Cxr$UYo#Q_Jn3R*AW?UA-t4|x z?ruSJ+FTy2!F|t|szxzWv2MV;22kO2H#hQYS7=o&`KGo6$g^fqTHB~c;Vz$zjJt+E zW)X@mnp#KxS!c8q1-0ah*6zi+bqitSLp1$)V{E)<9wyCLVKa-JEH!FG!MZ4K^jlwd zc!%*o%lwXG*nt~Vvh`S z^wTLMxu(q%xs)$uJ}A_ymG0}X;h9#{#1$!k!fJ@Qu@P17UT{DnWdY&JN{~Z6qn$D1 z5LHHCLUucH)1eD&#sGITyZL<_d39FE7TI9*>Pc@mK|1_<4EX*(CKJrtFy$X+dV7eu z2;B^6Pz269+*+^ESY>1Uh1Q6fi~@_4m1a3E*N{S@&rho=6sGNT6Bi#RP`4CQ5j0QR z#n35mEI9tafJ%DLu#LLB@I~Q^Bs|jS&x$lKYYiI~;bhGF5b!La37pg@c4E5Udwu6P z%JZRA(FNg79H@z$0UGu+kaVYoLt+C<^r2%GWyqN4M2*ghY|>|QJuU^cP7Z0YWOo6i zb`u`6==l1E>UmJFL3P~TW;}4TjrU3m{Z-sG=pwn5bNjH1L#y)>?8V>E4I@l zM~2(`ZqSvFZDjbvCCwGVW*H#9W_*SFShTFpt#xT^=ZBbqlk4}ktBxgl-c|a3Q-k?Y zB*{u%*N>uxOBZ^KS7klT zpTdHs2xXFo>Fo4iS{XbeHax84**&^dRnH{X+*{^}HHYJX{91aE_9VLmv((5LMPG2Vw_OKX|8EDef{An|u-Zi99 zpA#N*aO=b)qp=Nn;Aqk~r~OQullr|}0UpVej`uEOB~Y}0V_HNMhlT0E39f#2D^7t! z$;~ZA6@Hfp@@8_2%fKYF`VSuEG`2c8CT85G^BLDR;NsCn49}OR)v%FEj^dFCY8tiH z0>_b|DhHmSXte2ca9H&Fwm0&zUVKUiLt!3TW1L8QhEO1kPKL+Sh}rq z-Rg~tS%@}AVkw^oulz>ot?nTq)APR_Jgv$kj*8Sbqq&7sGykECcLJ0_HAArw|sUD=0FNOZ=3J4j^>o?3bQF-qLInun#6KTQy~7g`G-~bzY4o_0KKrxI`immFCwsRc#K*yzt{1I zc~zBG(gRFQc-auuSIn&FQ4IDHO)y-+!{#D#V0+2imv8)EZ&;p(rdGk!lfY_pw5i zaR}7%NC4sL>Dp0P0*bHk_LW+Nm<=Or&<8I5vwf)0y2H^>;Y+yPYF_{gk<=+d(QU*_ zd+2ZCeBd+RE|W<-;;NtI=Erjo$vJeuSJoIoiFp|ozV=_bas?>g~jMa>?z@a6}xnI~j zL+!tCX`aeI2ugP@7!&T2J4*V&`CgDF!lpu2J02wC>T`17BwFqi9OsMB3Kv^mmI&I} z4Oq}>P}d(_xF(#rHOf^8-z)gf9y>HX-nOxNJ<$dtJA{MR@~^3UFOky?SjMjciDfCd zExkGKUz$@;;FfA;SIFT>zI{~iv=pdQLjLl;-@+iSGEv0irTN;=Zze7xzC)t+W=6^B zPbpR^r=cf>k(eT(F7Ur1fktk%E+un6Me8a@KZdE7u`@)p37{U2RXG6W*|IC}OOF;` z+=Yrl>B4nXp=2X~IbQwCMyg4kwsmY%Y$lYpaCmh+D@s-cJFZhtvA^ANJT zPJ3!m_2@Yz_}RvS_iv_O9xGiO>Yec;|JwZ61t9zwtGqvQh7n&|S8>}(JUhKyTG4@1 zYFsrF>{(Nwrm8ppP2^?l0F9irx>48LzON-7sj4p<0DkFmKs_QL8uRksE5Tn9P{}6# zwofm{x_mx*igf9YZ%sb=&NLx&6Ph8J=~>RdT=LF796f4*to`oyobW5zEd*Kvw*7C2 z?vZ-wysSRgdcK_cJ7O>`yNa^{Hd|i+5AS(Hwj^l~e<|mnJzEvQ{L0CyLT$V_q`%PbWnC$Dt2AMj!K`i?| zT8kH~`+xr#>d!Z##+NL_pP4?UwzWyae`WCN&^>)7voe3sL#J9x^xeo4wKon4vpu$zF?Z3~Zp^a;8+Da{uF;~67t281ca`zJ zZq63^R`UGWmLUC=*0r}zllx~V23`EmqdkI2j&>vRyC8ff50fd#v*8{|_-I+lxW#k} z%aTx*0>5zD9=r6OqO@etqk|&x4M4rldp@r6CSH9O&Ic(oy4eKQWiARGYySAuC!4!% zda_5xaehr_61zp|O$jH5m^osr)5E2+;9rk@Gc{4TkjW(hA~ zkF9QXa^%dWY4EhCyJ)?R{gCF-&IQ`#;h(bNgx-F}cokWjlWx>l zB3PPE!}n@N#G!i7t(9lonD#N`Yx z#J!&i{W+4goBN(^%ORG0z)~#3{Z6U!dq0uIr%K9m$t%QyjXBGA#hq@NVyvt~wjJWo zjy^wpl+A2G$7^G@RM@+ri@Pz7IDZLk*Jiw{-I-b(?oXgc^$C~WepPvIAbMnv^#Dq? zKeTy(?~7Yn9OiAb6jhBYGa#U@qShHwU@6Ygi}08B%(1Pk^iEM&OVCfaylS)^&OAqU}O! zEaR8#Y#kIrlGqlFxF_AiY%(9n!vyG;( z4y*PNd(2dgULPUkkC*(l>RBF+FkM^|RE;EKfI#yZGV;f?{-iWtd@G_lqv{oalSu?J zxf3RgGI1I>s&9sL*~ID= zbvY*N9w(UCUcOp5D?{>81=<4dv0^9k&oBgh>y0^1eOUP~y47!TzV};qmecC2U@6Zo zpv&IQ?WmGUr||OYecxKF)Ye8#+^LyYnq3y8b+mY&;Vfh-`R-$B6B#<>M@(5ckrkIw zy%Ep=S?uRya0cTK87-y4rPYpp?)(a0N#~_sLB6%cRKt7dt&a~lw23h1uyW_?|F*62 z-93{LqdDm(MvpTZ$y@S*rpf6(E2XD6UoEnHO4Lh(1}d@Uv{x-!faAov-s&Os4NG@y zJz7-XNP4c*^)}DnQNI52a`#oJdeJ#_a4={Q-TiJjcl?t`KNy-0=CC&XbNY<>o6h{~ zfv1rEOydy9hpakvb4w9e__c4VNr%4DwM?yV^dlva-6Ddaj0M~T2{=Nc9o6&uKF*Y`0w4mOxXYH<<$&^A23LTOh4%BV$rb$ zb6OV(>0lPD=KPCXj%nSdIUMp6hrSi#YLHBK0^A7x zGL~Frz$kg~ojK|2R;R(s5`ycGA^h*;%9-Bo+PzRwpE}FKNh$W3!DhgJ?=)3uWr@#8 z#s6T_pJCBuXT%_$W1Zmvpc}F$1z?alh1sp4=X9vTIjOX1%Kq$gpxkt^|_CRKDb$-eb{qBa9^!jb_@jr0>b6 zbjbfX$k*6=QK&Hse;x zjn9x51c>;h$Excra>X``WIBbWJ>elXc7DxeE|7tlBSY5Yr^G$ep? zE&5c}mL*GbUhry_14d_)1rDJXnx`dyUVDx2tc`CSY`0)+NC8GRcSZo z5?w_AGv5OkGxW;gxIXyaxR9v@_Es(H+f3&C?*>gY`N_nscXf1&-NE79>*O;a}&3XwvIvy)g@k48MMht*5Qv;rOt6%Q*ir$qi1^zUqt?da(b~qN2*ev~0XF;)IIl$yd%2ZnOBDVHM zy7eou0)x?whStvF-YVswC#I(*ci*L}TEcV~<7?j6uim^gJ>253mO1e9l2E!RIBb zaKJ4m2agT=4sSJ%vYlT8f>=cvu3&u=wj-LAwb*rJ zAuHDVl0TNCPjte>2n<-!etYH|8cXOpu)0e@q@c8z*mRQhS3 zl#lywuJxja=a>&fqx0#=zhS}WBE0tPs3Z3^iG%Q;dN_Ens1Rv6|8RkAtR~RIMKye8 z{a-tfE}7$=v^X6m3B{gc&kzUYU}@Ry&U#WZFpnM&|;@d67U(RK&h9h7PtU50%K14W=@1Z zgIp?lo#u|NqbQpLYb*h2R3%Ptiv9xz4?5_gR69ajOtJ|7*RlEbQq!dMfwTvqG{Flr zfmw2LJNV2BAjF!PtPX2#+ID`tmcC4(PO&LKCI}yfGw#w<{9XWQ&nK#n@;4LesYMyH zZ^tA)K?R1GXA}17zEw=+2{nL*a? z{%>9LcVF;%(#Y-)_)vZ3N`Lu(&ajSNlutAm=DOG}O)^=EUq#p0jG%=OdisUU(~3?Z zi?oflYYJOH9f{$jq*j8Ow6>hqr5(Hv0}qMyiEtj%+0m#e5HS}z2aalh6HkM`uC$~y z9}r4#VTV$3ixtZk+6HxTyzD%L-LZZv^pd7vJKN-kC4T{*34B)lBa8Z4C&$$SwMHu= z@W&lnl2$c6$p@@&y%l!~TJPY!uiPmh57dlCAfn^rjrAf|u1F>5tyro9?XUQ=P%atV)%r|HJi0$CkvUI8T<^r*}83)|BQ&_2v7R zYY2=ei!fP~o-2aTobL+KzaWdMOKk5`0i(V($08_-!C2gl8Ip1{&zlL7$k81WgFo+H zD;^RkMr~fMU*ojYXAcgRq#OQ9uC>eeJH$BrX8v>d<-PNIchqj-P!JbGn-TIxX`K)M zeLCW6$PPz9@0*aiCN@2iA}s*%doP6EJ0VM~x60K7`kKlcF{Zrj z%S3w%$XlP;Yxdr{(ev8Vo%v1o8}Bv@_JdM}ZcBM}^&0i3b{@mn(mg}aOTkp7+y>g2 zb?OFX+_R^7b>hg)DzB57yTLf_UDQ44jdMi}c1c22?>TLB?x$@yh;gQ~G=Cwzr;x6f z>{+0|XFOwH?e@NG91v=PN!;?gs+^{Lxbn(}`pIXFbC-xCH&AfUQGCo`5+;;JIf5DE z^n26PV6E!{3y$nvYMK`g=8G!lglZwQAqY971Av2*62kLogLA3VDi;BG#se(;5m(8j zvK%uNO6RmB)w=La^Ds%QxQ9jl>~0Z3#NlSt;k;)(hKQ8l_~~dRKt`WV>U55kvuk67 zLl??9LT(+M2l?jm-G(p)`Ehqddinmj7U#x>@iYD!;>awdhy9BYv|Q(om!bwK%Sgu2@&tE?D#DGQ?=3Hn2+`tlo>v}q`w zzCfV5jb7#Hxvz88we!|W`Ty=v{}zSWxzWWe>T6||kLhlqRN1ZbxO#i)+UvhpeG|4GB&&pd#}lg$h+8 zPLAnVW7S8px@ks?mr%*dJY?^~a&G@cKAv=MFdyD1MY__T5JlFH#{t6Pfv-h%{}hC5Qb%QI=KY8ES~BgP zK>SL%%0@|4;2he7IkzJ9Egnum7>}T%$aF@UY0>x)O(_H`y3xW(_Plc~B0_oicua=Q z>Hqe^P~+LIX14+DIahA2%ep=gXgT(~${TRo^04+*%g8j$3Jixl0u@l*pd)sX3eN>>XxQC#Mg+^xyiVCUkZLw)zK; ziwULp>OaKy`Z+3F#l$k1qQBT6k|{kExM*+35>j(R^JRh<<3 zCAEPg#pg_ip5!v%2v-k}9>*HbqHONUb7sP6M1pz_X1{zs$|2}YKe*r7_#Lu0`I}wL z40B!=hkhqw#L&9tAKWpK?)+oF{P~lG@4Rbp4Y=;kRUnJ2UgMk-CmD9W_Ak0P0i{sT zPK=1@924*nKyQ2Jg`&WM%_b9To1qidwc(1NZDZjYwP;%B%F1DzM#I{kJf~M$|KYKF zZ<$Z+)xl)HQ<)`5>e)`wkBu(Qk11;~Ow`fJN8z+4X8g7*Q-DjJ%JBuRm|7A!ELOb> z%b17}*O>xHN!u&jn6=MAe~J_R*(yhM;u08Y(59~|Nbu^!y!x);qF^QSW%V^M12c74 zeKr52871?&*Yr?7dzyR6AJYIx1G8Ei3-M;AsgIJ;7bAaB`s+tuouzI!>$wi3bdFL7}d!uT#8F|Z%1WKlQ5b})PrC%P3(){>GG+2b0%Hx zZ?AUnnnhE&%KY;+^L(Axd6KB)eXz%A%D+bYNlDL93+7VdT2*mvV`05Wx6hW3cV<_w zlA*%1AT%Hhr2eB3|8Wn+NMS0#ATjd9GCQ?ja ze_U6KFJ}k(AM#(FEv1E|@dfVpU0uK{LayJI|1-kz`)`CpqxthA;! zwaSF(winH2(?uy7h-gE4Nq$G(Ql-+WL|PqRm8TzA531t26zK0R-iXlggy$h0RagXK zt@8QP+S^AJ&%YyG_EpP!UUY0EH!QtIrTohkpPU)X*lsFKC(k!8x@6fxt6=AFQjwmKub;8mx4>@b{`5x>e_I9t}ryR-s;^yMDO zRTvI*?}JqFYds{XTJ8WH7*MUSIu-#o+&HDDhM9UmZOl@cmWC+Q*pHfKD}aWo``OWD zq^BUS57MRG9t#hX8GLYl)opxyiwZozRX(rl;*ja=fc>@#5|YWxKmYB-OqM-9&N7+( z9_-lVrQ4lM76{@E#&MjEu5)IzYb#SK?F9|s%)K-!hbpqZmeiLwa(G0zlTt#7HfAj# zjv0s^KrUjIAX@S9o(uiNfEHaTS@WOe`xv;c{9dbexoxTUPkwUq?C=DB(3JmZx$SmZ zzJAl9CvN8*1Ap}51NhXDjF1D(KMvM|TT^Qp{H@1;ejswhdD8Dien`M!aZW0+fBqUP=Whv zCDR+pOPDgB^6`gChHTPn6x{U_Y@s_t#(TcIgKv3*Ln3&w6M3wAxpuXXUYt4XpQ1_c zqf@dX0Hmpm1u1bcpPD`spUj8I)N3j_LggG-1C$;lF$J-e$lU-7qcnVK!E$Ym4-TO#_bFoH zC=Z@W9>s}!Ham{JKQ#L!Ce#J-Rwf!-?3+GyXL~c(%ynON&`IKay2Y-rYq#Wdnh`YFRk}Z&I<4DPO9Kc2qA9#cbg*ZC+g~LY~SnZ zGG640Aidw1j46ug6k|Dfen8K;gxa+-7niN(+fj+Kevs0>Z5jMrZ#Cc&!*Dr0tCw9g z@L9Cix;RYg6p3kEp8l_KxxOoUO|^(};qB#B(SR$w=&osfvp!u_!W<6TR#$-oHg%)m zicws{PGJ0}?|^!#wLgadxh1oBR>aH!QjM!q%SVQeM|+m+M~x>tQ6u4PD@bnrw#^ATGd^nso5I+w(%JtUC za##J|bkA#V+s%z2uFQdJ{bbEPjyCq8T&R$7h=BAWIJ_(!CQBeR>JUc3rXruj=UXk$ zhhw4d`OV>-ApCO03iGk~IVsp2KfGS<{+aGw?*B!xTeY2SluV(kxO$kwZ>vVl9Zf&4 z^}p?n&v{O>2;UeuSd--!s<|@9uL7X92T-b4BV4$zhgYe{wIGB@QCfIVPCN{(8swa;s#TV1?+nyN-ps_5VBsLX*hP zJ+(i|$m-a&g5S<0mM@OmHOF;P;Hof%| zm^fx$*>X?0&V{o(XL(5MK+e=J+$3ZZ$%lz@@`>>_g!!9kKalF!F6!CnVn0dth4omO z=2>Nsx33qeQ;r^#@cE#-y*cpmuMGqy&0%&0HPbrOx-i@I51Cwn$Z-hht;GwDQctwA zhR;hQFBN^{OCvi9+yTO^%=}iQ_7+VIU5fak_vqrOcfadg;1BFFOOa*&y&{p#F7|(U z>_JroU(a4Lw0q4G{h0LXBXgseEZ{V-=cG^+{%#ps#@l6_UOUETI0AHr_SsYeqYb@k zpTv^Ry!%2<0-;|)2HNzmd}17a_Q<(c9y4+0^*u3s=xiRi9GrQ$OCMNTKh*9k z{m0rA3oHzWxu4U zagxT3HYZ!;3!vpUn{+6zG(ULBcwr<5JE4;?Gkb|}wd4nYPCY1+=Z<)4G_ z-0oVtj{P~-M#&f=yUhsHwauKCebDCc2jir8t(3A$P8RYCAk z!16`35-ZKS-jva@8?CCyo)nnhmp|$#iu_1U+df{Nx^(v4;?{;ugQ*%=61q~E(S+u+ zA{&?qYs8&u1XTjHTMZky61^T)`dO!lPN15Q=eGJr-E_g^1<#)~9-PnWExew#KR$to zYy7nEzT&sNn|~|Z*VjcG&vyHZ+ymLSeV~;F{G*w1YORIbREI7VWXi8kr63!la9!yn zcVq4>1vxfsOOTEtTb7C(RFl$KgJ9qxXG;ZO_N5x8#dds?fmtO#$gl)>z1l z8**#bzH)iyH|sl}pCjHU@40$dp{>Q?nCpzGk_?*mvc*JPT>#xM=Ai@Xr-Hpi`z8p5l3b zO2_QWMN$4#f5OF^8>Ea+P%fH(hn31>)+G6a7r9;{3Cqr`N%)K;)d2C7|E^=lNCD4?Jn?k3adCXnqOl^Mp6+DRjA2xxO*KH9^9#9RJZ)?pCuNdP z-C7-dL9@JIIYfdlYH0RzMo|r?WWkp|w1sSI^ z*|2IL*R&@|ZVam>l#lk6UV|y%F*s3lW5anJ$nr4{wxhp!Fv8Qa{jG_iZsZ$PRoz5M zPrisCx54-{xAkkK^{SJD`F?LEJz2)Wz@Oy|Wkg2zTLL@k&MLwY#L&N*BWg+iuI{m}Eb^N>agWM861kHbn?&BWM&&?ls^G24S!w}2myNNq{&`!YV z!TIX`>@2;rH^BOu>ZqXU-(TA=CLR9XJT(8%_^ZM)+wLFzvym^n9+)L&VAidlk7-&L zR+42BSGcBXztGQWrmR;OXTzgcsQbLfr9P%2)I*PG+DvV!Xv;OJvYE`=ZJOD}hG_8f z`yZX>(RIzJuNT%P6sEV*4df zs;X2WIb)B2)zTez1xIVx3Rld#p`6LVL&D@tgT)C|)DzTimp6#a8}F3H9lx#g76U$U z0@u zx|XnYF4}0$$uTPN(YR9Sj0hQA)~K{8bv&!5?2=23wiGl*`sxMokIjGc78(u{~(Il3TdF<-%F5zG$PS|PH&w)!LR z?BvY`X;$$hCT6P-^j_;@>5jJ`Yv-p}Htf`@X#`bDPjhQBFJrDognE_7yIA{ixYh}Z zX&lm9O<7%W_1iPs7}-hhH_B<(A3JFNLYPE^?AoN0E}l>oK7~+Vr_f%Zx4!#VkV$Ex zsa%_4|9ABTwtfytAM{>aKnZ{>+6e55fh{K8Gr6osrEIIe)+y7u#^o55?YRQ$X)h(I zOM9wTDjSXhS`=#!NOgG6N}PT@f5{F(qafXuLM9Jt7G1>0RZCOE<&7l&YQDsSGIyE> zJDxrBMhfz|)Z+IMNA0iL7omE~bd@sJ@3_7sGC8C|3K^{`T$AbmyViucH30nLn&?Hn z8L-xMd`;n4P(r+m{#spgXYFDN>;^dyrY=Jh-(KpmBJ=#nH1SpE&X~KDXZFbyP8`|H zXAO(1A=9lbm6^`tn?B(qCVIaV{&W5-m-ZvhQtCI4Q%f`c=#6lPCnPYxXJ^+O__F#- z5k;zpH)5s*TGnJJm#pVM$ExCpx)o)|WqUx;e9SU1SYmh<2|b)LYUY=fzctJpFOArX zfysf1C=e;ToUYYPd4E+`k00qc`)OPxa7pT(s22#=y)$ zOdb{kMv01FfPt-8OwS^{R)GR~@VcH}i(97G9c;W$PKTR+%plX>xh}4kkis69^CbP1 zS`-~r)uYY8Md$<^t}4l2Y%jN>i155XZR7P=pNJ?uGKN#j#jz?<6m#NL=q9!5Dj}fR z0q0n$-Z~)ufI(1>2<*=NPq}qHyrS)DQs+DuLCrO1zEYaYezx$GZp_s2XIZF_C|9nH zm07}bRL)kpf1v+-_RkXF=;K>;~;ahq2t8@b@tDOfY zbIKnVFGeS20#+~TNuT#WT7?dnUia7+>qfI5BRFE%|7d#==<=u$tgggJk%1}?!rAL= zmADVKtiVO6>R850KhT;5U54J!+_5ZJ;LvyV#GA1BV(u=OX_e;pHScsO^>VSZ=3zGl zx;op;l`a2af6>CC%WazPc8jxSRc6mMEbFg2pXg;h5YSWO?EV!1+%Mv9?Qm+<2PX45 zDBr7i=4_4KG|o3l9epyB+}Ny?+%OU%(K+=y#N%p4Sl!Vn(zJ;5N*>UkeRDJO*vJ3) z;(#IeB4MI^HDHy-EOoA4BcY7@3hr5ZpjlPbV>wdwS6yXQ=?0xFPQN|j9+DlPDG-SI zH5*5%JT|knn1pV*yL32Dl~-d1u5l!J^$5K3yMw#wX9vOkhY6YRubM{7DV4887f6^Z@{T|88EGvt8?*bIY z<@qey-O%Q_I>Rg@v5BrS75nV4M>~Lc^W^)LH6T@*2P>blYJPORc^{6GwonQ^!AG*D z0lvGm(xt`(dEksK(YKS~bPDfDj^laTjz1EKbeEvp*P!Y^X#R7sk^k>t!{6DLT^8T` zSkbu3{{^!Z8klgClTWxzrnSU8?@z*NUPuVi5E#n-rt{1hFj5Ait*B`ej5N+sVAmBA z`H+LqCzl`0_Etst!4I@S)@M#hgmuBc$vAf+-I^&RCHQlV+IQ3=myx}*Q)xDmm1)QN z9FClSg!0?{u=Y9oro>-NEAJj;+LzJqsR&Jf_9yq*2h!J^4kAfiiBM8d6E@jyw1k*i z_rx3Mpj9pObh_-jd+kc+ow8q8iw}tIo|h6WRSo5mA*4>=4B029(!X0}P`_WLC9atX z_ZQ3j{&n!get8H{#)hZ@UM90q7yz@5W8EY9jeBETRla^RjF7qTZp+N<1&Xx$m94H&Gp>N zI3gFyc0;k%{XLs2+aY`H4Yt>3T4uq^G-hTH0_Bv@mf-YP0;kD*RN?zCZG*~x-hj|2X%dz0#}Vw@f3+R$cvuoV-Md3n zw<_*dKIVGDrAsuT`M%cK_Jt$nkaVcn3e-es|It)w zL>@#`kXLHI^6_fQ%IxYoIVsnt%E?;18Hb*Cz8o$Z{b{^G# zxmitHcWoZf+JV4QjhbRyB1kXU9=EOHZ?voKaL#~;$DAU zD~!RBs8q>wjViH8vihUnV?O_nse6pC$ob~E_&S0%QnOUSdGm(Y(JM!V>3>|6ekX z%_Oi*I^XE(xXvq2kT(Ck#xjI##MXUWE$JS;x?33&?-P5!0%8PrRXIEHZPC}|>3=@`( z_uBSz7-XBwY|@h!j~FHh&D`#zu&Z`oe8l9~$xi4yZL-bvoETE-Y5G`KTy@-gjrWmq$MYHnuoYdW=KVrt?uDN(?wPo3%X8Mrq)O&I?H&4Tr`vdQu1cq--b-r#`;#@t z$ISMw&OaCU0#3gQ0nO}{QCh7td0C1OUtTC&*hXwy<Uv8G@%u;|_w- zmi(_HADUNfn85tA-a^4_QOW}8a$Us!NDJEBht)R-GDKZt20@}xF z8?hON;0_(CFn7b<7xBC+zBut~Xw%nMzSv0R<9ZQXNlQll@YAPvzVJ1i3&1^n?~9bM z)W6)QOP7N-t`Of+=3u*P&gbSIwQ{q;75RXbqqIyk5Ufn3cP6N_O#vIn-H`3(AK$GZ ztw3AAUg#1&qYu)`THuMX`&>HvZCT< zqL3OhCG*5BvJ0~%mJKxU5g2Nk>)E-!g;~I56TQuooYX+Ry8D%3YF0D{Y#*oRZqBIV z76+xe{O+CE>OXqAWB4h|1*Q9=SoxM1g0%8bBCi(WnqxML7KY+16yS0!Z>Q)>e#^9^ za`F9SZvq&YB1vrE=Of9@_WY_-~IF)54Zs{9WDoO5)G+5E&1jkVon- zm1iGxjxqmtiCwCNWuR{uid?1GVJ*{axO_i7gUSu@kls~kBf@9TM4lm%GL156BLmJn zUL9O2U4>a%)y9QuC!}pt{#qHSjCmog<4KwFy}B*0qUmyy{CB~ybe4jMU}E~hgtx4q zRjd2)XnFKNJ{;q4X!pbDC_Q9OR)P~k<-wIQhT2~%ZP4ef?;g|dlT>b#3+}UYFQe?Y z3Ae>=>b*Y=4=W1Vq>3+A}d zm;Iyvd@8>$^PtCS?j9$@m!y4hmE~54DZnLPot4YL&7&PUhLn~pZk?UGWl~#34VFR_ zw3Z~G9&gbw+0}i0y5OC51rv?zKmT@pd6+IB?2oDi{1BsJdKx^e}|WJ%r0Ei zO~F()%~iwp_DoGn`pda0_S&^86Kr!NCYnHqbNzO6mRd(q&^Bo(84qnjWD}Ax>N~8ezm|K~DqdZATl!bBzI!;bd^N}YQy&ge{#PGnL#W0_Q`Mf$5cdP7-)X%V zP*rM}Z1d3N1=`{nM55HIGF@&?i7Aokt!;&`?Eot-%K^~{yQptwKM_pu*L2`_ca2A! z*0A`fGE7CxajvLV^vr#iKPmmi^7CVUs`P=-*RpKe-d%wqK%O&Q`eH-;qIhs)aVMrp*}P>aabb?L!sfJe<^0)YX};2hv-Eu|kIQb(dou5(NSlMo zc%>+bBR^Ke)jeTiPUkkYdF#ktHC_F$)w0^LPowmQ`0RFDHP3cGZ;sCVV*!$PNLz3H zg5Glul8YuC`5h`uVJ2uFz7eF`Tvs|BUmWwc~kF zxV0Yd^ATCTq_e$gR-bS0)_gM>yz)b*wQ^4t;#)T3Dd!B1P0c_5smH71s7>)SR_kU9 zuAi!Gy5{uu5ueW?sLmhB|MKIOMOZh1+6;<9U3RUmcu5Hq2VO*6NUXVWQH)qEDk~fV z+M!G_G|i}W%lmR`?tHtfgW!42Cx;^|7c=|Z0DgQn$Zj_H_K;65`%pS2dce9(0Uw@` zm(fD;!{wUJb2F84Rc|-meD)uGU6IcdFD@xf%>^^JM*h_=Z<~Im%GGOIc(3@(IM;UV zQ(_S^WhnYg?cA!{LMv5J*M3e$rh_JIA~21P*xt(8Jua_5^1p0^Q97}j+tYMtD>|en ze~)`xZaW(I_!xd(jxhwx$l%M$EvI`{y42P6j`Ml*y&_dXdI@@5x?&lg%X*W3^tC>k z#3QF=zXyDwxM~Z!!bAF``VlKaxFcRF^X*{8q%4)Jq%_HG)4bH!agwjXsJloEwu|e= zz+Z1i%rT1bme|czFM|FqLzdLZn8B;Y(SzL=be*+c@>r%JfB9i_)rl=DJ`)n{$D$RN z&hJ(96iIXWhlV+CEKjL4%30yLU%;Ec0#FSJsLv0KQ{0c(jh~EWFPzmEEx%i*9ByU8b_p{3P5LYa6hqge zHKeX+xOe~F)y7w4YkFzEA5IAP_)PfDh8rJauEl-9Ljmmm%2uRcNFCJxcH0iPP3l~098CS2wVi!b!zDRv}&HfxjVWUi@L z`c3h0^djUe@y!?+TLfLi;>I<*zbuY@=Uizl`3bm8idnS_#M8W;HKMB?phZFv$4ut_`L(Oyl=;*ZN?27p%Wo`{!=EQ zT$wXu`{qkAh*`nJ&%@l*H13y6N6?Bj-GK51H>l=PV(qb(tDj7irSRN7hD(oZ>o*3z zx1+1A^wuP2SI!G9HLgF26X8o-fNWYBDrbDY&y=@khra6EV^)Z#KF3liA}YUu+RXTW z9wLthfJ8PMXvR|gk0OL}xZxK&u_Oc5V!Uvw8uHBwcUOhiTf_igbkTB~?U}KbNm|K0 z8SJ>9K(s^x0=2aW#gbxZ6seM)oLLS*%y~=ze2zR>c65aw#lP8ki1QY{2=yGCj_9a2 z>1@hxuGLS;xbcr{JvQ5*!ieQe)%}dUAM_I*YC7KRwgMM&3~km|1&0?fMMX|T-hml3 zk7Eu4Biv3>rl$NQN(HM9f_I%*^kx1uKZHQa&w>DZ1@GWwKgroDfOFQM!<_;ItW5TX zK!r>kj%KoUy?1ng>tpvars*ukSnR-VQIy_Th?%hWy26+&l%BFZZ^v0?p7%w%@^SjAbi zb6->B&4uCar0R6*US-HU$GOqmId3sSd&3-TeqFk@F=s|{H;vYMSyjfBNYWC74Gunt zB)-$iV3RVWt8ngP7~i^hvN+0g8O_spUV;Cw&v(ax-^-ZqC%MLMYkqfvHP^g3?oW`v z(z><;{Rc!x^io5AERxBKl!0Ux!UQHV7>|9ElZ*wSO-52MT`L;VYEE2(MJ0QTSY6x6 z1TuU$nD%DI6x4m6dn((6!`2Y&+CpK#u~I$KZF?E&PyR%o$akW}DCp-C{C)lyOL`n5 z%t#oC)~?>Zvb8t0O<&@}fn#83-+;_eKF_;O*wNo;6T0oN#X|a>_-S1JP!l)s?Tdde zhyX&&#|Yife_V!194SuZnRY=WZU&9aDBacQiy<6k-AUBQzevlZ#KjDu2A0~mC|K1& z9J$u-%E%f{3WHtLv=n-%O6n$Pr8?p=CnAR+9~2=UYYO8yU5;Z$iwpsefkUg{%7!FKV zztTYZD^BbP$+a9#z^(-RXyst#?bpiNbbT%?_J7hVodGRN)v-&fj~AW;v&zGK0ybj0 zxg^bv+E&=+?A1P%%27nj*>v`8kuoEp24uK#9q6%9;vsUS4U!Idwdu~d>~JLzC1au` z5WZGSHviPt+OSE&JxC55WyD;ezQ2?fnNJy5sP;lWtW5`p*D6vE$sU!!GpM-| z5gz-pWH9lcCIswTKLxDMTj&xsLp2&Bx%wh8MjX^|gX&tVK+7U!b?`O`kt7_mDC(75 zA{ojY(AK-#P~`cJ+aXp1CIdbugI?1CxG~tjV$cU-KR2zi->xiLz($}ySlp1Q#l-`O zi6<;ZL5N0Rn0bQ8)sj+fm`lk-oppE`$i*maSrThtCEv{HrZ)}T1*pkxD}+o=d7kB;3L4AnlKfTl=eXo#%N|^EcW>_Z!q(prjc0TEI0?G1F)*DXuApEnn07}rqzvP(@5aDA+@@gF>PB~ zi86Sdx?J&`=(I)M{j)9SNM?F{Sc7kr)R|uWjLm4YKt~fzfS6j!0k%LX^UT|Vc=9Y z$3IsfIz4#y`yd-HSg{exst1LXb+pnlw5>0D?cpU`%oi5$CecKH<%CNq$=f zk%i3q`+WFL44x*70Lk<1_f8B=pQ;EO6I)m4%_0r*R*0?+O_Gx8WQf}V$N3)pX8Ut= zw+nBB`dG2p{z)%n#xv+ANOKa`Vup-EH3O1|iK(}O;)G&1Nw%s^t?Et$hY+=3IgKnd zxgj>55i!vyiUui%*iZPzt&PzH&W0BP5i%wk0`YFmWc5$2*&LSg`&i;G9oGSvw~*=* z56*s!yMp-za>)X_(Ik7(9Qmr4{FMtkd3SA@3JH#W$!dRLo8&QYAGI;8h1VF|SZu@? z+|#TP;F$lRwaFztJWYFE=bgj@s+b6~!mF&munVpY#ZowC5-mFlL#_I73OjKqB~=53 z10@kEYJ#X;T2B#8nqZ}m;f%|s^yg>su~{`nKmdO0A7kgxbs4bV;rN6R7Epp)Br+&A z$i&dW18EkI_wwY2!u05Mgt_+P023^&9O5(pA(gc1;9mgCgxLnc8g&|VdKq=TVEURf z>HSmN6_B4s`*s7ttdInxmaf!yr(Sc(Ud*6ju~2?aaypQ$*4j+;3yFj~MMa7Q*a*rX zasOVjCt&VR=KZPHe=^VD|1yu(dQ6hAo?;k~DkQ*ED<)?3Lp<7T&qhjIwsC6e$67TC z$`-o(G>DZpwPJXypn^3?{)3I7V`p6<|Ksg}5Vg`FRXEOPR4mpJ8;<0lL+$5Li-cm{;2(ZKt zq8NH$n1si~a;17IPY5I^OtPm+)G4_IVe6C38x>o$7oN$?^m;JX^W+CLJs>CkB#7ky zB?t<0x-vQC#0$18H4C8qw53RlR!?aP?8 zfBRVKH_GJ-H#iJ940@x%e5^mgAF_>9+@(KPZ+`rsSf$J-S*>`MCQov!7Z@v~U#S+O zD~yu>x`xfdyWpo$g;R#mQ6*E$Qe_^&mn3-UR~f@taf-!>#X*e2xr`bFjHG`wiN)#d zt|`A>J@$OZD0L1tD`4L$^qZ2FsDc60RhuHtJht+*F|k!+)6qRy@Cn0irV+0wO`*yni9<@(;0_-C(tEOW(mkLt3kM@$^T2a*~eD~ zy&W^O3`!`YxH)m#){>{%#gj}sib^8Wbo4;`#vw{dP?1<}i6ZkQE78hHuC&v8)8#J; ze~%2b!Q!$1jMDnzmow@-vqjy~<9p2IsUYBB&6*iJ>J8c7KRiFk}dBSv^Y@JD=_ zI=sr>{-H^er->qxBMJIQZ5V^JIHT}!?y01U3^aP460}fMp(@D?6V#kZlme*O54g9t zCdjf`*iLxtC%{zAW=Pm9SS;;Cpqrv0#Q&I*n@V(_59H@&ERtjA)l7ri<`Uh7#%iuw z>8By2c!*q3#SY<}nmX3NWPV9e7VSAzCBYCTh36u#R!5#r(xt*ZvJ;Mf7c1}@1&joX z!FZQ-|2HIv#XI9X8)n?E!v?_n2JfK?S*Ezjph$Z$mWE`jOeh4$H30>C?oN~NWtNH& zfkq-Fb&|aq-8I4Da{d_#d&cuC^NkYV$6`5V{(Qjw6$?L;skeqZ=R^r5K#frYHCQ++ zBS}hTp{6VnO~9;0T#`i9%e17uLgl7b^_Hu$Oc_e&xfg4V+#x&+4FL}k7Xk7ATsUnQ z%m;#7euV}s(6g#wu4JZ7Ck54vA~r%4Q$OgH!5n4>RwU7g+~QfTAs*nRYYS0#-JTCj zp9vg`9gB?^i=E8Rj>Y3-;nOxmH^DhoHz`4NdG877LLzbf*2P`mQs72|Qb(aES0hk#NYb|7VC=9PqI}mV)MAccQW;PJW^EjWHn}Wx7{CmnmnF_- z>2@m;7zM;RrifvVG97W4yStE#Twl{}i{TkJctdW3|1ycfJj-1uj5RO}2iDidtAlU~ zCMzoP%8R`_jlwO)99>Cl8zqy2635z)^`t&A;@eyj36Js-e%MU6N)08Q5C2Ye%HgV% zbq$O^VT%KYPrp#Kp&3&2kLfl!8fItF(ZNBu{AS{>sm&nQ#Z0=dV`Mmzu_h`D+!~n7 zV`#Z59ET`@m0~oQBykWZ|$6X zw~{&?O&-2PD=|4b2vt`6!@3r=Ij)1%82ngV#2Eb3w$ZnLw0?|HJ+$~gZ|AZtWTN7o zpVy2c6{8FRai!H1kI^d^C(v}7Flkq{HgT9Kx3F+)lr2z)7RyWm>SS-=cA~I(AFEkr zzVn}p`0d1^O~wC9Y-sG?vDk+Fv%uXB&c(F3BuiS7T= z0yn^E859`yu2lULI8cKUh>`?fnpGy!5A+6iKso4!A%-Pz?HU^(0Kp$)Q^^b4DN6Zw;S)hQ&rC5_x#0+tzZQz&L&RjTR+(AKDdQd2Lf zzmn>UaKY_E5at&K7j7381`TlG@c*GjC|>SQ?D={n~n|9#BrDiwq18V@xDK@J9xDk6v$IsY2e#OmV26k_Gno=ibo= z@$Ie1v>8og9~jOB~8ONb5KLS>9=@r@rQoN1WFg2|%Q2;x1K2@w2I zYb0@5zJH|tOo1d3h5Ax)Y*fp}jzJz$>QScyt6%q3iCv<`b#;iy0>4C5eAb}}D{2i} z7kz|uM?Br!P88k~ab@Aph;3ilbVPmUKl9(1eT!W(b}@}MmV&#$6=Z0`L4%bTDGYl) zKxN7zqugU*7z!mKmNKZ8Lni^I>7F_o8mwFcAso+tXKygXF>~{`D8^x5WB*qYu~;>j zO`UFi@C+C;*!I9C7hM;5(1=@Bmbl*-L3F6q6i$=Z#JiG%)2WhgSyJu9+^T|+WVWR70H+^IlvY1n!}1Xb8Lq__;ju zn(lrf7;t7J4T>`ad#BT;q!S6{_GJ+me&0=-kGC00lQys}5$HZaqVWqHMtCg8#7oqF zLtqH8DeZ`dO^*QFabwZwlrSBxp>zV0Lr>=;@ol@zgbp7zEk1L4WLf4yDQg=}il+g! zG<|6t0~C2prR#!3xtcoLkc^w;*fxWqz2eua57})pQFQNCzAP0Ag2n!5lmR13KiN9*_iQj%umebTrEz% zdg?Tee}nYR^$Okq1#w&kv}cwiRdih$>}j_S66{*%`|8mWAo5CL{&p6M$kV`b`=?#l zQf+KFro$YVN5LUv?guR*BF{Z9AqdapF_K)`q!BSY8h2HfhrRb!%XJ$}=$jFEU}t-TDV7$b~L*bIU~tUt!32yV0J& zxunqmv<2r&rs2YWTHtxsyL$D>3|Mvee401T?h$};A~#pas;(s?pzY(>H=v*wq|laZ z5`?wz7tG0H?1my$gtcp6c4=TGgJ&<>o=*4IFfCq0)IBuIg-^ZXDNU|(I82PMaVaqV zN+)j$oeWm3Z9<%)UO!l*L#O5cfdFk10+ZD_+>xu{xiZ75ZsL7+1y-v~Oe8$?KLyIiqa-{e&rA zux&I$cby=gs}fKB>o_pFN4l}nKbrW+CHI-t&U5FX zBQ)(YfF(wDx#_>2p@lsS!U%_e>r1;aZ>741`5gY3`sjPAt+xv>1xy6^9nzd@o}aOy zRTwtb}YodK^vG@tXL0YFCFQi4Zfvs&Z%!be#9-!-H9l@NqOdH z>i8^>550`^8NTfJ&zo^rcgkp^bS?cOD9Ayei{d9Db76Y_ucL9!dmx z9?eRNn^6o##B^p?cLh2%94jwcOaa0aeNCKj37D4mzW7m2ci$JUTQ_GmXIK+z}1FO z$l!5vD1GAihqyLT}gRJWA5mQ+r#%WpP3V5S9^Z{W#$wy?sXsY z{E)=m+?XK3p#PL)?DCIx^7QDt+BoON4?=T7>$liA{Zb3;B4?>A17Gina;4}(x*}MZ zyclJi(irXXZ)WJ}gJ!i4NmByHKt)jWvQpASFt+4e0bk1(j(vAq*Ve+o+rLq2O*uEB zF*ddu!|?VqmH$J>eB^^fks`9j==rA|57r3S)nq*5)*CA=1J@DN1-rv<4Zpxp@$P0v zBX_xHp}?&Qrsv>ulSmJ**$vddR#gRE*oPR&Z6TXU5-lYvD7The#S=6q6?E^b2?bCDr@ z^93OZ8M7{cr_nA0-0II!R@*Gy5w6RX(SVhTmXmWQDJ_KAR4BXeFR9qFHO0nmb2mp2 z^(t6!Y`_Y~3fD1;#CQA?{NXW__%UK+#UzJG9^0-3xhB~4r+bhvkcUA>oK7`YJ}YzF zqR92pApA0RYE7cMlx0&}%#f&Xj^|9rhG9wE@`hPSau?DauLPlFj^n)7LJ?o$pFHw& zJ$T052gi6brzrs$3|t=>!7qi7?)yg{QQJzQ{Coxl_%4-dqdykXaP*!k3Jf1)vLDrk0B5fx)PXL^+7$DC)V1N@hPX`>n^)WnF`nJq3G(p8_#$GmW;O!t=Z` zjsDS?v&Jf5ydC9Np!2+sg*SRVPE1QXZ$)B8m&TCdrF2PWkdac~##5D{iesccF~Q$T zm48@b$CMK$UJ-6m#%*Z7l)^1w>>SyNkcD%Fc9n+BXq1Ep1b<8>%U1dtRC8wnWv6Xi zIhG$?au|A131tb`g(&@?X#SuWJrb5AddcP6tc43@pa8t0M}Z@3i2Cwlq4fAr*4Va^ zK-h*!LNa8~$z0$&%}nZsxeX z4%E6V<0t$Lgq7|1>i2%b)aRyAf4m`MPHYOizjDzzvqoR~xUFRbE}xWLV=^psc%A^=jOJZXY;;T0$BPFU*FBoVecs;pER5STG9iLFmXh{r*Ix?O-krRq7SdEl zOKzZTJhDFPYw5*7J4@S^1*i%8YPzPVa_6yS5V$e1)oT@cfP7ES_p5Esik3Z`ozc>I zhY2u|k|#CEqR$K4N_o+vFbf4bz$*pqx}Jazu>aaTiLsn@&a!oT`r2a(X?_2L+~KqN z?3wkVC+1#=5P7qG_fW`)m6=Pas}Ri6g@QDjtgw|lL|O>1q71l1bz(4@46rPYGMz~I zKCL*~GkE(KcCY2Lt$KQ3sY_1x->=ie6e?f+5-7)yl~~tH-m-BrW^yuP@;^PAGjM*bzh;Ip=ZIX~JTJS0BJYe<344R)W7pDTDfNR_f`Q|+v2(vm78nyHW zkF7ijtvuyd#r@|he9PZpW#fA#I_BvyG9h`?Gs4zZX$^}sr(xf#?6oM7ozCyk&$of; zLp{4-g8)FCnzmxYi|a{qa89; zw}{fBzSLc~7r3%Z&+fd;xt3iTR$1x(Q%)^=7%3}W=j@dB3B;7g8k(O+fywxF{nz(C3pTw3PE@P<-@Z>Y5q=fE zixcm=j2!KBDabhA?)X_$_n$1=KNK7<4%(j$)vn_^vJ4T{+4vkPB+To?66SCngDVNF zt#A@0xSWiL!c3Y`r_9x+G7oK&MMh1;0#Ac?GoV|`3g|XXYZSjZ6%Vjdf)Hd%*kV6Q zUoU)&@4ajWk@A)^bFuuzFz5`Avr|G>ZXZGv@?8>GkTOMn_Rc6NQ5-Fbt@JcEC0c@_ zEXEz3NXM{@jr^6042n&m(4~yR^{dN1#NJM9CH}W{l$J)}koU!TmN(`%{9yk}$nn#E z*2Dc3fLPx6FW3i4Q=nCjRNN0&jY^46Y8yr6~Bb=ukUk6P?QS+Z^TIQ7yiNfh-RfB3TZx+;U;7j z)^*4dW5HY1(V5VNbX3Y@6E?CFI+~PnV4y4^?`%Va*$hr6@>;8^(Zht98wBAS{T7gd z6dZrGlQ;EQqqW5vL~A;M+9PHPn?2_jVKAE`L1#Z$4w1X5hbxIeq36*Jbt=gs41!(f zE$>Ey5SM26tx;SS<+S@ZsZ4Q*S<6;A?^zUG5s#xXIkfIW^n@`xF#TnBFvi}G;<|fd zHG6O`5znBx&4o{GYBw_}P3xzzQo~P$S)$28p$Ss0B`@wqRXtMN63-&*%DHP5-35;) zA9W3c_lXW=5}z*#fdCPYv1epS1_=I$*Vy|C?zEs4;I{{4%7PqPKdJ=DxtSKcc7|jg zM=h06x-Q|HRy{GOlLZeSa4@;|x)(ge3kul|1ex}t@HJ=w?^`VxC?u#PtWzT_93c2Z zlGf=fyb}?hfD_fVTqApRa#_vTI$CubF^P?OTnvq6>M84no?f+88PwJ|n*H67#TU!H zK#o13PE;o;s~-)i7JEi}8xZk>5Kghbar$|Qov1cle@g8&>i7a9cTe&*n>0r{+=ziI zSJQ!J?ScHz*vs7BagyfuN*z9Gw|`bjMLs=a4z?ntS%z0iG8nKzgbJd-Cd}eU#grKq zU4U2(`Q~4theHSDDyQ|Or6w!PlRrD=G)nVvjAAM4L7nrf- zx>b4Na;<Sob4} zp~1(N9*QA@{C=SvPMX2BC2L9x-JTY4=K;oN*#2i`|K+gJpdVB{ z?lb$YincE_PgJda>n=J=Gs{|W4U51ki4v6L@8#KVEQ(3BI^;HUEZ_})`h}!8$81ZO zb!XOFy%CLa&U#|>{cfnL`9IDNs@u;ON8;6eKW%ZFcOvJ?&-CbLf99`i?O4bCeu)_9 zizZfGt&>%~A2N57&h}#_Y`Xd($Z67mS!T;~b-{vD7xVk&&4eb7{ef|ftNp`6-{Ko_ zp5~wS9+)mbd(V-Z-xqJ}^83iP?g#>~;Nj1e335i`PjQ+1aI&wn}Ie zM|J{$bsu&{Z|wMmZ&AjMP2-F&?}M5|k*MG&3G(qLc<6x_?I}zr1=a}11$ubS_K948 zoaG3Y9B?eg9u#P?6GUhP?YxxaW&{2`kduOv%$waYQ-0W7$4yug1`zG-X;7V?eKuB#F`z%SB#C zV=e}B25)wZc*I|u;Z-=~A31UlNl(qDZwN;y_ZBh}5UFs}9$zYxpZnuc&7=!0Ckjs! z=xIA>{X)y zeQK?D%vR#Gri8c}YxL8sqG`{uG>WL|fTp7b|IY{IQGbxiW&`b5n*ULRR`>rnH!X?o z$W}gbco*y;zgFG;Wt}*B`-JE^b<*=X96JQyf+_L(n-lAZ=b@*<3~gBy{IcXu&(pv3 zKAY2@!vGTP$*(2v(#L93{HR4o-k39OenWnA(6nT#+uGZr@(#6RYFR9evguy5(!}c^ zLOJX|M+TKHpH0f*GLw}q9eD*i+0W2pfVsT|3-QfNdp?EL|g2)bem@%bV|rqGNX~P9q3MeGfryT=Zob% z;;)kV*?WpO@$#ATlAMx)xAr{_xn9eX1ZdKP_&5SBElrp_t4$+f;>S}TH~plA4hkvG zbr1nfGODG4wc@Z>cAA+x+tq{&%e>T5SVD6Ap>uQO|KI0SB(3Hc=3-cIkd7Qyn$seen_w+p!REl}J)K9hXRjFS(mRI?6>pf+m?t*|;Yb zbM8GEjvV@ri>8?O?{Zx(}k z`6vplm)w*as7K3gq@DCQo{wtx8z7fB-q;b#?e8Qb6HpYlt2;USOe~vWor}Nfl{G)1xS?ooTlLYJnVHv$ zYnuC2ix(ore^%r*koVa5)oDa!x5y3FyysT;*!P#uzFSKiSHrKS?@#5cw7DzlxaQi3 zs_k|Q4eyRFspmiNk~wyxl!@S%-4heI^iC6~YO&dzM4xRTnQ4-r^Z=)vTcD;WoXW*t zW;kDn1OtuwW8*fI$RUC~q>`%=3%(@>LlohScjZQoBCrW$M#m`Gar9RLZBjP#a8sI_ zBM7{2BX}A^yt`qbC&uoXHqg}Oq@J`@7W$j;dHZUv2 z`u5N(Aa;l6d9uqCis>?b&~E7D2TKMM0Nx0>_pq`X(&y(hs!LSqyVrC-*8Gc*-d>%pC(_3Cw?{`jn0;65?ox*h;2czuyv@ zCG{Q*=MLR=%uxLy$t)8Pq8vW(>v^5h42|5!2+X&XZ`q4r_i=LyV!)%5gSV`4n2xuc z&vG2?=5rBsupukeGMw@xmJ)CNUdwBUmp_*Be>{g%o(XXkm6-Ft9g+3r@c`<>{moB= z7U=Q&KU)LXo-y)(&Anv8hsS_CmfN07?=N;gp0i!4l-<762|1bAaF*|NBj<`sMN4>a zt=yaat=R87gcbYkWm43cq2fz@_LnqL?YPW`_3>+4YGp9Yw?F6T^+fKTsljjpY_@r~ zo|PsDo({(qfD+bd$#02~%=IEBrYR+PioThIRx5IV&!}kB={YIunA)+ot(E?IBgT{z z3~o9q;VPlJ7%efKt`74ySh-to5D;k7Do;P@JzG|aInpOA$M{b=WMO)N07F}4exe-x z^Cs+8nSa5Sh7u@a{Bv-o7S}?Abr`@ALTfSyo(?UzvT1UGV>#!Em#P)2 z#=_!)HNtx_acD4T3U?3;BxL}_;ADco#7()gw?*e&zH?YhA3sIC*Dkz?p7DRWJPCd^ z2yoO}%}!chcO$k+J_`)yItF_NXKHw{MoNbP99gMg+b7C=UhxKqs0b_WyiZJ+`{YlI zg)>yz^uV|f=~CthSs2PTs`#2ZlBcak3Ze+HaMHhz0XN4b6IJpkHYS^%LD&}R+OX}F zWq{m+4o&U+P-OS{g~TJ;22`<>^|}$jpAXkiKc$1u;kE{sbkw2I&|f6Ljb7bxvDZc- zdVmO+E>N%-4u7RhPsTu3Rxny)hyuj!NX%K>WMXZkPsVK;RX~+FTl6t?-jcuKzFfJ2 z#m76YLF{BS;k*BR^}%8`Bk%37TTj zz?ut+ozN}JZ5qW{lO%!BW$Ra!w#)P%ewyOQJ;EeW+(dD!)p<_miJ_DofK|7pJ@iPK zqo$?Om$t?#*4#v+FkGBNlkW~#Kz(9l1i2+gPbT0bA86r}&$7!6HfgxgO)(-tLg=$I zTX)mff`4H;mW5d)b60>}pJq+Pf7qMP;BI{5FI2&wM&F zffc#QBo=X*3p@2I(n2G1kCYj(hNUo5P=Od5IhG4p*VhoA|_S5)FZu?ROmGIMfm%a0i z!h1y+3RQ2LF5PWJfj>98tXf`6ZMTII11#*yoiG?8fotW;OY;;_QD6;=gmp6%&AC|} z@>;|>VGwz_dNVw#L1c)L2&$Y{ICOXvM&?VFaqwS1UWY=rw>~;4LF%YBSdpBfJG;W?C9Ru)U zuzw}QVJncBj=9b*6bTVop6(H$Ab6XT90?_dA9E!h8g$+TIft>j@ln)E%`FUe4*kc) zQ1A6R;SxERpBU_5>VAGfc(0gC6ml8H^3+Jh7=Sxl?Q=f7CkpjQv4RYJa~XmQ?syq4 zWOBu2gyp`em!GBd0(+gbr)b?36s{s?HIh=L?v|;KI%Qog^7?pTTP@6Gp*o`N4Ah&|9p_Aj}}8h-QPUyF)9NdHSjAtIwFn*VtXe1Uh1|V)z@>$Sj~=NrL@li#k&uo zmxW%d<2u^1>CS)KUd#Dzb2YPnXh!-q2W{tVVFI%TO14wh)J5fLBV)Dk;PEDtOvW6q zf47N_?~)-S_fVaECLwrNF{{~Bci*izw;PfbH8uL?tL)EUO|<4!r>TrMYnIsXQphh}aV5Bz$qhNkB|2Nr<>qvxz}|C`7t| zi+OuR-VrUyz!Y1RrU5AjQVF3E!62EdigEK@;wZ-!3Z?p2B%^6DOOXQ?UAodhpMtr8 z@Mvc!Scs_ne!jeJmgfUs1nn*-bc7c|hAvteA6b433w!L7>Te2=F z(8ol7&K_hS+K_Yy-RF zjq{aF1jE!ZgCq6XEG}ZKbsoBUxYrcUZ!XZhYL%4_JqYqSHxWRS5K>~tb zg#eGSwqVYv;m#=07n*ME*n^wWiRpp~Q!ByN0LZrQH`z@t@(i}J(+2vm&Bad%?gPbn zm7g9f*?^uI%c>2v;=CmV72#+hOxA4cR1)w}I!8L*{iPbc_ALchlF&pwu9|g~iz>+h zCr^C{Q_j`yZ1s!Z2EF#Gb>dwJl4s+)9BV{tBp1fV9-%Z&iWkRB@h)nBDfn#E0=}i3 zC%A0azgsUaF^+$62UinTjkD)iC7SRDss>UIq!B_doYGf1dNe+%9HLe-Z=s7k%I*lc zVxw{)`buq84lDs&5~#dpg6913;gYPpyXYup8#{#9S(^q_^3YY*9eQHPp}w4%O{0#h zqY9xg=nbP~Zm2ctz;Eli)&2md5xz4V4OaWG@m#X^4qOYQ0Yn>!{&fo9_^8@ft0^!I z-qwg-vk%|xw^e6AAkEEhAk{$Xuc_?JM}`se-xW=!5a$&I+QX@T1m5Y`g36sWflhn{ z4&_3}s|r;AN&{PMORQn;uEzZnPaO`l`eEaj>Ha71|7}&ClKBRIHC3U^Z`A;*plirn zkaa{X@K_KD?HJ{F%cYdxuZEAsqYsBHE#S;n3v0_9&tL`xjsrkTD&L@E)*g$0!@;|Bl_<_)XV1U2_L4-Dul*02sVMu{eXP133sCE!M zIxU@UffN_^2&_QZfU|2ODg4K}P$!n}F&0btR7?XYzPSH%;_~$1{sfbx+(W&Y^mr9R z9m)R&tQa3e>2zfWs|cpY`xSK$`UcDkgl`i{F?{C$TEx^huUnN#e5e7GBS;rg4}<|Y zqjrS6f_29yKdGa8G`snzi6*G^N8c0-KI8j3W=4gjrL~PRC(~MO0^@b|$JsUuvEdBAl*b_%7oE=hdocnvI(T0N)#;KF zOT~e1?Z|JhImc$=aGxDR+)$;epY@0>0F}n@WAW)jYa+Z!YC!tf#t$U=kj~9pv0EKU?1OM$8X& zyJ@mKhf9lgpBBTm`b6n8P~N71DpB}<+r|8bAO{DG8VLOr%&2&P$+-XP$SI{~cvN$U zK^SSNCYhClnaU^wv`ws25~@R-F+8hNaaZf$i4t5iVidZMQr58%6+2DDl~ZWeNu+jf zXA&8kb{N^*_k?Tvjz|&d$x-`a*(m)S+Iq%bC+*x^gMgn9y;lAQFd>}+Ms_z}I)brJ zSiz5fzx_-Inc$EqN^-%Me%{Gw7L?roA_kY6dJUq}Yk$a)7w{_|zmeI*iDr^NPm@j@ z+7h3fr_m%OH7Odyz^xan9E(d?&by+kc?CRA1Wv}RK{}EJJ%k)peL36)ha6)zVv@0I z4`|@EAC~9`MquHw&=RPHjj#Ea9*$1oY`#}VOgpB)_1iE!kp5rYKescTYY zYfa@OcXBH?@p`WiI1bA7g}$W@0&zK=?<~J!$a3d$9!)0OPB_#ywN#_D5=X4XYf}4) z({&ZJF7eRGTJ8jtiuk~{1ZdDwRy&p>)5_DK#mQS40?4VOC$LOnq7*2ckBc=v$3UZW z{XxI(LpU|+EO0w`-7VMn9zqmbkw{yrv}5ym5y}li@!8gi{KywCu6-f_Q}!2fePU2` zOtD-UGehlCbg=6G`L@Bt@&6TemO*i@+qTBtp&_`ty95vJH11AthakaSgF|D%-Q5BN zcMlH1J-CyQ+azo6b@uiPL~!d`4qzM4)EyT!YjI69qg3 zG)FQZ36td_slb*(sY8~pA!<|$NCTjME1x6h;hN7}yacF$OCh+Z_JGw{>A0s~oYF=D z5B|Wy05C|^`}C3h;^#PA8Jk*a9P>g5_ZLn`20j(Y%2UM3iz@i?@i%EXTca4QQ0G%T z&I7&A(BRhrp%uW`lk#?FRAaG~q7t&&E*;V>`0Pgxx(vocfGFbyRG_*j16fLw?B3AM zk-o){@XL~EHAmWnD{R@#5_Y?-Eb-w2uYsv@cW-xo-Jp`5ZOzz74YT+5IWWG3t^ny50*cm>E zCzE6BZIO=#J3Zn^h%NJi#{t*~C<(}XyEr;61Ij2bPsUi%eGqRe@22f zGXa;M`~KyU0+^kasX=M>2udz0aias@5h$gMQgqUz!e$9o(Z4F4GT`!_oja0f0_|wZY2!e zORcTwOh1$gkL;>Wx!c==m6@R4CTIaMcb<5}VHopz0#Hcb(RQ6kgZ!fDrM4zX8P!RG z?4vIg!w&|A`f8Zi>U6%1kldxLto>OAUq+C^lTGw7fYDXJk&Y;K#g(w!u96;QS8cB` zUt`ozb&Q#oX@l~!9WFl^DCM4oKld6hboF)Zw%biZ3_zpd?D) zCpJYr>|JD`xMvZ>&;dRCf1<+xdp@sx!~NLejbk~*W?KOI<^#YIdII-dg1hHm~bDHua4Q! z0d;qOY^yS&8O&3d+1mTIWV5G#$CdSV)OvcBaV%Y>Uy4MMEGz!hvw`r6>Ah0ctvc}7 z;7UiTCR8&Cy0cv3h20rf?)|j{jZvrHw}>*X4$DB6=C$HedRnFrUaozRk_Sdc6~Eu$ zcE8vNc^Ni;Jka3TTN51&=ihnQ|9SIk`6t?TLl?_>_V^c**>slnL#Po$ij@=kU%L>= z)}$@>ur0kfpDmGQOTN=tH;@UXn|6b?Z%$DZAd3$p=nYAT17F`nQ9c$(!aHL8{0YD9 z=7M`|%@8uyPA$U|wWS<9w+E@%{Qcf5D5jLhmZE0X>y%Kw>t7P07(LwlcwB%@z=0e5 zwdTw;UKpT6C&wX3FaAV(<*B&V#)s-Sw|6qvK#?(KeDEZM9_v1>SM+Z5@vi@Kmq=vr zNg1c-jkG+LPW2dCAxk`a>5E{xt{Qx$@$^{1d)6Ewd#l51CQ>z^Bq{ouY*s*_v>XdMV4@J;r6sY+mX;`vIDWlX1fIbj#lAc0t*qZfDb1g)|lVNaO z4meKY&VvW*1cL3;M%XhWYzoE~dWH?NNu zmM(5*og^ER7P)BNRqe?Twoxwy5>(?{Nm)8bpJV`l7Rd_+C5;>vIy!;(Uq9kKCX+L` z#x^eKpJf07 zG6!YT9IX^B3*d{FKwx*#QD6$9CW($*ip$rm#O@vtFZu#LUAS#A^(Uzmyu4z(rZlYu z(lXLfkvt7BPhnx+;+RjCM-@}3DGswJpOHqp+$wfmdmPVGBqMNL@qB2kuewPW5xQ!F zY0vd&-))gYII{sZI9Zv9E;pU_0y!L!PeGelCC;*w{RqZ~wd z2qhAH7WZ4p;Ub-aQhe0C^h2TqGS``IbkCKJ#Sv+*s#!4+#s)dRb9)*OEI!s zI@Yg0P#stbxOEr6R};!LSNN8wYg>@cuCJqES@&VN|4nE@<4<`hn$on%+T@^{0hR+( zf1zk9PWvoIsQ#b2KIdhD`h|-V(`Utozx^J5+5l_1(`Fh=7xfo_+U9R>xPyQyfe53^ ztC;2K$hy%`kBukE<5lm+$@n1KXq}+BR@3VDTh8Fpd6<8>K>hSv<1d#%Q$Mn$vJeAE z0HOX0G9;Bfhf>72YMfmT*%d@)*dc1mS=Q7sqlyTjWrExpvHA-uRikt*4`1IoG1T^tH8s`1ptfI) z50t+_t9rNJ`#9~~7}hL)fmXm>SS3wu=J{x*D&P}c?lGo6!E?B&y(=hhl69%iSky4e z4{tq&qARwH(NgEV24}`?rudagSB)C2uqi4Ainmy^|A%`ch;9FCze9{ebI~TMLrN?; z@<}5@?qDJJMw>+Cf(|ks^b8Yn5p;>*9$iz!=&=52Lxh-DgW-1nycJ8-hSlH)-U42h zn)U^!0(!2WEX$c=mfz79=2<#)gLcAJ!>WSoS5|4ni1;8ToG#cP#a*6{vgY85jik}R zF0K+5zerQNq@7qd3svOiG^8xW7dfo9Dl@!9iFm5|Iz#l11zSe0oAw%63dIJNXr_c^ z&M*tOf+R|~Mkx50Ezi8{GlNwT1Moja=&gP$RHdMTwikvq1*|5QSPsIUndeo%p20AF zC-OjMv~W>F)?Q}wG;2HjVJzaNh}i+DF1BZHjKl_;UQbf|F^gY~{ri_z8%(t~Ww49G z?|(R_up#}Pa%e#iyF{gGhSFS-wWEgnA$}Ib_!%vbu@t9SQQzbO0`hg11As@=y~$RW zHSTJ^_BJbb{1v8;m+I32c;+sc%RR!=M{vfqE%`; zbnN}mH@1^0&`<|I4~lIn{PzEBL$NZ_%2j3(WrtCSsSLVi_wOnmw_G{OzMcDd{`S$+ zouF?d(2X_VcJu%3U7wMUTdAKkwlWJn5=rCJS%W(ehk4MnD9g7KoLo)ktiC6b&G#v! z!MqiDZGbHM0{AJP49GUgaJK9X>6!G&FF}_#xURs)SFm8=CD=x*Dwdq?x-k>r?Vxtz zyjh0XWMbtL=Pl}+rfsnAqb3(V!*RYBh!bXbFZ5X-p@moYeKOmn%H3i?roC+A*kIK# zN1eVER%`PN;||@YUVcqK%dtVd;lj`Y2_ITt1;tA~M!FFR*pfx8A4gyH3s)vC&Pr|2 zJ2ZaOSJTMQ)e5zOrB^9Zrk#vm${v+91wyu9G zCXN!`Z&}+GilZUalr={*omP-W5L8nWiE*}=$tYS+v@$S8cqlNSH(E)tmdjJ#xqr5j zB(QBkWt>YbdSB}DjUQa=)sHIBOT_@Nv>5*gQwWW-00!D{gMz4dtkQbW@AqxHKQiu1 z#BfpX(fMGB*?Z`Wao9$6W58voAa@k5+z$Mq(76T}P(rmAAUp6tg15ZMkC>ns$FSO8 zM#;b%T3uL>D9yl(TwT`|QZin8Gbp*;=B}$}(uVWVmEFj=DWh&_!fxHsQlIqquk4oc zZ{?{WxnC?vDY>-kt`VlwQEBYAzf@M?TPUgtx-CgSJGm{Nen?a7_{EaRDOF@Ng!rAr z;}acJ#O%;;Ob{JTApRf2_*Ee;zfq0rkiscK!LSI9StD$8STyB8s$>f>4^RQm76Opj z0ZN+1*ql2%kH0=VDMq^mRntZcEe&|0xV?QRILOj(Hs133U}Mm1>75lf_)0~s3e#*^ z7A|_DqWOYd{}Ix)rz+9%(|~|Z9)g7?457iyj>@hMXN`SbeH4~8G_PTjie0q}2aI+8 z)ZXur5ot}ou7wSs;~Caky@IbxicLzK2QO*gGy8bH>3$2*Q{OptuECa2IeXX=i3&$o zNyLr|(3EolqDuE*^4?!L()dK zpl`d3BjYSLI$k+nw!~+PpOWg6;JH7qR|JhHXkP5@6&1GJbo23PnUx`DBWBQ{c&m#q z44gsaEl%4Wx>rbYj%M5HaB4&s&qeM&S1wS6rYl-z5ex>^@?=Oa#tKZ+iPrQ!3w^M)NRn z{k_x2Zl2pxbCLpX4J;7pu$<(b^i+e$(t`5u_c3zaalg?&y_Lp^tjG6d&dkG9L0R29L~HqKbg`x4JywiH=iI&$k<-(eaM2!K02Gh>+3Im zx5biDnB@yGWCBGsIizCL;XCisI;MoiAyVLdGJ?obgwTL_xY>u`LO8Tu5Cu_w^X*zq zMY87-8f@JC2}e@i=7Ie$b#~E|2|fAc%^G#h{GUO|6s$w{f#+kRZVkV$i>k3qT z3@>O2_1LJN)cLEdQtGwlUB(cj*HfbofO3|5zNHFYNR7-X_%%QO+WGP)b^guh=mliY z-fCkZ#zhNzHB_)OdAUt&RG@xPwC*FJ`(f($fcjJEo%~}*LVLLbqwe?cZu-XSHpQg8 zp^M$>+9H0pSBHnob2SdQG#_4yz4k=>JFmumGt>K=!F-6JJ8A_a$R=S;^_o+(nkHIi zwC;<&FVG>Qr+(*Rg;Zc`p);AGj9+8Tug}Fu)4kIv%4785YM{ng1{aGuc z$4(+2!R82v_|+TNJHXK&H^}IIGg7pmcXF)KHprpH6Y`*WkGu`kzCijdQF#ou+zaCNdL{N zj+-2%JuQ0fBNDL^3=A9eRQ3myJlL1%nE0bWzA$2o(rAh~yLqaJ3O-9|XH_Up7LG7uHKP(FMfJ5x_ni6o*dU;&&WSty~1pNdJ6HO(sf~ncD~+$p}cZw{%S% zi#?@8szEie+oK|JggFIT{D12=Iqxd>VSPT*_E8-R;q0PwM8>Xe6l0QK&E2UY*514S3)B`+94h z{-_T~aej9Lk6Tl}(mC}<{=wdbIuE*5B~LOO5EW*_{ciGyrO@mWx-2^LM2fh zv>8-aoG2xfV~?4w6}vt7dUGsbe0sULH`9POmBYi%{Q2OSZ=opv6Z{#KQb@btQ=3=z zM}A!c*-Gh_=Hb#mjuFz!o0@EEP7!>Kf>Om9XBu}C4okXk5(m51NaGgR2xDP%Nx`b> za)TOm0~R}_l=oDtS5LZE!oP0tmTG4_D|4Rh7(Lrxw`9dszC zr(6}IFh;Q-2DjG`+v_q`8apTldfc$$16`t}r){@NqOxKeFyNrB&m?|oIpXDQ;&88v zO*{?ozJEUy^OX3uY-M2~bzwI7SqolVUQ#MUs17OhG?5lZPuGSt%9!_lCD9N$DZ2r5 zL`JP8Y)zw+OR-{XprAe&Uskj+W6t4B#k0zdFPpHIOw4Y_{3VBI2-d@vtG=Ub;ReG7 zwQ0k*X;6nhxm$VEF-p@(;Hnp>)@WoksyY86oD)x22E@vl?dR0$mD>s_U`I;ho+<83 zEKIDkYX>2-w{1dGCT1!SrwCLYKz#PQi@s^ck0t(gvNL(BOL*w+PneA)9{-h(FsLQu zyaVCuyL+|^?4Zk)qj9l`h2YC#Ci(85%R`LPAgK~PNkEjo)W0bQtcJiR9hH;RM2Uhq zkwk(txs5Y!0YC_O1!flb+2BbM>5T=k)T8g>6cB7=!)^Mz-157?+0%6_z$B5TY z`hQtJ)Y_sRB);oAcUq`*yZf3IhwnV=l;t$)JwmewxoUB_bD&lFvE1U$B|V1^fjSstkRphlMF0^*%GJRX@IjrN z;DL%n3&V5eq(?2VNA2H42vh3{oCt|WLRH)CW3TPTq}Bdg(M$^+m_H??7F>q%>SoBx@+Ibh0r;2Sm6XD%Qi|Lg0Z zm0qnXw2q$qys1=FpW!Z|6RywuGkWLW5A{4lD}i(B|FkeX&LMjGz0Z2)ID@v9&RMf& zQ|K)OIiK4q_lo%>zq)FmoRL<*=LSIodgP`Ui<;tV1?}~`!x+I?m#2So6eHXQ>HmCT zEa)ABfa)#Cl~$@Xm*rIL3AZ zYQ|wKx-8L_9>lp`uqEO8R%)R#-K`>;{YXF&Bu|bcx&dF994g^lSa11b#bQC4-ZYVC zRP8uKVN#c%b9(J>=#k%ay$F}@7D(55xY~?qu~K}4tn%W%#Gk8;CVEt~8g4DSdHYYX z#eo>L3;@fNT!b*}WJMyOu@r~JH9=T)x=z7d?8aY^pa2C?nu>&xYfwSbT-tk$%n3v< zMN|Tm@8n^dCe0~?;0C)lmTkoH1ODf-a4tDp`()KB#)FkJqtR{a(e-mt()nctt;iSe zJMghC+#}iD)`pK$yX!o7EUax9vpS$&aL!Qk>RycLvoJ-@5}tQj=qD6gu#nRe(6-XQ zWw37`=ZIozjE&j5;t@klV@dU2J+i1TSJ>EK^Q?c`UcMa*Q>5PKj&^g#$Sa#L_kXzB zhl7h+>h6*XU}yLH_3alIbxs<1!TTKnIQ-uI8-A}wOi^^WMZbpM!LQ-Bn@^-m4ekBE z;kPysG!q0a1aiQDUqI*!6mG%iHGHPgL2p1Y8v`N%{-q{far3T)!C zy~sgrlsGbBWbBmjwTe^I-BlALp(&h(QfE$$2#wP1pT8b9Jfc~Wll9kVdXkr#@OPei zk<$i7j&2VeL}T}PtJxw>`Uc^?e_$NOv^-AvSYF*_i^&fYZ#I>%15P%EA$JeYn0EGN zFef>jQUe=eeBh!P17Lfgj3H~678_#T2EqRG4fpsyViCNg;me=54gSqxL|0(%pCRg) ztV3s!JT-wZr`GhBWji&~Wp=RRE5?f7;CLgR?4fJA-)gw9NBz z%mR0$HZy5T9!@K1`XWJc-tn??VOqi;P?y23fp=-WQk%Bk!eu7`rB-2!h#bS8o1d-H z<1w$NnrXTmEXxe_J`AYnrj%3Mqt6pqYZt-6|3G$#9|ocIGr-gCV7Oa3Udl`LM(-n(j^s`2>$4#N0IzB6|6w zjfA||K|diTb+1q6;I@E-D(o)50AJgcl|hDkxA_+w`5f5GkSu)pfbs``V;gnkBv4di^+AvQe6B%{1Hf$&^h?@jMt_1lU+jG1^@0>5);lE0-`Ow`#jg zH&YvvZyAr2KwWXh$nCX#g`pqSH)e-*$c{H8JS&YA(6-Fy zR5=l-%7E0{SSly*c>%*-aD+d)mN2+(0V%m zO}|O8^&7G(7m@LA`~0kKiKaQ8@K~n^v`!2~Oboqe)oB%h9mMp%AUlq4R8t{xmr)6a zahH+m-gXQZMb3o<|Ho$T1*n*L#NH8O&66*u2~e5t+fn#Nlt~5{gKun{ODSt)re22G zPa)&J@X6GinK0B+G4yxbI-TCRZE!AnV`dCJfv}S312SW?B@jZBCr0_zBEZdAQ9+jc$e;qdC;K0)f#U!|j&x-JhXO&a z^tKU0DD?b2n7S*c&ug0IE+7KTtLy;sq=ahni(4jW=V*$IJoC{yhOi+8kE}7h;T*w! zIvDDL%z;qm=}p?1yp-VB9C<>^*%N39-aFu5duxpC`Jsn;6U%8tPXFphjMl+v-=5LR z>AJLy6jhM2chHu;hj*sncNm5Yy=qOru==L!+=EWtt?XNwa=$9Hvu* z^jDg}Eg4b_M07ge+^E#SbThDSL%oz=|2B{HAq_Ep!L|59;zqnv17Y z{Qc=UCu;GbUoWAR#0pIQ##Oilo$35asx=c+_IFV8P0b$_rAFA>cV#VHi&NMbcugpa zujZ|It`2Ql7z)<5Y0$ow^rv_Pvz z5mC@S9uKmgkO@obk@Ibe+6BN=Y0CUU94HmBK1yb zVx?hD667s;PM~xb6)&e-wkj z>ph&$)QB{CHuvN$sMwZr&wGEL&){X2j)LXpw%8ZdWf&l-Xwnk2&WpbBX3heJ@JU%D znDn76u3!wSh{=K&%lLB03OA~V2m(!-#S4-pm zuar@h9ny~&fhB#Du(m7J;I7SCJ0en^w)C7J%3B5HoliUgY{$oe#xXWtg z--+*6-PupY>rwv~&%LzYdK6qCQC2YD!UzX~OeD06hMN8-~h*O{C98*!7?*~T(6jd%pTuAg)_ zhfFYyz9)rDRTz!7ZAGBF@|;gR9z8cas(_}tc8W~+TpiOJ6QWt#dx*bCge+d0Dx-;l zR-Ju}oET%A*t0A{&W(il?GrHh%J?NI8*}cgQ;@icEg6;T>ho%wEfvpDDXn;eG_86@ z{91^%+)hGEEWt*g_!CQY0NvrYc4mp0(mja(+JK6fdsnr#5|}Fxsyw9|zQGGW42LwW z$CTY%g}rc~HzqlpY4Z=$IPiOT9G{l4rF z8be3l*2-KSgw^4(IeZ(6R~?x&cQUn}`!M0CZ-TJ+iEMfE*&&=g?&}+46gIdw1uZcq z`@M?i!*5tZ+&n|creH*isWPp z#>|IJ-{zX^i}g9RF}$aXidPRQoGj>99{OG;o6PokzNRWKgg;j_Qb;P&uech5+5?&T z;g@-}T^Lp`Y(e3cq4cC=vBe0MQ9!TGPGjUqov52!s+08>#Yu(~op=E+lBQ^LM!K)O zl8CONcJ^FghQAJiI!qKX7Y6)rMlTt|HRsdY{QjsM^m2FeSvl!!r#1EgMRFJ^1(`s3 z%!U$r=dW%uqqW8nbL?!B>a{xTW-a2Frg;a!H6fUdm7a)G@Pec#9D^E*F<(<)%x<`& zQwhG(&068zTw+Yon5qB zfzx<1YaRHCj=xflE_mukf(R0FXzI(IoKL1Bt8J+TusYK&I9Wl-y8o&CKYeHp_E?9u zKie;8NxLED%VQWoLE3O16b*=K>~@l=xGO_x{1$?dgP;z;0{`(CW@eOf8RXo^Q~-hH zsAl@^6K{HReQj%?HMpTP-aAMyXSnFB=C5-$BN_rE`~#$XPITB;?p9|_`G>flQvVq(&Y^v%w5m6(l7!QHEzR2Ah+KV;WTXDNzdqmB8=sD{?8e z?q7)?2nGm1!03Li%Qx;)tJo_lLT}$xqzx7UUz{xA0Gx-9V0REKSdRm)PMzRX!f2;f zVR0&!yaU9))ULGFlf)>e)aGciXlpnRjy12lnq?cos;oKUNfsA8+iyrwjTExWYOL@Z z0vJsghk{D;uf*Ui)o*ala`Z|=*hjX0fcCKG)cJti7KP0QY05i5AjP;ZhMbN9(GZ=j z$|&kWc&NFcq5wl?NWn~)smFh&ZOqYL`Cz5?Tx`R;aqkmP4tq6kIJzN-IyKsE}Z+K9>$+6z(j0Pp-ulEk~RI>xFm|QBd$NCm_l7=vAzHs;u1o+cuhi zNIy-w!;(~lxrn+ELbkTJ5;;d^uM#!ofWqZ{gkmz&yQJuQ3rN6>(N_iSj1Y?PRHJta z)SW6R1)!8wcV_|-|M^lfhHOp}C9C)A{SOwzs%tZ~Y^rpeqW@%iJhxN4gV zh(}FZaVw?47Bsr=ZhNr`D)dPzXJISY-K+SOqGyD2TZ|d)pxUy{=Jm(3&x2`h<@Kc ze}9E;2%~qBg?;>}1VtM1=*N?b%%ZtXV&CHbab-qxdXQGVvqoc_kMOF0?)(GoN`C#{ z(5`b!E3mr3ftA!V;6>)SP3+!d8oqD~8mg)X83qVh7NEXB1Ms@d&Y%K3rJ;XsPR~;C zR7BNDQg?}A@dExs`j}{|J?4knsW+*1XvprR0^`Z~0NUGoD=S(-a0XucrWy+23*@d_ zA6un$mkYy6!L$#&);qV(2O}f>oSf{UvZD1(od&6{PTQ*z$FV{p`fuUZR~gfJ(G{k- z{wIdv4j{c{IAWsctoQUWoH@2dZkqfDWcK)%fJTHOsgiKd@o$t8-8pZArTG6qn!3CB zf%ShNP2-N2k$+JSroX5MsB?9HXRnAN5Nw+if$_{-h3Qv3)5>`#rEmjoyHQ?NMv;S4 z@L9A`d2K+VFp3{&cnYH9q~MtgvKA{q)>lv*)sVdUq|=}+m|4R9c$}72Lb3I_J^-~^ zg&tB>l~en=KY)T$=kFT}Vdn7j>D9OZAD(P+;%VrpHi_!|F})$taZ>sF=Ltu~K{mR* zG&tYu)zrFBYL7a51Gl3xWLAA#+LFc39gq+i-&=dh43h^iT7H3OU= z&HsS6Yjx-RK^hHDT%be28E}T6`(Cc9j-I=~kd2CclIWn<3Q$$3E=^N!vFuEqO_oDj zj7?5~pWiY-{4wV(%G|v?@mndvU7kH^TUR)pPPAfWvqgTBR&2w{Ti6yT+;dB$s$cG& zOO~v}nvJr^H?zwnhIGCmNR4vKs_F5mA4dOaCwM_Ppj2x4k9i31W9MjShM;EX0bXef ztGksz5Nuh}fog}$*=Onu+Ik4;R((k7QKjhxIPhASb_kZ%zfI+E6kEJ0PC@sm6g%#1 zaVvdDPXr>p6TRsj7d1;dnk+spin9RRAP9+v(ehN2&NPtHJd8Qj42GG}uooRlWH=+{ z@*FhjZT_%zfEz3kS%x1@Y99WJtS)BbqCXbWL~L%P-oH*V*z=LINpn(3x;U!NM0CjgHvS_rbFicjDAY!(E5vb4v2`VXW^ z&*dqBUxBZtI8d0u;7o?lVy^Pbi|tE0+8ZB1KJff_*-(PY?VEFMmQjAV6Zkd~`+(z< zpMxQ-5!Qw#QVP-f3Pk~r;4|4-B*CEV`;~F!E6}Z>G`1p7G7cQ0lcj=j>QP}qe59>Z zL_?t7VBwvh>yfz|P?A{9Wu_WWkN@_c)FD6^oH|s6DTRTkK9#&H(wlc2v@N5XMDVzd z+2r}L|0*1s6%upk?~ima0}SbSl~_Gg>ohPeSZwvqK1V~B6}2y%L;|>hC$CFuH%3*F zwNe;AP#YFxPTHoLYx85rRnqcp5`Wb1$1zYhXtE@e0;LO7g}LiWPfBm+lfXoUlv!P- z?nL-^jU?Ud?_VCen5%G6*ux|ECC16`8mb0}fGaaw!sPTm_@>2NP82eeD{@Ii45<{kPeF^IBz`BAptr69LK>{HcciW!oyU-2;>$YP zhj`REqJ=!Pews5qyL`SZNaQDABQoyA+n)1)2*?NGJ4Rscn`u=}CtDj^^g2y(Gj`>-AT4aP>Z1EaB@jEwlJUvJ75qlCxW?z|70Aqq)8VE6ib|0s`s@MX-OL5cJ zD&VC!A@nCt-70_I`yJU#8|E>|1wpu6Lv4F~or1v+5B2yt`tGr`F>IT% zpI1x4cc4Q?tsNoZord@WJ=i)&e%?yl`tGmv!zD81PGul6WC1nhBu}r(W^GX(>^Hrs&kfr!yfmmUM*B&jDkw1zCdlc15 zCm`@>E$x6Y`xS;$lLmQ#_BAeI*NjW`!OS)yfIA00>sWUd>ZEIp-30|9LpHZ!1;!W= z;_9K%ieTbnFG*^prc!0P-K6Rgbf^$+Pd~sIiVZRyLg5CF=0V?N_HA!}RH$t1cnGdu zUZ#@i-~|q`Y-6P!E-@=MnG9}&9LVY{A}^r<$8Na;i0fvV>JUze%f*^K5S=M2p36p$ zlvEvwPio1wb8(Hji5x@Lz37!-m;ZOAP!}Sa2^EWjIM1 zRX9D~TCE_}r2*U|;OBlqd0KRoC7zMCqafd@KuOrf3%0SxiQ#+uc< z%qSHV;7l8)Xk-iWWoJBXsA&@s2o#kG=IRK^y4HfCcQj_}d3_%L=58W=T@VCV%xZJ{5BCz$^3 z@li55J;(_)#Fk7SdW*mZubMpyve5myWdR=erfF)6fP(!j8)r`s4=K8Fyw}tz6l(=L zjc7}j5dlDlJrUSqFYjy6cpKu$an>N!Lp!=@s zEqb*exJI)FF=Fa_{mROj=x1#GApM@XSX^c;6`agAZP}Wpzot-BRTW0(yMfeZp5`S5!e@B;t`nOL1jj>U!Q_Rc`e;j*{X|q3-7Z=;jC4&k4Oxv&7x|s4j!%0nIp+)O- z=e>PD^yEJ9@UA9?ROHWX=+=C6NN(FSBuB_=>`#h@Vws+=gAC9$N(avb_X;1ADylW83|L1=esU_v+rBz z;`KkFLU0tGMlt+_2)-2VkEhKiSMKE5!UZ>avc!WntjeUdzy zUVdmHCY8E>`V^-P3qgD>jjSKn$zrAT21P3_ka75Q4ZL@6?(sh_YmQR_twxHT6h&h3 zF?UD!3TMWLVDN4j{C{`D?m;(`-80>XxU!PT@5f+diiU255dS2c>WYX}p)5SG7IaRP zP}<9Q+L)_hDd2VD*KwLe14X51=$jDzNAT*`KU%8|xjc*1BaR^pQiJFEH`8J#G1HHc z&0mluJ@?T6Y!fH*seRl%zX4e_Jb;NtvCD)n^8CcCzy8K>Re-By(Ohi=4&Wj)u1jO% zczAbv^%eUQc$EB43Mct*3P;My3V{5%H^%(y%3q$r8Y8hjjoeZWnl@;+vWiQm+*CH1 z{w+&lv;pV9h7~Bv0F#}4j|&xRKV84vTe>4VBg|WheB7#!rso z7>2%0|NpgA_T$n^p!L}gkJjLE*Km_3zcRBStKhZbPILQ7vgpOHH?pS!v?w2Xqno-m zHG8%n$8`qp9b>KMzKh#Z$CGqw4ec4_f)|V`OH=~qZVUnu<#0Z~?e_F?o9}%LO#n@s zGIH%G;Hz2nCP<96Tg+T?Kn9t!S0qR7$Gsuom;VYZqem8KI7(c*@5h{&({}PxHM9gD zUthwF$JgESIMANX4r2+y8Gzpt^I{5@`L>~z(z4Rk@=v=M_kH{hMN}5U>&g$lO9jv{ z)t{d*>%ee%Q<6trz&2=NltVgb3C*_FYoRuzQh|WzJuY8EdUzxkHs#aZ`EM$F+&JlL zJTCvX#eLRa*7a})az+{5+{(O-a^|!>$(D~Al(JB1n#0S>6ZZM)g=d{Qz7{Z}4 zuaUt`K!Q^fv)MH|piMUz#jJKW=uts?w4@}o-ZX=hN`=|3_t-2oa&>X`u1mQZ%Kk*q*g5FLytK>5|IxM~qmP@PDRrlfv9fwF zX~j+0j%DuDNVxU7W=yjC#q)7>d0ubBC~R&j5zi8xz6<4fSE&2t=cEtj%LEPE;Q&6{*k=P z7tiRvn(n){?{Zf;s~%g56nZiCW7BWOt7nuezRp96P(v~s zwb859Ms;650X1veF&xM2;hI6eiSZrQ?I(Vs!;4N7X~-YfF8=Vrmlcbw!a40rheZz- zeoIkTTIv8!zaDk_h^Ko70*`CaiPg=i^WV~si6x}0QC@k$?`bX8v$N4zXTj3h&~m_k z^r1XsQ8R%;LUCbkQxz24#ZdnxJkG@UbSQQg?{1oXIayBVzd9U$8x!!AzaCba diff --git a/x-pack/test/security_solution_api_integration/es_archive/serverless/auditbeat/hosts/data.json.gz b/x-pack/test/security_solution_api_integration/es_archive/serverless/auditbeat/hosts/data.json.gz index 00c6963b16937c6c0d0397f4d082a03a781f6536..1bb35f37877b61f72181c83a50c0fcae7331bc53 100644 GIT binary patch literal 183940 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%0AE0%>U4V+gz&5q!6V(;8ZOj=$k z2*CjJuoGs1qybTnjyfqy>M0nzR-mVbY!Fm$!UtPG*mqyOf;GNMp`Cx^{%472Aoj+O-g~y4nu^@ zl-8snn}@rD-8N%dGi$%Ui?r#QtRTJl7V|&Mg%* zHs5E#rBcY8!mhvpRGS&rvUiiU)V%^rTK&w{p0QhJbFC5lWQOG_&B_zrVW%fXC^>Jv zEpYT76VjEL;BTARJBzDT##LG30TR)lhg5$YvP5%Fm;%5DAG00FeqFq#lS;ZMY?iHruAl*B0zo-(gDh?_`gU#_4)rg zneK+SxuM$Ek2P=lD^c~L{XB}EZ52Kh5-7lOZ2V-o-7Csj9U~8S4H;!CDQgLdLF#i%Qa|HU^&s0jq-dz)bdNGp=-fRQW^jFA7SzX{FRYkp)=lngc#TUo zmyqiVZZlSQg06rafo%W-Z1Y*=P`FbsQG8(Dh4sBGM+Xr`-%R4W(Fqd8xzeHB3DljU z=DCjt5UbkWUy{67sGmM^zZ*Zp8CQstXT%F_N|ihg^UlFv$ip+c_s0a^RU5)%UE(bJ zJoA(VtbaFuUDB3qDZbk6vjzGEF4Y`l{hsy{>?>w3=D%EnSIyY~DQ!9$=C}?G%61R= zi-<^)>7hPU66Pvd;E4%rh{G}()FOmRnZFAUpAAj^utq%fzGJ8rIpbalZ!US2L9n9I zuI8_m%deL~4%6jZZKE6MWd1eJj?T?$0hN)-hU*y<#$je{6}0kj%|Yhx*MFBe;lSEFoaO3V^DE z9tT<1=sg;x2zty`txM>cKHdzL6z2lN+NIVa!Cw?A&olxMPG9r62J5O$(W?w~YHM8= zy>$t^jn=i%CT!}@t#=nudc46^e`Tz6idfXsD!^J|MJIk|bpm0p$s1l6#?|u0LD!OH zRk>F1pH}TgYMQL5(~jA~CdbgLXVCL4EhSg_#aI}%UPl2~{6XW#BxS}1P8!)6Qx#Ob ze-3UYPA1$x-u|_oOH@1TJ*`MJhmKciA|BYvol+bL{B$f+pO0*VYs~~N z&*~ey2wB&rAPo7zNT5Y@-v?e_wANYtn;)qXqr#F05JAfeSCKkYN9+o{Vz|&sx~NKU?*2zESYZNP{@lyo`FrrobGd!55z-lWLW2cC z=)BC6WeTavXVXt(LxUvERj~uO0dU54>)z_C&mqXQ2)gVgI^KHDARHI-zYg`{b|4Fd zOXVtxmMlOUJYgb6J(iT4o^f;G;^utMJr(H6VYy$L&0GmvaSa_#xXNPZb;-WsFSGYC z(B4g}jy|QRyx$Tzg_>Gvfq)GFJ+vCcrDCDB^_uLZcpPmEahUZ_#)2b<64O;OtK5AN zNQTvbHH@azvIX{uBnziBmHQ`~X3HGuKBjd8?z^05Qy;>d?4m3Km&fN*-No3;x^hhk zLmsi)F_0YyNwWwzn_>%U=_%b8ZksCO5H$ZlSsnKXV^Lw? z4X;4ZFO~U*gCvxxcwgod2xi-cEy&kP&vJt|B`P1$&4>CppZk%DO`b0~`(C=(iZ=6g zr@A(fsZ!LseyuQ*j92=CF@c`|($R(OnCI=4n^T|B`*2$WS$276rL|&28B6d&xowK1 zN1))ftfaJw@tDj`OqIuPn&$mQoB$k{b);QP$*{5J6|c_Z-UpM*e6__U2)``%m+kb$ z^7X!>JMSs0<{=OAX#ZxFp1}H-ezue3c@=ccs#<^&S{b5d=rOxePG9ZB4UbrL#S9)|H4Zl#SuOPpZ5LEYM${DH+|Z-=JO;rzVt%^B zmmgC;f*3a9)yQXE*0$Z%(^VbkB8H={umA9Eg*S`|Y56;RP93R;-rWSZNS^KlCrp%C zr7{jI1k44z9?S9%n^TXuIoUaYqU;E*vmmet1+YYohbw`YcqK)$;|Kh!9@psXxoAfV%(sOgZ*ZYn}o`=brp&Dus=zCBB zy&8ZCyUfzO=3JAY=Wfv5A;^`+sxp_C9FAIuv6zq{{48!H)17CKlq}q4upFwTv>}3@ zN^@x2QkkrA{ydre%cbwxZR2h88lU1Hqu5mpmOuapTE;5}-d8v5OjxfQ{hL<@n+)Bq z=;6pm=5f;D2~z^O52y)U=0)lI+i0jzw?xmv>7_v%8JJiJc5w`UirbRelp8u@3Z|85 zyJzRfvtSAqv#-=<^RrcB=#{&judQ~^`QvjIgbA7IW+w_b2~L)}UW)!IftLC+si!k3 zLV@Fy>fVNr+Aa+S7o)~QyF|{eOHx{^s8*?|B!OBmZc6frH^%N|t=7!)1EtC!Fl#Ex zcMp|px6g;(!yDNSEJ31lp4VM^es>`9MiJvPlD@YTqt{wTUs zq=C4MOSU*#=)lbXigMl{bHvH4!pK&a(z`n&4dd#FZn0m3i{`P$Gmgb$ponjyV=u`K z6W&~bwt%0cAIPfLMk8#NVj*J5%N(Y4#!pb#l?68!5F_7>)5`bbV6L-Ud>gd=#o<%z z$>^nt&hw)Ghr4Fx9Z_*npdwIP@&6IKYiceEktjV{jqapYKOWu^m?f*bMkwSwrA_@) z6c*1&Ccp1QuLl^+EL4(9@&Pl~E{z6-`FRh$Ew8i8^u$JLj!GoT<*EA#T#Y(mWimM9 z+4@Oa^RKV=hbKbKSx2Eu)5U4z(nx1fX64FEFdr}*pd4M+&i8i41fMyeb>~84-0u(9 z4YvnwxXwnUX>J>X`2m}UbwS1IglQ;ZwN%@h^=Ea_IxXaV5`Q{I8&GU(*@!}`#}&K$ z^tw~~$Vse+?b2e-mz`&&o8+YDWOkE3GD^-;QyZg+LCiI$V2@#q5T#+AsPNLv>1OG4Ts|Ha;() zlSlm2G%iMc+*J6!BukES=0mHT-YGuAdiv`8ZeP&VwQ-*vJ zDBt1L+*m8Kx7`tU-OZfUcmJ*-1?**xkpVRd;W<$fJKBpwS4nd`&Ktu1K!|`5SQ8``Lp>3xc>rjj!b^~WB zF`+DVng+v|909)(0%B)RjSnm$)$mv1T+?|zAX9Hbz$3I@W=G$Opv=lW@k{`+zi_qL ze%_@&;Ey0{()Sf;Jm}9Pb=B|+&nX2v48Lf!w#!KvAd7i!fxd&a)k0YZ(ssbEVyN=l z<$$oJb)>W>sWrTrk$p_1pDOzAow(5V6871`rbS3`En#x|J3N}70?N|Ra~;T1(B3sT zmY;sUoVO$%UyLi-&RC{OKlx#=S+m@3bi6#M)p~lezui}J!U9)ko$*=htR^)SdXkT* z0nhx}O!3E0a)IJ<67>$HN@mX_LUagH_#nV=aVmpiZ}m>#M)D#q+)hmH zrjl@FK%5}yqT#tm%Njn*L!A3y;%S+$JNdyMx6MBuv5T?wb?f$r-S(G{hb7k~0H)d0 zBhWRyKc(PX{zI{Rlh;&tj{Nt5W4vLA*{cQg%ad|=Y|RmI<3gTn)u#P=n3+~~gzoBP zmbek-BM|n($B5gu?EZsX+KY)WZB{|_pq$1=lC}vmDCk2~#dgB?!ydzJm?mQ> zzKm)SrOaAtoWssiV6VdtHW>E>BK4E6|$|O(= zy*_|FI$gd+Ns`mjtBlB<#&BvsRDytnX2cf?p^;9NnMwYbDE#Rx9z)!g&ybA6s|6s5 zJ_bi}wIRx`{qtiviEDyfPK{j*iMROZ1v|&DsxCI`7j$GMG0DY=oLaUQpZb-%7wdb! zZAdwNWnnhs|Kw4oUrsqzRgISzp{9^LH#Z}#fIsxg#)NOh9T39FLrn*dZDo-iTCv1E zKKspVc|f;LD#D^5jHL)dS-rWAb-flXV{+?uxqW&>s?vT6{Z0A;&uRx+7b3fKa@trM zUK;5dNKid68!#Q9m{>Gt6}nEM)~JS2DxusdA+f^y@J%<1S%F#hkj8gZoruQZR1?`* zYHw>C8q>!Pr6DZr*-r>4&ODG)JZ!<`ojv~etIRXf`qm+&ec)!IzR%a_d`iNeF^+#O zXR)x^Y9=z!OQS>s0!{jF%fE(8vj1vL74I*39_2U-kn%cS@eN59` zT1El{Wd{gVL3gi;wzI}Z=znS=Krm5ic6Kb|+kVA%JI;;rV!YT#JHoFKUfiqn?0TMX zj&eW8f%{&!h6gq9Hk7?)TOI>a3c`NCq_KiGhh>o0yBkiU+Q#yau~UY$=4DFv;$N^f z1dH^a=+W2lXkEFwI(qtT+vhtoi9@9)IPU-Oa(mpS;aj3}{iUznWmH^?|Evca9D#nf zVt=O!lAn@%LoI>+muWm`%?TpzuY+MczV*~B(OII*VRBI;A3(DVu4^iAw{X?U@%@wG z)gc_y@&1g8P*J__-E9G)3;{VeY8MrSp;Ok7i=#55X+pI-tXD91sjYhB@Jg+MZNgn_7e`lT1YCE>QbFAEbOvQ`XPf7gvumetC&!8Fd65 zwWki=guWh^zz%>RTAiTlYuLd2nvRV(&BRxjcr8Uyv>%j4X1Ix;*1en$g}{*~%21(y zl2-viSYb>|de~3_*8&IZ>cnv8oG6#`qasCTx*q%a_q^(8jZ1Ux;@^N7Mnc(nJ@0On#nlVoHC>`iSR=PpKDjxV;|&5#DPc7%1+z0@5c)AI zob7kNlvY7zM!xhCxO+$(y4h>d#@zTROX}qG7dVa0phL4+e7p7cMZH1&dLdQli^wsG5DO14i2LWb3=TCL9t3SN z+sdM4{@r0_))jFW#(=f1xP7!$*({_Vlpt=|gce20-A?9YbJlB6?^Aew5qF~9?JR#d zdEgeCp%SCUk^qoGD-2hYI#QQ#aZGpVE?idSk9BD5S4S_GJo&)3XQ^&rxdFtY;brVm zl0@6BJ=hrDdxLktmJ+ktnU%2RFt5x^H5Gl{r-$89)A#xT)yor3ChY$jP8L|U&BeK%@$O*DCjGa$8~P68~t!R>^J^OAVo z;)G60Le!+-G>e#o6f)A3g&E3dn(iG_~ti zF?AQq9HvF}ro2xGQSzXfTEsx}*R?#EGLySNyNsTlCd207KdB`;ekes}5@D#u`cvVD z18&zm)F>nasBdL&6)>nsz7cm~!1jhp(}OuKHjGuAWnbrt7_W6teP>%wz1MYjn~U_@ zrBm7RCBR+5hjp62=QX*Z_n<%Y!2k*DQA0VIr74m2#n-q+Bm?}#PNWcc z>0%ACLScOp1VME~Smy#5(KES}m}dgU4i`nFO<2e8x=v`K z%4f}mu9N%XS2%KUSQi(|4df{rgOjCpqJiI}Muzb0VIEMqV#Uy7;h;@217$Q0E^dig z_n?pJGeX-buN~pF?vR&ncqBFVRd-J8F7|9@a`WC>x{D0not*pb?-Yj%3(_Pld~dT&`5= zSTsgBll~A{^6oYp{r%x}RA0c=4;KO(B@Q7STmhWAMt^VVl}@-B1MEC#uGTfZJ3u%( zR|aO_&VboybjUMQM07A)Jst+-W{sC*oJz&fSz&UNik4Kg<6GtyVJLOm_(aZ&LPLOO z$iUu_Ik|jM%U8K6NEh<7=-aKeUoUvL?!0Uj!1dfI2;1lYyG?>5L9NR0z>g`f)VBv^ zyz9I3vy3eXj+kOD5=pHN_c{};JV~0uMox)U1E6KZQYKD-9z|fpQWpFPWV3=|E1Yc) zrWGSs zDkWndqj4vYoV4bStdTM#L1!WtUUy#t;x_|(`an1+OmX7wCXD3cDy=OM;llE>38ux+8c zxOh=oHS4e&JB>aivA2-wo(V1;GW)^hmMIiCW9iumSj-}(a$8!kZw!%=2rfjr6cP+Y zJ9V1-=KH(9HvOo`UGla4MpOW%T#+RRZBX~;ct-@+p(&@@Mq*SqWI1}VVRo}c*I&o5e?%q9^NbKy_FUVNRu&5lJEu&n-png@s-+pWnf2sIAfz0H0e;+-`ez`@q z+e^U##shwr7YTzylFjj3>o=Zbq2Dxqn|=_*Q&Qsqofd2Apbgg`VBxl4>3>&hzeq+a z=Zig4n!flv`X#Q?|JT99$&_=~{(fY+0mJLnRu$}4Uv?5*C8Pb?lD@%KUJSWD?@DOe z`_QTP^N|^6!8RuxcaRaUi6gsX3`mv7`KwYvU%0YkhZ@31Bi|tGTV6U1H&d9U`DZ=H z!QSIY3FR`R@8M#7u5sP5ZKAvCib{;p|Fk5v=_KG~X=WQ`+d~RLpUl1;Qv@+gv=n$_QbXB8Dq?nR#f;0pAS| zm}Lh_8(3J=;g>g}ZL{``(XEsYlw4~m@W6^=GnQXvG~7jY*V{GW6~I#!!heKV;Ef4} zH?8KewYvvyPz1ZlJNHU5OJbPKQn)~w^h%VvN>8Pf)g{3L4$m?gT|m_d%#2lq3uUZ+ zEtyHe-uN{|WGQ{CEDh2$(`1iNJDxlmrA&sH9>cT2mE|po3z5SovHi2BPI>~3{>@XJ zekQ$aXYaamW}Yqk#Qk?_Ql+5d|J-;?l2B@Y>RqneE@%SDU(fY;wH12qk2(-845bP! zmckJ;omE#V$hed=G?_6iw>8N^fFtznSoagr7hLBK{w*y`mZ`WRQxw*~Wp5YxhJ=%D zt6ioN?Pll4!iwL<7h+mkXR+a5%N#gzfcYT>wvRdO)MDk2%#G=KiZ#F`N~D?z_9LWv zNZ!vTk@;5@uGQctH?jYpqA-o}l8B6VsOJ$l8wQjEZ?I>fG+LFla)lA7YfmK95MN4IVUSy1vm9-n=pEiFRAbov$et6YLut4_i;Ya~R?XE26=oK)o*| zq0ArC`oX5ahON-n^_r9a44zQsPW_*a;L+33M6KNt?2pXlP!GB4f&tf2q$uk5q-07- zlOkivKVuXRYD`toQp1zL*A*E9|$eBL$9Bf>#a}ABE^N7IVxSj9rT9( z+L`SgJ-4b}3`C(~!Fl~SvYy5uR24?28SA$L^dp@D0B~xueE8fXS#i=w3q-sUPtx4x zoiZFh4>Hv^?hMV{z2_V!ZoW$0j36NL+O|Tk0sdJ z4wIKDE*ky4qI&3J)RMI;jm_y-OSJ2R#j*;PDWxUv`PM^<8l6`-LE*d81<*GxKH>cN zx89e&FW{(FbMBgzCFoxh@zqUvtgbps8o9xSa&}@B&=;#dac*pBL>2OZMM-HA8Dm_8 zOb?kM*BhwSN^S(7C`Ynee1O!nCA4e!dybhAKg!ZtKjvc9$$mZiZ>N*24=2oK`QxRL zmj?8eGOK}^fKUK9QN7Cuj;g(qU8sH-Z>OA1bsXnCmvi&f$A~vN4leNYXfY_$?>d;M zZNl*}!_G*4Q^(Dqa+SA;k~%aI-AT`vOy#HTYtFY$SY-vi8b)+Y&KOe;?kdlFJv1ye zjG#%NZ}gS`9&A#V_Gh)v&KOTtPLu(3Pe^WZ#&@CxBcWaBR`KT>*b-17->>f z@4oxJ!>1ra*FnIenC^=OXQ(CjKrg;k$VHj-D^gRpc(kf$gclu@hsaV;gJ58w?_GLnH`;OmWwvn1MYz)~a z(|N1eZG&)iVKD9=M+%K z%4|oCT-)qompYxo798zmUFOLR)mRv}$7Ob^d#ZiGkn#_PSD&`u#MG5D*$FUTx~Gae zhKK5L#V;$i!^u=GyGfbPsv4ri;>s4rhTLpL2}J@%@XjIcxA>5xB99gE2{Hfh8P&pT z@Z^Q8ed!lTt4up*82I4TxwDdHfEn_9e`-@Msfs>XUs?B$@dLQfGUYv3T_%E$H#cIc zo-fj&n`l+e@J6r?5qWcf5`(9wj6f#jT|A*lOk#P^eDE{);ZsiCt5wZ!W#=xJ;{U%74P_b-P($Iy2cbCBKK#Uol)AvKDqLuyOwWYh6{fpzgCtwN-Pl=)|n(t78yUc>sEyfdGJ_ zOXLfgt{sfKAK`PfL#@25iTouz@A3ECg?Z)Hqc(s@G+gHtGCL|kk>I;!KTz6aj$w{o>HphLd{RO51cHX}dg!^9! zVt;!7eREx1&5Xvu*@yG#zIY}nYz%Ey5m>W#BwU0z1=j-0s*D@|>6qy)j?7GOx1>0c z)Oom^7po zQkJubE^*T$s03LA!X(qhW=c%eC3%R)wX0A1M*vT>F2v0+ zw9L=h%Ahwn`Vs`25-q}dmZmMu#;mnjNvl7w^8m>}h|Gm1YtaMc0CdsGL*I&3=nSom z^BR%bYpgMDj!3eAG6_|ffv?(ecWy`9M@6d8x01k|TAKm8T2FX7a%>%-Qy4~;Vt>{_ zk$Ke^?Xgnq#ZBLNVQq}jDEe`C9ZAFE)kM7y#v38Q4z&N}<9QPZkblf5xbvxa^hrKLb8rQ2On1&$c4otR6 z67LI4L2bM=-dI?0;9uk=Z6n=vufdHPZf!WuohG|>qs6aXB-x+Gx6+-vIn*93+a&Mc z>hticwJ3l!0G(HTYmu%-z-am{E3Itsz=PC9BvYNJ9%YD`7V5~18)fYzgfTBKK7Sv| zhKpp$w$d?UsiR&Bh&7RBkd%{TW(e9bnz&(}^ z%n;@@Z%`?YVM8b+dB&kULf4e%r>&$b5x}yA8*=#$xv*(M#WGpEB>U;ZF+=fERzd6N z$s-teDcb+r=_Z3J*w@HNKuz*yx1IB^PN_H2RM*;5`BwF*j{C1*aSFp8UyX$=*1V;+ zh3RUF6~KkR$;C@%85rIe>h#lhBA%-B1Z?r)_!JYg^dyRLRcX3%MR3)Md=n+oSsPrG#KFPuARFM06(<7 z`XV^Ay$IH^CI~Y(a$Q7;#oMOu>bh0X=PuDo{-=c@%sF@0P0$|m_U+tS8~#eR3F{qy z$2ukT9yb#&TJe)omMMXzk|{*6$^&zRe$Q09DSs4kq|x?6G|26#x3P?{e2Thdy{!B>sIoZ|e#+}6!*tZUM47Zp1O>sZ`zTyiu-l>c#o=E0kv10S;k#6_9a zE7Ska77<<1F7sA8>u9Y`z9c~4VW>p&hzEs(D`x-)o@mP@0Y_Aw`-=;_#X>lU;C6yN9Kg*&cS#Il|)aDGwC@@K1Xna$e_-g|y{eQ;C($w`S=v?Ed6MULlqPV2WH^%ZvD&NW zXw9qpy!uzlVi!fWq4UXHuxD+#XIn@iBxnoeo$sqH4C(*3g}HW{py=FnkVo>L%#qI5 zjgn{|5+?StqCqDo6|;I8k=ZSBCz^&AT+<_B;WDvVLBj?&O4&&I!*zW9}=#K8#xl}lp@U1mDc~W5XsY=J~W|OnJ1^c*UpLPxxi(g zKKd<^D50Y)QlOv`&Q>b-c5MhhNT;7%RYU6il$3e8ELrK;sslmi@3JWT6Q)DYROcGO zG;9HDmB+tUc+T*%0=gzTM%@(SSXry4*OHuhDWxy6XWck8@DAXi83!UHOovLdfd`aKaj&w>xg*B8)TE!QM0GDO*QMa1YziA0x&4o!e zhLG)2Q4rLLA{Cm>lvf&LE_?Q^Y8-wom(%A?t%T#xx{ua0<_DUUxBkP?5M-S#1s=)C z{;aTI+Amw}&iC2xw-mFam$-+yAQ4-MOZcKTSeC{?zE_u=@^+ux%>CmYJ6fJ1Kcz2E z93=9UNF-eT9=(aQ^7kG0zm#^(sfm?>+Eiewe`1n`()cqh{JbEk;!~TGvNp@rHdT%* zLaeXl7f)WM)iY>mTFWqmF0-We|L7U%nmG+0^I&;<$zW4_`#IJ2X#Mo<(ss=Og_n=V zTo?Ht&Z?Q8x|Fdzl34{xY79pdl219_n}#1B0C5@xHE8CQw8J|)V@Le&`hqNTae+D- zEMzIt4pxmK_;b`AW3YS6nx$dXL-X_{B31pEH%PGxb}{ArzM6{_Ie3my>*q5yZJDEC za}1lN-H?SBG4s}w@7vE2!F+|s*}MEFg6rp8?c1**^RYkD87txpM%Z`L-2wz8&%I9{ zlkRpGD~$(jZQ*0fOK)iZEch1> z@{WK1z5kkX8RGOFwH47GaKS|h^vS8F?*#tQ0W{LDmJC$2)X z1@kfjR_c6M?SoHU+l%W4oV&O8th>p)S8n*W+Rxg)sO#QV8*p!FcNdCEK23X>ki%Z^qGg7g|F^D=gN63qHs)q7y8-~Q zVD_uD!Ue7+mAEG(+9?V+;I`rmp`&Lj1eYZSkkySjsH4r_|1nF;p{g|CFMIIr-ugZ> zLclLEdlMXCbhY_pBp)>8e)Zc|U7z55w=V{xZkG{1_!8kJvqzEVh5%bGt8!AynuYf! zKPu?616iYlk{kh>8BO}*&)X9c;;(8fAo^q+bwzdage^6J)s7lj76lH1V}1odtIlGb zZws;vbRcwQUg~zVLpih{n+@f&2!zWIO8!#kdu&e{Uaqwvbe&WEY0}5#($6D zI8Lugrt|G3QRKAT7vG=rRGVfeRP3!Gj{)*!3iUlN}Rm79ax@G($d`<-Q6A1f&wyW=@jXfRze>1zVGkz5A5?gKkR+Z73Vs>O_UFaX71(c z+m}@|;!j7`mNO1Z>f0j^$2+fDyw3Ip2Yt=s1iOHNUL`0D3v`~q^pBR+YXk(vh6GdW zT8p-2Ti}8Pf6>Q+y^o4zHsQp2O3{+o&&qZXGLR5S3L>J|zOn*Tf)v9?w$9lc#N^wD zhf~B#eUO{S*U!&8&_eoC#d2pg=Q+g{ck6r`P{^wSwPWT67NW@Z+_^n0StgjPsfg)H zR028lyXS}qXo3Yl|8T>nmkCFYW`STzQ|8IACKgF)tG&XSTuFpTmC5JK4BUzM;~q&Jc4ybl9;(9*!k@Kytdb zB3aH5UMLas95I9AJN=$zHjhOQ_2@!Yof*QVXRBxKdOEaVMdruF??;Pvs`#Yzw3D2R zkeuiSSYLE;h!s$z%1l7gYW4WT-z$XkQ@_hIENtcPJTBrg?zcw>Yctos`d-=((@T;S zGsnZzbCgv}n7^IH(jKT=!V4btYJBeSB;7D z@Ax*@ZgR{-DG8LWe6O1%Y94LmsbSD(O##YCG>I{XDypD-+<6L<8!;*iX=O!rGUvK7 z+0(=CUGe9pE~3*Wz}M={9u%hIO_^J%Iqm-8kzz_;c0@$UHknif(ldvlTTHHJZ{I5icRoO>mLF*N&rJgPfh2#rR6-g9i#lVZ)5+_)S z*=6mU6|P~1;*iGdIu05K)fraW}1JwLTudTv98Q&@Z0QID^$F%J4O?_H^kN zO72W#qced|Gh6n)?t47Kdt{TJ>nq8UJ;R6m{^g0S{itpcP|B&9IT|2mZ5ksNnU&oy zF^|hktb~taFyoqp?-`i6(2^avbG7i%zVr>xp*a5-1IQJo_Z zhb*l_9MRGlJF{nzX+=!r+vYzNpXG5;;`HFZIvWbSUpSdN_>o5m<_S&ZU zYAihAVt9wv_AcuZbSEQF8u!Km1GDFD{P1RLWY*D$FWRDY;msRUP14VIPU`n>UJ`s% zy~fw(>`td`5K9`42B&S;3>#Z;@uX4sI<&VOYqhtzD6Q{5K4#Rl%oVY6cB5gR7p5i^ z#H=CJmk_WeF0Wxj9{6cYO7)r*MgiKgcyX+u944c_)dO}>H7+G7fCF*1utSqo^<*t} z`!Ly63mgC6yXHJ@A9nHFOXHkeFFwWbIj(ftp>S92ua;lh&=wshS=}Wv*Trbbu@)jH zyu%)r+gxmj60%cftlfS9sMp0SMiB zX5m;Z2Ng4}cgNqiD8f4K<+kN;B|az@tyhN#!s~^k~ZVIe;!(*%Di8o zyxGRegG@&$o0s?>5O>cRaDuxk`O9h+%*7txNtBzaa3}>SXwnjCCa&n=tOygo#)ZsV z9|k{HgzN^Z!V*|@^gkP=c@_RN{m8_%XK_wxG%l=X?`gY$4}JZ*i+*_Ob+Ksq*-Cfs zf!`iClx&kZ-XJ}9Sapl}>?|fX;1hJ!k>C4ebKegNgA&DRh`g?^%7QG#M8)@rDqJRE zwm$hpWexX4$dhpnJ!L^_32J35;owpAs*;a_>$ca2_GGQ_BtGlR#gPlz4CU~9O6&Fk zXvbo0of!AiB!&1Iu4#lJX`#q#^BAxkMPbZ@a7Y%?#5& zrZ{|0;X!2n8SgEOk;%+=N0DcQ8=vCe4kHQ6iC`j7v&+l#!WPxI=v+Q%E;E zaj$xHVX1raP!HZg(E@Ep)6(3APQ6jjgrPpTCowkHBD?KmA`+AQ2P|4aBNst z@>F}-l+W(ayD6*s#QzuTs>n}mo_m5p-uuyH*cPK}lHgF5FEDSa^;jO@Q#RJg95p)_@o7{{Qar!#kCL;+)X<3UUh=ioppqvdBb8c7>- zy_!(uZhwC4H0buz^4<|-yqf}WgvWGkVl?lsJSWSIdJG=`nj*MS(n4_CDwc)aLh0HzF!tDx4TPIss*zOd zuZ#DLEoWTsZ?97=W}A<>{H$uOG#B4a7^@2vYz>Ov&$%_}zb$C@1tyKc7)@f(hBEA6 zawH2B)ca87%M{~oTOZtoqK)rM8Ath^H?t?M-)_y9CPb^Yw>s{Y=f#c;GdPCEbKm_K z!Z!~COQ+{s3b*eY?%EZX>WGCC7=Av8NlKS^j7cvdP+aZ#o&hFV9r+~?4~@hI<1o}2 z6hUhL{L`RFW4^GQ&7O$HFAB4otIGW4>auxv!~#scuBv67<*0Yn?cm@j9QM;HCEGNK zBMM6M9?VCP83P^I`h;e+P}4$PQ-AcM!sg`Sb9f#_7YG)fCt^mNT_=HKL zwUjR)oOFjJooXN;C`R)nFl0=qoxl91j;h7mS^7|D-DYahkY;*3*FRkQ{p$p2?EVZB z5Vm|-0RdneNK`GO%G93}<&>mgF3E(as|*1qBY3D+;$QK*1BAk7dtL_r(cWWlU+mW9 z4cETIKWo16YG;J3{=% zsOU3rb>}6|W2;eC)#%AZOoG_65iDC=k0;c}OPGpPe*M;5&VAF})@85DYiq|3)yA8C z?S~~Lt~ezNLKVVGfa(Ykid3|4$LD>hea(h;U6YdE={m{RZM7&Y-KwYrH@F4{?j&56 z@qC7iwJ5$VqT-gU$xQ-Qp_ikV&&7oA>hj)nyo9J?tLeEvra+^QS5`V9k1NdXR02^y zG-qZ9<`ZAjX1QmcLsZx$2Nh(OG89K*N)62bxBXHk1r6zK8h&EBmDou^8XkeZ49Tta z7xERmL=`Orl!)%imlToTH#h%pNE3OndzOzGhny-Kz(_1||M>73kvIb-qjdKPN zG^6(Ay*?3|V@|v;N`xr&4JND!C z{BHJ%x7XbkU54;L$8yoMzeWY^{lV2?*g$p>S?y})gAHSgeWdS#Pdl|}XG{ca9tD<7 zFHoL1t8l*+Fq<%dY^q!kL;ytYphky5-}14EpSLB%oJf&RrYVCUvPd3EI3Lz#)%mD^ z?nBb5&U3R!uI)^ZsD-JwF*%aECq9{XPE2?L^2709SD3?T0-|kb$;<)FKlTAq>=#bW ztF0*he<7F{g@N(c6IikseOSu>H`0sgp>8&d3fsXX@wc9C? z4uyQ~;|v?avR|W@7xyh}9^Oms=Dap6yk&U7s;W(;cEAY5#C8L)PZM<_y3gRdRN`so6{rsqmJ zOfR;gScuIXwe7-)3o&M@r4G_i!Hq;TdB`!L^&(3s_0IkwX~fI5!kRrp$1tii}$?+n2IX|!%g;NZ{^OEsNmnqJ);n{x;OOv&|$59HkA5@jGN+aQm(>_Z{WlD>8*zLNPvT8EG+%6$4=u`n^#U9K_j@HO(^aVotSn(E+MS0)xn%F3;luJ-H8SB(9C^5=;U;{n5NQA ze-+>Nu3ic8<|IUUet1fY2s0SPLxP~}aM1@^v%{PW+%dV(}!{;qVAU2Kt$p$;5 zT!Ulu-Fi_f>YWRDGj9bczvr(crp-sa!u)TL{);ai-*nroEE&I65w4;Tjx<<|X<;zvWXM$fRQ*Rx|EO6hbjbI?H5B zNAjvQ%JW+E%$13PoFW?%&Wg(n+99pHX(iiNnSpZoJ)TobO|$%i&CB+;RK;)W4=Y9; z`geo%G&q)To6etsI)SEMW~dnR0kCO${gtnR_F~9mO>swS)vs)O6m_o!aKXBpALq)t z6d$1(Hbr)}u(0C12gw}MTiF*uw9o*SvD|z5$%nn#iRaYW)hpbfu&mH#z+OSJ2PV^)jY@rn4QXY%^z?Cnoz~*v z;$00&w959b-G+Y(fAO%wF^uYEhM%hG`meF3>VV%cK(Tr^2TCoc_3xxLtY)#J8~kCJ z0P>R9J7Zb&?nWa$^s3!Bk+?t|B*mSVEcwVjxO`nFlifSBfT5TaqtuikQBh%ZTX46C z+gN=P!XvvM7B*RfwWk8RH&9!#*ev+4;l;9^(# z&e2y0AY=i-LAuGjg7h)H2seb>Vjg;!RNfcd2(0K4v3Vlbte{Q%4<#9$Qqc8Nvr<00 zOy!H#iutGfQ?JE41=(2Mx^^mz7~UKaLvI%~43qMDPJu`q6oz>aSaJ+^6uMfurRZOr zf?7)98#C_bmy_1RLcLHUBF%PZ)3wh1qDVc&@QwsMXh3H?>2lO(|9Qa#XshkpH_C?H z&?1V6?vk}u`>Pg;Cnd-d$EUE{&a-dv>TWRa(=W-pL+57RVA130+~YF+weXswE`!M8 z7p)GAs!KkxS!~#_B09jrci%_Zi81mJZZCxaN{QW%?TkpA{9ijHW~toppdpXLuJ=$~ zpD{PPeVu3grDds~{DwWWqgCu|%E%T+_M~}A){iVKF2%NtP#lWtH2z>{dWo`n$*wMY zyV8eeN&!DDf{Wku^@*j{)TqkgNu7R|Q87|N#=2BsC#gs!P2L>Hu!JG__^e<>XuLyj zN5KLa!`&%JTDa9IfoGtaeS?`CR!ga42f#B!V^5E0i@$ z_9!0KOo$f_g>wPZI%Ob~!Tna4zUjFm%H@rpOzh$ud? zx6ca3ahguK9#40aL6H@%t)=1zR%d77)UO|R(PsR?^1OH%#y9J(c~#NFDfH_L=J1`b zVN%I`=63NzZJ=)m>uZU7|HLw?g-Jeb`1?0nH@l&}KSbYbH%$Q2$)j;r$W2Y^A#KSG*Xp&Jp-+KLWMH#X8XS$w{^z-mT;G#a#ZQD4j2F;7cRS~9 z!=Hq;n4vb^2iw8j=4P^%ZR)$~_H7SpOWWHvQ5YFR!OFJo9ZY;c#Mf0efhZEHE(y z#2}|x1P2RpMzikF+oXrMC`fr1HmISOy2l?e@K0lFd8~BUyXDQtyzke%xe06vw`msj z2xD~~-0&U1`Wd`oNK$)GJElWX8>2nIx_+@1<1XD3Yx!D^qxhW|s}hrfV(&?q$- zSqv8n7@k0HMp3Ln7vn^tvFtD@=nWSe$YN6{rp;`ese|H*43pN>_T0qhW`1(qz@7-M z{E!j4?PbHEs6vwg7EI4lR$BOC!c%k0*4tH2U(F~BytUs%AB&i zE44M(Q^w^}e8Yy)F_EJB!t|KY&$&jKWp!27?v!SFjO%Ps`&UXv2aSWP`>+0k`*HlH znp`xYD#lIwh1r{odcExZbALojGy`(GEJj0)9p(f@0A94?S-6S zl$kwZq&9%Cyq)a6ZbT{<7^M&7xdIjS9}9hDK#qe3t+KK^L`3`TP%q`itORdyZt{Px zzUlFv|45?J(b@NJW|42SCywazF@T^k)>YE3oh=EX^qWoWR7Y2;w%P{xbpo%TE zfM1q*u|qucTgncAO2y?I2YT#LaIyRXk^;c1=CAWx5up95W?tNbEWSyQo;pf_ z$EB2;noNZv;oGJl4$b&5};kQhHm^syJ-D6hQg` z^2UijcC6>6>Hk60^-8`w|JFs1PnHSi{w*9)>N%lc6^har^?_Y#l|`tq+Xs%jAKzaa z>bEOq4cK2s){kKL>K?G7(-1@=O2NDc3>Tm5JhtLB zK2v)BVr_S|^*zR`kmrhCxdv_+l;Nc>ba}zPSkMwlY92YWbVvR#^NrGzPY{AwbLw8p8i9mdA z%qFo284@?iXcn^4Y$tB4<4;Rb6XfOPcMQ);AlLho5nLRkem8p~;uBv)w>An88;L;m zB+wATOmXEK=F+()YpG++_sTj9or?8`dz-<(&-q=EXJMqcIQ{DM>y1Q4R`hCl>?>wY zJJ9J>T;$pyu`cedw<>kl7|`W z28%eQL(rp$%p<@uV^>jO7Y>~`({B{fu!=MuD0<*db&D6ym~oqexo!SjRTtaBO-vA+ zMC~K+IUC?uaa=O0$dEo+rrcuf!i&6Xl76Rcvw*T|2(p7OzmI)kFC2p|D}HyoH&{X| z)aQObrc*JpaZq5k=><$i4x!A<8*&oerKPR4AG=5Q99su`a4Am`Q0>XhBrty_@`8vI z@~)Ubwzz3#^hILy3o%P@-@X~P;UwQyka5>T#luiJ@uv$@32s|1=NG$x3<82mkF(34 z@Z9M|>N8i!JEolX|Np^m&9r6s`RwN~% zIODZ0K^8S2PnCV%B9W39MFR3UG(VO7Dk41@&me#XKeX)ZdDD`4cjJB0kgs_bWCP69 zUbp3&wmdX$yrhVd%T-*KrT_X^BaBTth5l=TUzy7Njfec#7VkvFwLs6LlBT=6;3nq`tiRoosjRlpQx2cG!kN)a82s-lJEY5|7y6y0SGg#d+^CUy3rp18BO2kVc{e z^T(>IO93T%ce+6wQ81ePe|#@&U`I3t-m3Sqf0E1sWbdkZ@3k7KBzh7&nOB(9@_FMi z9bHh8ZVIrP#f)oDrq3!=C6f)oFZi4g%?{&P=->|1!?n&GChDGT?W7iJ>Nu0Q+Si{( zpOm)&#l6%}w`Tr-`>5yb(s-Tzn#H_BC4TS=rC)<<{TJWF>0S#`NzlSQq70NX8XD#F zOI=8#7zFpb&fs(CT1-uCQqK9K<(=y`Hdo|mx z`!Aa#IG}eSMIBj=(&*Yg$cDQX_qZrElj6Cb zfA_}nU(sTTmP~&H(2Vp}lrX|G<&l`!XmCT%u4B+ielLRyA(4Uh1=c&9CY=iv580-9 zksUvL)$n-Xxw|X-;*<0bKzgs+p%61EFnxN$6?R*P(d}i+8;uQ^Hlgtbe07;g>~4q| zPJ~rKk%5yZ-2(7=`x+Co9~20TP}`*|BV{vgOy$PeM{*nC5ha(2;4NujRt$^qc^ck4 z{T9CE9dGFHVkAJ#?vEmE27h=RU#|Z}<9h0uuOVrf2=;&YN&G3{yc%~=rwOH>W8ck4 zP3wcQ=rD9UL5$5|q|e~^Fzod%l71XeGt(;|6Ll~dghhfx4|@Hf>H>ocnUgqTXu8*; z=|ZpR-m}LLmAQZSoAdXl?P2=oWSb0x0_oAivRe$UCvnl%9Y*U+oP3LKVJ$aMhnlCH z4mrQ{5-Ot`MlEyA5&>9{0*R0x!P1cfR`GgDhf;C$Xq37YfDFwY_7tgP%&p+}PGSCQGO-7B@g~asPq{hgsh*5 zQ1lP zQ_GW}lWBqlPmo%Kkj4>blBf~sja6ZH<#{$O9L+RQJblXcN@Or|Y(xk7!^X{J-a$@x z`MUoNpZ%Th=%WiZcd}3xLb3ljaDR| zjQt27R1GOq4T)4pR3nz0=BP!^KTnOC4O0N322uwxNJk6!nuGCpww6kHJFRY-{N$Iz zC#E(_*OmC*lnU|MU#>5?U0q(^D|gR#5e|Yp$;?>^Rnx17wJca0FOKs4-xbQrv-ah@ zxLI}&Pj8p4;J?@YIlY#Rc(+%m0cjw|P~~nnX;y4cYqu&ws0%(0Ou})g?+xrFRXPU) z(nI(d-PF_*vhh}@G&Dyz80>Ad;%aAKzH#(0et@G&4NX6X5xOWg^AxbsSVqsRU=7sp z8>XSAQ;zR!tH5@11QFt7v>&LRS5`&lYMgx=T$3RR<_JzZ2 zf)YnZXj?ZvIP7uyRwW#?+~hA$#eE!#yh&T;v}pAtq-Y27dYPcc&8)z@6d9i5F*x4{ z*eeXR1Gt<{@B|fW#qN-WY|)Q+jr1iMlg4yk{L}(*Dzq_h1;rL6yK9w^oAI)cYe1$b zHRmAQp*P%_bY;09FyX4d>f1lboaYmtw)t4e08H`H>1oQ!Oia(tI){twJE-nP4@fq6 zbVgsJ`87;>l@|}%g=jPV{!vI=)D^K{?52q}3&$NQC5cX!0|8{FK*JO{)Fu~i=Es)p z_@f_4VcymI59BS;P0Nm9N7CwpnUxh*CxRZ9;pYaV=^~qEW?(Qydd#qE<9N4rc$dL% zq#jFS!kB#17$Cc#(%B@*=!SN6^933&Lq(}hh}Yae8F$hYlpSB_C;~RuA@I# z@gI(_wVy}dzHOWvSL+`mEI4C+F8J<;kBppg82QJ3M1c}+EC#d0o)uUz64>ypcPL4j zR96FY*{t^ox;-`2)6EiHpjnRBbnaAt_UY93YV5o0-v-4_NQ+;lt>VD(fU{48;sN)i z@^0IXjh?Xa(7YM{fw{enA@z&1t|NlA|d4idhhC1_ccj zlJPNZCxP2!&|osB2}!`v@AY_T`yMDhjgglrG`><))1N((AH>f5cU>z3Juu1_hZZ|6 zcX3aRIC9mSa~{N(u+!WBF_XtQ-yAF3_kVcn>-W}SoDll4Ipv5z0H1+UL>vH|gksrE zSLJ!~_{6fAB9_GvrhSEeOxq}#S5h#Dt+@SwA63K9%|yE2GEC>H{nN#Dcf(P2a9d%xS{rE_VcX6Kg2zA3HI3>WLa_eLK-K#{*A=btflrFtr0JYZKUD*5W{+qQKB zV{nQXqAI$9G6^#^CK2kfjKc6rd6{6CC;`-C1H$A*cH7yogkw z$#kvlpO))1=W$lNeb;0_lFGlz#X*r8qci|mKXsrkk7piwUs1=3DXJ=Ph z$o15`BuSI4pE*`VGtfK!MR9zZ-BDE9+Gl67=X@Gb(n1Yc zx$*T^&{dDXSV*jpnlhtGuvu3;VhYFPOG+3zX#&Gr7iW07RXaPYMOc<~sNZVIR_g5P zz20{E>$yW;^-Dx#v`J!kYK-gvgK1*@yB;c^GksT0S|@+{ltG(*4noJD_*pH{(z`>~ zdCkaBMJEgueB_UmPd* z1K76;yrlAIMy&oFSYqE~bwJX;OaovGr^d@d2o^@gx>GMNtskjjVYnmQK8pWa`+{ z-QP>~Et+=aR>#$0yVu3p?8vn6U4J$E6}|@T#(n|s<`B~Zk%Qtj&sY)Gm^b9~_gUne ztf|ofC>DYsh)A+ICT(agifrgVl^+Kyr951`bhVQ$txJz95i=zVZ=j zZpanc_z?=ZAEBx4{8K#dekiO=o3Zb=Os>a!FIoSabs6&9?|S<9O}Q_kHX0rIB=5nz z!}1sz=>230cDIGw4ORlEo;r`MDa-0)zc;}b3wnHvX~Ia)!;)D|A-fGKM&ME4%C;wo zRzy56Wy#OAJDGX+u$cMKVDDtCeY&gzN@07oSQYar6Sc*PglqH!PkP4hZ(Cm*weE`F zSI-~JCboN(HpZWNz;aGE{$u6>tI}2wujQlZ;%U_5N3R#>>t~euqkd?TZiJxAzvxOV z!n2Ul1Y@(yaPF z=Q)#q>JplUpr7fNHAV_#{KVr&pFr9lta29r-zsNd+96~V+^1a;x0)lu-^Kd9u%rwu zBSR2a70M7i8DSN|3pcIrolmQx$I(-Zdo(ZL$n%|)-%ZnIq<)N!^$GKuXW14RrE_zJBl+y=;qCQJNm&yYU&-Q`;AFc<@=!H8^^?4DL9X6HJlTTc>zilF z6Dz++4C?2ZkdM&GH_aj62H8p@pvd&T8*2@k>?T#ih5S$O1r5yTFozBs8w#P?YNwRK z=*eW}|2PPfcfD#+c4w5H;pwGbDp4nl6}eF{IZbSFzVuRciCl|jnlZ4_EX1QC29sWd zFA9eMBM=ob#NdiQiy?r_Xj2yy3b-7pvv;s+w!IX6eJ6IbxFimq<-KR`J8|?X_$w2D zAG*pFsmmhy4O<3l{khT=zLrgPOs`z38%v~_3!<2wfG+48T@f)Rqqo`&mc+-TXfAq% zDY??B!wtQ0ZZ-8xNe4OJH7n5>#GQpN+c|W%Wq<3z;hG_Bc5N|n&OZkeqxKsGZP1Xo zLtJX!WeshZx%?#zuP~g=cWntYw$qiWb5m(*oFG(>(p|xlrZ1SjM0{^lufSf9RTC^k zapYk^ad|aoRWqwntR|Zy~3qdW1$Mx zn$#iS#T-NH01b)lZy&=|{O1;3h>kwhPy}?W_~pF590auk1-wv0-~Z!LwzOYtHRP2K zAmYvlSlBN1*{=r6r4coA&p!%`oR=kwYCtAQNhr*0niw?ExL!&nt4aXD>Ac?tx@bQP zNspy}LOh&6Te?NID=fD-w%?VUuFthrgK?s_Mkbm!PmGS_bvuAsUX&;u^D3|wMWv_C z@{v`e)y-6^=7|hGg*YvY_&6qg82z8w zLQc{;UljY#op1a(G@L7WS8+RBuXwYy$iK>eSVFD$vSqoDwe`V-0dcDFB1iqNunQ|> z>-jv1r3(nGZ$kH?&XylnMt* z%8c`D?r=M(Z-k_e9{Gj7Q)YI~FC0~KU5)iI5V$tIL%r0dWjZP_-oybt=+Y=-^SYeC zi>)r=w{KtYPu1|$WU#jKDmjye`J9@unaWnz~+ zU!htf4)}$UbsAq+9Cv9dF*=9-;7uHN&*2qE#+z7d=>0tV%FR$a5b9<3wmscHXRuiJ>fpP zEfu4u6B$W~xq{wp*0qi=0#?`G{U*^~JolJz?11$2-q8i~ApvJ%OsLOINiMv)lwjjU_=fxiFV9DAqtcQ*T#tX(leq;zGgLuD%LuR}n z6wtUbw_!LWH`;xt5p5rL3+UF8z)Y4fPzN>Y!M=&0JF2tis(ttz;neFV={?$XF zBtuD%{PPLnNQlm}W+wgq=JI8#@%#s(%K*RBXXmYl9`Jicz@>PaCkLB9lZ8 z+b9m!#Hv@0j1XxRs~A~I1eWwvjhYPTqi)0eUYRs^1`{CWvRwIjjuBM)G}Uk*FvGDm zbJFOzZhZWO@|3o$1d&n~(bwRcBL8bScG7}kQdhSjroacmNMV{V*?q#+ z96MS=*M}NIh#L09HtKBQmyF4z{f*=S6R^^?>YL{4S?G`Uf(+_@AtTJn<&1og*aOk2EzD0ptDttfXPu8IcgGK^FxPtTfJ6M=@o- z6xi;P(dcEBVK>C88zrqo;07YTB#$h02tZkQhLsh*d2|gxjj9$#48LSH+3W|KI+T84 z+W-)^Z4NQNN+KSLFq6rFiq$U0c`SBQw=9RZd$hk#OlgT~5xbL4O?#f71cd3NH(+ZC zs;U%lp+i{~m|5WZRMgnEAFQW?M{z1&or+|D^<~6A{;cO4~SQhyK zj%leSn{m=h}KV~QHojlQ%GRI(R+M`gWm zjGaAyJzRATyIUD+g$gST$-;)t)Z_q#NwA?Z5$VmF3xC3kVa${|M09C9WEf8MI4UcO zh&qr+g?zu0;pc!}X(z8>>W%@1sqZML9GfZWO6Yxd?*@4#B>Bi%@el5J)uPyhO(@mE z(T&A&i9?5gV3I!;pXzQKBiK`f*8IQNK0cwXqJI<2c2Nd=g4faM-spS%$ekg7go68X znmdy{4&Hk;79U8;ONsYsQZcB&k?E;L#p4Fl=n&BOMPB6)n1`I(GUxJB<#EQFH(j?? z-3O)5Tw!jf5)^s(YFIm>xkpW3Z!T&o?m~-wu?{|O$ey5q5|LQZ z1uqR_9*mN0c|Y^;^q?x?kNTfe0nHwjN54X%7=_~lGk9H{fAG|5t61hqhZi@M_Y%~I z-Ks2C6|JUaM)xIIX5uS_0X`+Y_2K~ccrR`VEg7)r^hvQ@SYK@H7OImcheRT%rl;#8#qUEh zmu?%Zm6Y0kTXaxs=?_ocJLD@C=;t8nD(s+}->xyZrK|}o^*rrHh*qv{$lZevXxxYN2 z`io?-i8M-fpaAOfqIA}ccECR9!@%&b*gv;>o3sI2kei5VV9g zNdi$~d6DV@)WLiJd|-Bt?c<(5#lKthx(?dyDWM(r$i(>*J7nU}m%r@U81BR*@$?%@ zeX`_R5*5=2(}My5BSPVO7w0rm*ju+!g%j?fsY+H!8JzlE$wPuc*qqKb6%CLWCpo5( z*D9GwXZb$8xk%hyMm%5+_A0Ys8K1zuW|81-?bURFyy)a;qdN@opd^^(?Z=lM#U;eLsrHNxSZWeS{~Mbd5^prf+dAcgKfM7N zjuE|LU7C6qaJO|qvd88o{s->2vb#giO=b=l6HiADD_Q^=Ph*g_oetkj{IWAxf1%i> z58F%MVF{f@82LObIYrdx6|wkMMli@q_R?X*B-teu03#(W?Gf0z#*D9u(kew9*ZBZ_ z-#_6CS4w)SznV6qEYokz3=ZBY-&(qIZ`={TxA#qbfYBC>Z{+u`w^;0FJ3M6DD0;v% zC5=9vw58_-UpCi(uTm4gelg$h@BG-I)0*iKTe-0BuEce}GwJFCQ(u|iX+XdQ>7;dW z9h0Ag0<(FUtQ6$1V7!zMLpy!Y*NrcjmCyTbFOMF^^Ztq)-DT1Kj{jKtBAc-@_5VpZo|*0Q$2DZEM45^jZJo`}ywFh{@o8T2 zj)q`JyPc?Btvd+2z{3M(P3=clUW6VA$zU~N$DceT?F$}hvm~}!t$C* zME|F@*kn3Q|M4#~qs`68vD_`d-fRQCLJ*^{%|mi7g81)hzR7P-)Si>v9-jRk(k@w% zSxZ*HPQ*4yp)aFjV12XkGHmm#TS)-lp#UdC$Zw zZztqK;ntu%zp(-A>|0y$RTcNJ?llLlHcb=Q;GgSszw6pf-zNr7CrHP@Q6A}2KULbc zbb`B1fo~8EaGOWH)R+QEyqJ-rAAgRvlYm`SPZ~PA^0uVCdqcf}Pd}6)DT7UG><9ny zBM+|N1#!nr!`7}Nr!E?IDiy=kb>-$8=|&rV$Ne71(yF@zZTqa*kM|l3oVW%wjbJB= z5BLUHN4l>wJ1%wxOHzB;atKXeLh=I;Oe}kDN~swZRxrSZYC=AQvEVU410&Inn;|c1 zGLr!l^jdvrCIb4Ro~H!db3Sz;tUUF18iL>3&hdq-s-FEQ)gIxl@N3lJ9lZEHzw*;- zlmqfDaIo^fwjyu;oOU6;uvCw7vddjO0yxJE@jQ8ub1~3KrJc?|MR1?Uk5`cNO$_1% zfV_Hw%&~z(Pl5Av3ASfbLUmx#614AVnmLMr`88^VUwe-9AlyRscrm0>za!n^_WXO@ z;T9QkAjXiifYAG6H2;5BsIJAmKPojA*eit=Al|8bQ0{^q&bn&wwo+5LFX}jRN?}G&Q z-;Gp#Cz=&27`=j_Y$?{&MkEBMaau-frkmaDrYCgQ-@EgNw(K18z zg5I&gZn0iQsL<^rRob+^ex1(X1o)^C5xRh75uX%Yi403A`Vf|+poI()EMRG0Kk+r; zN72tCdFM=<#i;b|!{{F08&g+&A9gNhal6@OWOU_pxCPbzvF5TsAN1@lo?vSUwmMev zYI$r)cIf)qf8}jI^v5k7A{608wJR%_ol?R%9Z=%9_c?uHz^U zk$jI!9#hZ#)lBJ&94JF-m%OYTlYw8|ZRJ6Rdl2iLr=QYhOtAYtV5DmW^2L^QF$aE@ zMgC|D@uo@-E6du4y=HgVyVI%UwV|JXS(@Y5j#u_ZFSW(?FB5_QY@gw@{*Rh9R0A#{ zZNTz^FZAJzF?E$fuY<+*I0fxS?Gsr};d{rZ z^iCog$dlSw{!xKfW*iV%isT>?JZwpo4igC7mI3b$oY8yxMp_kWJ&G= zmXS2cXCHok4_I~H6{e{L>;F^u_9^sNYqd|^j3`sQMY`UH8j4X9uMWkX?Cv0_LsQW&y)RXia_{{mo1?i00wh^zIu2i*0F_xxF#_8h6L zHS*Uo{dM1P)dG#9lJ7p&mOSut)~64w*?!WMESFN%ESIyM=+U)35F1-nQRQ%u@QC0& z&o?pWyLnYo1rPp?Cp#xAq6UrGwQROi-`$uZXdC{d-`~0|jN)o1z4;gW#oBv;W)QCS zguJf(l(qMRd0ZRb$+=&N6*8vZ*b6b4H<*=WDZ*_$^LAz;kKLl196g@tq(SI8{jN(g zU=wlKc=vHIVq{!{^CkZMR8vR%^dj+s#>k0W{>6G!hljU=vY!ebuU}F{ zqX|Mr>)&N2MRL({m}C1sVFW}|yMc|P?N^Wn#n&v2FXr{Uij>XkOY@|HLYYOEB%x<4 zX;Tw9=R=Af#D(6-m22Ex=y1a~|+?tm|-Jk7y zzVrFT83s5j1Zi)7-hPs#ijy0n+Q+F+iDAzj>QQd*P&sjpVEab7c2v3VMY&9YkSLN|gN%Ci;{?Cnc5{T;F z*u^9E%9k6M~+K6iTfn!?GGbrW1g@>-t<223#}8F@H`lH|Yuf zWHG^=z%+PR4X7I-_^^YCcFr8)>6mXnP{O8M`Eaj{z@hz5i{7iwN|F>#gIR6uCmlt_ z76qz+!zyKf7a~nj8Bju^ew^NZ*ztk7g7u`C4V)=(U2;Y?u0IHvzUtp!vY-znYnySL*Kh8wBeU(SKOKGusacz0Cj)2W2Yv<&zAEi{tWz< zn@raE|BI1Z(Bj3fv)}Xd8^17f8XHjp6DkKd&b(cxrVg+5Ei^N%AJLmT+%!@ubAo{Ek+7_UQ>0zuHo)ve&8M zhchV?T_J3g4Uw)uVcYOaf@Jeu$}_Ys(=eX|Evg(iuL?%;M<%o3Ek?`ANoL~AWk92` z&pp&&s|!JpFGsDRDKSpImJ5?v)Rqj0NF>!&zU^gI7%E%_DZ zjuFVv=+F$!Qp}VEKyFt2mx8F zY(8m|2$7#y0W>t?1=lXh@k(Ql)8$^W zesp58_w*HDQQG?D+}A6nNmOOKpLjNC*$^+UpeoiN-*rjcrPKfSPfIh{#p zjs#;`i2V6;@+6z0rst#aIEOA0K57?xh=dB@+ zw8=^S)_5hA&eFrx0r_2%65ToSifNlx@7H$F+J0ySt;LdqyX0q|*21!j>b^QpA)V%Q zR&C6-v&NIbNe#s3QnKu%7MdY|Q&>x%w&8i8hVtp+e3C9|-H!%-Peg5DEfwVzdcp=( z2!C`qhpi*KLP%=B<%lUu$M66#Vm@GJ<-<`b4uoXbjGO}8zOSraSO1Fa;kofaO}*;n zBG+>^tT{r3weetuAot$18# zKS$E&jJNLZgm^UX>s2mZ@JeB42e#kCYi2k1n!_6p^4U|h7%^)M2Z$ky4s5+Q8b-ht z%-zxqh!XWjA7(5nm>xYo)nPuE)94S@y!`TO$CjvkyJS+hUfwfrob@8zgEq?S6WdXJ zkggb8ZpzdbOZDcC%9@KdAI&XVYa3RI$_aI2^MoIe#KJ8GAk!Q7%8H?B)Jm0^0lHF* z1Zotkv|48UF&vm3X7=n|Z|c^}LWU-yy+P16USw5%WH>JYHDFsfuj5vh(R!LX-}u9R z=5nHGJ!rC&JeP%EQqcFW2wxfgIIzRE%^>mx2Oo+dnnqtcB>cOe<0^fX1|k`zXkq~e zV7KrmX3_xj8%}}YX}W_iy^4^HoR49m0r zp5c5RKl!bOMF)7)lU&ZRFsX3acHu^9WTFUnb zR8-UkFyMA}ZnAxR|h;g5Y7=+xfZqgIeeRhoqSFu>N1RHz`;S%*Ymr#+fv{xaB@W6fYZ&lBE*c z)9s|SExU7{vy9=kcHwHqD(E_oS#Kf4630eZbw{YDZSu@#V}`^Gs(^hPGhFhk$2ZmN z^@4Xzt2IoaykKY8?*v)uG;xgOGBxl#D_((CEUcs7GpfE_rB@ji07zp+}YLSB=p5@`Jp;8uJ zy%m#ZEJ$rn8QJ~n#~mQVX8j3d07dboy@lDZi zq(*V4^`3`rpUi&RZItB?OxhsR|FLWk7xB_uo_yZ@pbf*v+q-EJJcW9IWJ7sC;{`R- zruGxzhab(-piz*K$PrV#jzwn8BcnzQ@u_aJ=+p=mJmw)NwjpnzdhS|$?{-%^!R#BW z?IW{hBH{4~4OFlIBMh+tMg#`<Ya|xl(YhqX zfo_pz`%VDH&pKWyoK0d@W)0y(hF5PBVmN7jTF1;#Svx&}Tq&|;KK$6#`$bWnvHwt{ zOK4PRRIzf4*=WSE({-W*PBmzvz@|f+-*)|Fh}(Q$D#&C3k_XNJEW+v~R=&)H6s<>% zdzN4~il`X^HCTR)rt9H{5fe-k9f-wCwph1wfFfQGGX@TG5f&mu5^zO`A+RT8xgG++ zyeH&;=slf!81{t!X30JUdsMnQ|sq zne9n5?Et&5rarA7XCRYR432#(+wiLGMW6oPVA&t*>npV>`U{iFwL?j_3Sx>Q>RYOx z#@l5Xxk?*GqovN^c_Dj5N$3y~+oYKEy2739w##vHEa^^(RUY2O`TKE_^35dIt-LA< zxAt>nh)Q!s=hM%Fi{!qAw1TXpih?$PR#-)!s^LkXrYrAS$hpya{rFNZEjChhzppDW zy2v9%PIXXe%0J9lJ#s?A2!~r*l#{qt3RuVSJ>gXx4O6EK2D3l{fE_nJTj+xipnZA1 z61}eCasKF}vfr|~`?i(De$A%1*7dHH&e%J#qK-NVh8$+3N`g_Zgc#IBY!?tDh0`vp zf+{_h-k&)CriOBE_%(#10CnzYt@ct6whV^^oGyZ|r$3`+quAQ8r$&AjZ1Wr8N+Sf% zi4W~Z2kr$t&E)95C}R@2d2?;}sP0_!W@h526cqjKb^nX0L}iVLRXbRUK5-%3oPV@| zS_36mibLz0-R(^&vOpZWKc4&`pNNncn2)VRqmNm=M3$Zt|?Qd&fOma1wnH3|A5sTxiN%iC&Q+W;dVJH%_I9D9d zt`L9$kSc%Wm1xc`5VFQ6tQW8mYv-Q)D}c}0TV(+@16h^lJl~X+ zmCNb9^wGR1@qb`a1+n~J+3go77K`;Pw}YjcSAUlmRhy#12UPkFc+x398J=LA0@dzs zQM4pT>HMQEwTvDN`Ah`h*otk%&q7kLwJWE`!3H(-y+b!YlY#oNm}ni|@%BhwcGV>@ z^^0r!wfKjzk4J$LS$H|=+zK#xAmI}9%c~@;u`R}>&ff~U$@@gpsdMHVmNdg;!bVZp z`YO@k@@~0rCHiJAi1|H*B;ikqdoT`b4KJ&q7nV!r106XHs`8-Qks9 za_)%oA3`D}F^(uPrhJ4H-GFWW!^kxTWItW8Mk=lC!ZD=cI;#P>US3|d)_U~YluE6X zaqAi>A_qo!gSP%s*^beP4N{Bn#jDH1;&?aJfq2*d1F~WBjJLN{?rgXeM|S_h7eRAK z*>t7RP!(XLic)3%Tv_W1&QsHV#fZS!+~vAMKk~)N{fKPadwVLQEk(W^l%+;~v=yfM6 z=kIvb(p!=AHZF?V%+S6x#7H(+gnT`DU%zyQrE;Vw^uP`}=qvVMOy0;O%*kHu!pu>% znsncCfpH7K)aBie>L{i0h=YQTcg(`ra=1$v@9t1}2fxp;{4{nTNw)kbFgdVU&Bk?pVUC-JYAEyZ3jXXPZ|ITS;SUNdx^|+BBt=Z&e zflSsLG=p*m&bR%qE(SMCyn4DsI#)z)`A!#hd=w0>V7>I`s|)?{%nCXvdX#^@-aHL?<`p+z z*@SQiV4l^l8veYv-VLRAacNFr8ii&5gS@UjdJ^3{5LNA=^t@Evv+bqL{OS7fgw7Kv zxcIo(+SWOsoU;*!E7^XI#rp|$GvUkKv(3?^%j3ltEzf5-<7Pj4faH#1|Hik9IHGPrHHJ@`FCdSf_30 z=b_ILdMeMKs~A&$m(A2)7_1j`J*wefyIqz!k}!Fyri=IJC(ZUwQ*zgKA=2Zs$n}Wf%cGtKg-_Hoc4x`dmys6( zQ29k$_J+bCdGLn9GslKPHn;4b$Cx=j(I~usA1^}-xkk)B+B#5PnN@53ijk?7%Kqbi z>)=}OYyFu498OJnMhT`=`x4`xA7KQTp6C?tSBF&Q@>^L8A)h`EB@4FXAlg-C^swI0 z7*5D!VmOC8(H97awKF&!K3cJ1Wr;xM_#f#{n_yTBhUr9eFt_*T+3~yll7AAG!(+v- zlhI3CTxNJ8e=<%-+o-z|ZYNgvNgz9A-J~vJ9GB$wubv zI30>(Gk6=4^wd}8%BEkWjcQkbmTfW6@tGhVO^gXclW}PIXt&vo$Me!JnTbw_kV7KQ z+1UUtQz>)baw<5hPvqrl_5{s!N9K2gWa{zcq=pRC(oIz`{sH7pBlwr!O6T6bVa;D& z*|%ugul8d!G4zD6`U*sNJ7Yy^=ql>|idX@i#d?SlZM%JBC7mc<{OE1VSf;|7XN3YX zj+cLe(sxt7(pz|Xy^95-x-$+QU>rUqg3!VGhuPK@x6r|E(YvgK|A3j4>VX6mWP_D!hx}jJ7@>mA7(*DPY60ZK! zPHxw#y0EH6q^Y}9s+yNfh$Y8TfYtd{@pW3zRRTiVB9Cd73SwpEY$VeJ|79wfoM51+ zK9z>)+-dizMg4xdRm#YO-NtkKk3oPF^VIQ7(Y4 zJ7LQMTkh*BZIa3x!+zBs@-IAWzUaXa$nrPx2omvHv^TY{u(Fgm@e%x)omGj_Ff@Tt zYo8!rv5Xy6tCu%c81(BpKKj6A9C>@R7aq?WH*}obkC+}Id(L`)?_}&~@(%erfAg_) zre4y)0jWP9Hg*=t?M-gt{YQxrsr|ET$7`?%O~^=oSB9TM!}APJGxg06X|y8ct(ZqN zt;8fv9!OF>IsS(z12&G#XG!zqf<{HgNHk?-X`x0p(G0k9eRP>dU$5)dbe~od+i!z6 z#K#8~rW0W@FDwQL)u1IBYXHwhb=nAWUeJZKl8whq#f?J(T-T+Nc6qbWLWp%cPP`Qg zBWW>B(*y}YumiD4BteUqSvM+VAQVyZi&0{#4ue3P zMlj4Z$&SMChGsAI2XX2c=;PPyK9e}_ud&;PQb{ab29(&O$Bcy)OmCH@?`0P`!P14#^ZOs2#qVg zbA{4bE64f;do%8=bze(_qzT&PB-2tcUmYj<^OHmEg}>M;VlWqILv{{|g(!pVLJDQa z^KVp|mYvTKr?<~D@0*5MH@7i@&R_=Sj5F_%HAdg zGn;s<3qR}R6#4qDyS3TeGH~WV$^^&eG~mFDba08Uz^{OQ8wm9l88sW5IJcOUO-tBH zn^XNM-@=@BIn75KL~~nYCmyUj?!LXFISRbO32Fot6%>dn3#08t8jzfxYSzm2SyjT~ z#t^@AU*}Du5A(Uzx^gWgcZ<5V?ehnFw4j{-GZQAqZ6nUu*XED{mr~&8D#wx+kKM)S zb-#l-Aw^4Vfgq5-WN@Za|Jyp_)a&m+dkNKj%~ZcJnw`>bJfiSnb)OC`4)?Jf&U#W0 z{2UgI-mh(W>gaTMKen-zJ@t1AH2;$E#i)TIRXJ|jlu_`mf1Nj7c?yeg_KqKNs=eC; zy$3D;til@lR%T__IOd;hiL5%~XZ0hDnHniqUK~4ox>~FpexC%>mrS;eog8WdP<3LN zzmO||H?*?iCk6TJ(@CbJW(4OfImB9$sWG)u5KFM-Nm#8>0-3W?@%XS{>MPacm|4>dKLR!AbjL}!&fG~ar^;&ax zKs#}=XV=z(z1!RehKj0Mp`(257V{XXFS;8%>jsfFswX4oUa2 zFK@(eaoZK{Wl{YYo4#B$KA(?*SdF?*M`v?S^#c@=kkQda@WTY!lE{AwAT!+EMr)In z_!pgPe6!oB!QrRuin&oMD**0C)Uy%ua=2~dYBWRy)5J(~1bbY?5zcpRJ>jJle2Clv zCgcj^n&N_JTiR*~ZWCh&ccJGu&O|FGI>WL$XB>5dC$)6XFd;*=eFH}qwJJ!hhiUtE zSoboB%GD;DR$FaA`&VSSBXVT4_7^Hd$({y>tB@~Q zHeY%2wljmRC6vhm;jQeq5r0__nKgy5k-c;tEy%P#d2^|TqN~7K5*>b;-zS6#(5ZC+ zIsR(Kz)FCc+@tR*wv5(Yz;%-P&lR0#F?;evRI+x=bSAbpAK4lChg%${C1MC9_@{4C zgY;*lC$jL;wJhE@NnZ-~Sw^ScEt?ju=B3h-{e2#(?qI6oE}RH#e4JI)soZiu)|yx8 zvO-rPD4%68G3%!X6$L5$%K|*0q7(_*>eXW6A?_z7M{*V78?uL)uzB|#rb1}>FW!AM z)J^s7+pWV+Yr#?Zx(u3|Twz}yw2Q8hmg>m4G91~t(;rek>uUR=SJbrmaV$Xe-HB)CW$a)>Wi@{t}B%m@@juAVU= z&YzB^yFtU`v#LmvH97c5TfATW$ZpNmo@8o}`u_V=pQYYdPb(u_Ibi<>`I_??Ks=}D z5Q+f|OCtIo_ChW?KxvAp#Z7yLsRdBE?|5rSu>7llT|b%n%DCl@nKm$u|48B66eqBo zhHIM1zNCI+`sn-n7-4;RYm3ZVR(q#6Cel*9n}#n~i|$$-i%c5cDDNN_93tycP0u3O zk;%`SNM*t@EBvJo;fSg*a%oZtSotX^X4}>71N+>k8ZNfE8Js`@oP_-{s9&HlG2fA5 z;tCh(6gXMqv;Q)gyQ3;G~ z0e@`Z-Rn>~cbrp0&Jn!&#~qO4b^dTVy-Rb6mEVJsYxeE7-{}&MD~&Bk?2J6~jU&)* zx9i7Ahf#sh6&*xAm|U}))}4LdPb1E7rKX2wbe$>)oHnjjQqr~D-o|t(cMS^bZwhhM zJ+Nz;nJ^rc^(p>?cz7yy48XSN8PtL+_9B)o&fl97!~!dEvi1>Q(9 zhD{gfzct^UHbe-bcy_og(QSGm07Yqde?SC3K`{E_w7Jehj8c$lsr`FA-7ecdQ1RPUpvAgMP z0!E@&>FYTe^CBQDXTBdKt&!6VhhP0Vv-7oFSs%3gAd|29$Rx9BF5C-w5f(Y1IdG$i z|A#r2r9MCy?EX6nZhp+^J!kVRUA9~)w^YmX;aHafyT{cND&SL!5)?R<5aW$l_8Q@^ zrgRfWY|!e9wP5HbzCYjzBCE~jCG{d{Yk9wqj?(PAWcVm;u}S`OM;(*5nx!akNjHUI zzMS$I=NVx~{r%aJTQ5QJ0a%5COplpjUl_8bK_%TvFPi*TIuRt}-P*eHLc&z2HlphjQ5C*Xs2?>;p*R8;s81%tt_feK!AO-qMkL5Mkc79AC~_Bu zbV1esTkICECIb&lsG9ULN_G5L>*C9%5<1Endt(qiF`_Ud@z0>3certW8Ef9|-xP)3 zgj34m>iN-=OapDnX9dBS8zx8G_bYVCI*7xL_Fzup$oRcP-q&ed#x_~tnNi?o0ic>+ zyB0|oi+9SX)oO$#OJeiSRcouj(T}IXP}iX)wW_wD2&5y6o(Zrb0P=tKb19w=O50D< z^&sl>+N4sp4r$)cwvmd|(w8H7Uq$)DB#EQb`Ed*OaU!JS6-Es9A;G&ruIZ@#{6>== z+mt)+n;K-ET%h{^v4S$y~dt%gzg>iO* z#3EazQ}0na)kkbi+Y_PjRgF^c2*bbscwm{Zyc;@ZsuF2 zvyS5wyX<^9y0PD@MRf5x#`l6D6N*lt?7-iN`e1foDgYwK+hWkQz+@0U`i)+;dY|tn z8`~z8p5MKgR)nrqIbOJ*DzG~cbwO&#Tw+(l{3}5_TFELrxMM zc}4$zXL{f*L{;z|Bd&xo=jRLW8^*lw01$au>os@+kiz|$ui5GN9V+-I7%jg^bVtan)QrzxlX~N|5Tdden7}*5c2r|bomoO~i zBefv5kuCaD!w~T~pEpip+mK)=4_6n7H7|8%&$jclUbWgo`16#@ zT;w}q^U+G7*tLSsM7Ye%2h@xpnI2k$Yl@6*wsF4M6Z0~}_jOA4k;T%q&g&{(+5=s75%B` zgigtm)w1EmzN6J^G!J7aiU-Fq67&|)?%jZXLXs@MEfTf{T|KL(Rq%prhxA2xv(#i!5hm5FxOJ|G0J*WF~B*=tVeWY7dbpvc45LnB@ zA4h08{dhQ@b?MpEzPeX@(dpqtFPfvp@KCy&iJw~#0uV#Z4U!i-?A3JU$s{T)hSXR&$?oLd$ZZ;@ia@$eLwrHt=EGs$nmC4k6DwjuTfm- z85>$&EEiOq&xW1gs+uT_FDsP$KTz;Y(X@ru{`%%~vsFFo7v}Zg{Pz*YRGc5}BJtup zCf|PXpJ_ql)WRiRq&2qbz{hZ0=47mAdhD=?=kVGnnr`zOpD))W5uRM#lu236U{)5Y zH0X1_$o--&;@ON;_GBj17VMnUQO9`~LL?mt9QYefl5;KYCq2ZAo=;*%&tvl)M$hx( zq)88!Dot__v%gG@j48Aj<}9){oza@0mLScni8f2X2T$x1x{IJKW=4(>?n}GbC~=~m zlxZUxIw1A&xR&0~0GT%%D__REEkcd6lJY3S@+1`U=oJ#_Jw=QK?mSAGyxoMz%9M1k zQ)(wZpzLC(^z}8n^w?9TZ80JwWIC|LqP{Lk< z$4W$V3bXmSw&wAP@si;4jV8VdBJ!AZ&?5VWzFw4f6H^@@uMV#Vf8v37(xa`CsLSgW zMUMw=Z?k@;(z1VVuB1!Pn8gzx!s8_y9vv-H^He@8zE=D$uVz{^qy!COoCWm0w?PpT zqzMTa@OUH8z?2eq00UaKdUz=}Dmn^(iAJ;V*A+5dBqem#c9B0dV;RpYJT2YI-m5M< ztr^Pm<>IWgF1Hc=ap|+#SxE&2S$}D?umIGN5NI=)8@C=qnL?6`EjvdS2|0535Q*K2 za~2~nLL)sk3KzmyJyJ>ojTPnEajcfib&96Luy74rX^S;&X;Dr-*NQY;#9D8)_O%!s zZrUc_kfp%Qaq0Y$o>ym>(5DDHQvWKIUMr6EU36$-g`OOi784w=krhZT1-@j%Zga~o?31U zz6nx%HFo_SJdR3MH_7^$N-)JUIbEsQRGonUg2>C)<6yM1r1Ba;wd-lgx@@t_OG&4L z?YjMuNW@J4g+{>)NI#q-Ax}*Y%XD}eQuAS0b^hp&PN`bD{i!cs!Rni)`8qbAM?q{~ zP@08H>21~p(wS-(f;Vz(=PJNKekmEo3C9xOaQsX+w}rx#89N3Y%xzuD1?{x!!`19}yHg7+k@`f+VUKh+7r;CK`d!Vn5X*a(->BMBSYBVfF_vt!+qN` ztDy{K-1-Gq#ZG32iYTxE5QatcefseO6y^~)PW#=jo%JPAUq@#-Nw=m~Y;?4W z^Akii0;evLfwKr8-Ba@hbBQ@&!jGA2JUA9a1Us?6wKEB|rU$-3SJ_XP8uw2q1rGl9 zp7V`?c?_gwtq+ad+vgcUwuEmV*k#Os45%6Zg%&-F-(d?_=D)Eo$KX{IeU%m&yP{#gg{TSFKZET6m!I=>731@5 zkN=t5FN(!=*T6G(^-V=n;T+v;Hi~80VF5abFZEu+f75Z!1MMHF*=LlmGnQJm4d^+4 z5KmgRv_X{GpBY}C1t>BTAEctl3liHga~`R)FNA%~{xqJt0C>-NovGI&tX`*h(6fA2 z?SI>f)>bpZeyS6S6SB&Q-AY=`DED2tx7BJ?^r2`CSKZ*tdVTYfkCr3sri;G<{YMm) z%lImMQ*G01h3ze2-4C&0+mNA>wqrqv)OW|4=Aw&4r>_aIz$9CUgu8yxnG_mzt{eMs+$x=d00smm=G6BT$~$emVSkIPpC*XroU) zcXClpj@JbLXDb$mpf&pwRQ~j@Xs_s>+hbt$j-yXCW@pbZQH>vsZMmE#KY7ac&CXj&5gv#BTv^3TqVo;r^Pb<%>CUvAaFRf!$$&f(uhC9@Lr z-@5EZW(=`^y9%=?b;O7_vGw{YPv)X@q;_Q<1ZUK&4v?novvz%VFDR~LBHr>Re$n$q zO}~6pWO;AdFUUApUf9t>h>b}z@;i<5+^fT&ondu2leIZxydW-){-LP>jJi>AIU`;a zrAk4q8SodV4?W=ngyjb0IT0>DBk^zaq^G)(z*~3J?Xr?o^>hi={!Pd(e(V*ldXi{^ z6^9-H5?9usH|frwVvhJGBM%C=92-U)`GkUz*Aqm=I+QzTK5n<6dpIMc&T@((`GKBP zyttLiRVOFil`U2*k`Kd#c7Oaruj8qUuj)>3Jum*Qtx?{zCe$sb*D3ziZ01uKR6>=S zDem^j{Al0)&>g5|YGU=gc=S&9J26`w)$*$~wCz2EYT^D`Td zk;8FIq)N#k*NYQHx(#!1O9 zFkWOaWVGbl#~&Q#DP?QD+K!h`hD%}mu-5~9|3szjlvK>^N|6Rk@A^H(`%V?+z(1`r z-j07cExz8k@b!LN1*!{=jcde#YU!yGVnIVR#@R!8pHNO$+Bey238h-R7_Rde2hI7U z^d%)GDqi;>T!SUX;nB`%%$N~Plt0p?hEao}zd6fif7&5LoSAJNXW7!dS!8ijye+eG za_TstlgmH8?Ktc45p{F9EwklWy}h}(dl?tic{0=>?~wk%PxXkR_!+epQ3KWyb%(d# zCcbEE5Xrm8obt%_CFu4cxvII;!-0~>cGdmp*&h|bjJg!24$N4__&ZP`q}MVO5m6XK z3QDpwA`w6_<3+J_%_7r~o}qLX93d~L;*UmZGfOJf>y>up|65ZzvQ(O#7UfSWkBJEF za7z2YWCo)9KLWUvZNTg)O&2|Bw^{j;egiVLFz2hP+C}6ylaK>wVv0a6EM`6SY3lGD zVU0Vb##aA{ZgaIYO*8h=8bh_-luS&tf2iqtSO3ak*D&iPHcbYA=ik$MUx?@aUaYJ# zsQ&inR5gTM_t_mHk&qwbZ^96f6nD?h`E@4$6&|%E)fAKNgnv@Q+XfltZ{wO4ESc#$ z`&5S4`5cx8LPyV`#SW?BudsF7o&ao=h5OJcn(aiGlR#q*;J(OhA%66X-nZK;@UD07j8SEzE*U=MBv;6l=o=B9px)4Stm?iV~x0HHh(5qwt2PL_=#GX*2l3 z?ZoAMA4N^YO7jT{Sy@f!S8uh24#C;vHn}l=(C?5rIci{B{AW;sAb*lhjD^i;98@!n za4I%~L-b>@iYXBu9t8a)cxHp2N_$N5q@Z1SL_8$5!+0poRkRU#iXolnn8lg+a`rfR%AHx_kB}%EQ4L`AU$P=VW;Sp&%3s@x=?)!tf8+I1H;Ni4K zKqKQd=?t)nfVxgb`3oavC{j&Q3=clWH#o_AM@b2%nF;AHfx8NMwEtH71o?Mue%L-u zS09g5C-F6Uj1X>J6Q;e^w;6%s+?hrU9{%+$Z#$dL&KqOl!?xF}Tod+g22*Wij0!yK zZS#Aa8veSjV54=)Roq9{D+Rw>>JDb3_%%iO=F`<^hUG<|GhHl!-FMto)bcNwDy&V% z8--_3WuQwU%qsbNx628^t>zBIz}yIaGbqvV1O5S%hr0wNGOk*RUOpo&zch}u@j%2k z_~hBut@i=;M2=vFS7zFvkHES{EaN-DuSBV1-;)2p$riey3S0+J9F;hFrCzJv8UP^?bvns)qlY_&p*DqyiC6Q%lQUx_ZibygnQ zvs5%ra5lVWWmGjq)Rn^UQ1DJHf{_>p6DI--4IwSx>(U$MF#hc95L?%erUt;H`c0(O zBeB9o^CcT}?ef;}byKA^q6L|@90nb3R?ql4C&SyAf^Ps-)bb#eo?{`^>t+^|uC;}$ zs}F7ov$Q5hCKDW@mzj@bEz<<>@V(~zk|yM{)cjP6`)e8|M*EED!}bfuY%#09Ke3^s zbXXCE(Dt;BjFUwC7W-g)ie20hLD#AzJb@+^{~0wJQ7YhF_fLvwBFWcQWFvp*wHz81 z8@#5T`MoFhD0esqo#rNB$S?YCal;MAxP~}ElH3Rtgdm#87}|2tOsab zOlz*Wgct}#wL_O&SnhKE0Njt;6Js@^WpgJP#H*1b=gnnC$WlR~;eLk|s67GFRHe+_ zv^Jb(*QS1b?&P>EDRe+Ghiwu zt6Wbe?7IbF+o0}XL0~pOj`G)3Xl{$`C|(EILAquo7WToY8wNzzvp8Xr0pL+JVUbZ#q@N>%KiJT8-kh6rp?< zGtP8ppXmPEMrx*n%-UiRf6b#`34@Vf=OLA*j|Pg>DR@wT0V`Yr`0b~M;`o1f)4WUV zjm#bEunYYOejqgx@mY6C{lxy5ThMUfs*`k2X(Ga}u{m1V*pc3eg6e2kzpMBih*L z8+KwJDKzhU#qjB|Z$ABo1zw^`|Fm0#)STE|j80UQ1zk9-+t4v}qgJCsvS17$}D*y>T5-xKO_O-}l*PJ%LUOD<+`@Z`V zerhDGJyjZn$0Q@`pl4i`qxX^M3UhRH!^8fT$4gS3&T^p1vP1Vx@hxzB?@6S)qOOA?a+!8?jruevBlkeYh2)ou~>)YBAprV%`^${7NGGctGxLpCL(H?&`vbc8 zzx(GmFkj%`{`vi7(6D4Ng)M9ke-)#!|8q6ERM8?9H<4|Ei3U1T!PbEEcHvIC1uOey z;`<1qH+W%MDv|CHVk9yj`M*Ry(yP9pK0frusEVIvoOL(&_|EqiQFTv@fD{G)jh!?O zSJ??|1mRSkJ<1o2i$VoIhQF)b3p>uzwV>0{$*~&Liz3nyCxR3(&^kV{+9+;FU_uz; z@LI<@uH!j^5INZ)s^J=sd2$kVmB;nybWPQyZ8QF)F}dq*JFYDxBhD85-?Q5OSbUhy zhh`;J#<<8|pCuoHoq??YotL=Bun>`|3|y1(GMLdm>vI@E__6LDK!b=yW&V-Q__K!L zr6TZ5r&@q-jwESlfy4rlfKZ1e_~YyDkzk*^Hv92&UkB^%$Zg+0bCK%9v&%_9*ySZV z5jLXN1ylwwq9z4_#rDLNLrf1>yf-U;X~N8#in}(ko=T14(Lqx~eh*~DHnO^F0gZhk zCoz-+lYMFpc0uDkFAQPOiNEOO^lKv^dL~R_Qm$MADs&LcR;2H)VKdoPz zryZ;Dq{DHQkBlBo(hHc7puv_4&C|eUiN@x>bK-H`Mm9leL3svvRQk9rQ9{o<8-H>9 z?}nxh#-r?IBwZHypUV-Cu(lE&FP^`26?QIn+1aZv>P%hZt_|is6rSqfSNocN=hP>z zhV^Qk29_6O$Tgqso?g=Ozs`5!$9s8jYKggV_huDlWI)iwfs0^LP!;Mtu{7H$OUmQ$p1&5o7#K2zi=B+?sf!ik$2-I3V)$q+UnCm*amf&JMFg{Y zymNr08^o$w@|$Hbb2Rd?noa1aX@eHlnJI{}G^b$KgQqfV$k)sv>0cWlC-r3Cd`zkJ z$*cI(@-#l#u;-nYrPK7oOfj3NyWs=*0*93r#BRy6!m){j=x;{T3KBPO!`}Ib_d}ZL~Oc~)F_xqTQjW#r{Gcl)pCLu`)MzH_OU27+j z>De<}Z|Xg>OkPzqB|T1&GSbO4mFq~;Drf*5e&**>gq6S%XI95@7XUJ)rwx+3QKeym zk#x^jfMSugGzOj%Nbl>ah||U6Web$o0IXHjQC}!8r;}%cXYuRfO*5!hr?xU&Nmc3} zB#2?>W-Z;o4s7>w+XSYp5VS!PEBm^eFDr@47Qe`mi^=~PVOl^1@%33m11Bw>=>B0q zDD_7Hz)m!Vk?_rkw9v$`rB~2;H{|};WVza$J;yS1{oqHLx-oG=&5*h=Gbe$F8}rKQ zUbqLJ3*7pe;BAYxSpg0YA4oYsRl`$)Dq(iS3>ZxKCX1_(20wpX9}Pgehak@`HfXV! z$5BU(qG!}LcyN6zBjfKFbDYbFD1G4yD^nsGumGd)k0fr*- z(Wkb?8yjmUreoI#3@6?%uqp>e$#)VU<;-_RkZHv-9K%G$hZ6WDfQqc5fLU;^Hg;Z1 z;)xI;uEMrN!rhw+htXiv(mFaz-36-ER#MLcE#;V;r-g=FmZd+PPq{YdB;5(<%%R#k zN#B^=IYraHLF8!p z{$%hrP%@(?RUI0kA2mkO&3KY9TW~YyaL$Hk7gQlB!svP4Xwy`}0FqgDd8~>RcUf^o zxLv1cX|{gx(YITDZ`XP0n6jzq25M)t&_Fkt;j~Drhpdchx}ZLuCADNP7wZ+GIoIr3 zJBBliWEO@PL)~inV(AkwspzCgYU_tR?PkMOA!vKm-Bm>9mfKcnn?xsDOh zKJ`fkdRyC8Gc=Q@1HGZ%TgV@@eI6cLPNpx47PTx}E5qd$AjCQU*x1+bIUY^ny*f?N8IXjS(NeUWG_ngBMc1PBf_fq@fakB42rqL^qso~@)AE@^IQ zjy)PwNf9dPPmW2s@YH7H-HLK*eRsRjc7WPO3x4S-W61ws7o6}7bC1Dc2I>=WL1H1DIAhGO8Aj2| zvwz837};srwm!HY^XLY-MwtKgzRIC4w{zL1bkml7T5WmGmr75qpFuh=Qfc5X57aYc z*0!zZY}0gJq!=xqbu3%_!y-6K`i4sSt3Z6fuGdv;0LvRH60m}n0<+}>8S2PKR{C;9 zF|JN}PYP&_3egAAnWq0o)LHm7!G7;wN&yv+P6?^e3>X5^J-R!jLAn_wEnOSkV{}Nj ziuCAi5TsM31O$G2{5;S1_ZRHNJ$B#cT<1F1o86czJAA(xfEcoU^MXTrb!>ZZnl+NUkJ1hKuah9foh1yH4rg}o!`?g*!&EGSioe@=2;esf0Wjduv|kUCpV6z_6e zvs~9T@kBLN=*zyaK)Tn5`;+% zi=uYYHtzzNhoMI^;~W={04!0|@wYhwSh#yfjXV~OGjFh*luyIIy*ym_2x78j9qexYfPB-;9qM1nvyh$(;k6?$^zz^jJxfN$~*d#rSw2IaZ`V{=vxyJ zWf7uTY7>%N4m+SQMQ)s`+-}d%l59dHB&=_2^~iVS`_LH?g4-?>J+AMdC}(;PEX7Wu zP11nO8k2C9LSu7@7^>3;NGXKiib6a)`j7G#ZvI@SuV+yn-|Vj+4lb{DoBX`Lw1w}` zvZ&SNAoeS;t>32p4aoyajoXASX^sguUsAAU^Rm9so~$>2#4R&Z5JPAGD4>^E=@)5E zhzwz8Jzh1>H_vl}M|OI0ETO7}l-(sDqOn%-CMAa{NK^Nx)tU~QE7+)^zlp}#gSAE~ z#Za7;9PkylaAS6Owsf%W*q!m%k%qYcjguk8V7ocz5Thneg5<(tO>GpAqvqr8f>X~#Yj?yM!#h1D zws(2T+RSW3uxN;V*liG2>*AMy(@DM0hdF_&!@0J(K8JUXT>U@`l8Q6bQ}(jUp)0`#tLC*t?>LCVLz_qXrL1l`guRE%<+K3n9Uy$!8Fs*{TU zrMa^5o#z#@AK3`$`rfoO?O20rkYHV*|FRsdITxEmtdUF+3+^Lfmo$72 zT4c2sYH6O^?7>%DO#7IinF^fRwd`ubEoSxNe<_JT zsnZwjzjv~CS60`rzN$(>W}7$ilgqqdvJV42F7Ns& zBPjx4V1f_X2*phF39iR^+m;TzGYdEy4bm)YpHnse`If5&sgNd+nLMIk!^rn*LktO# zUiI!+$?lpDejmUvEtTl|3B&IdY-I5X6TJd0mJp{qE?scgG>9}t?UUnzf0-H)lA2GUqN-$=)?X?( z(me2yE*9TK+{Y}Fihh~?%x~RGY-w3CWfnZ|rBEi0t$Im(jqRck89A+0^1&$LkyrZF zlawJ9$u|#d+9dpSH1|zyNC$QgSR2yVy2jM?qISWlPr7zZ=pP@@Pu zMjqX!Kf%Oosl%K0F)2+-iJq8n@km8`QAp;vB+g^}tH6PPcpcW+=GWMe_Qo-%mfy`o zp1)^H~)_t%{Lapg0gA ztMJOjjZrraxjW_2#I#+t^#fMLYzg-kF?I@u0p8T!P~v|>4L+n79n0Ed$+5uMh{c+W~=i9R!3^^z=xY;;qx~}s7m9o z;NME)#`Vl|o;%%18&>+Lq0}@=E@bg_(xM%$BUlLQH0)wc1GQLL>exsxdx+pKrcbJ` zWBpF7D9J0@Yzv8-!(2uZNjYQoS`!3-{FHR5pi;%`{}!~t(So3dMU zd_*8R)8K`*1&|j-5*vvPI&nyyIm=}GCHtt4&6f>s$^UpT;~OLPF^qX-dG1MN0BK39 zRaiWhNdhxAZCFPA+p-W!tnKidwk6Ie?}lQVX)T8=4RGtzzC8n1}qL!`fGx)Bu#i+>w&HVo9RC9T_s=OtFN7)9-;`=Zf%=fa@k^TqM zNblghRaDF9NS)2kRys=*8`3)aA*5Zp6wX12OnF+a9f}EMjX`H)7KzYpB~V(t(N#_r*>DYH!m$Z{LE^9Dc{@$ z(ue6WaaNWp8!~gIrYZWXEiHI=6q-1*zlCLV+jl)=+LgXHHGIn4#?cv0VCnXCzF|GH zNE$}vnh{UulG-91RD%I?hCVZc$~_Hn}j;MgEAf;8Qj-^b>|E+tCV$_*;6m35J9ZV znTEdWD}$#J3W9nb>J6o}RgalG-RPH&nAnn46s0sJ=ml6qhL|)4pfbe5_D^`zgzu=( z<@7Y8gOC;X#0|Wk4sgOEDJO$(Ct5HBKL5FyUS_^>THUP{UC$5lM}^#a2(_NuS6uri z*S(u+{Mh*)?t_e<6VWsppVebL_Gdc(*Qh}$x`u3JAj8DmFm*JPY4;7uGmZA67h3$F z)QDo^MnWZJ#)eE`6+;XG?=;V%q1);9QtwFt@#FTe*vVn%f=}k{J=T1i-~H2mT0&}& zN~DUJ=_5)zOlQAB({B@hn0V*~J@F8a+TGNpRl|Ow&dMCqk(;4PkRw4O_ zLJ&lnjXvT%9!zV1Wdte7o`kqpNfq(on&+w^}xtS9MF-v~0Baz22|R z)M@VG{qhEW@Zr)8S8l`hIKZkjShb)2poJN%-^j%M#Wk5bD*u-%!f$g=upN9SJS zy-fed!+Z&ygZ)MEh$MPM)$>JJPDYp&6b)HJ3{Xce!kl_pq%ijc_eEntbj8Kf5GpP8 zyVrJP{QPaJ&T0D3PtIIBoaPYaiGBR)pPlAR_*OLe)IU3j7^@asKpWp_dUa0${>_m$ zRrhL>){395oDa*%&djGRk;%VjOi5P^Ayt>ylVBreAtbSn#WNr%D~r>cBR<8XRPG}O zgqB*dmOKKh5nK>AYQ(9wGIe$J<^7VyZ?!m^#mjEit;)`Oe2{JViHa{hH4UFksDn+N z)~=G9^k1mijrh_hpN^buFJa+zZq<8lNKIaAO@$~E349Xgjg=Bm#Ja#v&qqx*!4RN~ z2jjA;ZhV|N^DUQKOd*y>Ak8ywF{3UlnJBEwrSd&^blD3c-_z_DkzeNGAaZRY!r#E0 ze-U*ie0;!tDY`K*@?)ION2g}(U&I)j$3;$MIWoLq;iN^QJGZ#acH;V?FJ=6j#_KOf zB&<#tcG+QZVmruXi%@wdUUb5koC@)`30Czaexa2J?fKrUfP|%3A}I)qw<lpy^b}l|nBPf9OOoUFU+=kpOk$)!Gi4`$ zPO|4uvJzDf>PAbb`l`cGL&Gh(#3ms=$+^42=IwTPNvYG4Mne~3yZ-v0xBlbFWvU_x zRC+lO_V6w2$OKl)QT%_POEdh)QwLChT?@wcj$FH6*}A`)iA=<19px`WEH-<~T!I1w zoYFP(&XS0uG{0#H5^p6}sFdg}!~v^FBM;TXvavBTxk z-_>lqFwMq%rpQz-Bf*r@QnBD@EE&tCKG}?0$;OyHA-9nm91i#SNx4y$udg6#niTDn zzU=I_0J^&y{LbZV;b8jDWdr{2vdK$7uQS;@f52qdAK(#!yDZ-72oZdIwR9RARoxHUWa0~R^RmH2hY z<>bRp%MJ(Ahnql;Gt-9}p*Ik8IKI1=N>Ota6sVC|hU%RUL^WE$jeMQ1l}(QM$qaVFX_`{pO`?0dhEFVnCi{-231-ey%t5W42r<_Jo`SNqPBdua^fagk|{6+s-sa= z{hhdUl?U|Isi~LyrFk%C@|?7IFE}l5Iyb`aA#Lir8I)PC+iepr>EFx1T4_Pg}@#65XWYf0maMs2#1RY}dt)m-*Wjx4kuVz}dUpIHqDl7%u zOh>l2xR?nD4)`(6Qj3kJ`luyRbTV5#AhX#1^2e{t;F2v#Khvt0NM@!_ku!VBUW-VRg2gegeO^Nu%0 z%{0d*SXaN554TgM?hTFPdxDfox;j3zn>nuHIc3!to}PK@#S;4?Y+VT$x^xXu_O zRpYsNw`Fs8xkR!u+tvU!gsi2G0#U9rJ?vk){m~7QA(AzE@bIVk`oo>Y?cj&g-Jy0i zWdmvJjK8JVw10*L_|QbqF;ROZ8#L17CpOH7cwH}7Nl0{~UNviAAj8o;uxF(WVuwY? z$N`s#vuUhMEa)v`B6#Gc$I%6ua}RJz?w#cCN*{NuRDFCW=%pBWpsg8<1W$WkJpZ|H zGGdEr54G0otp3;iW|pT9^R*Jj_LzXgwLJWNk4w$6DQkh?w^e5$730Yg{0xlp2n80t zn2hi~24mgk0#bvJzTzP=w8G!z&$B4&c@s@>h`1E~_HEWXfBUxffqnS=t=jITRqVqc zN%Pqo6OuxX)PGJB)eY=r?gn<4%pbm6FK^2@yq5IyXNJoopS*)6eiaV=mTZNLy3<5s zR*=;kmO|GeE+?)97V>J&U9a6#iwHv+6Bifc;v%D^&c#0m|FlRu(Nhy#m}m2R(CvP^ zs^**~HZq~JKZFbTJ9V;xm=YJzp0PO{ZRp44^c?NeMx(uT{AwXp)6iNbfS@+!Qg57{ zbuuNLQq6!?DZbZLJk$m;$*3T41vMZqLj^$fA{#G%lL4}6myT_s^Rwm`HbQT9`r4^( zF6#ZJYoojjxj&cudBe0{v1KI$9r_=akbAIg(9Me@^88a+<*xhXxe0~(q46rIr~0gf zdU-}ynjCvA-2h=%oT?ungKzVBqu1aQmAJ zXFHK4kf4&G`OS=@Pxfg0=N1v+l6#+Nn$^Iy8pRnVRR8_2G$}a~o^8-V;MF`hx~eRS zq37?CqatQd?Jh-BJt@_8ROOAXq{&3_?zy=;G>)?`Ek#iutU5>@TZohO5-#v~3P*44 zwBvF5g|A`mE67=C3-^O&iw|!evkxt|%qgL#j$=KF?P}!+1W{z$DscrWnV1G;T|nnf z4|D7;%re*?WOm$X-9Sa{k2X_Tg?!P^vy`JRR(zW2D$NSWsvabKrIh3q8NODC(I?!S8cihtCt zW%bH^LufVPr|e^#$!O)5g+bx0h-W~ur%OuM{kW+A!?*kn806|;$vw=aBl-l%M!9#6 z@-xb1h0ESe223xEWXU6xABgIM|O6JlPqlyrW)$lU|1vO%7OryZPTy!KG3q>dJ zeGCsp#)LTjTdG%xtk$CxdzZI$!4lQ4Y3D^gCuLQpq{7txGOC8Q0(@tgsyn8*=6=?q z`r+8Nz+dymJdaZu%QxMjXq~T4-DUIwJeeeBNQ<75OA(VsAkAqCf0>dTzq0wcAduei zKK}SU-YbF#nRJ__NLb)Omt_bM|wC$Hrig_8pvolN?1R8tyFBnF(ovJ);KKn zq#8?sF2>*q;>+TPxiqP9gfrio4Oe0TEFN z&&-NCEyEHBdi{lYs1#n1X+g``=Gusz@B}6#5Bfb{K~SoNY$;{71_k6YP_cnU438Y zjb(4&c<5oVE0sRe{VT9S(B=}vo>@2-)WlL|g!#%Ha^V6|mlBZ>X9LlY!n?xWViQkA z?Bp(a&ADC%^qaD90ZA4UVpcxuN4_yjW$wan&CZ7*c0otWJ`dx~*NM%;hxlISd-E5N z>l&IY1HOrY@ArFA9pZQY)WhDKHB^n@~DU(9{$V5{kg(bN@4Guzg~L>SvsMI)^#0f_BCcKlp`6 zHKg1WpE|`3(aVnr8-3Yph~+XYZ6Zvl*Jcc`uE5niRoRnFz&ptsa6D(e>dnwFu=%iEUPEQ5x&4!)RlA31r`Zw+i-z*s#dZlzLXQ)cee z^dWz>HIym%kZH7;{|MBu9dyu0`M4b(#Xho1WF~&G_-ctU0*4Lp4JiBw1*p+(M5L&E zND9YMEKxAsW-LZo+h*BUFqOD^ z2%$!7D;5<4`SAd@d`tRQ&*-3KRm6CYno@{E$C!yFXo+m%*4U4SD$FMmVv6lOv-Xm+ zi+BYV2E5B|3r9;zXXc1OIO+B+y>C|qz0iGpu7@7JrjLt>&xdbEA~gPD{DwYuc8+wd zZkFgY7&+**8IE_fWzt48mYNv}10uBP$v9LYiNaeJ#Y6~H%F81mMn%O`NlOPmMG%E8 z)cXZ)VTSNTAVq84_GG#68U8@VLo%vyfGm$><*$FbyB{6y_`Z_Zahz9*N~pE&PW3~+ zu||z`xP2i!2@!1_;ydUbg6TW7fhnb+Lf^;p?b66KDoYFSl7%af?6|Rc(jhbT{Cz}m zKY@o7Lb+ZRkQm|40R%g-Ox(~#TFzE@n7M6Pcf=Yk4d?Z6=B{|D={R_J1ia$ciOb6Y z-AEDE;u`D9{Do?9VO$Zmmh0#c>3Dc}PyV8K)co7Qj~F`gqFBL78c$4JKoGb@nIvh@ z$V`Tl7WZ82@u9SaR2m}`SL?k>HRW3eU)0f##-{Juge)5!0R+vyQ~air*V{&U0RHRH ze{hY|$MaO_c))kOPi%hC-lqF*_Oy*fhHElQ_YFH(ox&34FnJi@EVkO{^p2O&ofD)8 zZmey53;mvnHj@;U|Q1nR-{H=Hb)8 zsQjw4~w~=9p)6aqNf>N6NgWidvx;Ux$su3CB`sF7~^gZjw9Q_4e;` zi{A@t3E=;HLrG{y7x8+uE*!Z znQB}QMO`B_v6LS;@jP;2+FK3$rce%s$A}n@44P0y5Fm>Aku{a1xNggutfQlloQh6O z<*ajA7@{T~OB{GtP1NxcY`!;)uekt{u_8>mRa5x*A1r%QiC zqgwIr0L*M5C6bh64Klhzdd$LV23E8#K7D0(K%rm~D&k7nb8Z(k>PBQpxjoAs(2xHz zrD{O)cI+0;BU(LoT3(*GcoJ+mS?iO1Z1wa?j9U-ER9UR7RKhf!V(+(O+4$9`(H7B4 zYvzYHuNX_DXjdvJW9got{f6GpjGq*niM)y&24c$RowY1M2R@!)4NHOV$O;%+HX)w7 zBLiE^Tv{5mQcyY>)q%~E82(#E>hF9wV`q#eskx$+`6H?}EDfoPe!PX*9o0~&8TwV8 zboZKs!um^F@wBCCf9aAe4dQ?2;$%_JAkPNNHlFR}HoQbPwD&7f&g#+=S|={q>9>Uw3^0 z1X33`7OE22i>8m;Z(~IyB1;dvikaB7#aPfZwhG{G;alOXeSlJxkQ@`9i>(&LCEQYD zULtByIj1ny`xOM%7pe3}0_zVz8E)>c6KmQg zW5BL;5&jXnlO^k*8xs>rq!bd$G22AS7{#^yELrmx-r;Y8LS-{$xM4CI^1u;ZOs(>(#O_fp6El6IvhX0Q1g<&}iB0_c!t6=(*I&0# zyN+tx(a4}K$FyT_JFdWJ=ELE$BV$-rl`BvnGfP>eWJgp$Z^6k`a#`B8`Tp2eEszfj z2751w9f8%Uq`F-;C|s(={FDxHX(t8dlILN@R*@sn&(zjVk~gZ~Eu>`U6jkQ%_|)%= z@*+MYJ^l_6)uo=-9Y8e+cjlfo^(exoyZ)ub>fgB`)D z&%0FJ^=x&aEa@W=RlX6pBQNWilQVsEbh#z?w~2@EQj34e%Lj7&i-V7AE|GW_59h_p z&Dk$sL6Qb4i!MVbZ+ckdIN@m;!)15*hezq&cWKQH`nB%Mu@vK_=J~g*woI5WP?QEa zDZrpnJZP~>rk^~%?9V9paU)jsS17RHOrw zlvW^?miSU+%uI~;Ovt)(&;anHTPk7!RzzchCWyk6)Y+L_E01kN!fYp=mJ8D{oZ9IfM+sF>oWfet``ykO+4 zWv`s;L20v0@cnj+&2-BtZ=eII$dL!1h{0IC+R5AvF+XK{W!nnAr?w-?iqSs`Noh#Ow9k?pd}lZienK zy`ZY{Ns`JxE;xcLyVlDm*91!y*QU27$7`2<>%7D>8=29GM!Ew~J}0D%b&)B7z+&nE zS8FDOi^+ii8JfVwbV`qnvq)k3m*Kx$ar>Zn-Yk{WzU<5I9u>)=JaPEcpxiJfCU_ie5!$ zQ<*j1Jln9BeBO^$C`rL7NuCHIDQqIf11BkxzN+Gpq_nj@*NuWcH`lcC9OBtF{EWY7 z*Py3XJvd_ewx!i5xLkDAl5=S!D`;Nl1AnQun#MVU!Ti>q|D0MTd_-f1)#H4m?~|F& zMNZ2C$F_24zBtwPjI@TKVLFjtu9gCG89LIc+^Sk%y1v-BnqCKpqYidD@YDFHor3-P zz;eA-{Rjb)O{L46_iA7S9z=Ou$8V^ejG{dA<%4l+2WV~=cSnTFc`qrwE8 zJl8*!$glAlM_kM2pC}untZ7IcL-2MLF(yeC=+MHX9@u6M2crtep$hWk7_PoB4Jly) zK*cxY@70soZ}BPG@nPq$**wkda%sMn1UWBzmPY7)MC}47dz`)8Iq{jE@1$iu%?iK! z62nTAhBF*){nga@(|K#TLF6>JJWJ{NnQ+$>1r0GEx8BDT?ItA&3KHr~5kQCN(9uQS z3}7yz+n3jK0&$^QsqJL+RFaii53?pyXHUd8$JB{Db-e3A$RjInph#wJy6vZyWg2gb z&%DCHcoQ5K?|n4a-ocs_tzZ}mb{vXu7@EoyCTp{Kn&!e@@tG>@D0ZaJNZf1lmY}lE zZv|lcCEdl;cqZo-Yts2@XLo6CwMyh5a^E^F(7gs%nN*%4lc%h_`FkP&%ZGY@UlaW zSI;T;@QHn5u`Y-t-i&waln*knXeE~qYFn|HLX%I$s!~?Nc_gm^5rB=u7}qe&bzcHStUd=TDBA!ttVK$(17^E+K4G8iTml1zheD zRMmCUa&`wd)g5!+)^KoTAB?wDmbVJMx%Y{R7=LJ3bpjm*{eHM7>2g9PhYzt@LxErl zMEL;PDR8+ zRx=E0r=EGC0=hO?IBjRGHRV}7rEW%4P5-n(JJKR6bBl$f9R_Mj)U33k#B+DJQSy)` z4=|d?OCBh%e&h#KA)ldS*$9_vtix3zVXnV>ZDrGGlnh zgsgKz_3Nj@g$uzNTXw|)OtOT2jZywD8YT-1+{~Q;#}LCH2MFa&+L~6F$=T&GZPq9k z*YZp!T+KhW(6Tq%`}O%kx|VU*$cj!IvWWFR58+IH%R+kV?d`g4tk)lQ#9J5Y)14my zCC+dSW(%C+A*QLq6E{lE6z#{IiBNhr+GlYx6-f&(7V-MA)+-kO^a5BDe}-8(K>z*Qh;Gk2n(PsX`S~Af9643&_gNJ7_$$c;F4p=b>5BE)7F!g zE#@GHpod@Sv?ad6DGl#ZE>5cofd8}19Fo~2FCBlRTH0=Y`LxMHF!f>K^Pm>(4;Zdy zT{i{(;vp)4dX1rk7#kolxyNd!QLpl~U@#&S@U{OL-eylz+7m6s(vR`r3U4W`F8rIH zv=C?RT9l!iJ1P$~VgaMGm)-gcY@pMblyY^2yY@&#q*i2ussOGRj4qm%-OSj~r zSaeH5b5ef-0zL8Y~~wb&)w(CgA^W!5V_ zZ>>e3Mdj}G#i6xF+>AEMl_atTshK8^QvCn1N}jtvi(b+Zzg4GDmVhi?hC4fA52?`L ziDKR*&d49kFWb!h`E^)1KRTJc!T!c`yunMQ09RL*qU^6hR2Mm;;Rb-n5PsmH1Os5SD%@`~u2vd|ys@lA}c7*h{ck~6;S(yLpJghx}^x&#Nz zdUqs}`Hblb_W(2V1KtWRm@)LvWVrR*0`vYZVBsXHO;*qr%%CqxVkmp|AMdTc>?m2%G z5o+-W1iF%_t<_6LGd7*EK5bWK+opq#rhsw1UZw}Iv0-Pz>t#jE98(MptHZdu-{rI? zddDo0%fx4Vbo?C)RWWRydEVfZz0cm>jIZUP<5^FR+_UohXM=%k?~SAFjP5t@!C}xz zN2`bZ{bXrF8_afMc69P}I+mSw1_ODnsD{EEQ@$bq9`U3$M)#{jDf}mvHI8XGwc<8U z_vEuUQKyd9T+V$_&g`)Ho~kwJ!vlh;Utn!*tVIca~$3+ae9+zYLgjSMt z$`u)pBGrXTbJ&Fx8!5a-gt03lRFX^d!1Ax(l|G|fdSJ8RBU#~$A~OC=TBWxQ7_RuM z;zIo(izNNI)&6`Jq9^%iDqgr)jb{b$C$C_xp(&Gcq#tnjQ%0u_tm53JM9LLd*$Q9N z!_QwLwb{viyhmE}lm$cF1XpTqz5VsM13RAwq$gG~q(o@RF}_McEdX}PTggjz9m6X5 z#vey{D&4ImU2SF6++PHu)4ou%#N=<6=_Lfn(!ddnL=+?`%@xJQcPChO-{Gv1;h52# z^+Vw3M{szj*@qQ!Q5M5b;ww>iIz$Y`z{P0^j9sZN|jT${N(oGYEt5@(yu zpO$5B?V2Ri;Ha6~L;nTDa~rn3c*Pm@l4uA+e3Wl*Hw}Wh@`yBPj9tSbf!^|iu*HWW z87RRd`%@-;={=J)gC%8z;<-ULVCxf(@km2g(MiN@IwDzpEHh@R9q}xjLpe zvPr1}HyCSbi+o3~Kwp(6EFg&U+56qoQa4)CeD2^s7o|Jf_@DWgpCg)Qd^|f&O6(!Y z9hI6e60H)pzh1Vkf&+(CfMo9U?D)QGIf3a@_Ac4#=cM*9Bxsj74j-q1bxgiqgE9<2 znj1U6rSwa3VDnMwoF(21s=kA8t)8|;jgpxf*G1^|jk#~5LCfld`8r+;JLS^M%6UhP z)b=~)G*g??3S(Q17(O(A%g&;rVRx$s%zDD5L957NFw5wicz*g_rRls^SSi z*4(%X^p7-47?lY3v`NJV-#GLbg$dX!(lP6r1v#X9{=O1)n*Y;`2q8x#PP z>p!tZ4MH>@E^8`XAx_T6i*&3BWijs8O!V-i`R zej`Z#8C_=`w7$9~WQl@|kMtLACwNE>(aEctB+Fs{43S<)MPJcOL}-sP-FuuRh&fdf z3$JK;_!{xwCa&HN0neY>x3ei3;QlA?&e_+=t8LMneoEkraLpP}^DhVCS8`;zhD*f?NUc#3-1xI=tFRq^h1ONoElfv zjzIoVUsOJ;Hm-uAW@c=<>z)Ukk@U#sZy#py%vNRp=NI1Q8tKPV)P^p#<#hjiKR;ay zmSi(yc*|&J;fg0}Kez^^!$mJ)C?4t`FeiyDR-!)!lTuvPgg85p+0fPnZ+7~~OWUVo zT==*>P-=aN?KFmEa6tbW;gy+puz1M;v)hxWdD}WuNeZOWV(%HR_wMqv|u+XcxHuO7H1+%l_wbpgGWYmhK52}Qd9rBLh|Bw z@Q&S^9Z_qP?N2JUjK_M7#VKfS@A=D*{1qt#XFb6wF13LuG_xsM!RD zDi~l%Qels&uAI-Z>c{9BE2$7fp}X|DmaG>L; zJd6}6&lDrK9$XPbJI>x5fovSB##zDVig^_jyumlaY?DtJ%GKGD>{Ma^*z_OU0Ww-K zT%d{%?NDk?O6w+dQ}z_Y-VZ6gjeCBiU+V6wQyr)aYZJTIPd3vcQyaeie6&Q^(O1KK zxZ~N4wd1@*a`lv=lg&XK=s8GkN)H#uY{hG;{8=jr&WgCzy&HPw2j284HUm$H^9iG;C~nQ~(HJe{LkE%*NW;?#Rcl6(pd#p!wrE{m?W>?@3^gRWnI|!)wQ?KL!?vUT`#wkex}_ zv=2Guo%2rKxzOdFDINPEMNCy4fM4K=agJ^yRyn+gCi~PbwZ!|J)BO+VHtpY)uKW%=&WgcD?}(a1*B_Q7?^|7z80PV1gQzN|6WZjB~tL*us9 zPi(|`1r>c}csk{J+&}F-G01iXCp2KH2V21gAv6XoOjo9dpPci8F*#?kLdh3cre^Js zdCEV^q}BI~2O~YXxb(b*F{=UT68idXnlA)fKIKAH)Uv|`tC~bJ6O>*b!`@^h^fzov zavS13kCb;Q`OcHmxZ82OB*>XxYQ(U2gh|7%i^MGb@9*YnN%?GtHqp{GKhtttl*(N^ zos__Zp$URYS=1wbQ#wwxZBfTP(SgB>HKoR2Jld5OmoFA55CU^ff#10k7yD=RN78_4 zW(hs5Tf^FgFhoNwUu2m}i}f?k+$!nEnk-uRgbGYaU^DMNgnVd2XQT>93cqO&A*U2EumKFnf(?IH5dK0+_lh0bcsn zbqds-$#3ro5vFVw{Zqy{6}XwY8-_7jI`gN&#!6R#AzJN^};X zKzgJbOX=G;D)3aC&JnJl1V?PJhe(l9ov>$SC3vpneKdpo9QK~Rk+sKG%HU%Tv2dvu z@k?`Z8#fb9AM*BEs!itZ4^1g^bMo>0;b;4c*NtSVkL7!&fjK8LdK((c*Fu@3FkiNx z+MDIQ(-EXk;SFc0JTH|$^B}CLmP_^zAa&29o|oLyTF{`{N`Q4`$oGq>8vMEKcHb3@44 zYTo(RYfn$gk}zUbRcHN$5wWkr1*RqPOa)*G*n2iKuYEKx?`zQZnI|>nk+dauj9Gxu z1GyR){{8wJqxeIOETus*r>V5mWzHmH`j?`CX4nfkY=J&<99H+yEQ`tLfF?W78D|gA zW8rWrdH>O!-!~mY>2HN!CPq3fUoWy;M#A?Cn)S}5SiYbw0YvB~WO2tnA8<{u2TCi~sI`-F|O zuu+gL%Oa4fzcgyl+`>sCEN+z@V?pdf@zAB;X3dk6=JtGlbMYcTvwu|aT3^*>yxMw6 zX~XdnzI@r)?%RwZquQQ5L1WBc9o{CL0@G9j(YCP*rHBd2K` z&+*L9?L_}MT%}*? zbnkSu%8;y{eb%0nFIeQ%0Ebzc3jIP_PSMF9O`@qB(bpE}e1c}2`K)eN3+Aq;3d|t+ z2~YN0=O2$Ko=%%XR${~6ikONFudNz#w+GeZQGgA0WSO8~R+u_l;khvM{&a}$t48YZ zK=TCF&BBwtap%c1WG~sr4`l*OBx8;N&#W5cOF;HyQlXp1LnX!GC3JIHm9NsJ-Rr7> zoB+2~aubcGVe4%kq%`Fb7PZtxnrwe{*E_ z#~%+5V=1ohL))O1+(*l+d;c8nXOh6toJ>bmgD0sf%YKrt^yef!CX2P%mM}u(U0`@e zLv7v^0`EL|KdxPzLU0?uFE~N8SAP}X(4N7?Q0%9U)-=dj;_A|~A2-xA{0Ye3(%yfs z2QF)ZZQpAJhnpGs-w()LK3@QSv8pAA`EWCbcT%3U&Vfb?FYcL8`CjM{)?!DvHZ{|u zT)OKD6BQ$}txvC=sWx}gsEb^Ne@hDi%pmI#_8FtX4{n#r!mO!l>trv%4|Nyio_EGH z{Aoz-@?Ono1fddB8du*`_ZDXBm0{CWE7?b8pLj_s+lpPyWr>)Y3XrEKEu8{^~pzJ41ucG5U$(%81qq_J(Yv2EM7ZJRT3 zV>D@O+jsi=8gwYddnUGN*ash6J|UI&n)8Q_n^2>5S|dtIjf8 zEbafCbBqOoZui7RC{5JGZ$9bT)qVYigqOs4UOa2ZOb~j%Kqqv+O-smJ#m;O)ZJ;tx z5u^fJxn#hG6oMsl!SA`I{#zSCj}UR%tUFdYux@_oZbkez`xvvT8JqzI@xF|L>yG{Ua$7%6ueW!aJNqxOmS1_&@6M{ojV?JQ z242b~YilIc*`t9YI_ZYVZ8Y|3+ciVKgNqlwJ3muJNjb9r#G1CgxKQ)3tQwY3&W{=1+!>Zf7Q- zVNx@!8PX1Kml7HK$chR%Iz?EGL8__EJlLG%_)=|EeU`9^T4-|7FvspO&z*c2=zNy3 z`laB9SpC7D{^5XO+#n47HOA|`{?++$tk#J^T~qMZ*s8OBo6*Fz{9U=7erxL|?QRcgK?X{r25F#%D*M!5Mc!*JL@|n5qnEG%xImf_jWQj3_tMzZyw^cHcE{z;HYbl-|2E_aZ+RR0VvkAy=Wna` z?$*alkH|-vi_u)rWSfW0X7rd;HTR1F5Wh(&i7(Q)87$|T|!#h)LC08#a)!yGd|0m9%ZsG}eM;G9vQceJZ_z3?7yqt<7&(mp*acY?h* zq<GRbKw=C+{gV~WXMs^}2+_n2g( zW{IuhK}X)_`JT7QC*xOBH0VK&Mx!P#^Bn=HjGw=Anthu(xcq!v8U_3K|ExMBfrjn2 z^BS%_`HaS+$**pPJ`NlOA+aesFmrQLpIK&0g~84_ux99eX>4(=R6AqK*%xRNAD&BD zLoWGM0?}#Re)uUpF`DY2h+wZT-8Mk!K-{<3d^MQX!igK3uv!nvk+?djaGET8+5^$H zTd#{dGh0enzm>>?T_;NmvcW-V1w=pyYAm(QVZP>@6h0tE;mM1z2@ z0`3Ku=wE@?^=Jh!3PF^@7{tIvV&LK0C^=j`YQgFEq+!05FDLu5Pe-o49(W=?0%MiG z-Bi09pw)nGUhq|Z9$r-lq5;eavbS0{^7Es6a|T?)rm*IPe8qij~L`K^gsOA zy}7bkbC{Kd|K3pB*QXd!y2q$Unz*qc>Z&yHE2w)$n?cmrcpb})%W!( zH8nRw8Q_k|GNSICndOhvAVYnQK9(BmJxXWg z!k%MFJ@N$nI#BETC>n}1Cs~2ivsw|@zQCbOP}R%5d3B0l5-med#>UOL`3O_vFT4xR zn2u6(ELeebU(KBcJrrq0JiSBWHTQY^q>MA|*600h+V_2H`s2#-g~be;sM+`FW&2*2 z*BL1!Fxafw_w^*?>;5+jxmof-g`-dGhv0c*6ccG{qo|er9ySs?41^FSmde(YG!yuMN)kw)>FbOuMB}7v6YVnEMyL zF@_w!%lh5Wwf@lhv1vop`Y?4-T9PmA4P&84_F7S{;iz34QP#@hY}B~=Y87hvmfHzU z_4J~{?1#!WQ?S`Ad0Yp;7<_wx5Y*@b&rIgRth1Q*`6f4$d>HXio2?-}JW~+1FamK1 z%1#EhjT7w-$8mKAO(3Bz4!)6rReUppb~+y_DU|AgW?c|M2{&?udCj6$aKD-iHL7Vc ztw&=ibB$}gQHw7DF69i0`8q6aE)*^}qJM~>NZu?|^hcJIh(H%>n6hel_kuDco)zsZ z=jOh#@S0uts_OfKpv31sVY`J;V8$n_x}e~nbkQH$r6FG|Vc5>oaf!%+MaqchQ9w^M z<@O%W?^RKAD^D+Sy_4kzL^-&#n6*^Usmb{g3&%kDLREjpRp}aT(P~3sRe;!dO+a%! zcAm23Z^vU}d!nUsgJ4|Y=$DcZV{;g`AY5U@{E>mO@0~+^`mb6(j$Gc`gu1vPil)F8 zMu-L@YIB_;MT|tyFgj#RZ}}j68Ob2sm8=Gi4&yxi2mJZAjbUf=HXcC;!93!bL%)z+ z3m8_&zgr$*V;wnqeIq4E9oLrsxSY6bClRBUP)~#9qREZ6$6@g()^r{^r5Y7xHzcp7 zBUUW$$E|K!i^WmnX^TN%7(+2$=U^rk7l|VVEn%2b;!$G{2L6O%eJ|H`qSm*m$rn{U zqeDn{S~Qkc{p`FTZ*|+gC`eW!?-(GFvkKL8{XP&jdeWu86@0m!<9_wynmGt-03P&Y zNX%el$C}gZ)0sgF=IqynprkCR5Q+v1-u&&$Ct}sGSsL2>iY$!LIJDqxM0a+`StXe- z4tECAT~R0Ar{-DtAaebs+^)%gD96UeA8f%rRaj_c(KjeH2##I#r!cxN-dR2c-I5Vt zQA@IX=wMuiX&4SB8ExJ-jH@z`HJ39?zSOx4hKOPg!+MT~n~+Kj5|+PoTbhwDI~X-v z0&)sQ)UrD8Rnsg zgxPNn#Z~t7Pfp?fsB+sBkZww~R&X@KLFPb{dV%U|5211`rHJ*jPeLxVuP3 zz%(5(E%*FXgz5numO22YAwOB8iYf7vY)8yO_AlA98~IYh3dW_&&wS%jrk+dvk)5o2 z;Apxq4BT$SI5_{XLD;QcTZrw5Xya~XQ!qMMYKRv_+vXKRUA~(OTYA}r8 z$5cj2xLRF8Kwb7kuTaRjuQKo67)CN*FaIdT1Tl0m|4OWZL)s&!uTHi{(>tF(KNADsy z2oDmE2Sh}`+`l9RJUVy^XQ#huD2`v7d*_q4pb|5==J2q4ZKKgpnKYuhgN(Vu54QSy zel@Lv>5wMX*$mw5t$?Ys73f<_WS+GCzx?!O5pnnzYeN`~47N$5Wv7KR_Qw}--27DY zPF3#u>yVYPQ7vg(9+k65B;(`Z<*$nuD+0g{y06nL3xrEn%kH%c?mV!T^8F=2+TUht zE)nt<>HbPHZOx2_M|!3|^dXq0@a0s-uP>QFWrg4~WF7d+okd<_3;9YVnl0oV=89`p zUEqRIyHOh)5Ka!O@yY&Q+fcOES?J%@8kixm0^s|D{}@ue-IQeo7gm~RT%jNemInbd zDreIX3#ek4v9)9r7LqcYoRk@l%9O9!i{mK4^kJ&nzHIQkIrc;n%m{+Occ&+xSOe$Z zI9xwt>h-#3&D|k7LX7JtOqb%IGNr^ep|9Y6Kzk&~wP>kWbV^-lDyU;r9FPpw_ku0* zn}ux0@PXS;2pj=y#2Z57nh6pcv<&%VWI$>JN-`8)=|AsTQto`>6NG>YjJJ$9ZH65w z@f-G#kEjkjUbcdfwSZ?b=*viIrQ(L0Qr=NG<4UscjwP6%`3t??N+Qh6|DbS)ypccd zm2pxHuIV2zZa+iiyeLE2dOLdQn#nWiJedVJ6y=$T_QfOhJK-&6`Ed*hUOw>t50)*3RR53jc=t>Enuql0&L&?k?09@JJ+e z3G$FWVSVB{6m{*vg59``##sPBrBaSpv{G)rC{dDJErtmjy4eyF9@`4Vubr{p=(|ni z9>(m7usl_%VdJ7F{bNrZh+>!jp%~M@DYlb=+wN7hbG-(#ggTNtpC6j{McDK#+Zo_6+$f!T(>$WyM6E zbIKAIPjw`B@-xk4Gs{kJWp5nBRxG1euLGbxN_Kn}Up`m7KFVCpYpLHGCZUsmoycwA0g8H%f z5=+Ll?Xw&l+;I?m#&lvdGQ)8N;S=n}(IOIu#{ET6b?k*S!dUUS#PN1?S3eK*Unq?V zC8PxDeh#wIab1g&;n`(*{0@?WICL79nkuE=Qus>cRqq=E@mD zaU+JR8Hl9Um2e5U6m}!-LebT}At4C8Qo!jIKpeDlYHq^q1Iq{s86ek~9E-CtRSU&S>)tByB#S%hbX#5oybOep=A!wreE zH1=pV@wjhp{Ul;z#RjV=$#TyD7-p$#toLrXUOK^jqKWtQHl4#Zpg z&BM5=|FT&EC0hr+{psM)ix+_+_3+P2rtHz3ZZ{Xs5XX(JN8@$$)50Wgc|WCmgu~bE z-9}{GKCbH`Cjb&tvLUktRGv0njMXpxPe+zNR@#ck5AW$tItEfU=M1pU)3W`4pKd=J z578T@B6Ui@g)NJnlDMFDYOn3>?pUOU@Nz_|I@Ow{5A|P8zS5+)mX!0Pi3yQo)(Orp zw?IoF;bWt>QMYiJ6WAz{sZ>xkRt$f3Aa7XUwg+o2ZXYe7>*N+9V9;R{rRfNiX<8Pw z{$+qplm5*Ry*?cD&@e;lz45UPM*&?HG)rcDR!F8bRuZ;h#E9gric6Qce~)aPu$2Ef zvk^d*Fn7j$pO^Mf71X{yNN>8{12=Nlj1B@d?lfYqZy;CiuNA8Aivkh-NW)PDrf>o5 zTFJ34#yVO|>FaO@ZlSa@oCq6sHei7=GEF9mu=yTnP#-j^sWsMc!I9|A4#yS=IRsbc^*$gp%@pfQmFpI8ClS7V%4J zilwQ1NOTSRYH3e))XwF8B;&8^U^uFa`l!ZE#5-mnWA?9Dv?_Ud>Rd-J#L*hNxejF< zsPzjXX-(JXmj13qQ-SW3Bt5KNly*3zCA*v@ZMktVNs4t5BPhX{l?z*nN`;`>A;@Ri zW9>v{P0^0Wcdo-Y>Q^hhn86{5S$ec{S&z=lV#QwIP^Lw7E+!Fg%mCwXJa(klN zPv@_M-}w`mNv=_!!n{QJPp)xNEb!DA_KbLT9iLKOLZ@F7UF~BP$m~DM3mpyBki)M$ zCe@c@L`GAS3Wwke(G2zFC6QFlG@Gy~Kb)~RG@EE`@4oU8oYuP<#Hvll@#S5Xs3Ti` z2xZ+HwFVqq<6B?JzI4B255LtnKAa06IFVY2<$aq$J%n*u%x9qo4YwY3fF+PSnQ8YL ze-kl#2XqQjwBrX!Z1+_Rp zxf1ELDUi>=v0g)KdutVs{?RbHquK)Hnf6XJ!F!JE3!vxUDWnvu`kF!qyH5nNBF)F=Qd6U5u zo$Rb+0lmvX4Ogo~{mpaM{9C4|P3fucg^3&%yp|jnxrLnWmVpc$F?_s^Wv9)7$Pk!6 zQc%lalp(3Xlkf7A6K7}j-AulWBJ;O6Tm^TAL17x-MwM$`WdFcUuusNM1)g>*{E>+O z+Sy5Ri5yBfXctIvsg-#m)l9TnjUNJ^dG?d+y|921xt=(>M~acgJz<)nbOm1&#+x-A zcyn2~lICVXv#)R9RpxJeQKu;$7?*#=nA3V~0@I{edTG_(aUBR#0lS;~rf!|j&vweG zEG!4Ue0aOuoK(N{5;E_hpP43nicT&#aR}H7w*aXG(kcOg%8V!GRB1`ycoGXJz7{*I z(WXj4PAXd>=`vaQ#gS0xCEFLBAr=Z|BCjkIH*M{;7xAu(DV}SPZ?@xW)u}7~6jdjoMNNURG61J3o_(_GsGz-n;7?yb8qoGx z|Ho@pdJMF}N49#!a;Po|e& zx&|;X#J}5AQnXN?Cxmo0Sg0MC-Cd;8_w%$34Zequ6(rTlQYDOklSC~JQxv6wuO!wk zs@bI}PbqbMwVX#nnVRJ)m^9+8~PYM1i_m_l=#J_~brw7)P~! zT*h!Qd!C;Ljupq_thb=k#WTD|fwYP|Q8VO46S|&_<3;ij zc{&wf$$()?h+U(Sq_vs>4C@g~Kd1dPh@b+5vpO7 zq*OyH9Q`B12+|%Hr68&S3bY@i>b=xWB2q(4GYcA8`;sFT&9`vYb!>HzY}%rZ4T67crThP^w81fg9mugag~LR{vm``Z=bDE?lOHEv7ATy6`V9jO4mLPQ7+yt2+=Tnhfhx~a z+pUvrO6;O@g`=v>r0IIoth?msZS%vkCn6KMIE#%bt9C*j3|skF4`5aNo?NX7>pXXq zs;ByXYadbOQVwCJ3iG9!Ba6cDhdF?TA+biNS5!d_}tYaAG z#qt|nX6GPoVS@Y^`GX8@v%*B=z)CWlE*Ioq!~51w%o2M12qlPJ>ApMMmKoaHPqXxq zk}9->-sSyf-;Gol$uj>SC@o9{V{=i-a+x@caruE)OOJg?nv^yOjbUHocsyox>0-r- z>UrBnoQKA#LhW~g{hr_^%1DBZ8UU0eh zXG=l*hBP_qHXLbq_Ax0F1!)coFHV^U@@c?6lhJ@`+G{=sv78Bu%Lh@b)!KYIcC{+! z`^t-+13MjLrpGY7y2qUAV zZHbOe1?8EQ(&}^pt3n2)OcJ?dr)(e6k%$JN7|ZjeZ0AN)@W&Gh?#XL`&ghiom}s!& zSd@POl+mnk^NMEetS2~hv$87E%t--nJe9RW43zSnYw@xS^bt2h*Uf@d9F8|u+>s~5 z%7ALba1VPVLFDj|p&=9K${6dLu=HT@%;|OSKh{1OxqoIf6tJEDq*$=09ZT7>=TBlL zT)~pe8@5vt;e30+_PVbqQv^q$uDZ|J=f2<#Fx{fpcVrGAx9p^7_WsfwzA9={z^<^C zyy^owkoAQN99PjH_{J}Reu22ExuB9>QrW8e7WZ8rYU+yHgu#sGb^kOu4Ot)nbi5Fv3V zx-w(vic&^yL;9DS9eZ|Hy6seiy}m8xTgU6>+x?OqzlKd-`|BoFX2ZB=Mk0Hct+$aU z!}yjlYuA=HMIpw|_o*1)IlULhrF%WX4MZ&#U#=`Uah@^-9`Uiml?KEL<`QMvI2A%( zb&34t)8u=cX3z~PGDS|bs!(D4X*St{JT-|{o>e;NYO=dpJg9b~?3zm|x%Rx4A98;l z7y1>@<{)Fh#r+HWu5e?d%x{sI(^`;({5K2xrj;DZ2?%K+HN=uFVTX6KoSKrFsHW8X z4%^UH5;&>BgBNM$d*+Q@vFWwJHWnBx17El;zo@ddW&6k;QrxZu`y@b9+@e3jz6JT{ z+}^;+A3hX}z9#%&kDDsaNJxg)$9%K~R)PJfm;+hU_;rYi{-0ytQIz zlYM^reJJj2nx8vki}n12IeSJ9^j`F58(RNMxTm93!6vf@*c1W3n*Ji(hCz8+1M-8a z1`K%IQX0ei6asS`JVP`%=_p)fOqShqr zsF`7Bn$|?CVVWbh^IC1fYkV#!E4*gr%F;N>0WELo*RbBjC=qb^1*HX5$j!Sb*82Aw zbxKge5^Rl3?AEg~OV0ImsXC%ZmgV(3#`!0O^XM>t(>WFf|4HXeMLT*`?r-Qj-EVcr z7fvrU{}5l*#B#je#W0iE#cqm?XB*y?5+tas4kJ_{qb1LjNIyO6nmym==3M3Lf#Q6m zD{B??{tg!ER|tWh5Jmb2#SVrO5^^Oe}eh z03)AG#L`3yoCCWKA6qlZRq9>x`s?VJRdz6HcDP6bK^W>#@x>6+V#M`Oc2H%-Vk=c@ z3Y5WA&@dfOXWQTlzxvuk$3-ep{P#vY)NTJ)W}M~|@-Cw6WCH-+rNdPYeXzFI?VR%1 zWNJAS0<7bYA)(AeUZ_8(Xob_K{WfG{EwNa>uZ&TJHwNcA+T7hsVe`;_{3s9g*_`^M zOMmNbZbjVI*gIaYN-sWM-(2bB_aTpD$Vc5{u#~4NZ9NRqupY0Um+r~#Chw~4)JtrV zbbF@gx9_~Lpt10low0DUyys7rY)V6GY9avkx~`+vMi-$_H@SbVC%pk$+<4@SKTLKC zjPwG47*cA={@l#QNz5-z^-9;`FhR0-45D)QL0v&9(nA^ zt^r?`;-IwD;2yue=^)>`L;c|bzh@q<*V6` zxoRyY4U6>F4#c&o)dA;u!lV6bM!KKZDRDO#M-PG!GB$I&WEjB~Tp0r1Q%Ju7hvw}< zS8OBuy=?ul7LO>y&O1!B*G8{=?pK`R&ynH@hL~Ow&b0=ICvR21 z;UJSF6R9y%mfYutX3fl$TwvcPMsUDMvCSuKx6djJlcNYLeIi)3?7H?yRQ@vRL}9Qz zZ;xHlPwE^+VD(EE2SMf*2hS$U$`GyvmW+FB7=nc_Kv_5g3aC~)9ZD2ZC^xwB4alJ* zq0y~UyF+@BpV*A|AFrfP^5{&&>>?klYRUyILlkvx zF!gjIb>P;h9rKu6F7-LAyvdV3tV$5IFlK&){2rIWUa2inCiQ8Q{GQ8=K&HrsQs$cl zVB6$bJnQ;S_B~8th|(}=4SVlHhY9G8%^J!YaaQ;lpx|E4BCxwMHZ%>~_Z#7Fodfj5 z$|@@NRLo-hP1ATx1yh;v*O3v^EvU>~j)i-6-3YfqDp3I}L_gG&p zEGnG5Hu-8D<1SZsJ_;C$qD55dB+mhkk=Im)wZxdny9 zhRWzEO*VvjcIky>`L7+yE^I)6d7Aj(-C7X1ab=U0wMxb8Vlg>sWd>ZoSs+s|WbE%A zX)8G|qN_b))}S}M2<&sMxd$7j^*|1cbg1HD-;SB*tp=+>`?Oo&L4VWk#!0_j(s*yV zZ4npX=Tc`g5bMAO5@t224}r>3T!;cVa;P(~{cI+sm9Wr4s@l8);XOYOZM%sJsxsF3 z0V);3>qwa3NnpLCJd+?SiIh;F7NiB#vtuSA+Z1|~HbI}jDZ@(uJz}W2Y7IxQE2md2 zoZ@U0?QyiR$3YBV-pv1W#K>6N=a%@KVkLSC`Lc!q06AnRI(M2t_I+# zwtlI@05jnfD&aGh*dpUVbAKcb-t!!X$5VQA!K6X&k0>%<|E4OT$O=glfY~3RFIMLu zUiJFAtPUpF52Q&_jQ!SNot z+MfBPv4IQEERsl!3OW1q8w7O%`%N-luta(&IS?qb7mT{69(IioeoedYH3RwyKd3M) z0!1v0M6{hwfnR`KA&Q(BNN4KNGSpGZJ{P!HH*pIiZ(Jg7N5YAkbsCZQq5C5f%F{z4 z3;Wo!mK1%@q6BqaIl8m0;z>kp=UiSWQ}%mes)i8F4|uu!7#9=b#n#5VO|#&pNP!>? z;$E=7@4L07(_8*YpmsM}GgVf*$akAL3bTjERTMTJ zBIQDF+UR{483}7V;a}z??nw5jdpa%aaFPFO?IB>i&Hk&9e@a(W^5kp_GTig(9qQq1 zCG~G3c=@YiTZuGfL=O|=%^7JdzQi-=}Y_TYk?@MG(aR`WE2K?fR+SJwVgVAv5`weWaa2efnD?k0E;b0qQ?H>1^LcrlEhD z&H9)o#_Lteo$QI;w3^BnU9hX3F}JlsSGtseyg*3^*&e7#r$}V9KITN2(@*$*;u?_@ zAfsLu{>QM8xSiZ^EINFuUHM07nz75l=TyI6a``il15wO=SK-2sXG)tXro&;L&2MV9Rm&R^f`RERxbhHIWPV>MAd^EH>}ePPhu4-lnQ zJp87KS{7^&r*&H6Gd}On!}gAGef;)Xl2DTT14d{ z&L;>z;uM?)B6g)1rXy?j4V&-g>jTKpYeS{m+#&v4J>uJrTD zx_DN4%!pkXC(FlKZ?3esnVHEEb_=(aFUit{rq|t|zMF#PyFHFByn*?(2{T=3EOv-X zN0XPNb=D0PW?{0FZvB15qcCQ5@v*GKPAR?g42 zi4Oc7uiEu-N9OJ_S^oOVBf*)DwGS0oogO`()Z-$p8dWRlg>6%-T9@|_# zcyMECv}{0uy6_pATkeU8)+3Xa_ji~q;baI~~RmvoAYgf{iiX8Fo9zrR98-6P6! zYOiAuwmQ!`4qODBU^faYV{0=_33r!Q2k$a$lm*`tJJvw%ba$!_oCa%A=esh^-M2hE z7^J`NEKGWoQLE_NP-}|l!$t;{3Co6OEij3S=wtUKz)lFMIWZ|RX07Du^|YTpb9$3a zOpTu2>sR3oxY^q34-HRhR`#%TG8%gEZ#I+;!&)I6Ws=ZSZs1{J5v@OEV#mPT$wi`-Ew{5ty`CTR;senZcBL2Q5mwFiJ~_ft zGcxYWXH!&Qz`#99G9^q>ADV;i6X!xoCh9|L6lsWoRkfs$#$ITpO+%4((%;7F57HNA zBu;Z{PrI~AByt@Zib*6Q*G$HzR-%Qf?e`>LrZT*!XM-qb@1+@Tv4tK|juUCdw7mYb zv3ScEb4h@MLDRKN4O?pbEK(wHZ!+?yK79)|7>A)*B=mFeuY8dRiK1Niwd) zU0LlX676^-0%QPY0usRh=2nEASR09UJB`}={TL4MvYI5UnyybM5~8G3DAH_%(;-TE zn6fCfr_|m@RZ*s!%KJ{7Vt!BkR})6LMg;OyfDcxeT;%WT+op%MW>p*vZC-R4qY*7m4ZDE;1Q6*Zb{OtpEX|t`(KmTg14ko~%ci=<mTXiN$Fi@-Onz=*m^#1ccUV>5HE{o z?aHvClT)EBJFfqq$;UFXP7k5|9Q`oNP7i76biEEK-2q@HPn=o97m}3zDd8=sPeVp7 z#{)lrEx#%?wf{}tY5WXY=?XPsJS7q-j%y{&L3ULEDeH+mMDp`cBEau!6A~H3wyfwA ziKx4esQVWaGMLk%Dp8Q@g!)g3eJdHT(=}EGqgE*FxRDsR>Nv2$dzT;kZ4vuqOO-757-!hZxy-FPDpEbMps_` zT>5nl2HVI{gI_ec&~pi%wa|kK^dBWX@yiVLAFv}na^9l9#hJZBT0dv0@{GK@uiO4n zD0m+fwUSgoHvlu;3vllDQ>7kO7Y?>|`tc=wm8X*g-9|+PT3ph~MgrQ!6Lb@w$mWyN zS(8V`Y==hl;zyPjr*YGwH%MQI^iBtGBQz=qRoTVY_$d!t1MLNWJt;gZOFfL%7QGa@ zAC|xDX^oF(wAuE+s2D3mhiWT}{J0C07b_$ACqg+FPJXx`%%+9}g(oUPf5Q_yF2p!o z82bp?65{Wug<%T!%n|Q<4|8ILS$)38fjGe%2}fFV-`xRH6?o}S8gvV{J;;mqe79i3 zRLnUfs!y6#1IW6~?OwL-2I@Gx{Hjsgg}2RWPdmxadHF`s=-``hkFCt^2-J z{JWe#x3P&wMEMaDAO9SZB=MzRx4QNU=otfvd<9;e7)uUY1-%ct826y$KDnoVO5nxB zQADi_Q{nEg`cVTsSAGJGtKL>9yACMzRWI@Vcf==F=0H(_K$uIdT6TRcrvJHV(ytgzD|>Vw-T2FUiK{AAec!@OcUAEeJZwEC7^5x ze9gHY@Vg!_gn*LwyB5Z3hfY61&4ZDLqy)zR1Je03yE*0Mq2;ajbd`Sl#n+E{u2A9@ zMA6XKJtJyP!nzVqZg1j1QGPK3T?AXv^B|m<%H1x)+P5ouuAa+D)=uI2 z2f#3Zp1$f%nPZ;5`hOkAYBy##uN(=>(@6T?Li*osxDC|t+02I$e5LS` z+RO0M=B3}((-)a4xzPgf(Y8_JOPdwOoP|`Px9?hM=}=;6`T4BT!LJ<>6C2yHYzkck z4id_%7#m=U>BI`)s>BZkN@1_CvFqxvURAN|>EnEx@Jyc`;g!%*s~1E8fN1 zT)b)nrTPK>Dw5)>7wyxQ%cTJ$6N)qpIV5^Ocxoi9Z*=$yjqAuzowTccP`D~tC=f%c z-|A8~R61hW(|S@67{7&K#SA)1*_F2m&fxWHW#fzb_S*4H)xBN?P4?qJ%Gv*XcK%Wc+Y2Z_SoSAX5mmnaBlC;9WuhrI(EihG!0F}2NRD9mDA4ww$St>I`~xa>zR>k z5$!5AzD7u#_75%%okAt5Of2Lb}snLwPOfj#)IUh_%r;s?5{EH=^uQ^EdVevO7 zM!nlZcFaiOmQ$0D7IK^psjST1DPFdbML^?B5-uGh6V*hlEL5QNc_7@}EH;u{Fk?<~7NzaGeXAzo5&K}03+ zMVM-2SL>U+^Hhm2YGcD`f$Wc$xV%MdYEgPrf5)wY^Et$`^YiJDr%8Vkpv0 z&0V6VZsPr)5a_Riv^S;wbiJ%Sdm2pBS_PWQ@sP8DAE(L;O{y8M@ijgI6bCi5D!<>3 zJ5)RL$@y~YS2L&Iw?!!HUh<*(GealO&ipYN*v-WOu;V8BLA$)-J{> z1VSR3hRY4994L}>D7629;`Q~ygpf3?nGmaJcRBhvaVb=wtxtS{{d?98f^l$~V8IU| z6(VL8vAgEsTJPkoRIaDMvZA*&^l2sc94i%pH84z#xMwSLxcoaxE^1d2pkUbbYuGhy zi5E(8lQ;T*yYJ4zulMT14QJsLbY1F^3lc(XNs?+kD#V>gxKXp$>+!ih4n9^PjzyJ- za6~B>oMOaryZMfMnwY&v1X1&N8Re^P3%dE_jyp#W!clX&W}XNvPRajCE`Y-F#I>v4 z)X{xdDzu!g-C{2kq1Co<8Dm{>(9tVu>iSQy-7DmacQ3(I>?MviTmSn1wO{X4Q{Rgj zcYoII?Mwcr73wowtz^q4=WDNiUyxl7p9;`Wqt-LNF|C65}9!5rZl|!41XTmswD1KB1H6p_%1j;x?z}`T9v=T5EXPJ>2Qfibn=QtKt)UibTj@U91B-{uqm0$$uzMC4N?^+2>R&ZdR03`aKxZ?+6iuG7M>{&s9zMJt zf$n$6D+N6){%q|Re}F#sh0KA(J@~Ay%n^m0;G_KYGyfmWwC&b(|Jyw?v`l;nZgmN$PfC1!uPM*@qKr% z*^YELbKDEuIT0Fz_FoNs1VwQurb>OBg+*$H@*a#hFOoN(3llu$UIGp1eKDq0hq7-B zp39>)5ah=7iBGlPCya~;PE}V(_Pn^l1|I^~JG-1bk5~F zauW}Y>A}2mGlRJ21B)r8j07O;WPUikV_{P>r~#feUsS4{^IEsSX7@`Y1eXQE^LJCE z(bbe@>-4hUE>bj}IE&mNnnPqyiW5YP7e!U32p4}9y9!p0*@b{Na`<}D4A^+$ ziG0dB7I!stZG67Ec3ac5NeWcmtlo3n8a%3T_Ht>53QdtC#bBI3e+)JTPk1-xz!{=TG3@hC`!| znt4cOJpw09JUi5rF|v}DUyx$KU4rnhV^A3CUfy)W9Kb~T9TuF4BSJ#a!oWqr3O*_L zQ9fY1w5y2brz}G#oHGW$e6C*W$E^HZf`9_&g_DOq1AAy}!A-^Tf9iV6pt#y*4Hpdo z0wfS5NN{%&Ah^5R0E0Wh8Qcl(KDfL4;2MGr7Tg^|@IY`qlf3W#zJ2QK^KYuAYF4dP zv%2rT`hL1m-`-6aJhjR2iUgt%@*58O>%1AG_?<3+`7Jh2H2kX!8dImsDE0l%MoKe1 z7UD*#H)it05Ug=Gz4!eG>CW8xGoCI4$0U~ovKU2ckpTHp#u=Ba16lnhDtSKri-MTO zNhniU&GcC6k2jnq=@?}YmyoQ)0V1&OtJ&R5@bugA=`>sk65^BuXLVH#C^ZNVT!k+3 zve&W5;$?Q@AmTK8cRil7c( zzJjH3ry~W{s2~fCW=PC2ulsSFo}(Hckb|8G>amk$LnA zW_dI@_r`;!Z-8Yg<1^!;o9WWy=PjnTef3cwITHRNLu~v-@%lQIBpV!_9Z4x`+j7<5 z)TX1kT7CLfiRO^2V|UXi8yT;tas7hwRZtZm#|AC4s1n$^k>5D5U}aS$bFZGP!ga%y zd~M6tJi6LLWi?z%dEEKNWP4zS#POck7BDVAcsg)n$Zfh7w|0|Re%_mINn(9!{8GZ* zxahHI`K#8kOClLIrtk-<_cX%NHQiZGygf&n2b-+4(+U(8V)oo&%nS3u zG;gR#zEjm}Do`}r9*HKtOcy_?oPV@EglOVAe4Qq$GBKvrC932yvpvARuf6m&OXSM##> z7skgX1OV6c0t4|}@Cu5iFW-x5L)8Ud$6ytBglafVf5sl%4QQw1uIX}%F;a0bNzapp zglzsxB%lZ)t2A;Uf@-UqanbW>q&=4;!|P7em}bVm+9={r+?Pn0ld}mI z($Zn(>L(p4c5n6-O^czw&yrnyk*fs<6$JRCqH2)Bp}`^hBM8IiuHY))Ex&jRgv>d$ z_;YCNX;(V81AZ9MBzgGL{)7z;f>ZFP8Oa}x#W#514W~I+XYF1fOMead%5XtJs!F;) z3mdxV{gGcl?~~-EiE|IJMq?TapVZ~-?CY5vUIhk=oX$PUvwJpmiHSCmvK;fg><-`C zgW1SEB|E%AhpQ&9-TQTe+f6L=7m*CC9p8B+de^}Dh3D19s*6sQx)rhGu=T#>GW31r zlF4}qd0MjM+`FGMl6yT;zU^3K&Oh+Z>biNFg`|4FBnUo3no3J$x_HwHIRBPVt$!+W z-Fz3=Y|jFIL_-wvI%mVtsX4SLgCK@s%~Px|H*DTdA7Y`9f}J)42O*@XQAxkooMQ-B zZ&VNutdBHEdPhPlh1r(b*)3Bbcl#rwMWvJnor@QT^pBN|WedGHXWFqj|^ z98>jCHCdRx$W$~+q^+=_Av__PJm@Hjl^p3fkS6ifUM9UoAC?@t{#rQR!uvW~Ffy}0 zUiI`>rNQY|$dLoOpRjcx50iwp^)GDw^c$6t=El01V7HD z&R#8>^su)#3k>{|rod7_x}xM25+`IK3|lknQO~B%xPz-0UYRdN%`vT}q*<Y|XbZK0_LY6Xlkci5pqUJc|l2`^{7bH01JB*l>i5|ykb0r?^){RAloXu^33mi3c&>nbWfabX)c-%O+cUqn;jT$Lh9d!c576V# zU~hLlN7=*5^Oj#?;W?MLdl$Rv8<@l~73fI52cl5WR!Ndu{&2{&3r~rDR)cP;J%tu^&m zHiW`u`6~+3%1wu7l$|}Cqd&R?ItNtI{xUhrCq-Ny-xyp#78|TuItTDprv< z``yZtv(Yp0P;IFBNC(hHeH>HBy+l&0gx`UdGD#hhc!-JKQ|UEQ#9!0= zOllmdOSp!78>PCComf*dc+70!6nxiSHfKh2Ds;C*Vy{}Iz*so=aa>@{1p#F@hW44A zpO}(I<>kVtv|>FS(h9ofT@UUNuQqLUj{>SL{6CQ&e7_CtZ*mOQw0-EQP8|GMsb08Q ziBx4ckNP+AYiFq+i~^bnPz>xG7>Q`~dm&%KH63h;Gp)ryBMCazKpKz2N<+bocul=e zdxlAymLQdw4#Rf=wTh^R0wq61nKkHYcv5@H@GA1@IjxzaHo+I(2NgLu%>%S1+`tGcjZ~!?t~Bbo+Bn-(-)md_FfssPE+sg{V$f`A z9G0dS+V_+^*Dh<|05L>&1b+BzI3<5tx8B6&!R0}m(YCfgN|UYRH&555`Ztt}p9RuxD#;S~%9M_kjm^}M0i?X!4OH-Ipr#XKm0v2K-m`~XH ziNA)Zv+gXB6inxtO&AtoX?bMUp3@!hlT@;y3H+O$5wu!KD|Ky%0G0FpV7{wO7Q9Y9 z#(U=(3YBa4^4wpD^6`t{=>)${_oMkmjRKcX3xFYNbYVXJ z-HR^h;zPF!Os2h8lanm?ji^`l^J(Gt36{Rsl^;#%k>=g=jp<=nK0B9*!JxTT3;h>5 z;kRUHuaUz*7UKK%L@FPRoCoyelejHcF)=3`*i-_PH}aAGeQ8j~sFa_GCf04^6vKdh zgokq{l5_)*9aHO;dyDy$s&~JJ$M#-T%+*?9RLot0W(lX;=*@U5kMIjRQqS@pc%B|- zUkDUTHO_gtOJ2JVrMR#aj1ptwKmg4<8i&N5D(qZ{@7MZ%O6l>H%S$B-h=Fl((ImaA za5ebl>4kCsmA$Q4In{)Fbp-CJ%xLWIGvKaO0}kHe;PP6z*95E=*_WRLTK$c+^3 zKp-qd`QX}N)S67XYH7^HboTr8)5AU^RmV2#-$R@oRWUG z_vJzVZ(442wNGBlo#&6IH4nc+kxeE76TSpbig<#x+f_|9s?n%1tjpC4!NULxo^aI& z98QvLAtWDUJ)a|^VaWT`I2Bxh1FVZ&T@1z&@>%D8P3-5Iv zdzZOW(ENgfa|FKg-yVtrt&W9Z+cR#iuWW9fF76t>N}=lA5QC0UXtsAdxU&kN=*}LT z>^c1Kn_|O<_x{$sArnenafu7U5|54SHp+TdeW@CQX<8bsz{Nrv>wR>!BtXS1S+ex0>$o#SD!(63#i$ry_PD2&g zwmA`+T7{*f29!kd8j#8?I>?P+1)7xuF zV-rTo`NEP*CEbiD`g`#FO$U~+N09&dCz`qCtlEZ?rEi}95zI`;boemAyvGl>tN8l> zpMG^*p`y9n(0n!e^JOm1Coy3fGMuIZUz2&m;0IrP*DF=Q$Hmn`|>N9l9pb!?|dhVU=bh%`|vDp z4BhH+8tdT8D5>W4xN~Zb^DMzRHOlZ(Io0ADZ6oV&b?nfCgpGqBM@}9wV8h2UsArKeM!mT1O-S;v;`qqCHh;9Bwe6l4Gb12e9|o!5Ke?|xIe zsX{vK1t3e1!m;@JrLa14r4?lK>;&4kI$r0o61D9nt8w~_nqt4ewo|}#EhcNl!g?ja z9Z}vA*=*z7t@4)?Zn4(9=pc2g*D;z?hr_==(b`2%9NCp~l89C1&9&Fe4S~gOuVrqt ztYxNJ(B#I6>CM7ZW}bXTA ztCHDHTOt42KlRPlce5~&y1X+z^t2_p@btsaVj%zC`^majr{`M0X1C{x?br00-4C*6 z*AHvzri-i}-d}!U&~k1AsFPoCAZYv>8g~Ut>s=-Sv|W;@@UouPW?GcbUF|2!u%oIa z9nkQy0{+TG4T9a2H0Z?7-CKviD2hp-+5Pw@kZsQ-`F&a}x;=f`He*F)hb zMI0lJfsLJ=Pp}@A_D7py@CWz#mA!^c-QuDw z0ROMpI&DH?pZ*>eA&2^UFF%oob-sae!S>G6nW*6;yiq9HsQ-~FO7ZrK-4~oDXu*q* zPM;K;D}m=tW_8D0W@TW|ln_#32NC8=dN8iVJj?$TUgxaR@*wl;L}B$9;_*|rE|gGb z`9)UY+v~GA_?3)8>gIsAh7Q=pm<@+@a+Gc0R?(!RfP|QQNEdHjLpk(rR9EaGB6Eh^3B&h}OB+CAZ$QA$ z<8yX#ZZ@bE?XjxByDt6{$lXfXHSaamEA02{YK$5|_szD2(tC6IGnX=Eh-=~5CeYv8 zi_6>3^O1e4V2H(Ay!8q(5#D7jV|?-$8QPh?q6C{gn74E3({t(HkJ^J^2ww}QMf^f* zLCKK)DaR|YKQ}7b2+blQf0V*=yTxi;^nOE3BRxWWPSfavhc?~bH;Hg9@Q9@5I|U4$ zkxRDV&hfVmrH&soO3g~Qjvi<0j`8#es*2ha{AF&y@3zmQ_tx+NH+N50{RvkMbhoIg zF^Sx6I;=%vK7Od#Y5uWQfk6?903!YbdZncFg;+S|c$WhP{)2U2fZh$6@v~{^!ZE?& z!J+&2=l$R~LB$&~BTM-_je%VpOj1z%Jr%oA)9o zdiuPbB|AQcWwX`PwlPVwk{9Vhmo%pVls_ywx9#<_JKWde$M#*0CbJ(J^a*yty`j^m zZO?CVfYZqwd@)#d`fvw1zRGf*WcDEmCt9Q-*wrUp`?Z2HrqJ2f-ewt%$uxK)={RLa ziCC?1<7EjY1f?wu0Yj_PMdJa)V8WnnaEPUR_8^a=wYPZ+T-I59WON%HYfQ=PU9;!e z>Wf)mYce%95?>}+i$Wd5UXNQ;vZ?T~jnnJsIOlFQ{O27z*Av0G*KYGu>CW2plVR!z zX@7&*ydM>-YdT`8j`?&#fgQ$uy!z5vDpK)DH8PV!KRkwQFv48HT8Q_sNPQE7jH@_c z0Z0Myd}u##_);z59Op&PT=X{&V8p zu}+>QF`0;p$6mbu&0tNwHlL=viCA2; z>^S$AM63F68Cq8wsikT=xhfgD>(^~0zJe)i^n#-eXBhKuOSUL*Goo1;l&^-rB{``A zerz;!AGf-n=!l{3QJquk;OTHs%PP_QAj|r(RoN`@O_Za-K`@xf`HF|#f~br@u^yNP zfl$0KDF>-}Y@*2S#CAs?hI?`8n%!oj$iFnVskex5`M^SVBz zS)Cy5$audhh^5tBBj^`BiuD5HzG_0TWC^j9tG4Dg@7mBXgI<=4^=2e}T+0@^5Hbn( zG;*K!%io(W{x|DqP4Hi<4v-dv{1>J-mn+|f*c&Xr*;=Z8JN%8gZsG2sR4P2rt8VgD zrN?WWLSB&0b3ojFZoPq6;gcJJxQBPx`KJNjDI^C!byCTP*;-oz{UX1*Sjnk z0DSA!@#qb6GqwPtpa6-A|LJOY@QBeiarq`XZvl75A|5G$ULx`7J6V=VO!=s5B{Z^N zGl#GE*dtf5E3aw!am|qKX*@OwalxMvH0+S@kHhK1h?VzRgXP|8hx1O@QAV7Leip?x zldIzA4k8mH#@XN|X(1Hb)?ls|%pD&rkp3=dVRSO5>zw$RzNNiOnt1WWkFg=-!vBHe(HKVO09 zoTO*u?ee{c%mU$ht1khDl*3(~>cjM#Mygp=1)o+A6!Tg$es%A1K$otUdM>`We4s0e z;_j#gdQqG4HXdn`OOPT0-qTj{qlGf?NFd;8{6+EzTJV)CSOCQ)bjB{TDmX^Bi;_QO ze?y7ujOect1P!)qBp8&3FRl!upNI9|`pnrS=)SzM^5PB4R=!W;AvObI&oC@=$jB@A zMQLh9UwKEQ>v8AEhbBFK#c}HJ;aBNN6d)0uD82dvHF|J1P=DY%3=&-=o&0du?+7^>rXocBbW+)Au9If z8~r0+unWBT5*69)SKKmIkS%o%cO#-yoiM2*2Igg>LB+03Z|228$%D9hM9F4JI?!+Q z$cZgOGzrns`KB2|AywN(EOv$Kk{9`Wi}gg5M4QqRJm>7;NxWl&a!pQmh|bM z!y04vr4;pb_7zH>?h{>h6ww%R-IiI*7Ka*kV5f) zYLq?bCv+9m^F zv!8i)vL{}Bwz#0s`Sh^G$XzyXAmG>43hnI9q)-eMlz4Cm5jN=0{71~ug^^mD#wxvG zr}Uhby*ZHW_dN(I%k;U*j=hncq|&*X;4Ql^{QO^GryGl5Vc)LV_527XeX13Rtu?P)uGRLmy{QyMR6MzczLBve<>$mvzZ#ix^M!TMtLI={^nTc8r9VLLPbYl^ zS4DDgS@NwJ$?ILr>2~|?ZL7-*u$Nq%c>TfoeC+qcx?O@|DK4P5 z*+Y7nT+l-#q@={_C-N6Q&~kR1B6D>I+0qCq)wI_fw0$;<(D;S?%4V z*P?PokS2PnvPOn+nVcZiv?03V%BJG-OaG8|OhUy2GoHTJOl7r&`cmP3ncikJDqkPX zeOCx%TPP7^iP>b2EDq6>jhmH41?BgktIDFmJ5jD2OH4)OJIs-m3Ycrihal9~R?DJ~6&6ptjEM!?Y>L0n{q;&8}aIlY8c+@ix=%9d#@h}uc zJ44%xgszkfk!as0oJp5K#tLj*s9I>85#gAb`I3t^*(RYTB+CF@qO=u^)%sR4&N5hX z7ZDt*@ow9AIf^{`g~^dMJ~K~$5Z4lqS4R?&(&UHbwe3r35f@fob>V{09pqyzJDiFR zK?#`mBp{m@hBWz=%?#Q<^Qjv(PXE7)dYrI1>{8J6*w5e8_IrRB-hM3}G({0PW|)xx zK~g(h<>|xp@AEyJ%Nm7o&CqtF6YLf_GGU@)kHuUSTme?L^7mmXmqI?dNXiJXJN=aV zh9rZ%+*lkXs+_IdHOjHhc0d{PQ0IE7)XGR5R<&>IB(zAEvJ?p*@MJ}xzdu;-iBFbbi8a~7#x(t-2L<~G`as&NYNWyCMaQX#Ar8LybU zUNHFhqa6rZBjZN5VF@GWA}GU`!l|ENGQdb4t~Go1PK-uQa6ogU1ZiMC78x?l^K=&Rl7QbH$Icc#}w{YB)tuj!&ED;S!dhr>DxCuXIdr zX#Ri1=tgU>>{}kmFBapT#3I{3cH1naBz#$%DOw`qBwU0za?EfinJJW_0vC?B2`&%U zMZS6`sVJ7;SB7BQTC~LP#yET+!QembfXJ!>Xt|_zE_9pzk`9Dj^!~s41V!V?qr*jA z+vVqxQ33P_x_O)4Y|Tk3p_umrQITgQ0qgHSxRt12v`|g)>Hd;6!}RSuZ>%#^P}BD# zgHXfVTUTa}bq+^O#TTVyc?eoOueCNI_v+>WBfgu^XA(qB&*PihgW-LYc@>dswWh4w>@kA#0>0xsOgNqh@F#(4$ zLH2*KUW^czKdhG@G8tkfvzx3E_kZi73;#w?%DenIMrzX4SCbVP&ttci?w-J6yJO6W zHm{v&C55L`rN>^g`YDRce%T+5A0Or=x2kz#(rJ2R6J^uf;O)yemj8~ zGf)u!HV|Gq^hf7pb$0VM%@%r2`grTv%Tg;u1&Jay>GQa2>S#O7jYwK?LCc}?KDyx6Jr-u$oH6cTZLmYzN`SfZhE+?+Q zE2dot6qE$rFWoU~q14t1@AzHhY@yetr3HfV9_#vfaju5)Z0k5#6zY5?o5Dt?jUo6a%z^ z^>{-Y(alKn5Gqt%-_kUu=$N76CVW^Lwrof$PiSV!=j3t6uNr7{H(o@l+RHSf$bJHmd|X18*E1pj)2QcB zn>n0EtaaP(R1CT!*e-D-)qy)8!2_(XxMZW+y!$$PI#c!30xmU~n1gu{xA2o@N3SyP zW>JG%6C&1PH#;0MyhwhhfcqXo4OyG-t-<`;n3?v+bGF|MF zVXk9=PS3Fq1gL)`FFNBS0yz$(QTHWkZq@AXPI!%d=lK43N?>MDPM?mo8q4y3%%UD? zCF?FVkxCf$a@y2`pt!Q<9K`6UQPYxMUjn6 z;=g1(&20-RRZ+46xfXw@ie6QCFqZraJ(7iU&gvqgfwa@|iG#np>WF6SldHIbh0oTc2b54mhK@2+f8p>jUp5K^%uZXI19oxhZ> zhc<9Pp`qMbn@pcVXsRk&({CsJv3cyvx8r>u38{1-b=;bir=P}73mgcJ#~+5``h+p- z6s#$va6aw*G#loXU^b0;Yn;&5hh`KA7?3kcxX^z`{Sn;rc1XguGRjwAh5TgUM=hF5 zPlmhYnz#!;BHo3nEgqAyoA4@p4EziU<_NuT7YQrkb@kV;LQ@5{ER1S$=)ZvaTwHgx=1=y&A zx==IK6BXon>pt*cC)=!WJj5>=4i6}vB21?Tfl{?QksaE|ZyXp{RO=RR@)68(4g_TW#la%N(HC-@{}%8_?jUad)&;KYTEmTD z!5S3{F0DLxE}ji!LYOZBwZ)X0CH#Z#)bpO3S%dBt=T-d4>78?Sc5f_n+-BR^9NjB)%_(*V|hp5U!6@UA2gEBMv%DD&S zqow8xuQ)bu+uFgsXZOys-uGQ0#!jWHQ+0;gc(jP2=@`g*@-v(n8){)3ggN=;G0wlL zb#S+M!Wv^9i&iy3KDD9vBhfgCGqT~m?+K3BQD|7;aQsnwkPMCej70T`z0auOhV^SMBobg!gXi$TD z3kl@gD9pW6d!x2beTi(aKbVdXrq*?@X`JJ&&8GDkBEA-;q)1yA!)I+Z7X-89q zFY{Li(1PEc9-JxRuiK=m*Gj$cGOg`??`4W<6&sbv++fwJQ=}*bl<^xvKcXzN%%q@> z3?H{(s7(CD3D9i(un0b)@L&$ci>R;Yw>XT7C@N0!C_=uPHhAn1;8-1A7!%+KOydZ9 z+FPu^xcXLaA5FP~d*!~B`o-F$i9Xp`E(5S*w-!71z0k?*=i-LDUv1FK#yxlm4NGQrPeiiPK&K~xJe%d(={Xc8Lu zF*&)IbP!soeT&}8Z&74Ob8$r^*iwY9{Isl6&?tXBos7HeS+s+Pt8+hho7`w4bMIn( zB_e=ZPw>{I4-VCkW8r<59Izpgz|btpLrL&h3PY!CsL9Mhey>|8My@U;i7 z#sv`_%U=klYw?+JsW&vN$!3Wq&y^GK&}dYuWR>bzH;i-E{!(!$FRP^5oVIYv4sqLa z)+lZf^I8!HO$1qIQMb`s)sHb>0*yk;z%?T z2osJd|05h>-P5b_?&+N47cvs9$y#63uiU#zgh^lyzogGH$`v*vLF{j(E8qZ_$bgwY zzP`r4zP>+p6|>k9FKjE}|LvH`B(W#aphDC*0X%D0dhmT(N>q7-FUh?^l>Ea80me!E zAG~*RE<|lXH(@rzMmIyfkWBs6JDB%m)27h@Ff<7F#fbc;w4RIBP(`qb;8y=`Qta7t zX2;V5X^jEDAE<#YxS@HKab4-X?e(El=P8QWMPG^7pI#Zw=a(v0GxVDIRKeH~V2$1^ z4bYlc0(*AuNR_mr-s2! zhbCeo|26BvZ$uCZ@p<{V*kFBt?6j@Hj?b*Yj?c*cdwfRv%&{dr+3JjPL?Z~-*ViWN zdQQ!r|GJ6b6|3Lr3%H+8TiCQ`{N8jPt2u50Lz5`~O_FD*JOhFKC(*>PX$`y#9`8;z zED`pIJDIdgN`pt^Mdel9w8(9~L*41+6V@0Df{L+I5n4;5!IL$ieBWG79BfFFd0nHp zzS%+<0Y6@`KMSUgf2&R~YQfj9ynvraz%v-#;ploAZdI7mDdj{v?nME!JR{-4j6DLu zXr|L{$TZRf9?X7c=d%?UJFJ=20gbdH3DHjVJ7j3_NtuD8)YN0G>8inCkx$SBx${D1 z$9iyy6iDc`l=J#P<^wWnpIm%UA%J0rHLhC=_rqP!E5jFTh_EP|+B*-zyZ#oX0mA#6 z5jd8JnMSruH=6;DXDG*}wpzknM z_lc*Sg*{zN_=PKA#tfe0Gx>RZ-(|gTFvaKx`)EIbeAWq*yA5wg0zco_e|@^c#duOTP>H>ns- zzzFyv)3!o|zORT&-*p)ftQ6hQ`|-%p$vDvSJX9MO>UIVEw$jn#PPi4^2ZM z#byo%G@467B5|aoDph&Po8}6DARe@(sfWQIIb!LL1vW7Q-QFLc`U@nwTcK`_AG`}U zZzrz(vF0N}zO`)zil6SE-3WUPo=O$C6Ea1=?b{{pdk;wo{=`7(oSwY3*rJqw zNx9mfFF^FP;#9RYx0K9unGN&8Tb>77G@Ghc&vbb;hpOI7Oz}CYE1gu;B)VOSc;s--Nh5s#njXU}^Yxj2C@;GH#;ie)2Oo`-QxNe~x||{2=Ok zt?j$brXS8qQhqa)iB6BX%~hE4?Re_m+V7X$jjQz%pCk1W0Aj5F^WHg3Z6NyHY&Gl* zTtD|@`vP5_Dh{^OwzqLTRqIV~`Qt8n{`390&UHw2C=U$_Z|HZr0`&sW)&O68y0|Qo zB5=31ek}3_{ROc~hH7$7NzuV}^M~1{Ch0SPqp!=E^$*@Bs%p|>;Xw=PDB08f#e28i zQn$;4-N=dJHJb<3Cug6VkR59dsmhpzM#a=;rUE1GoYF(~o9&5b1HZV??(Az_xla5* z6&^l;!<>}0$P?c*+~=|lYv7_MrMR5lyxQr0&7+9-bytT6H*c&x)PtAkX(<6eMo&z3 zDa{dl=JUK-q_)4UGt>`%H$P&cOOU6)h>tonXwcb9U{Xn&R$&xYpIxkc2Vhsbu-m4&T+86C~ zO^@IB-B01FE7r}M!aPatogd(9r6-^ByZ-fJIOiLumYsfkgKNR(lS>1Gr|psX=WMt2 gX9JJx>g!8){aXVX=J{r;p=ZPxn{-h3+6TD*2Q{GbD*ylh literal 186535 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

      SfP1v5 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 diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/custom_query.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/custom_query.ts index 2b66bc6ca49bb..0fd9938dd1999 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/custom_query.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/custom_query.ts @@ -69,6 +69,7 @@ import { deleteAllRules, deleteAllAlerts, getRuleForAlertTesting, + getLuceneRuleForTesting, } from '../../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../../ftr_provider_context'; @@ -117,7 +118,10 @@ export default ({ getService }: FtrProviderContext) => { after(async () => { await esArchiver.unload(auditbeatPath); await esArchiver.unload('x-pack/test/functional/es_archives/signals/severity_risk_overrides'); - await deleteAllAlerts(supertest, log, es, ['.preview.alerts-security.alerts-*']); + await deleteAllAlerts(supertest, log, es, [ + '.preview.alerts-security.alerts-*', + '.alerts-security.alerts-*', + ]); await deleteAllRules(supertest, log); }); @@ -2750,5 +2754,18 @@ export default ({ getService }: FtrProviderContext) => { }); }); }); + + describe('with a Lucene query rule', () => { + it('should run successfully and generate an alert that matches the lucene query', async () => { + const luceneQueryRule = getLuceneRuleForTesting(); + const { previewId } = await previewRule({ supertest, rule: luceneQueryRule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).toBeGreaterThan(0); + expect(previewAlerts[0]?._source?.destination).toEqual( + expect.objectContaining({ domain: 'aaa.stage.11111111.hello' }) + ); + expect(previewAlerts[0]?._source?.['event.dataset']).toEqual('network_traffic.tls'); + }); + }); }); }; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threshold.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threshold.ts index c1681d65c05f5..0ea928d67b491 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threshold.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threshold.ts @@ -133,7 +133,7 @@ export default ({ getService }: FtrProviderContext) => { field: 'host.id', value: 1, // This value generates 7 alerts with the current esArchive }, - max_signals: 7, + max_signals: 8, }; const { logs } = await previewRule({ supertest, rule }); expect(logs[0].warnings).not.toContain(getMaxAlertsWarning()); From fe1b7b18ae6416a915795c8b38399330a25ee0ba Mon Sep 17 00:00:00 2001 From: Maxim Kholod Date: Wed, 7 Aug 2024 17:11:49 +0200 Subject: [PATCH 17/44] [Cloud Security] cleaning up unused code (#189798) ## Summary While working on https://github.com/elastic/security-team/issues/9661 I noticed that we have quite a lot of unused code. Therefore the clean up --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../common/constants.ts | 8 -- ...et_safe_kspm_cluster_id_runtime_mapping.ts | 32 ------ .../common/types/rules/v3.ts | 2 - .../common/types_old.ts | 4 - .../common/component/multi_select_filter.tsx | 11 -- .../public/common/constants.ts | 3 - .../use_cloud_posture_data_table/utils.ts | 22 ---- .../public/common/hooks/use_page_slice.ts | 21 ---- .../public/common/types.ts | 8 -- .../get_enabled_csp_integration_details.ts | 33 ------ .../common/utils/get_limit_properties.test.ts | 31 ------ .../common/utils/get_limit_properties.ts | 38 ------- .../aws_credentials_form_agentless.tsx | 102 +----------------- .../compliance_dashboard/test_subjects.ts | 1 - .../public/pages/configurations/constants.ts | 10 -- .../latest_findings_group_renderer.tsx | 4 +- .../latest_findings/use_latest_findings.ts | 6 -- .../layout/findings_distribution_bar.tsx | 35 +----- .../layout/findings_group_by_selector.tsx | 79 -------------- .../pages/configurations/test_subjects.ts | 22 ---- .../utils/generate_findings_tags.ts | 30 ------ .../pages/configurations/utils/utils.ts | 22 ---- .../public/pages/rules/rules.test.tsx | 3 - .../public/pages/rules/rules_table_header.tsx | 2 - .../public/pages/rules/test_subjects.ts | 4 +- .../pages/rules/use_csp_benchmark_rules.ts | 1 - .../pages/rules/use_csp_integration.tsx | 35 ------ .../latest_vulnerabilities_group_renderer.tsx | 2 +- .../pages/vulnerabilities/test_subjects.ts | 2 - .../pages/vulnerabilities/translations.ts | 12 --- .../public/pages/vulnerabilities/types.ts | 28 +---- .../utils/custom_sort_script.ts | 37 ------- .../public/test/fixtures/findings_fixture.ts | 63 ----------- .../public/test/fixtures/navigation_item.ts | 22 ---- .../server/create_indices/ingest_pipelines.ts | 22 ---- .../server/lib/telemetry/collectors/types.ts | 4 - ...ection_engine_alerts_count_by_rule_tags.ts | 4 - .../cloud_security_posture/server/types.ts | 15 --- .../translations/translations/fr-FR.json | 9 -- .../translations/translations/ja-JP.json | 9 -- .../translations/translations/zh-CN.json | 9 -- 41 files changed, 7 insertions(+), 800 deletions(-) delete mode 100644 x-pack/plugins/cloud_security_posture/common/runtime_mappings/get_safe_kspm_cluster_id_runtime_mapping.ts delete mode 100644 x-pack/plugins/cloud_security_posture/public/common/hooks/use_page_slice.ts delete mode 100644 x-pack/plugins/cloud_security_posture/public/common/utils/get_enabled_csp_integration_details.ts delete mode 100644 x-pack/plugins/cloud_security_posture/public/common/utils/get_limit_properties.test.ts delete mode 100644 x-pack/plugins/cloud_security_posture/public/common/utils/get_limit_properties.ts delete mode 100644 x-pack/plugins/cloud_security_posture/public/pages/configurations/constants.ts delete mode 100644 x-pack/plugins/cloud_security_posture/public/pages/configurations/layout/findings_group_by_selector.tsx delete mode 100644 x-pack/plugins/cloud_security_posture/public/pages/configurations/utils/generate_findings_tags.ts delete mode 100644 x-pack/plugins/cloud_security_posture/public/pages/rules/use_csp_integration.tsx delete mode 100644 x-pack/plugins/cloud_security_posture/public/test/fixtures/navigation_item.ts diff --git a/x-pack/plugins/cloud_security_posture/common/constants.ts b/x-pack/plugins/cloud_security_posture/common/constants.ts index 3c08632a262e5..053dcc4867bdd 100644 --- a/x-pack/plugins/cloud_security_posture/common/constants.ts +++ b/x-pack/plugins/cloud_security_posture/common/constants.ts @@ -85,8 +85,6 @@ export const LATEST_VULNERABILITIES_INDEX_DEFAULT_NS = 'logs-cloud_security_posture.vulnerabilities_latest-default'; export const LATEST_VULNERABILITIES_RETENTION_POLICY = '3d'; -export const DATA_VIEW_INDEX_PATTERN = 'logs-*'; - export const SECURITY_DEFAULT_DATA_VIEW_ID = 'security-solution-default'; export const ALERTS_INDEX_PATTERN = '.alerts-security.alerts-*'; @@ -157,9 +155,6 @@ export const POSTURE_TYPES: { [x: string]: PostureTypes } = { [POSTURE_TYPE_ALL]: POSTURE_TYPE_ALL, }; -export const VULNERABILITIES = 'vulnerabilities'; -export const CONFIGURATIONS = 'configurations'; - export const VULNERABILITIES_SEVERITY: Record = { LOW: 'LOW', MEDIUM: 'MEDIUM', @@ -168,8 +163,6 @@ export const VULNERABILITIES_SEVERITY: Record = { UNKNOWN: 'UNKNOWN', }; -export const VULNERABILITIES_ENUMERATION = 'CVE'; - export const AWS_CREDENTIALS_TYPE_TO_FIELDS_MAP: AwsCredentialsTypeFieldMap = { assume_role: ['role_arn'], direct_access_keys: ['access_key_id', 'secret_access_key'], @@ -209,7 +202,6 @@ export const AZURE_CREDENTIALS_TYPE_TO_FIELDS_MAP = { manual: [], }; -export const CLOUD_FORMATION_STACK_NAME = 'Elastic-Cloud-Security-Posture-Management'; export const TEMPLATE_URL_ACCOUNT_TYPE_ENV_VAR = 'ACCOUNT_TYPE'; export const ORGANIZATION_ACCOUNT = 'organization-account'; diff --git a/x-pack/plugins/cloud_security_posture/common/runtime_mappings/get_safe_kspm_cluster_id_runtime_mapping.ts b/x-pack/plugins/cloud_security_posture/common/runtime_mappings/get_safe_kspm_cluster_id_runtime_mapping.ts deleted file mode 100644 index eed3eba2f7220..0000000000000 --- a/x-pack/plugins/cloud_security_posture/common/runtime_mappings/get_safe_kspm_cluster_id_runtime_mapping.ts +++ /dev/null @@ -1,32 +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 { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/types'; - -/** - * Creates the `safe_posture_type` runtime field with the value of either - * `kspm` or `cspm` based on the value of `rule.benchmark.posture_type` - */ -export const getSafeKspmClusterIdRuntimeMapping = (): MappingRuntimeFields => ({ - safe_kspm_cluster_id: { - type: 'keyword', - script: { - source: ` - def orchestratorIdAvailable = doc.containsKey("orchestrator.cluster.id") && - !doc["orchestrator.cluster.id"].empty; - def clusterIdAvailable = doc.containsKey("cluster_id") && - !doc["cluster_id"].empty; - - if (orchestratorIdAvailable) { - emit(doc["orchestrator.cluster.id"].value); - } else if (clusterIdAvailable) { - emit(doc["cluster_id"].value); - } - `, - }, - }, -}); diff --git a/x-pack/plugins/cloud_security_posture/common/types/rules/v3.ts b/x-pack/plugins/cloud_security_posture/common/types/rules/v3.ts index 85c38c50022b8..a00bf1a8077e6 100644 --- a/x-pack/plugins/cloud_security_posture/common/types/rules/v3.ts +++ b/x-pack/plugins/cloud_security_posture/common/types/rules/v3.ts @@ -18,8 +18,6 @@ export type CspBenchmarkRuleMetadata = TypeOf; -export type PageUrlParams = Record<'policyId' | 'packagePolicyId', string>; - export const cspBenchmarkRuleMetadataSchema = schema.object({ audit: schema.string(), benchmark: schema.object({ diff --git a/x-pack/plugins/cloud_security_posture/common/types_old.ts b/x-pack/plugins/cloud_security_posture/common/types_old.ts index 6b290979a97f5..f77ac4678a526 100644 --- a/x-pack/plugins/cloud_security_posture/common/types_old.ts +++ b/x-pack/plugins/cloud_security_posture/common/types_old.ts @@ -36,10 +36,6 @@ export type AzureCredentialsType = | 'service_principal_with_client_username_and_password' | 'managed_identity'; -export type AzureCredentialsTypeFieldMap = { - [key in AzureCredentialsType]: string[]; -}; - export type Evaluation = 'passed' | 'failed' | 'NA'; export type PostureTypes = 'cspm' | 'kspm' | 'vuln_mgmt' | 'all'; diff --git a/x-pack/plugins/cloud_security_posture/public/common/component/multi_select_filter.tsx b/x-pack/plugins/cloud_security_posture/public/common/component/multi_select_filter.tsx index 6eece8a31c5e8..ecc5edcbf2e73 100644 --- a/x-pack/plugins/cloud_security_posture/public/common/component/multi_select_filter.tsx +++ b/x-pack/plugins/cloud_security_posture/public/common/component/multi_select_filter.tsx @@ -27,17 +27,6 @@ type FilterOption = EuiSelectableOp label: T; }>; -export type { FilterOption as MultiSelectFilterOption }; - -export const mapToMultiSelectOption = (options: T[]) => { - return options.map((option) => { - return { - key: option, - label: option, - }; - }); -}; - const fromRawOptionsToEuiSelectableOptions = ( options: Array>, selectedOptionKeys: string[] diff --git a/x-pack/plugins/cloud_security_posture/public/common/constants.ts b/x-pack/plugins/cloud_security_posture/public/common/constants.ts index 73934752e819a..8054917ba7462 100644 --- a/x-pack/plugins/cloud_security_posture/public/common/constants.ts +++ b/x-pack/plugins/cloud_security_posture/public/common/constants.ts @@ -40,11 +40,8 @@ export const DEFAULT_VISIBLE_ROWS_PER_PAGE = 25; export const LOCAL_STORAGE_DATA_TABLE_PAGE_SIZE_KEY = 'cloudPosture:dataTable:pageSize'; export const LOCAL_STORAGE_DATA_TABLE_COLUMNS_KEY = 'cloudPosture:dataTable:columns'; -export const LOCAL_STORAGE_PAGE_SIZE_FINDINGS_KEY = 'cloudPosture:findings:pageSize'; export const LOCAL_STORAGE_PAGE_SIZE_BENCHMARK_KEY = 'cloudPosture:benchmark:pageSize'; export const LOCAL_STORAGE_PAGE_SIZE_RULES_KEY = 'cloudPosture:rules:pageSize'; -export const LOCAL_STORAGE_DASHBOARD_CLUSTER_SORT_KEY = - 'cloudPosture:complianceDashboard:clusterSort'; export const LOCAL_STORAGE_DASHBOARD_BENCHMARK_SORT_KEY = 'cloudPosture:complianceDashboard:benchmarkSort'; export const LOCAL_STORAGE_FINDINGS_LAST_SELECTED_TAB_KEY = 'cloudPosture:findings:lastSelectedTab'; diff --git a/x-pack/plugins/cloud_security_posture/public/common/hooks/use_cloud_posture_data_table/utils.ts b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_cloud_posture_data_table/utils.ts index 6628cc7711a82..98270ffee59bf 100644 --- a/x-pack/plugins/cloud_security_posture/public/common/hooks/use_cloud_posture_data_table/utils.ts +++ b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_cloud_posture_data_table/utils.ts @@ -5,28 +5,6 @@ * 2.0. */ -import type { EuiBasicTableProps, Pagination } from '@elastic/eui'; - -type TablePagination = NonNullable['pagination']>; - -export const getPaginationTableParams = ( - params: TablePagination & Pick, 'pageIndex' | 'pageSize'>, - pageSizeOptions = [10, 25, 100], - showPerPageOptions = true -): Required => ({ - ...params, - pageSizeOptions, - showPerPageOptions, -}); - -export const getPaginationQuery = ({ - pageIndex, - pageSize, -}: Required>) => ({ - from: pageIndex * pageSize, - size: pageSize, -}); - export const getDefaultQuery = ({ query, filters }: any): any => ({ query, filters, diff --git a/x-pack/plugins/cloud_security_posture/public/common/hooks/use_page_slice.ts b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_page_slice.ts deleted file mode 100644 index e089724b25909..0000000000000 --- a/x-pack/plugins/cloud_security_posture/public/common/hooks/use_page_slice.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { useMemo } from 'react'; - -/** - * @description given an array index and page size, returns a slice of said array. - */ -export const usePageSlice = (data: any[] | undefined, pageIndex: number, pageSize: number) => { - return useMemo(() => { - if (!data) { - return []; - } - - const cursor = pageIndex * pageSize; - return data.slice(cursor, cursor + pageSize); - }, [data, pageIndex, pageSize]); -}; diff --git a/x-pack/plugins/cloud_security_posture/public/common/types.ts b/x-pack/plugins/cloud_security_posture/public/common/types.ts index f2881c1798883..d0d491c256e0e 100644 --- a/x-pack/plugins/cloud_security_posture/public/common/types.ts +++ b/x-pack/plugins/cloud_security_posture/public/common/types.ts @@ -6,9 +6,6 @@ */ import type { Criteria } from '@elastic/eui'; import type { BoolQuery, Filter, Query, EsQueryConfig } from '@kbn/es-query'; -import { CspFinding } from '../../common/schemas/csp_finding'; - -export type FindingsGroupByKind = 'default' | 'resource'; export interface FindingsBaseURLQuery { query: Query; @@ -33,11 +30,6 @@ export interface FindingsBaseEsQuery { }; } -export interface CspFindingsQueryData { - page: CspFinding[]; - total: number; -} - export type Sort = NonNullable['sort']>; interface RuleSeverityMapping { diff --git a/x-pack/plugins/cloud_security_posture/public/common/utils/get_enabled_csp_integration_details.ts b/x-pack/plugins/cloud_security_posture/public/common/utils/get_enabled_csp_integration_details.ts deleted file mode 100644 index cd0a413648bed..0000000000000 --- a/x-pack/plugins/cloud_security_posture/public/common/utils/get_enabled_csp_integration_details.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { PackagePolicy } from '@kbn/fleet-plugin/common'; -import type { PostureInput } from '../../../common/types_old'; -import { SUPPORTED_CLOUDBEAT_INPUTS } from '../../../common/constants'; -import { cloudPostureIntegrations, type CloudPostureIntegrations } from '../constants'; - -const isPolicyTemplate = (name: unknown): name is keyof CloudPostureIntegrations => - typeof name === 'string' && name in cloudPostureIntegrations; - -export const getEnabledCspIntegrationDetails = (packageInfo?: PackagePolicy) => { - const enabledInput = packageInfo?.inputs.find((input) => input.enabled); - - // Check for valid and support input - if ( - !enabledInput || - !isPolicyTemplate(enabledInput.policy_template) || - !SUPPORTED_CLOUDBEAT_INPUTS.includes(enabledInput.type as PostureInput) - ) - return null; - - const integration = cloudPostureIntegrations[enabledInput.policy_template]; - const enabledIntegrationOption = integration.options.find( - (option) => option.type === enabledInput.type - ); - - return { integration, enabledIntegrationOption }; -}; diff --git a/x-pack/plugins/cloud_security_posture/public/common/utils/get_limit_properties.test.ts b/x-pack/plugins/cloud_security_posture/public/common/utils/get_limit_properties.test.ts deleted file mode 100644 index f62062ba37f4d..0000000000000 --- a/x-pack/plugins/cloud_security_posture/public/common/utils/get_limit_properties.test.ts +++ /dev/null @@ -1,31 +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 { getLimitProperties } from './get_limit_properties'; - -describe('getLimitProperties', () => { - it('less items than limit', () => { - const { limitedTotalItemCount, isLastLimitedPage } = getLimitProperties(200, 500, 100, 1); - - expect(limitedTotalItemCount).toBe(200); - expect(isLastLimitedPage).toBe(false); - }); - - it('more items than limit', () => { - const { limitedTotalItemCount, isLastLimitedPage } = getLimitProperties(600, 500, 100, 4); - - expect(limitedTotalItemCount).toBe(500); - expect(isLastLimitedPage).toBe(true); - }); - - it('per page calculations are correct', () => { - const { limitedTotalItemCount, isLastLimitedPage } = getLimitProperties(600, 500, 25, 19); - - expect(limitedTotalItemCount).toBe(500); - expect(isLastLimitedPage).toBe(true); - }); -}); diff --git a/x-pack/plugins/cloud_security_posture/public/common/utils/get_limit_properties.ts b/x-pack/plugins/cloud_security_posture/public/common/utils/get_limit_properties.ts deleted file mode 100644 index f09990bc3f854..0000000000000 --- a/x-pack/plugins/cloud_security_posture/public/common/utils/get_limit_properties.ts +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { useMemo } from 'react'; -import { MAX_FINDINGS_TO_LOAD } from '../constants'; - -export const getLimitProperties = ( - totalItems: number, - maxItems: number, - pageSize: number, - pageIndex: number -): { isLastLimitedPage: boolean; limitedTotalItemCount: number } => { - const limitItems = totalItems > maxItems; - const limitedTotalItemCount = limitItems ? maxItems : totalItems; - const lastLimitedPage = Math.ceil(limitedTotalItemCount / pageSize); - const isLastPage = lastLimitedPage === pageIndex + 1; - const isLastLimitedPage = limitItems && isLastPage; - - return { isLastLimitedPage, limitedTotalItemCount }; -}; - -export const useLimitProperties = ({ - total, - pageIndex, - pageSize, -}: { - total?: number; - pageSize: number; - pageIndex: number; -}) => - useMemo( - () => getLimitProperties(total || 0, MAX_FINDINGS_TO_LOAD, pageSize, pageIndex), - [total, pageIndex, pageSize] - ); diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form/aws_credentials_form_agentless.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form/aws_credentials_form_agentless.tsx index 867fc1af4d9ea..8a5587cf16622 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form/aws_credentials_form_agentless.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form/aws_credentials_form_agentless.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { EuiButton, EuiCallOut, EuiLink, EuiSpacer, EuiText } from '@elastic/eui'; +import { EuiButton, EuiCallOut, EuiLink, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import semverCompare from 'semver/functions/compare'; import semverValid from 'semver/functions/valid'; @@ -36,106 +36,6 @@ import { AwsCredentialTypeSelector, } from './aws_credentials_form'; -const CLOUD_FORMATION_EXTERNAL_DOC_URL = - 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-whatis-howdoesitwork.html'; - -export const CloudFormationCloudCredentialsGuide = ({ - isOrganization, -}: { - isOrganization?: boolean; -}) => { - return ( - -

      - - - - ), - }} - /> -

      - -
        - {isOrganization ? ( -
      1. - -
      2. - ) : ( -
      3. - -
      4. - )} -
      5. - -
      6. -
      7. - -
      8. -
      9. - - - - ), - }} - /> -
      10. -
      11. - -
      12. -
      13. - -
      14. -
      15. - -
      16. -
      -
      -
      - ); -}; - export const AwsCredentialsFormAgentless = ({ input, newPolicy, diff --git a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/test_subjects.ts b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/test_subjects.ts index 2a325ee225128..6c1a73ed735aa 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/test_subjects.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/test_subjects.ts @@ -5,7 +5,6 @@ * 2.0. */ -export const MISSING_FINDINGS_NO_DATA_CONFIG = 'missing-findings-no-data-config'; export const DASHBOARD_CONTAINER = 'dashboard-container'; export const DASHBOARD_SUMMARY_CONTAINER = 'dashboard-summary-section'; export const KUBERNETES_DASHBOARD_CONTAINER = 'kubernetes-dashboard-container'; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/constants.ts b/x-pack/plugins/cloud_security_posture/public/pages/configurations/constants.ts deleted file mode 100644 index d4a14320fe225..0000000000000 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/constants.ts +++ /dev/null @@ -1,10 +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 FINDINGS_PIT_KEEP_ALIVE = '2m'; -// Set to half of the PIT keep alive to make sure we keep the PIT window open as long as the components are mounted -export const FINDINGS_REFETCH_INTERVAL_MS = 1000 * 60; // One minute diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/latest_findings_group_renderer.tsx b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/latest_findings_group_renderer.tsx index ddb3c757b420e..535a465917d44 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/latest_findings_group_renderer.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/latest_findings_group_renderer.tsx @@ -185,7 +185,7 @@ const FindingsCountComponent = ({ bucket }: { bucket: RawBucket ; type LatestFindingsResponse = IKibanaSearchResponse< estypes.SearchResponse diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/layout/findings_distribution_bar.tsx b/x-pack/plugins/cloud_security_posture/public/pages/configurations/layout/findings_distribution_bar.tsx index 56ca9687551d8..811abe1bd1ccc 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/layout/findings_distribution_bar.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/layout/findings_distribution_bar.tsx @@ -6,16 +6,8 @@ */ import React from 'react'; import { css } from '@emotion/react'; -import { - EuiHealth, - EuiBadge, - EuiSpacer, - EuiFlexGroup, - useEuiTheme, - EuiTextColor, -} from '@elastic/eui'; +import { EuiHealth, EuiBadge, EuiSpacer, EuiFlexGroup, useEuiTheme } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; import { getAbbreviatedNumber } from '../../../common/utils/get_abbreviated_number'; import { RULE_FAILED, RULE_PASSED } from '../../../../common/constants'; import { statusColors } from '../../../common/constants'; @@ -35,31 +27,6 @@ const I18N_FAILED_FINDINGS = i18n.translate('xpack.csp.findings.distributionBar. defaultMessage: 'Failed Findings', }); -export const CurrentPageOfTotal = ({ - pageEnd, - pageStart, - total, - type, -}: { - pageEnd: number; - pageStart: number; - total: number; - type: string; -}) => ( - - {pageStart}
      , - pageEnd: {pageEnd}, - total: {getAbbreviatedNumber(total)}, - type, - }} - /> - -); - export const FindingsDistributionBar = (props: Props) => (
      diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/layout/findings_group_by_selector.tsx b/x-pack/plugins/cloud_security_posture/public/pages/configurations/layout/findings_group_by_selector.tsx deleted file mode 100644 index d8d8768df0cf1..0000000000000 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/layout/findings_group_by_selector.tsx +++ /dev/null @@ -1,79 +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, { useMemo } from 'react'; -import { EuiComboBox, EuiFormLabel, type EuiComboBoxOptionOption } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { useHistory } from 'react-router-dom'; -import { i18n } from '@kbn/i18n'; -import type { FindingsGroupByKind } from '../../../common/types'; -import { findingsNavigation } from '../../../common/navigation/constants'; -import * as TEST_SUBJECTS from '../test_subjects'; - -const getGroupByOptions = (): Array> => [ - { - value: 'default', - label: i18n.translate('xpack.csp.findings.groupBySelector.groupByNoneLabel', { - defaultMessage: 'None', - }), - }, - { - value: 'resource', - label: i18n.translate('xpack.csp.findings.groupBySelector.groupByResourceIdLabel', { - defaultMessage: 'Resource', - }), - }, -]; - -interface Props { - type: FindingsGroupByKind; - pathnameHandler?: (opts: Array>) => string; -} - -const getFindingsGroupPath = (opts: Array>) => { - const [firstOption] = opts; - - switch (firstOption?.value) { - case 'resource': - return findingsNavigation.findings_by_resource.path; - case 'default': - default: - return findingsNavigation.findings_default.path; - } -}; - -export const FindingsGroupBySelector = ({ - type, - pathnameHandler = getFindingsGroupPath, -}: Props) => { - const groupByOptions = useMemo(getGroupByOptions, []); - const history = useHistory(); - - const onChange = (options: Array>) => - history.push({ pathname: pathnameHandler(options) }); - - return ( - } - singleSelection={{ asPlainText: true }} - options={groupByOptions} - selectedOptions={groupByOptions.filter((o) => o.value === type)} - onChange={onChange} - isClearable={false} - compressed - /> - ); -}; - -const GroupByLabel = () => ( - - - -); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/test_subjects.ts b/x-pack/plugins/cloud_security_posture/public/pages/configurations/test_subjects.ts index b8a670e8ba58b..d27a7739ab9a9 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/test_subjects.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/test_subjects.ts @@ -6,33 +6,11 @@ */ export const FINDINGS_FLYOUT = 'findings_flyout'; -export const FINDINGS_TABLE_EXPAND_COLUMN = 'findings_table_expand_column'; -export const FINDINGS_TABLE = 'findings_table'; -export const FINDINGS_CONTAINER = 'findings_container'; -export const FINDINGS_BY_RESOURCE_CONTAINER = 'findings_by_resource_container'; -export const FINDINGS_BY_RESOURCE_TABLE_RESOURCE_ID_COLUMN = - 'findings_by_resource_table_resource_id_column'; -export const FINDINGS_BY_RESOURCE_TABLE = 'findings_by_resource_table'; -export const getFindingsByResourceTableRowTestId = (id: string) => - `findings_resource_table_row_${id}`; export const LATEST_FINDINGS_CONTAINER = 'latest_findings_container'; export const LATEST_FINDINGS_TABLE = 'latest_findings_table'; -export const FINDINGS_GROUP_BY_SELECTOR = 'findings_group_by_selector'; export const FINDINGS_GROUPING_COUNTER = 'findings_grouping_counter'; -export const getFindingsTableRowTestId = (id: string) => `findings_table_row_${id}`; -export const getFindingsTableCellTestId = (columnId: string, rowId: string) => - `findings_table_cell_${columnId}_${rowId}`; - -export const FINDINGS_TABLE_CELL_ADD_FILTER = 'findings_table_cell_add_filter'; -export const FINDINGS_TABLE_CELL_ADD_NEGATED_FILTER = 'findings_table_cell_add_negated_filter'; - -export const RESOURCES_FINDINGS_CONTAINER = 'resources_findings_container'; -export const RESOURCES_FINDINGS_TABLE = 'resource_findings_table'; -export const getResourceFindingsTableRowTestId = (id: string) => - `resource_findings_table_row_${id}`; - export const FINDINGS_MISCONFIGS_FLYOUT_DESCRIPTION_LIST = 'misconfigs-findings-flyout-description-list'; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/utils/generate_findings_tags.ts b/x-pack/plugins/cloud_security_posture/public/pages/configurations/utils/generate_findings_tags.ts deleted file mode 100644 index 66da177e1cea8..0000000000000 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/utils/generate_findings_tags.ts +++ /dev/null @@ -1,30 +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 { CspFinding } from '../../../../common/schemas/csp_finding'; - -const CSP_RULE_TAG = 'Cloud Security'; -const CNVM_RULE_TAG_USE_CASE = 'Use Case: Configuration Audit'; -const CNVM_RULE_TAG_DATA_SOURCE_PREFIX = 'Data Source: '; - -const STATIC_RULE_TAGS = [CSP_RULE_TAG, CNVM_RULE_TAG_USE_CASE]; - -export const generateFindingsTags = (finding: CspFinding) => { - return [STATIC_RULE_TAGS] - .concat(finding.rule.tags) - .concat( - finding.rule.benchmark.posture_type - ? [ - `${CNVM_RULE_TAG_DATA_SOURCE_PREFIX}${finding.rule.benchmark.posture_type.toUpperCase()}`, - ] - : [] - ) - .concat( - finding.rule.benchmark.posture_type === 'cspm' ? ['Domain: Cloud'] : ['Domain: Container'] - ) - .flat(); -}; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/utils/utils.ts b/x-pack/plugins/cloud_security_posture/public/pages/configurations/utils/utils.ts index 3e0277d7cd4c1..67b37ad1001fd 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/utils/utils.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/utils/utils.ts @@ -6,19 +6,8 @@ */ import type { estypes } from '@elastic/elasticsearch'; -import { EuiThemeComputed } from '@elastic/eui'; -import type { CspFinding } from '../../../../common/schemas/csp_finding'; export { getFilters } from './get_filters'; -export const getFindingsPageSizeInfo = ({ - currentPageSize, - pageIndex, - pageSize, -}: Record<'pageIndex' | 'pageSize' | 'currentPageSize', number>) => ({ - pageStart: pageIndex * pageSize + 1, - pageEnd: pageIndex * pageSize + currentPageSize, -}); - export const getFindingsCountAggQuery = () => ({ count: { terms: { field: 'result.evaluation' } }, }); @@ -34,14 +23,3 @@ export const getAggregationCount = ( failed: failed?.doc_count || 0, }; }; - -const isSelectedRow = (row: CspFinding, selected?: CspFinding) => - row.resource.id === selected?.resource.id && row.rule.id === selected?.rule.id; - -export const getSelectedRowStyle = ( - theme: EuiThemeComputed, - row: CspFinding, - selected?: CspFinding -): React.CSSProperties => ({ - background: isSelectedRow(row, selected) ? theme.colors.highlight : undefined, -}); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules.test.tsx b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules.test.tsx index 7abcd0c37060b..66829ee739010 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules.test.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules.test.tsx @@ -22,9 +22,6 @@ import { useLicenseManagementLocatorApi } from '../../common/api/use_license_man import { useCspBenchmarkIntegrationsV2 } from '../benchmarks/use_csp_benchmark_integrations'; import * as TEST_SUBJECTS from './test_subjects'; -jest.mock('./use_csp_integration', () => ({ - useCspIntegrationInfo: jest.fn(), -})); jest.mock('../../common/api/use_setup_status_api'); jest.mock('../../common/api/use_license_management_locator_api'); jest.mock('../../common/hooks/use_subscription_status'); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table_header.tsx b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table_header.tsx index 83745e5f5d113..fc714263f38be 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table_header.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table_header.tsx @@ -42,8 +42,6 @@ export const RULES_SELECT_ALL_RULES = 'select-all-rules-button'; export const RULES_CLEAR_ALL_RULES_SELECTION = 'clear-rules-selection-button'; export const RULES_DISABLED_FILTER = 'rules-disabled-filter'; export const RULES_ENABLED_FILTER = 'rules-enabled-filter'; -export const CIS_SECTION_FILTER = 'cis-section-filter'; -export const RULE_NUMBER_FILTER = 'rule-number-filter'; interface RulesTableToolbarProps { search: (value: string) => void; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/test_subjects.ts b/x-pack/plugins/cloud_security_posture/public/pages/rules/test_subjects.ts index 43b209c6ce7d4..eb723c4e7894a 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/rules/test_subjects.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/rules/test_subjects.ts @@ -6,9 +6,7 @@ */ export const CSP_RULES_CONTAINER = 'csp_rules_container'; -export const CSP_RULES_SHARED_VALUES = 'csp_rules_shared_values'; -export const CSP_RULES_TABLE_ITEM_SWITCH = 'csp_rules_table_item_switch'; -export const CSP_RULES_SAVE_BUTTON = 'csp_rules_table_save_button'; + export const CSP_RULES_TABLE = 'csp_rules_table'; export const CSP_RULES_TABLE_ROW_ITEM_NAME = 'csp_rules_table_row_item_name'; export const CSP_RULES_FLYOUT_CONTAINER = 'csp_rules_flyout_container'; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/use_csp_benchmark_rules.ts b/x-pack/plugins/cloud_security_posture/public/pages/rules/use_csp_benchmark_rules.ts index d580a7719ed0a..fd8c5bf794935 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/rules/use_csp_benchmark_rules.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/rules/use_csp_benchmark_rules.ts @@ -20,7 +20,6 @@ export type RulesQuery = Pick< FindCspBenchmarkRuleRequest, 'section' | 'search' | 'page' | 'perPage' | 'ruleNumber' | 'sortField' | 'sortOrder' >; -export type RulesQueryResult = ReturnType; export const useFindCspBenchmarkRule = ( { search, page, perPage, section, ruleNumber, sortField, sortOrder }: RulesQuery, diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/use_csp_integration.tsx b/x-pack/plugins/cloud_security_posture/public/pages/rules/use_csp_integration.tsx deleted file mode 100644 index 45d4743490e3f..0000000000000 --- a/x-pack/plugins/cloud_security_posture/public/pages/rules/use_csp_integration.tsx +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { useQuery } from '@tanstack/react-query'; -import { - type CopyAgentPolicyResponse, - type GetOnePackagePolicyResponse, - packagePolicyRouteService, - agentPolicyRouteService, - API_VERSIONS, -} from '@kbn/fleet-plugin/common'; -import { useKibana } from '../../common/hooks/use_kibana'; -import { PageUrlParams } from '../../../common/types/rules/v3'; - -export const useCspIntegrationInfo = ({ packagePolicyId, policyId }: PageUrlParams) => { - const { http } = useKibana().services; - - return useQuery(['cspBenchmarkRuleInfo', { packagePolicyId, policyId }], () => - Promise.all([ - http - .get(packagePolicyRouteService.getInfoPath(packagePolicyId), { - version: API_VERSIONS.public.v1, - }) - .then((response) => response.item), - http - .get(agentPolicyRouteService.getInfoPath(policyId), { - version: API_VERSIONS.public.v1, - }) - .then((response) => response.item), - ]) - ); -}; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/latest_vulnerabilities_group_renderer.tsx b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/latest_vulnerabilities_group_renderer.tsx index 9c88ef2ab85d0..351cecd83d502 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/latest_vulnerabilities_group_renderer.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/latest_vulnerabilities_group_renderer.tsx @@ -150,7 +150,7 @@ const VulnerabilitiesCountComponent = ({ `vulnerability-finding-flyout-tab-${tabId}`; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/translations.ts b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/translations.ts index cc5392bd6da66..2864ff7f29005 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/translations.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/translations.ts @@ -9,18 +9,6 @@ import { i18n } from '@kbn/i18n'; import { VULNERABILITY_GROUPING_OPTIONS } from '../../common/constants'; -export const FILTER_IN = i18n.translate('xpack.csp.vulnerabilities.table.filterIn', { - defaultMessage: 'Filter in', -}); -export const FILTER_OUT = i18n.translate('xpack.csp.vulnerabilities.table.filterOut', { - defaultMessage: 'Filter out', -}); -export const SEARCH_BAR_PLACEHOLDER = i18n.translate( - 'xpack.csp.vulnerabilities.searchBar.placeholder', - { - defaultMessage: 'Search vulnerabilities (eg. vulnerability.severity : "CRITICAL" )', - } -); export const VULNERABILITIES = i18n.translate('xpack.csp.vulnerabilities', { defaultMessage: 'Vulnerabilities', }); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/types.ts b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/types.ts index 14e9fbae28d41..e0c97ce6ff76d 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/types.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/types.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { VectorScoreBase, CspVulnerabilityFinding } from '../../../common/schemas'; +import { VectorScoreBase } from '../../../common/schemas'; export type Vendor = 'NVD' | 'Red Hat' | 'GHSA'; @@ -19,29 +19,3 @@ export interface Vector { vector: string; score: number | undefined; } - -export interface VulnerabilitiesQueryData { - page: CspVulnerabilityFinding[]; - total: number; -} - -export interface VulnerabilitiesByResourceQueryData { - page: Array<{ - resource: { - id: string; - name: string; - }; - cloud: { - region: string; - }; - vulnerabilities_count: number; - severity_map: { - critical: number; - high: number; - medium: number; - low: number; - }; - }>; - total: number; - total_vulnerabilities: number; -} diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/utils/custom_sort_script.ts b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/utils/custom_sort_script.ts index d184b0ed568a4..780cd539305b3 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/utils/custom_sort_script.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/utils/custom_sort_script.ts @@ -5,43 +5,6 @@ * 2.0. */ -import { i18n } from '@kbn/i18n'; - -export const severitySchemaConfig = { - type: 'severitySchema', - detector() { - return 0; // this schema is always explicitly defined - }, - sortTextAsc: i18n.translate('xpack.csp.vulnerabilityTable.column.sortAscending', { - defaultMessage: 'Low -> Critical', - }), - sortTextDesc: i18n.translate('xpack.csp.vulnerabilityTable.column.sortDescending', { - defaultMessage: 'Critical -> Low', - }), - icon: 'dot', - color: '', -}; - -export const severitySortScript = (direction: string) => ({ - _script: { - type: 'number', - script: { - lang: 'painless', - inline: - "if(doc.containsKey('vulnerability.severity') && !doc['vulnerability.severity'].empty && doc['vulnerability.severity'].size()!=0 && doc['vulnerability.severity'].value!=null && params.scores.containsKey(doc['vulnerability.severity'].value)) { return params.scores[doc['vulnerability.severity'].value];} return 0;", - params: { - scores: { - LOW: 1, - MEDIUM: 2, - HIGH: 3, - CRITICAL: 4, - }, - }, - }, - order: direction, - }, -}); - /** * Generates Painless sorting in case-insensitive manner */ diff --git a/x-pack/plugins/cloud_security_posture/public/test/fixtures/findings_fixture.ts b/x-pack/plugins/cloud_security_posture/public/test/fixtures/findings_fixture.ts index cb67ab59333e8..1fec1c76430eb 100644 --- a/x-pack/plugins/cloud_security_posture/public/test/fixtures/findings_fixture.ts +++ b/x-pack/plugins/cloud_security_posture/public/test/fixtures/findings_fixture.ts @@ -4,66 +4,3 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - -import { EcsEvent } from '@elastic/ecs'; -import Chance from 'chance'; -import { CspFinding } from '../../../common/schemas/csp_finding'; - -const chance = new Chance(); - -export const getFindingsFixture = (): CspFinding & { id: string } => ({ - cluster_id: chance.guid(), - id: chance.word(), - result: { - expected: { - source: {}, - }, - evaluation: chance.weighted(['passed', 'failed'], [0.5, 0.5]), - evidence: { - filemode: chance.word(), - }, - }, - rule: { - audit: chance.paragraph(), - benchmark: { - name: 'CIS Kubernetes', - version: '1.6.0', - id: 'cis_k8s', - rule_number: '1.1.1', - posture_type: 'kspm', - }, - default_value: chance.sentence(), - description: chance.paragraph(), - id: chance.guid(), - impact: chance.word(), - name: chance.string(), - profile_applicability: chance.sentence(), - rationale: chance.paragraph(), - references: chance.paragraph(), - rego_rule_id: 'cis_X_X_X', - remediation: chance.word(), - section: chance.sentence(), - tags: [], - version: '1.0', - }, - agent: { - id: chance.string(), - name: chance.string(), - type: chance.string(), - version: chance.string(), - }, - resource: { - name: chance.string(), - type: chance.string(), - raw: {} as any, - sub_type: chance.string(), - id: chance.string(), - }, - host: {} as any, - ecs: {} as any, - event: {} as EcsEvent, - '@timestamp': new Date().toISOString(), - data_stream: { - dataset: 'cloud_security_posture.findings', - }, -}); diff --git a/x-pack/plugins/cloud_security_posture/public/test/fixtures/navigation_item.ts b/x-pack/plugins/cloud_security_posture/public/test/fixtures/navigation_item.ts deleted file mode 100644 index 1edffe7af2988..0000000000000 --- a/x-pack/plugins/cloud_security_posture/public/test/fixtures/navigation_item.ts +++ /dev/null @@ -1,22 +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 Chance from 'chance'; -import type { CspPageNavigationItem } from '../../common/navigation/types'; - -type CreateNavigationItemFixtureInput = { chance?: Chance.Chance } & Partial; -export const createPageNavigationItemFixture = ({ - chance = new Chance(), - name = chance.word(), - path = `/${chance.word()}`, - disabled = undefined, - id = 'cloud_security_posture-dashboard', -}: CreateNavigationItemFixtureInput = {}): CspPageNavigationItem => ({ - name, - path, - disabled, - id, -}); diff --git a/x-pack/plugins/cloud_security_posture/server/create_indices/ingest_pipelines.ts b/x-pack/plugins/cloud_security_posture/server/create_indices/ingest_pipelines.ts index 2073f7b146275..7948fd9d90578 100644 --- a/x-pack/plugins/cloud_security_posture/server/create_indices/ingest_pipelines.ts +++ b/x-pack/plugins/cloud_security_posture/server/create_indices/ingest_pipelines.ts @@ -9,7 +9,6 @@ import type { IngestPutPipelineRequest } from '@elastic/elasticsearch/lib/api/ty import { CSP_INGEST_TIMESTAMP_PIPELINE, CSP_LATEST_FINDINGS_INGEST_TIMESTAMP_PIPELINE, - CSP_LATEST_VULNERABILITIES_INGEST_TIMESTAMP_PIPELINE, } from '../../common/constants'; export const scorePipelineIngestConfig: IngestPutPipelineRequest = { @@ -53,24 +52,3 @@ export const latestFindingsPipelineIngestConfig: IngestPutPipelineRequest = { }, ], }; - -export const latestVulnerabilitiesPipelineIngestConfig: IngestPutPipelineRequest = { - id: CSP_LATEST_VULNERABILITIES_INGEST_TIMESTAMP_PIPELINE, - description: 'Pipeline for cloudbeat latest vulnerabilities index', - processors: [ - { - set: { - field: 'event.ingested', - value: '{{_ingest.timestamp}}', - }, - }, - ], - on_failure: [ - { - set: { - field: 'error.message', - value: '{{ _ingest.on_failure_message }}', - }, - }, - ], -}; diff --git a/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/types.ts b/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/types.ts index 370c9676099d6..d5db37879554b 100644 --- a/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/types.ts +++ b/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/types.ts @@ -180,10 +180,6 @@ export interface KubernetesVersion { metrics: { 'cloudbeat.kubernetes.version': string }; } -export interface PackagePolicyId { - metrics: { 'cloud_security_posture.package_policy.id': string }; -} - export interface LatestDocTimestamp { metrics: { '@timestamp': string }; } diff --git a/x-pack/plugins/cloud_security_posture/server/routes/detection_engine/get_detection_engine_alerts_count_by_rule_tags.ts b/x-pack/plugins/cloud_security_posture/server/routes/detection_engine/get_detection_engine_alerts_count_by_rule_tags.ts index 026cf68819ab2..52ff94ad7086c 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/detection_engine/get_detection_engine_alerts_count_by_rule_tags.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/detection_engine/get_detection_engine_alerts_count_by_rule_tags.ts @@ -15,10 +15,6 @@ import { } from '../../../common/constants'; import { CspRouter } from '../../types'; -export interface VulnerabilitiesStatisticsQueryResult { - total: number; -} - const DEFAULT_ALERTS_INDEX = '.alerts-security.alerts-default' as const; export const getDetectionEngineAlertsCountByRuleTags = async ( diff --git a/x-pack/plugins/cloud_security_posture/server/types.ts b/x-pack/plugins/cloud_security_posture/server/types.ts index 82f80cd95f972..1190c0a1c5556 100644 --- a/x-pack/plugins/cloud_security_posture/server/types.ts +++ b/x-pack/plugins/cloud_security_posture/server/types.ts @@ -22,9 +22,6 @@ import type { Logger, SavedObjectsClientContract, IScopedClusterClient, - KibanaResponseFactory, - RequestHandler, - RouteMethod, } from '@kbn/core/server'; import type { AgentService, @@ -90,18 +87,6 @@ export type CspRequestHandlerContext = CustomRequestHandlerContext<{ alerting: AlertingApiRequestHandlerContext; }>; -/** - * Convenience type for request handlers in CSP that includes the CspRequestHandlerContext type - * @internal - */ -export type CspRequestHandler< - P = unknown, - Q = unknown, - B = unknown, - Method extends RouteMethod = any, - ResponseFactory extends KibanaResponseFactory = KibanaResponseFactory -> = RequestHandler; - /** * Convenience type for routers in Csp that includes the CspRequestHandlerContext type * @internal diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index d81b810c983b8..a423b5e64a1d9 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -13210,7 +13210,6 @@ "xpack.csp.emptyState.resetFiltersButton": "Réinitialiser les filtres", "xpack.csp.emptyState.title": "Aucun résultat ne correspond à vos critères de recherche.", "xpack.csp.enableBenchmarkRuleButton": "Activer la règle", - "xpack.csp.findings.distributionBar.showingPageOfTotalLabel": "Affichage de {pageStart}-{pageEnd} sur {total} {type}", "xpack.csp.findings.distributionBar.totalFailedLabel": "Échec des résultats", "xpack.csp.findings.distributionBar.totalPassedLabel": "Réussite des résultats", "xpack.csp.findings.errorCallout.pageSearchErrorTitle": "Une erreur s’est produite lors de la récupération des résultats de recherche.", @@ -13249,9 +13248,6 @@ "xpack.csp.findings.gcpIntegration.gcpInputText.credentialFileText": "Chemin vers le fichier JSON qui contient les informations d'identification et la clé utilisés pour souscrire", "xpack.csp.findings.gcpIntegration.gcpInputText.credentialJSONText": "Blob JSON qui contient les informations d'identification et la clé utilisées pour souscrire", "xpack.csp.findings.gcpIntegration.gcpInputText.credentialSelectBoxTitle": "Informations d'identification", - "xpack.csp.findings.groupBySelector.groupByLabel": "Regrouper par", - "xpack.csp.findings.groupBySelector.groupByNoneLabel": "Aucun", - "xpack.csp.findings.groupBySelector.groupByResourceIdLabel": "Ressource", "xpack.csp.findings.grouping.cloudAccount.nullGroupTitle": "Aucun compte cloud", "xpack.csp.findings.grouping.default.nullGroupTitle": "Aucun regroupement", "xpack.csp.findings.grouping.kubernetes.nullGroupTitle": "Aucun cluster Kubernetes", @@ -13441,9 +13437,6 @@ "xpack.csp.vulnerabilities.grouping.nullGroupUnit": "vulnérabilités", "xpack.csp.vulnerabilities.grouping.resource.nullGroupTitle": "Aucune ressource", "xpack.csp.vulnerabilities.grouping.severity": "Sévérité", - "xpack.csp.vulnerabilities.searchBar.placeholder": "Rechercher des vulnérabilités (par exemple vulnerability.severity : \"CRITICAL\" )", - "xpack.csp.vulnerabilities.table.filterIn": "Inclure", - "xpack.csp.vulnerabilities.table.filterOut": "Exclure", "xpack.csp.vulnerabilities.unit": "{totalCount, plural, =1 {vulnérabilité} other {vulnérabilités}}", "xpack.csp.vulnerabilities.vulnerabilitiesFindingFlyout.flyoutDescriptionList.packageTitle": "Pack", "xpack.csp.vulnerabilities.vulnerabilitiesFindingFlyout.flyoutDescriptionList.resourceId": "ID ressource", @@ -13468,8 +13461,6 @@ "xpack.csp.vulnerabilityDashboard.trendGraphChart.accountsDropDown.prepend.accountsTitle": "Comptes", "xpack.csp.vulnerabilityDashboard.trendGraphChart.trendBySeverityTitle": "Tendance par degré de gravité", "xpack.csp.vulnerabilityDashboard.viewAllButton.buttonTitle": "Tout afficher", - "xpack.csp.vulnerabilityTable.column.sortAscending": "Basse -> Critique", - "xpack.csp.vulnerabilityTable.column.sortDescending": "Critique -> Basse", "xpack.csp.vulnerabilityTable.panel.buttonText": "Afficher toutes les vulnérabilités", "xpack.csp.vulnMgmtIntegration.awsOption.nameTitle": "Amazon Web Services", "xpack.csp.vulnMgmtIntegration.azureOption.nameTitle": "Azure", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 267f2bb3c8f2e..ae244e5fd110b 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -13170,7 +13170,6 @@ "xpack.csp.emptyState.resetFiltersButton": "フィルターをリセット", "xpack.csp.emptyState.title": "検索条件と一致する結果がありません。", "xpack.csp.enableBenchmarkRuleButton": "ルールを有効にする", - "xpack.csp.findings.distributionBar.showingPageOfTotalLabel": "{total}件中{pageStart}-{pageEnd}件の{type}を表示しています", "xpack.csp.findings.distributionBar.totalFailedLabel": "失敗した調査結果", "xpack.csp.findings.distributionBar.totalPassedLabel": "合格した調査結果", "xpack.csp.findings.errorCallout.pageSearchErrorTitle": "検索結果の取得中にエラーが発生しました", @@ -13209,9 +13208,6 @@ "xpack.csp.findings.gcpIntegration.gcpInputText.credentialFileText": "サブスクライブに使用される資格情報とキーを含むJSONファイルへのパス", "xpack.csp.findings.gcpIntegration.gcpInputText.credentialJSONText": "サブスクライブに使用される資格情報とキーを含むJSON blob", "xpack.csp.findings.gcpIntegration.gcpInputText.credentialSelectBoxTitle": "資格情報", - "xpack.csp.findings.groupBySelector.groupByLabel": "グループ分けの条件", - "xpack.csp.findings.groupBySelector.groupByNoneLabel": "なし", - "xpack.csp.findings.groupBySelector.groupByResourceIdLabel": "リソース", "xpack.csp.findings.grouping.cloudAccount.nullGroupTitle": "クラウドアカウントなし", "xpack.csp.findings.grouping.default.nullGroupTitle": "グループ分けなし", "xpack.csp.findings.grouping.kubernetes.nullGroupTitle": "Kubernetesクラスターなし", @@ -13400,9 +13396,6 @@ "xpack.csp.vulnerabilities.grouping.nullGroupUnit": "脆弱性", "xpack.csp.vulnerabilities.grouping.resource.nullGroupTitle": "リソースなし", "xpack.csp.vulnerabilities.grouping.severity": "深刻度", - "xpack.csp.vulnerabilities.searchBar.placeholder": "脆弱性を検索(例:vulnerability.severity :\"CRITICAL\")", - "xpack.csp.vulnerabilities.table.filterIn": "フィルタリング", - "xpack.csp.vulnerabilities.table.filterOut": "除外", "xpack.csp.vulnerabilities.unit": "{totalCount, plural, other {脆弱性}}", "xpack.csp.vulnerabilities.vulnerabilitiesFindingFlyout.flyoutDescriptionList.packageTitle": "パッケージ", "xpack.csp.vulnerabilities.vulnerabilitiesFindingFlyout.flyoutDescriptionList.resourceId": "リソースID", @@ -13427,8 +13420,6 @@ "xpack.csp.vulnerabilityDashboard.trendGraphChart.accountsDropDown.prepend.accountsTitle": "アカウント", "xpack.csp.vulnerabilityDashboard.trendGraphChart.trendBySeverityTitle": "重要度別傾向", "xpack.csp.vulnerabilityDashboard.viewAllButton.buttonTitle": "すべて表示", - "xpack.csp.vulnerabilityTable.column.sortAscending": "低 -> 重大", - "xpack.csp.vulnerabilityTable.column.sortDescending": "重大 -> 低", "xpack.csp.vulnerabilityTable.panel.buttonText": "すべての脆弱性を表示", "xpack.csp.vulnMgmtIntegration.awsOption.nameTitle": "Amazon Web Services", "xpack.csp.vulnMgmtIntegration.azureOption.nameTitle": "Azure", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 287dde9aaaaf7..2e0d57450749f 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -13231,7 +13231,6 @@ "xpack.csp.emptyState.resetFiltersButton": "重置筛选", "xpack.csp.emptyState.title": "没有任何结果匹配您的搜索条件", "xpack.csp.enableBenchmarkRuleButton": "启用规则", - "xpack.csp.findings.distributionBar.showingPageOfTotalLabel": "正在显示第 {pageStart}-{pageEnd} 个(共 {total} 个){type}", "xpack.csp.findings.distributionBar.totalFailedLabel": "失败的结果", "xpack.csp.findings.distributionBar.totalPassedLabel": "通过的结果", "xpack.csp.findings.errorCallout.pageSearchErrorTitle": "检索搜索结果时遇到问题", @@ -13270,9 +13269,6 @@ "xpack.csp.findings.gcpIntegration.gcpInputText.credentialFileText": "包含用于订阅的凭据和密钥的 JSON 文件的路径", "xpack.csp.findings.gcpIntegration.gcpInputText.credentialJSONText": "包含用于订阅的凭据和密钥的 JSON Blob", "xpack.csp.findings.gcpIntegration.gcpInputText.credentialSelectBoxTitle": "凭据", - "xpack.csp.findings.groupBySelector.groupByLabel": "分组依据", - "xpack.csp.findings.groupBySelector.groupByNoneLabel": "无", - "xpack.csp.findings.groupBySelector.groupByResourceIdLabel": "资源", "xpack.csp.findings.grouping.cloudAccount.nullGroupTitle": "无云帐户", "xpack.csp.findings.grouping.default.nullGroupTitle": "无分组", "xpack.csp.findings.grouping.kubernetes.nullGroupTitle": "无 Kubernetes 集群", @@ -13462,9 +13458,6 @@ "xpack.csp.vulnerabilities.grouping.nullGroupUnit": "漏洞", "xpack.csp.vulnerabilities.grouping.resource.nullGroupTitle": "无资源", "xpack.csp.vulnerabilities.grouping.severity": "严重性", - "xpack.csp.vulnerabilities.searchBar.placeholder": "搜索漏洞(例如,vulnerability.severity:“CRITICAL”)", - "xpack.csp.vulnerabilities.table.filterIn": "筛选范围", - "xpack.csp.vulnerabilities.table.filterOut": "筛除", "xpack.csp.vulnerabilities.unit": "{totalCount, plural, other {个漏洞}}", "xpack.csp.vulnerabilities.vulnerabilitiesFindingFlyout.flyoutDescriptionList.packageTitle": "软件包", "xpack.csp.vulnerabilities.vulnerabilitiesFindingFlyout.flyoutDescriptionList.resourceId": "资源 ID", @@ -13489,8 +13482,6 @@ "xpack.csp.vulnerabilityDashboard.trendGraphChart.accountsDropDown.prepend.accountsTitle": "帐户", "xpack.csp.vulnerabilityDashboard.trendGraphChart.trendBySeverityTitle": "趋势(按严重性)", "xpack.csp.vulnerabilityDashboard.viewAllButton.buttonTitle": "查看全部", - "xpack.csp.vulnerabilityTable.column.sortAscending": "低 -> 严重", - "xpack.csp.vulnerabilityTable.column.sortDescending": "严重 -> 低", "xpack.csp.vulnerabilityTable.panel.buttonText": "查看所有漏洞", "xpack.csp.vulnMgmtIntegration.awsOption.nameTitle": "Amazon Web Services", "xpack.csp.vulnMgmtIntegration.azureOption.nameTitle": "Azure", From f3aeb81fd618e366db6c7069d46de7e220120289 Mon Sep 17 00:00:00 2001 From: Ash <1849116+ashokaditya@users.noreply.github.com> Date: Wed, 7 Aug 2024 17:18:08 +0200 Subject: [PATCH 18/44] [SecuritySolution][Endpoint] Open API spec updates for public Endpoint APIs (#187634) ## Summary Updates/Adds Open API specs for existing public Endpoint APIs. The PR includes deperated API schemas as well but we can remove them if we all agree to it. Note: The PR doesn't change existing kibana schemas that is used in our server routes and most of the changes in here are file moves and updating imports to unify duplicate naming/import. This to ensure that we can easily maintain the open api specs going forward. The best way to review this is by looking at commits. The major moving cleaning up happens after commit names prefixed with **_refactor_** or `645fbc0f1d8ecc402e81f9503c7c5d1473ef49c0` and onwards. I would also suggest hiding whitespaces when reviewing via Github. closes elastic/kibana/issues/183816 ### 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 - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed ### Risk Matrix Delete this section if it is not applicable to this PR. Before closing this PR, invite QA, stakeholders, and other developers to identify risks that should be tested prior to the change/feature release. When forming the risk matrix, consider some of the following examples and how they may potentially impact the change: | Risk | Probability | Severity | Mitigation/Notes | |---------------------------|-------------|----------|-------------------------| | Multiple Spaces—unexpected behavior in non-default Kibana Space. | Low | High | Integration tests will verify that all features are still supported in non-default Kibana Space and when user switches between spaces. | | Multiple nodes—Elasticsearch polling might have race conditions when multiple Kibana nodes are polling for the same tasks. | High | Low | Tasks are idempotent, so executing them multiple times will not result in logical error, but will degrade performance. To test for this case we add plenty of unit tests around this logic and document manual testing procedure. | | Code should gracefully handle cases when feature X or plugin Y are disabled. | Medium | High | Unit tests will verify that any feature flag or plugin combination still results in our service operational. | | [See more potential risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) | ### 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: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../action_log.ts} | 0 .../deprecated_action_log.gen.ts} | 13 +- .../deprecated_action_log.schema.yaml | 45 ++ .../index.ts} | 5 +- .../api/endpoint/actions/actions.schema.yaml | 131 ------ .../endpoint/actions/audit_log.schema.yaml | 51 --- .../actions/common/response_actions.ts | 25 +- .../endpoint/actions/details/details.gen.ts | 32 ++ .../actions/{ => details}/details.schema.yaml | 21 +- .../{details_route.ts => details/details.ts} | 0 .../api/endpoint/actions/details/index.ts | 9 + .../api/endpoint/actions/execute.gen.ts | 29 -- .../actions/file_download.schema.yaml | 39 -- .../{ => file_download}/file_download.gen.ts | 12 +- .../file_download/file_download.schema.yaml | 30 ++ .../file_download.ts} | 0 .../endpoint/actions/file_download/index.ts | 9 + .../endpoint/actions/file_info.schema.yaml | 40 -- .../actions/{ => file_info}/file_info.gen.ts | 10 +- .../actions/file_info/file_info.schema.yaml | 30 ++ .../file_info.ts} | 0 .../api/endpoint/actions/file_info/index.ts | 9 + .../api/endpoint/actions/file_upload.gen.ts | 29 -- .../api/endpoint/actions/get_file.gen.ts | 28 -- .../api/endpoint/actions/list.schema.yaml | 53 --- .../common/api/endpoint/actions/list/index.ts | 9 + .../endpoint/actions/{ => list}/list.gen.ts | 20 +- .../endpoint/actions/list/list.schema.yaml | 54 +++ .../actions/{list_route.ts => list/list.ts} | 6 +- .../response_actions/execute/execute.gen.ts | 43 ++ .../execute}/execute.schema.yaml | 19 +- .../execute/execute.ts} | 2 +- .../actions/response_actions/execute/index.ts | 9 + .../response_actions/get_file/get_file.gen.ts | 37 ++ .../get_file}/get_file.schema.yaml | 16 +- .../get_file/get_file.ts} | 2 +- .../response_actions/get_file/index.ts | 9 + .../isolate/deprecated_isolate.gen.ts} | 2 +- .../isolate/deprecated_isolate.schema.yaml} | 8 +- .../actions/response_actions/isolate/index.ts | 9 + .../response_actions/isolate/isolate.gen.ts | 31 ++ .../isolate/isolate.schema.yaml | 30 ++ .../isolate/isolate.ts} | 2 +- .../response_actions/kill_process/index.ts | 8 + .../kill_process/kill_process.gen.ts | 30 ++ .../kill_process/kill_process.schema.yaml | 25 ++ .../kill_process/kill_process.ts} | 5 +- .../response_actions/running_procs/index.ts | 9 + .../running_procs/running_procs.gen.ts | 33 ++ .../running_procs/running_procs.schema.yaml | 31 ++ .../running_procs/running_procs.ts} | 2 +- .../actions/response_actions/scan/index.ts | 9 + .../actions/response_actions/scan/scan.gen.ts | 35 ++ .../scan}/scan.schema.yaml | 15 +- .../scan/scan.ts} | 2 +- .../response_actions/suspend_process/index.ts | 9 + .../suspend_process/suspend_process.gen.ts | 32 ++ .../suspend_process.schema.yaml | 25 ++ .../suspend_process/suspend_process.ts} | 5 +- .../unisolate/deprecated_unisolate.gen.ts} | 2 +- .../deprecated_unisolate.schema.yaml} | 8 +- .../response_actions/unisolate/index.ts | 10 + .../unisolate/unisolate.gen.ts | 31 ++ .../unisolate/unisolate.schema.yaml | 30 ++ .../response_actions/unisolate/unisolate.ts | 12 + .../actions/response_actions/upload/index.ts | 9 + .../response_actions/upload/upload.gen.ts | 36 ++ .../upload/upload.schema.yaml} | 15 +- .../upload/upload.ts} | 2 +- .../common/api/endpoint/actions/scan.gen.ts | 28 -- .../api/endpoint/actions/state/index.ts | 8 + .../{details.gen.ts => state/state.gen.ts} | 12 +- .../endpoint/actions/state/state.schema.yaml | 19 + .../api/endpoint/actions/status/index.ts | 9 + .../api/endpoint/actions/status/status.gen.ts | 34 ++ .../status.schema.yaml} | 11 +- .../status.ts} | 0 .../common/api/endpoint/index.ts | 40 +- .../api/endpoint/metadata/get_metadata.gen.ts | 33 ++ ...a.schema.yaml => get_metadata.schema.yaml} | 21 +- ...{get_metadata_route.ts => get_metadata.ts} | 0 .../common/api/endpoint/metadata/index.ts | 12 + ...ist_metadata_route.ts => list_metadata.ts} | 0 .../api/endpoint/model/schema/common.gen.ts | 26 +- .../endpoint/model/schema/common.schema.yaml | 25 +- ...=> deprecated_agent_policy_summary.gen.ts} | 12 +- ...precated_agent_policy_summary.schema.yaml} | 30 +- ....ts => deprecated_agent_policy_summary.ts} | 0 .../common/api/endpoint/policy/index.ts | 11 + .../endpoint/policy/policy_response.gen.ts | 30 ++ .../policy/policy_response.schema.yaml | 27 ++ ...y_response_route.ts => policy_response.ts} | 0 .../endpoint/protection_updates_note/index.ts | 9 + .../protection_updates_note.schema.yaml | 8 +- ...e_schema.ts => protection_updates_note.ts} | 0 .../suggestions/get_suggestions.schema.yaml | 4 +- ...uggestions_route.ts => get_suggestions.ts} | 0 .../common/api/endpoint/suggestions/index.ts | 9 + .../common/endpoint/schema/actions.test.ts | 6 +- .../common/endpoint/types/actions.ts | 19 +- ...agement_api_2023_10_31.bundled.schema.yaml | 386 ++++++++++-------- ...agement_api_2023_10_31.bundled.schema.yaml | 376 +++++++++-------- .../lib/endpoint/endpoint_isolation/index.ts | 11 +- .../lib/endpoint/endpoint_isolation/mocks.ts | 8 +- .../common/lib/process_actions/index.ts | 4 +- .../get_processes_action.tsx | 8 +- .../kill_process_action.tsx | 2 +- .../suspend_process_action.tsx | 2 +- ...use_send_get_endpoint_processes_request.ts | 10 +- .../use_send_isolate_endpoint_request.ts | 14 +- .../use_send_kill_process_endpoint_request.ts | 6 +- .../use_send_release_endpoint_request.ts | 14 +- ...e_send_suspend_process_endpoint_request.ts | 6 +- .../pages/endpoint_hosts/store/action.ts | 4 +- .../pages/endpoint_hosts/store/middleware.ts | 9 +- .../routes/actions/response_actions.test.ts | 8 +- .../routes/actions/response_actions.ts | 6 +- .../protection_updates_note/handlers.ts | 2 +- .../routes/protection_updates_note/index.ts | 2 +- .../crowdstrike/crowdstrike_actions_client.ts | 7 +- .../endpoint/endpoint_actions_client.test.ts | 5 +- .../endpoint/endpoint_actions_client.ts | 9 +- .../lib/base_response_actions_client.ts | 7 +- .../services/actions/clients/lib/types.ts | 7 +- .../sentinel_one_actions_client.ts | 5 +- .../services/security_solution_api.gen.ts | 239 ++++++++++- 126 files changed, 1906 insertions(+), 1082 deletions(-) rename x-pack/plugins/security_solution/common/api/endpoint/actions/{audit_log_route.ts => action_log/action_log.ts} (100%) rename x-pack/plugins/security_solution/common/api/endpoint/actions/{audit_log.gen.ts => action_log/deprecated_action_log.gen.ts} (59%) create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/action_log/deprecated_action_log.schema.yaml rename x-pack/plugins/security_solution/common/api/endpoint/actions/{unisolate_route.ts => action_log/index.ts} (66%) delete mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/actions.schema.yaml delete mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/audit_log.schema.yaml create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/details/details.gen.ts rename x-pack/plugins/security_solution/common/api/endpoint/actions/{ => details}/details.schema.yaml (51%) rename x-pack/plugins/security_solution/common/api/endpoint/actions/{details_route.ts => details/details.ts} (100%) create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/details/index.ts delete mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/execute.gen.ts delete mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/file_download.schema.yaml rename x-pack/plugins/security_solution/common/api/endpoint/actions/{ => file_download}/file_download.gen.ts (52%) create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/file_download/file_download.schema.yaml rename x-pack/plugins/security_solution/common/api/endpoint/actions/{file_download_route.ts => file_download/file_download.ts} (100%) create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/file_download/index.ts delete mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/file_info.schema.yaml rename x-pack/plugins/security_solution/common/api/endpoint/actions/{ => file_info}/file_info.gen.ts (54%) create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/file_info/file_info.schema.yaml rename x-pack/plugins/security_solution/common/api/endpoint/actions/{file_info_route.ts => file_info/file_info.ts} (100%) create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/file_info/index.ts delete mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/file_upload.gen.ts delete mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/get_file.gen.ts delete mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/list.schema.yaml create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/list/index.ts rename x-pack/plugins/security_solution/common/api/endpoint/actions/{ => list}/list.gen.ts (56%) create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/list/list.schema.yaml rename x-pack/plugins/security_solution/common/api/endpoint/actions/{list_route.ts => list/list.ts} (94%) create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/execute/execute.gen.ts rename x-pack/plugins/security_solution/common/api/endpoint/actions/{ => response_actions/execute}/execute.schema.yaml (54%) rename x-pack/plugins/security_solution/common/api/endpoint/actions/{execute_route.ts => response_actions/execute/execute.ts} (94%) create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/execute/index.ts create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/get_file/get_file.gen.ts rename x-pack/plugins/security_solution/common/api/endpoint/actions/{ => response_actions/get_file}/get_file.schema.yaml (64%) rename x-pack/plugins/security_solution/common/api/endpoint/actions/{get_file_route.ts => response_actions/get_file/get_file.ts} (91%) create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/get_file/index.ts rename x-pack/plugins/security_solution/common/api/endpoint/actions/{isolate_route.gen.ts => response_actions/isolate/deprecated_isolate.gen.ts} (91%) rename x-pack/plugins/security_solution/common/api/endpoint/actions/{isolate_route.schema.yaml => response_actions/isolate/deprecated_isolate.schema.yaml} (75%) create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/index.ts create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/isolate.gen.ts create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/isolate.schema.yaml rename x-pack/plugins/security_solution/common/api/endpoint/actions/{isolate_route.ts => response_actions/isolate/isolate.ts} (87%) create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/kill_process/index.ts create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/kill_process/kill_process.gen.ts create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/kill_process/kill_process.schema.yaml rename x-pack/plugins/security_solution/common/api/endpoint/actions/{kill_process_route.ts => response_actions/kill_process/kill_process.ts} (88%) create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/running_procs/index.ts create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/running_procs/running_procs.gen.ts create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/running_procs/running_procs.schema.yaml rename x-pack/plugins/security_solution/common/api/endpoint/actions/{get_processes_route.ts => response_actions/running_procs/running_procs.ts} (88%) create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/scan/index.ts create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/scan/scan.gen.ts rename x-pack/plugins/security_solution/common/api/endpoint/actions/{ => response_actions/scan}/scan.schema.yaml (64%) rename x-pack/plugins/security_solution/common/api/endpoint/actions/{scan_route.ts => response_actions/scan/scan.ts} (92%) create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/suspend_process/index.ts create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/suspend_process/suspend_process.gen.ts create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/suspend_process/suspend_process.schema.yaml rename x-pack/plugins/security_solution/common/api/endpoint/actions/{suspend_process_route.ts => response_actions/suspend_process/suspend_process.ts} (73%) rename x-pack/plugins/security_solution/common/api/endpoint/actions/{unisolate_route.gen.ts => response_actions/unisolate/deprecated_unisolate.gen.ts} (91%) rename x-pack/plugins/security_solution/common/api/endpoint/actions/{unisolate_route.schema.yaml => response_actions/unisolate/deprecated_unisolate.schema.yaml} (76%) create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/index.ts create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/unisolate.gen.ts create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/unisolate.schema.yaml create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/unisolate.ts create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/upload/index.ts create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/upload/upload.gen.ts rename x-pack/plugins/security_solution/common/api/endpoint/actions/{file_upload.schema.yaml => response_actions/upload/upload.schema.yaml} (68%) rename x-pack/plugins/security_solution/common/api/endpoint/actions/{file_upload_route.ts => response_actions/upload/upload.ts} (94%) delete mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/scan.gen.ts create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/state/index.ts rename x-pack/plugins/security_solution/common/api/endpoint/actions/{details.gen.ts => state/state.gen.ts} (59%) create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/state/state.schema.yaml create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/status/index.ts create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/actions/status/status.gen.ts rename x-pack/plugins/security_solution/common/api/endpoint/actions/{actions_status.schema.yaml => status/status.schema.yaml} (64%) rename x-pack/plugins/security_solution/common/api/endpoint/actions/{action_status_route.ts => status/status.ts} (100%) create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/metadata/get_metadata.gen.ts rename x-pack/plugins/security_solution/common/api/endpoint/metadata/{metadata.schema.yaml => get_metadata.schema.yaml} (82%) rename x-pack/plugins/security_solution/common/api/endpoint/metadata/{get_metadata_route.ts => get_metadata.ts} (100%) create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/metadata/index.ts rename x-pack/plugins/security_solution/common/api/endpoint/metadata/{list_metadata_route.ts => list_metadata.ts} (100%) rename x-pack/plugins/security_solution/common/api/endpoint/policy/{policy.gen.ts => deprecated_agent_policy_summary.gen.ts} (66%) rename x-pack/plugins/security_solution/common/api/endpoint/policy/{policy.schema.yaml => deprecated_agent_policy_summary.schema.yaml} (50%) rename x-pack/plugins/security_solution/common/api/endpoint/policy/{get_agent_policy_summary_route.ts => deprecated_agent_policy_summary.ts} (100%) create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/policy/index.ts create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/policy/policy_response.gen.ts create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/policy/policy_response.schema.yaml rename x-pack/plugins/security_solution/common/api/endpoint/policy/{get_policy_response_route.ts => policy_response.ts} (100%) create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/protection_updates_note/index.ts rename x-pack/plugins/security_solution/common/api/endpoint/protection_updates_note/{protection_updates_note_schema.ts => protection_updates_note.ts} (100%) rename x-pack/plugins/security_solution/common/api/endpoint/suggestions/{get_suggestions_route.ts => get_suggestions.ts} (100%) create mode 100644 x-pack/plugins/security_solution/common/api/endpoint/suggestions/index.ts diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/audit_log_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/action_log/action_log.ts similarity index 100% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/audit_log_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/action_log/action_log.ts diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/audit_log.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/action_log/deprecated_action_log.gen.ts similarity index 59% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/audit_log.gen.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/action_log/deprecated_action_log.gen.ts index bfbea46ed9c5c..e458ae1775479 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/audit_log.gen.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/action_log/deprecated_action_log.gen.ts @@ -10,23 +10,18 @@ * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. * * info: - * title: Audit Log Schema + * title: Action Log Schema * version: 2023-10-31 */ import { z } from 'zod'; -import { Page, PageSize, StartDate, EndDate, AgentId } from '../model/schema/common.gen'; +import { Page, PageSize, StartDate, EndDate } from '../../model/schema/common.gen'; -export type AuditLogRequestQuery = z.infer; -export const AuditLogRequestQuery = z.object({ +export type ActionLogRequestQuery = z.infer; +export const ActionLogRequestQuery = z.object({ page: Page.optional(), page_size: PageSize.optional(), start_date: StartDate.optional(), end_date: EndDate.optional(), }); - -export type AuditLogRequestParams = z.infer; -export const AuditLogRequestParams = z.object({ - agent_id: AgentId.optional(), -}); diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/action_log/deprecated_action_log.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/action_log/deprecated_action_log.schema.yaml new file mode 100644 index 0000000000000..d46a1cbe7d9ba --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/action_log/deprecated_action_log.schema.yaml @@ -0,0 +1,45 @@ +openapi: 3.0.0 +info: + title: Action Log Schema + version: '2023-10-31' +paths: + /api/endpoint/action_log/{agent_id}: + get: + summary: Get action requests log schema + operationId: EndpointGetActionLog + description: Get action requests log + deprecated: true + x-codegen-enabled: false + x-labels: [ess, serverless] + parameters: + - name: agent_id + in: path + required: true + schema: + $ref: '../../model/schema/common.schema.yaml#/components/schemas/AgentId' + - name: query + in: query + required: true + schema: + $ref: '#/components/schemas/ActionLogRequestQuery' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + +components: + schemas: + ActionLogRequestQuery: + type: object + properties: + page: + $ref: '../../model/schema/common.schema.yaml#/components/schemas/Page' + page_size: + $ref: '../../model/schema/common.schema.yaml#/components/schemas/PageSize' + start_date: + $ref: '../../model/schema/common.schema.yaml#/components/schemas/StartDate' + end_date: + $ref: '../../model/schema/common.schema.yaml#/components/schemas/EndDate' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/unisolate_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/action_log/index.ts similarity index 66% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/unisolate_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/action_log/index.ts index 2d9f2d2f5725b..61a6f0bcbd5c9 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/unisolate_route.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/action_log/index.ts @@ -5,6 +5,5 @@ * 2.0. */ -import { NoParametersRequestSchema } from './common/base'; - -export const UnisolateRouteRequestSchema = NoParametersRequestSchema; +export * from './action_log'; +export * from './deprecated_action_log.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/actions.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/actions.schema.yaml deleted file mode 100644 index a64eb5c5cff3a..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/actions.schema.yaml +++ /dev/null @@ -1,131 +0,0 @@ -openapi: 3.0.0 -info: - title: Endpoint Actions Schema - version: '2023-10-31' -paths: - /api/endpoint/action/state: - get: - summary: Get Action State schema - operationId: EndpointGetActionsState - x-codegen-enabled: false - x-labels: - - ess - - serverless - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' - - /api/endpoint/action/running_procs: - post: - summary: Get Running Processes Action - operationId: EndpointGetRunningProcessesAction - x-codegen-enabled: false - x-labels: - - ess - - serverless - requestBody: - required: true - content: - application/json: - schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/BaseActionSchema' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' - - /api/endpoint/action/isolate: - post: - summary: Isolate host Action - operationId: EndpointIsolateHostAction - x-codegen-enabled: false - x-labels: - - ess - - serverless - requestBody: - required: true - content: - application/json: - schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/BaseActionSchema' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' - - /api/endpoint/action/unisolate: - post: - summary: Unisolate host Action - operationId: EndpointUnisolateHostAction - x-codegen-enabled: false - x-labels: - - ess - - serverless - requestBody: - required: true - content: - application/json: - schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/BaseActionSchema' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' - - /api/endpoint/action/kill_process: - post: - summary: Kill process Action - operationId: EndpointKillProcessAction - x-codegen-enabled: false - x-labels: - - ess - - serverless - requestBody: - required: true - content: - application/json: - schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/ProcessActionSchemas' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' - - - /api/endpoint/action/suspend_process: - post: - summary: Suspend process Action - operationId: EndpointSuspendProcessAction - x-codegen-enabled: false - x-labels: - - ess - - serverless - requestBody: - required: true - content: - application/json: - schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/ProcessActionSchemas' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/audit_log.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/audit_log.schema.yaml deleted file mode 100644 index 5d0ed51ca339a..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/audit_log.schema.yaml +++ /dev/null @@ -1,51 +0,0 @@ -openapi: 3.0.0 -info: - title: Audit Log Schema - version: '2023-10-31' -paths: - /api/endpoint/action_log/{agent_id}: - get: - summary: Get action audit log schema - operationId: EndpointGetActionAuditLog - x-codegen-enabled: false - x-labels: - - ess - - serverless - parameters: - - name: query - in: query - required: true - schema: - $ref: '#/components/schemas/AuditLogRequestQuery' - - name: query - in: path - required: true - schema: - $ref: '#/components/schemas/AuditLogRequestParams' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' - -components: - schemas: - AuditLogRequestQuery: - type: object - properties: - page: - $ref: '../model/schema/common.schema.yaml#/components/schemas/Page' - page_size: - $ref: '../model/schema/common.schema.yaml#/components/schemas/PageSize' - start_date: - $ref: '../model/schema/common.schema.yaml#/components/schemas/StartDate' - end_date: - $ref: '../model/schema/common.schema.yaml#/components/schemas/EndDate' - - AuditLogRequestParams: - type: object - properties: - agent_id: - $ref: '../model/schema/common.schema.yaml#/components/schemas/AgentId' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/common/response_actions.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/common/response_actions.ts index ca6d9d5e91982..56b1fafdb5a71 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/common/response_actions.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/common/response_actions.ts @@ -7,24 +7,27 @@ import type { TypeOf } from '@kbn/config-schema'; import { schema } from '@kbn/config-schema'; -import { - KillProcessRouteRequestSchema, - SuspendProcessRouteRequestSchema, - UploadActionRequestSchema, -} from '../..'; -import { ExecuteActionRequestSchema } from '../execute_route'; -import { EndpointActionGetFileSchema } from '../get_file_route'; -import { ScanActionRequestSchema } from '../scan_route'; -import { NoParametersRequestSchema } from './base'; + +import { ExecuteActionRequestSchema } from '../response_actions/execute'; +import { EndpointActionGetFileSchema } from '../response_actions/get_file'; +import { ScanActionRequestSchema } from '../response_actions/scan'; +import { IsolateRouteRequestSchema } from '../response_actions/isolate'; +import { UnisolateRouteRequestSchema } from '../response_actions/unisolate'; +import { GetProcessesRouteRequestSchema } from '../response_actions/running_procs'; +import { KillProcessRouteRequestSchema } from '../response_actions/kill_process'; +import { SuspendProcessRouteRequestSchema } from '../response_actions/suspend_process'; +import { UploadActionRequestSchema } from '../response_actions/upload'; export const ResponseActionBodySchema = schema.oneOf([ - NoParametersRequestSchema.body, + IsolateRouteRequestSchema.body, + UnisolateRouteRequestSchema.body, + GetProcessesRouteRequestSchema.body, KillProcessRouteRequestSchema.body, SuspendProcessRouteRequestSchema.body, EndpointActionGetFileSchema.body, ExecuteActionRequestSchema.body, - ScanActionRequestSchema.body, UploadActionRequestSchema.body, + ScanActionRequestSchema.body, ]); export type ResponseActionsRequestBody = TypeOf; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/details/details.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/details/details.gen.ts new file mode 100644 index 0000000000000..3f5305dabd424 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/details/details.gen.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Details Schema + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { SuccessResponse } from '../../model/schema/common.gen'; + +export type EndpointGetActionsDetailsRequestParams = z.infer< + typeof EndpointGetActionsDetailsRequestParams +>; +export const EndpointGetActionsDetailsRequestParams = z.object({ + action_id: z.string(), +}); +export type EndpointGetActionsDetailsRequestParamsInput = z.input< + typeof EndpointGetActionsDetailsRequestParams +>; + +export type EndpointGetActionsDetailsResponse = z.infer; +export const EndpointGetActionsDetailsResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/details.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/details/details.schema.yaml similarity index 51% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/details.schema.yaml rename to x-pack/plugins/security_solution/common/api/endpoint/actions/details/details.schema.yaml index 18a3ffa2c1fd2..ec3a184e6e9a9 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/details.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/details/details.schema.yaml @@ -7,28 +7,21 @@ paths: get: summary: Get Action details schema operationId: EndpointGetActionsDetails - x-codegen-enabled: false - x-labels: - - ess - - serverless + description: Get action details + x-codegen-enabled: true + x-labels: [ess, serverless] parameters: - - name: query + - name: action_id in: path required: true schema: - $ref: '#/components/schemas/DetailsRequestParams' + type: string responses: '200': description: OK content: application/json: schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' -components: - schemas: - DetailsRequestParams: - type: object - properties: - action_id: - type: string + $ref: '../../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/details_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/details/details.ts similarity index 100% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/details_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/details/details.ts diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/details/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/details/index.ts new file mode 100644 index 0000000000000..0154d63be42a2 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/details/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './details'; +export * from './details.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/execute.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/execute.gen.ts deleted file mode 100644 index 8afd62814dfb3..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/execute.gen.ts +++ /dev/null @@ -1,29 +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. - */ - -/* - * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. - * - * info: - * title: Execute Action Schema - * version: 2023-10-31 - */ - -import { z } from 'zod'; - -import { BaseActionSchema, Command, Timeout } from '../model/schema/common.gen'; - -export type ExecuteActionRequestBody = z.infer; -export const ExecuteActionRequestBody = BaseActionSchema.merge( - z.object({ - parameters: z.object({ - command: Command, - timeout: Timeout.optional(), - }), - }) -); diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download.schema.yaml deleted file mode 100644 index 8eb02883965b1..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download.schema.yaml +++ /dev/null @@ -1,39 +0,0 @@ -openapi: 3.0.0 -info: - title: File Download Schema - version: '2023-10-31' -paths: - /api/endpoint/action/{action_id}/file/{file_id}/download`: - get: - summary: File Download schema - operationId: EndpointFileDownload - x-codegen-enabled: false - x-labels: - - ess - - serverless - parameters: - - name: query - in: path - required: true - schema: - $ref: '#/components/schemas/FileDownloadRequestParams' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' -components: - schemas: - FileDownloadRequestParams: - type: object - required: - - action_id - - file_id - properties: - action_id: - type: string - file_id: - type: string - diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download/file_download.gen.ts similarity index 52% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/file_download.gen.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/file_download/file_download.gen.ts index e670d2070d8ab..a32c1036e3fd9 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download.gen.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download/file_download.gen.ts @@ -16,8 +16,16 @@ import { z } from 'zod'; -export type FileDownloadRequestParams = z.infer; -export const FileDownloadRequestParams = z.object({ +import { SuccessResponse } from '../../model/schema/common.gen'; + +export type EndpointFileDownloadRequestParams = z.infer; +export const EndpointFileDownloadRequestParams = z.object({ action_id: z.string(), file_id: z.string(), }); +export type EndpointFileDownloadRequestParamsInput = z.input< + typeof EndpointFileDownloadRequestParams +>; + +export type EndpointFileDownloadResponse = z.infer; +export const EndpointFileDownloadResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download/file_download.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download/file_download.schema.yaml new file mode 100644 index 0000000000000..8842d1b6acc61 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download/file_download.schema.yaml @@ -0,0 +1,30 @@ +openapi: 3.0.0 +info: + title: File Download Schema + version: '2023-10-31' +paths: + /api/endpoint/action/{action_id}/file/{file_id}/download`: + get: + summary: File Download schema + operationId: EndpointFileDownload + description: Download a file from an endpoint + x-codegen-enabled: true + x-labels: [ess, serverless] + parameters: + - name: action_id + in: path + required: true + schema: + type: string + - name: file_id + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download/file_download.ts similarity index 100% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/file_download_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/file_download/file_download.ts diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download/index.ts new file mode 100644 index 0000000000000..f6b87f11df80e --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './file_download'; +export * from './file_download.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info.schema.yaml deleted file mode 100644 index 5351480738e23..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info.schema.yaml +++ /dev/null @@ -1,40 +0,0 @@ -openapi: 3.0.0 -info: - title: File Info Schema - version: '2023-10-31' -paths: - /api/endpoint/action/{action_id}/file/{file_id}`: - get: - summary: File Info schema - operationId: EndpointFileInfo - x-codegen-enabled: false - x-labels: - - ess - - serverless - parameters: - - name: query - in: path - required: true - schema: - $ref: '#/components/schemas/FileInfoRequestParams' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' - -components: - schemas: - FileInfoRequestParams: - type: object - required: - - action_id - - file_id - properties: - action_id: - type: string - file_id: - type: string - diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info/file_info.gen.ts similarity index 54% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/file_info.gen.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/file_info/file_info.gen.ts index d9737560e849c..1a706049b067a 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info.gen.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info/file_info.gen.ts @@ -16,8 +16,14 @@ import { z } from 'zod'; -export type FileInfoRequestParams = z.infer; -export const FileInfoRequestParams = z.object({ +import { SuccessResponse } from '../../model/schema/common.gen'; + +export type EndpointFileInfoRequestParams = z.infer; +export const EndpointFileInfoRequestParams = z.object({ action_id: z.string(), file_id: z.string(), }); +export type EndpointFileInfoRequestParamsInput = z.input; + +export type EndpointFileInfoResponse = z.infer; +export const EndpointFileInfoResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info/file_info.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info/file_info.schema.yaml new file mode 100644 index 0000000000000..6199dc56ed1b0 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info/file_info.schema.yaml @@ -0,0 +1,30 @@ +openapi: 3.0.0 +info: + title: File Info Schema + version: '2023-10-31' +paths: + /api/endpoint/action/{action_id}/file/{file_id}`: + get: + summary: File Info schema + operationId: EndpointFileInfo + description: Get file info + x-codegen-enabled: true + x-labels: [ess, serverless] + parameters: + - name: action_id + in: path + required: true + schema: + type: string + - name: file_id + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info/file_info.ts similarity index 100% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/file_info_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/file_info/file_info.ts diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info/index.ts new file mode 100644 index 0000000000000..db1f6c9ef2db3 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './file_info'; +export * from './file_info.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_upload.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_upload.gen.ts deleted file mode 100644 index f376c6d81fc21..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_upload.gen.ts +++ /dev/null @@ -1,29 +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. - */ - -/* - * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. - * - * info: - * title: File Upload Schema - * version: 2023-10-31 - */ - -import { z } from 'zod'; - -import { BaseActionSchema } from '../model/schema/common.gen'; - -export type FileUploadActionRequestBody = z.infer; -export const FileUploadActionRequestBody = BaseActionSchema.merge( - z.object({ - parameters: z.object({ - overwrite: z.boolean().optional().default(false), - }), - file: z.string(), - }) -); diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/get_file.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/get_file.gen.ts deleted file mode 100644 index 22dc90c6cfd82..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/get_file.gen.ts +++ /dev/null @@ -1,28 +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. - */ - -/* - * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. - * - * info: - * title: Get File Schema - * version: 2023-10-31 - */ - -import { z } from 'zod'; - -import { BaseActionSchema } from '../model/schema/common.gen'; - -export type GetFileActionRequestBody = z.infer; -export const GetFileActionRequestBody = BaseActionSchema.merge( - z.object({ - parameters: z.object({ - path: z.string(), - }), - }) -); diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/list.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/list.schema.yaml deleted file mode 100644 index afa844135ecd5..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/list.schema.yaml +++ /dev/null @@ -1,53 +0,0 @@ -openapi: 3.0.0 -info: - title: Actions List Schema - version: '2023-10-31' -paths: - /api/endpoint/action: - get: - summary: Get Actions List schema - operationId: EndpointGetActionsList - x-codegen-enabled: false - x-labels: - - ess - - serverless - parameters: - - name: query - in: query - required: true - schema: - $ref: '#/components/schemas/EndpointActionListRequestQuery' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' -components: - schemas: - EndpointActionListRequestQuery: - type: object - properties: - agentIds: - $ref: '../model/schema/common.schema.yaml#/components/schemas/AgentIds' - commands: - $ref: '../model/schema/common.schema.yaml#/components/schemas/Commands' - page: - $ref: '../model/schema/common.schema.yaml#/components/schemas/Page' - pageSize: - type: integer - default: 10 - minimum: 1 - maximum: 10000 - description: Number of items per page - startDate: - $ref: '../model/schema/common.schema.yaml#/components/schemas/StartDate' - endDate: - $ref: '../model/schema/common.schema.yaml#/components/schemas/EndDate' - userIds: - $ref: '../model/schema/common.schema.yaml#/components/schemas/UserIds' - types: - $ref: '../model/schema/common.schema.yaml#/components/schemas/Types' - withOutputs: - $ref: '../model/schema/common.schema.yaml#/components/schemas/WithOutputs' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/list/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/list/index.ts new file mode 100644 index 0000000000000..9b1e437559465 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/list/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './list'; +export * from './list.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/list.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/list/list.gen.ts similarity index 56% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/list.gen.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/list/list.gen.ts index 771af7165f3bd..a63e011e1d2f5 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/list.gen.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/list/list.gen.ts @@ -17,7 +17,9 @@ import { z } from 'zod'; import { + SuccessResponse, AgentIds, + AgentTypes, Commands, Page, StartDate, @@ -25,11 +27,12 @@ import { UserIds, Types, WithOutputs, -} from '../model/schema/common.gen'; +} from '../../model/schema/common.gen'; -export type EndpointActionListRequestQuery = z.infer; -export const EndpointActionListRequestQuery = z.object({ +export type GetEndpointActionListRouteQuery = z.infer; +export const GetEndpointActionListRouteQuery = z.object({ agentIds: AgentIds.optional(), + agentTypes: AgentTypes.optional(), commands: Commands.optional(), page: Page.optional(), /** @@ -42,3 +45,14 @@ export const EndpointActionListRequestQuery = z.object({ types: Types.optional(), withOutputs: WithOutputs.optional(), }); + +export type EndpointGetActionsListRequestQuery = z.infer; +export const EndpointGetActionsListRequestQuery = z.object({ + query: GetEndpointActionListRouteQuery, +}); +export type EndpointGetActionsListRequestQueryInput = z.input< + typeof EndpointGetActionsListRequestQuery +>; + +export type EndpointGetActionsListResponse = z.infer; +export const EndpointGetActionsListResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/list/list.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/list/list.schema.yaml new file mode 100644 index 0000000000000..b91ba03c60b8d --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/list/list.schema.yaml @@ -0,0 +1,54 @@ +openapi: 3.0.0 +info: + title: Actions List Schema + version: '2023-10-31' +paths: + /api/endpoint/action: + get: + summary: Get Actions List schema + operationId: EndpointGetActionsList + description: Get a list of action requests and their responses + x-codegen-enabled: true + x-labels: [ess, serverless] + parameters: + - name: query + in: query + required: true + schema: + $ref: '#/components/schemas/GetEndpointActionListRouteQuery' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' +components: + schemas: + GetEndpointActionListRouteQuery: + type: object + properties: + agentIds: + $ref: '../../model/schema/common.schema.yaml#/components/schemas/AgentIds' + agentTypes: + $ref: '../../model/schema/common.schema.yaml#/components/schemas/AgentTypes' + commands: + $ref: '../../model/schema/common.schema.yaml#/components/schemas/Commands' + page: + $ref: '../../model/schema/common.schema.yaml#/components/schemas/Page' + pageSize: + type: integer + default: 10 + minimum: 1 + maximum: 10000 + description: Number of items per page + startDate: + $ref: '../../model/schema/common.schema.yaml#/components/schemas/StartDate' + endDate: + $ref: '../../model/schema/common.schema.yaml#/components/schemas/EndDate' + userIds: + $ref: '../../model/schema/common.schema.yaml#/components/schemas/UserIds' + types: + $ref: '../../model/schema/common.schema.yaml#/components/schemas/Types' + withOutputs: + $ref: '../../model/schema/common.schema.yaml#/components/schemas/WithOutputs' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/list_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/list/list.ts similarity index 94% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/list_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/list/list.ts index 720764d9cd03c..81cd32a045a7d 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/list_route.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/list/list.ts @@ -12,9 +12,9 @@ import { RESPONSE_ACTION_API_COMMANDS_NAMES, RESPONSE_ACTION_STATUS, RESPONSE_ACTION_TYPE, -} from '../../../endpoint/service/response_actions/constants'; -import { ENDPOINT_DEFAULT_PAGE_SIZE } from '../../../endpoint/constants'; -import { agentTypesSchema } from './common/base'; +} from '../../../../endpoint/service/response_actions/constants'; +import { ENDPOINT_DEFAULT_PAGE_SIZE } from '../../../../endpoint/constants'; +import { agentTypesSchema } from '../common/base'; const commandsSchema = schema.oneOf( // @ts-expect-error TS2769: No overload matches this call diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/execute/execute.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/execute/execute.gen.ts new file mode 100644 index 0000000000000..29c30de059fc7 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/execute/execute.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Execute Action Schema + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { + SuccessResponse, + BaseActionSchema, + Command, + Timeout, +} from '../../../model/schema/common.gen'; + +export type ExecuteRouteRequestBody = z.infer; +export const ExecuteRouteRequestBody = BaseActionSchema.merge( + z.object({ + parameters: z.object({ + command: Command, + timeout: Timeout.optional(), + }), + }) +); + +export type EndpointExecuteActionRequestBody = z.infer; +export const EndpointExecuteActionRequestBody = ExecuteRouteRequestBody; +export type EndpointExecuteActionRequestBodyInput = z.input< + typeof EndpointExecuteActionRequestBody +>; + +export type EndpointExecuteActionResponse = z.infer; +export const EndpointExecuteActionResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/execute.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/execute/execute.schema.yaml similarity index 54% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/execute.schema.yaml rename to x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/execute/execute.schema.yaml index ea9e7d7d98bf9..beafae76a4ba6 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/execute.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/execute/execute.schema.yaml @@ -7,29 +7,28 @@ paths: post: summary: Execute Action operationId: EndpointExecuteAction - x-codegen-enabled: false - x-labels: - - ess - - serverless + description: Execute a given command on an endpoint + x-codegen-enabled: true + x-labels: [ess, serverless] requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/ExecuteActionRequestBody' + $ref: '#/components/schemas/ExecuteRouteRequestBody' responses: '200': description: OK content: application/json: schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' components: schemas: - ExecuteActionRequestBody: + ExecuteRouteRequestBody: allOf: - - $ref: '../model/schema/common.schema.yaml#/components/schemas/BaseActionSchema' + - $ref: '../../../model/schema/common.schema.yaml#/components/schemas/BaseActionSchema' - type: object required: - parameters @@ -40,6 +39,6 @@ components: type: object properties: command: - $ref: '../model/schema/common.schema.yaml#/components/schemas/Command' + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/Command' timeout: - $ref: '../model/schema/common.schema.yaml#/components/schemas/Timeout' + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/Timeout' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/execute_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/execute/execute.ts similarity index 94% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/execute_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/execute/execute.ts index b8de2fc6874a0..f7f4394d66089 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/execute_route.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/execute/execute.ts @@ -7,7 +7,7 @@ import type { TypeOf } from '@kbn/config-schema'; import { schema } from '@kbn/config-schema'; -import { BaseActionRequestSchema } from './common/base'; +import { BaseActionRequestSchema } from '../../common/base'; export const ExecuteActionRequestSchema = { body: schema.object({ diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/execute/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/execute/index.ts new file mode 100644 index 0000000000000..be6732a4be40d --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/execute/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './execute'; +export * from './execute.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/get_file/get_file.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/get_file/get_file.gen.ts new file mode 100644 index 0000000000000..e5c91527f5431 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/get_file/get_file.gen.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Get File Schema + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { SuccessResponse, BaseActionSchema } from '../../../model/schema/common.gen'; + +export type GetFileRouteRequestBody = z.infer; +export const GetFileRouteRequestBody = BaseActionSchema.merge( + z.object({ + parameters: z.object({ + path: z.string(), + }), + }) +); + +export type EndpointGetFileActionRequestBody = z.infer; +export const EndpointGetFileActionRequestBody = GetFileRouteRequestBody; +export type EndpointGetFileActionRequestBodyInput = z.input< + typeof EndpointGetFileActionRequestBody +>; + +export type EndpointGetFileActionResponse = z.infer; +export const EndpointGetFileActionResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/get_file.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/get_file/get_file.schema.yaml similarity index 64% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/get_file.schema.yaml rename to x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/get_file/get_file.schema.yaml index d79c07556da82..a5211580d7e42 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/get_file.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/get_file/get_file.schema.yaml @@ -1,3 +1,4 @@ + openapi: 3.0.0 info: title: Get File Schema @@ -7,29 +8,28 @@ paths: post: summary: Get File Action operationId: EndpointGetFileAction - x-codegen-enabled: false - x-labels: - - ess - - serverless + description: Get a file from an endpoint + x-codegen-enabled: true + x-labels: [ess, serverless] requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/GetFileActionRequestBody' + $ref: '#/components/schemas/GetFileRouteRequestBody' responses: '200': description: OK content: application/json: schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' components: schemas: - GetFileActionRequestBody: + GetFileRouteRequestBody: allOf: - - $ref: '../model/schema/common.schema.yaml#/components/schemas/BaseActionSchema' + - $ref: '../../../model/schema/common.schema.yaml#/components/schemas/BaseActionSchema' - type: object required: - parameters diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/get_file_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/get_file/get_file.ts similarity index 91% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/get_file_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/get_file/get_file.ts index 4378042f29403..0eb11cf538be5 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/get_file_route.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/get_file/get_file.ts @@ -7,7 +7,7 @@ import type { TypeOf } from '@kbn/config-schema'; import { schema } from '@kbn/config-schema'; -import { BaseActionRequestSchema } from './common/base'; +import { BaseActionRequestSchema } from '../../common/base'; export const EndpointActionGetFileSchema = { body: schema.object({ diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/get_file/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/get_file/index.ts new file mode 100644 index 0000000000000..b061771b5c3c2 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/get_file/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './get_file'; +export * from './get_file.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/isolate_route.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/deprecated_isolate.gen.ts similarity index 91% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/isolate_route.gen.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/deprecated_isolate.gen.ts index 1318e25d827f8..4b179d47bea2a 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/isolate_route.gen.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/deprecated_isolate.gen.ts @@ -16,7 +16,7 @@ import type { z } from 'zod'; -import { BaseActionSchema, SuccessResponse } from '../model/schema/common.gen'; +import { BaseActionSchema, SuccessResponse } from '../../../model/schema/common.gen'; export type EndpointIsolateRedirectRequestBody = z.infer; export const EndpointIsolateRedirectRequestBody = BaseActionSchema; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/isolate_route.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/deprecated_isolate.schema.yaml similarity index 75% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/isolate_route.schema.yaml rename to x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/deprecated_isolate.schema.yaml index 4342dd85c7080..89d97c948c5d9 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/isolate_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/deprecated_isolate.schema.yaml @@ -7,15 +7,15 @@ paths: post: summary: Permanently redirects to a new location operationId: EndpointIsolateRedirect + deprecated: true x-codegen-enabled: true - x-labels: - - ess + x-labels: [ess] requestBody: required: true content: application/json: schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/BaseActionSchema' + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/BaseActionSchema' responses: '308': description: Permanent Redirect @@ -30,4 +30,4 @@ paths: content: application/json: schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/index.ts new file mode 100644 index 0000000000000..b4903efed2144 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './isolate'; +export * from './isolate.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/isolate.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/isolate.gen.ts new file mode 100644 index 0000000000000..d3e9ee2f2588a --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/isolate.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Isolate Schema + * version: 2023-10-31 + */ + +import type { z } from 'zod'; + +import { SuccessResponse, NoParametersRequestSchema } from '../../../model/schema/common.gen'; + +export type IsolateRouteRequestBody = z.infer; +export const IsolateRouteRequestBody = NoParametersRequestSchema; + +export type EndpointIsolateActionRequestBody = z.infer; +export const EndpointIsolateActionRequestBody = IsolateRouteRequestBody; +export type EndpointIsolateActionRequestBodyInput = z.input< + typeof EndpointIsolateActionRequestBody +>; + +export type EndpointIsolateActionResponse = z.infer; +export const EndpointIsolateActionResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/isolate.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/isolate.schema.yaml new file mode 100644 index 0000000000000..f721c69efa570 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/isolate.schema.yaml @@ -0,0 +1,30 @@ +openapi: 3.0.0 +info: + title: Isolate Schema + version: '2023-10-31' +paths: + /api/endpoint/action/isolate: + post: + summary: Isolate Action + operationId: EndpointIsolateAction + description: Isolate an endpoint + x-codegen-enabled: true + x-labels: [ess, serverless] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/IsolateRouteRequestBody' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + +components: + schemas: + IsolateRouteRequestBody: + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/NoParametersRequestSchema' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/isolate_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/isolate.ts similarity index 87% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/isolate_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/isolate.ts index 0df0d8d913457..787b2cb4362ec 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/isolate_route.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/isolate.ts @@ -6,7 +6,7 @@ */ import type { TypeOf } from '@kbn/config-schema'; -import { NoParametersRequestSchema } from './common/base'; +import { NoParametersRequestSchema } from '../../common/base'; export const IsolateRouteRequestSchema = NoParametersRequestSchema; export type IsolationRouteRequestBody = TypeOf; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/kill_process/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/kill_process/index.ts new file mode 100644 index 0000000000000..cd316877f542d --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/kill_process/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +export * from './kill_process'; +export * from './kill_process.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/kill_process/kill_process.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/kill_process/kill_process.gen.ts new file mode 100644 index 0000000000000..dc24dacf3e120 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/kill_process/kill_process.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Kill Process Schema + * version: 2023-10-31 + */ + +import type { z } from 'zod'; + +import { KillOrSuspendActionSchema, SuccessResponse } from '../../../model/schema/common.gen'; + +export type EndpointKillProcessActionRequestBody = z.infer< + typeof EndpointKillProcessActionRequestBody +>; +export const EndpointKillProcessActionRequestBody = KillOrSuspendActionSchema; +export type EndpointKillProcessActionRequestBodyInput = z.input< + typeof EndpointKillProcessActionRequestBody +>; + +export type EndpointKillProcessActionResponse = z.infer; +export const EndpointKillProcessActionResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/kill_process/kill_process.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/kill_process/kill_process.schema.yaml new file mode 100644 index 0000000000000..0014026664fe2 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/kill_process/kill_process.schema.yaml @@ -0,0 +1,25 @@ +openapi: 3.0.0 +info: + title: Kill Process Schema + version: '2023-10-31' +paths: + /api/endpoint/action/kill_process: + post: + summary: Kill process Action + operationId: EndpointKillProcessAction + description: Kill a running process on an endpoint + x-codegen-enabled: true + x-labels: [ess, serverless] + requestBody: + required: true + content: + application/json: + schema: + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/KillOrSuspendActionSchema' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/kill_process_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/kill_process/kill_process.ts similarity index 88% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/kill_process_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/kill_process/kill_process.ts index f3c0d4e8f12be..0d42cb8badda1 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/kill_process_route.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/kill_process/kill_process.ts @@ -5,8 +5,9 @@ * 2.0. */ +import type { TypeOf } from '@kbn/config-schema'; import { schema } from '@kbn/config-schema'; -import { BaseActionRequestSchema } from './common/base'; +import { BaseActionRequestSchema } from '../../common/base'; // -------------------------------------------------- // Tests for this module are at: @@ -41,3 +42,5 @@ export const KillProcessRouteRequestSchema = { } ), }; + +export type KillProcessRequestBody = TypeOf; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/running_procs/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/running_procs/index.ts new file mode 100644 index 0000000000000..d44f9455c9807 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/running_procs/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './running_procs'; +export * from './running_procs.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/running_procs/running_procs.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/running_procs/running_procs.gen.ts new file mode 100644 index 0000000000000..8050f78c1f72c --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/running_procs/running_procs.gen.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Get Running Processes Schema + * version: 2023-10-31 + */ + +import type { z } from 'zod'; + +import { SuccessResponse, NoParametersRequestSchema } from '../../../model/schema/common.gen'; + +export type GetProcessesRouteRequestBody = z.infer; +export const GetProcessesRouteRequestBody = NoParametersRequestSchema; + +export type EndpointGetProcessesActionRequestBody = z.infer< + typeof EndpointGetProcessesActionRequestBody +>; +export const EndpointGetProcessesActionRequestBody = GetProcessesRouteRequestBody; +export type EndpointGetProcessesActionRequestBodyInput = z.input< + typeof EndpointGetProcessesActionRequestBody +>; + +export type EndpointGetProcessesActionResponse = z.infer; +export const EndpointGetProcessesActionResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/running_procs/running_procs.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/running_procs/running_procs.schema.yaml new file mode 100644 index 0000000000000..0d5ced3b205f4 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/running_procs/running_procs.schema.yaml @@ -0,0 +1,31 @@ +openapi: 3.0.0 +info: + title: Get Running Processes Schema + version: '2023-10-31' +paths: + /api/endpoint/action/running_procs: + post: + summary: Get Running Processes Action + operationId: EndpointGetProcessesAction + description: Get list of running processes on an endpoint + x-codegen-enabled: true + x-labels: [ess, serverless] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/GetProcessesRouteRequestBody' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + +components: + schemas: + GetProcessesRouteRequestBody: + allOf: + - $ref: '../../../model/schema/common.schema.yaml#/components/schemas/NoParametersRequestSchema' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/get_processes_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/running_procs/running_procs.ts similarity index 88% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/get_processes_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/running_procs/running_procs.ts index a9e56e52ba292..e9caec65e2aa3 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/get_processes_route.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/running_procs/running_procs.ts @@ -6,7 +6,7 @@ */ import type { TypeOf } from '@kbn/config-schema'; -import { NoParametersRequestSchema } from './common/base'; +import { NoParametersRequestSchema } from '../../common/base'; export const GetProcessesRouteRequestSchema = NoParametersRequestSchema; export type GetProcessesRequestBody = TypeOf; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/scan/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/scan/index.ts new file mode 100644 index 0000000000000..b7170f14f6d48 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/scan/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './scan'; +export * from './scan.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/scan/scan.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/scan/scan.gen.ts new file mode 100644 index 0000000000000..302f5de95eaa0 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/scan/scan.gen.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Scan Schema + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { SuccessResponse, BaseActionSchema } from '../../../model/schema/common.gen'; + +export type ScanRouteRequestBody = z.infer; +export const ScanRouteRequestBody = BaseActionSchema.merge( + z.object({ + parameters: z.object({ + path: z.string(), + }), + }) +); + +export type EndpointScanActionRequestBody = z.infer; +export const EndpointScanActionRequestBody = ScanRouteRequestBody; +export type EndpointScanActionRequestBodyInput = z.input; + +export type EndpointScanActionResponse = z.infer; +export const EndpointScanActionResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/scan.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/scan/scan.schema.yaml similarity index 64% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/scan.schema.yaml rename to x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/scan/scan.schema.yaml index ee7e9c1a9a4a7..beea986e4546c 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/scan.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/scan/scan.schema.yaml @@ -7,29 +7,28 @@ paths: post: summary: Scan Action operationId: EndpointScanAction - x-codegen-enabled: false - x-labels: - - ess - - serverless + description: Scan a file or directory + x-codegen-enabled: true + x-labels: [ess, serverless] requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/ScanActionRequestBody' + $ref: '#/components/schemas/ScanRouteRequestBody' responses: '200': description: OK content: application/json: schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' components: schemas: - ScanActionRequestBody: + ScanRouteRequestBody: allOf: - - $ref: '../model/schema/common.schema.yaml#/components/schemas/BaseActionSchema' + - $ref: '../../../model/schema/common.schema.yaml#/components/schemas/BaseActionSchema' - type: object required: - parameters diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/scan_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/scan/scan.ts similarity index 92% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/scan_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/scan/scan.ts index 1c9b3c6980d90..3cbc04fdf4555 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/scan_route.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/scan/scan.ts @@ -7,7 +7,7 @@ import type { TypeOf } from '@kbn/config-schema'; import { schema } from '@kbn/config-schema'; -import { BaseActionRequestSchema } from './common/base'; +import { BaseActionRequestSchema } from '../../common/base'; export const ScanActionRequestSchema = { body: schema.object({ diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/suspend_process/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/suspend_process/index.ts new file mode 100644 index 0000000000000..ff4d08a07f23f --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/suspend_process/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './suspend_process'; +export * from './suspend_process.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/suspend_process/suspend_process.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/suspend_process/suspend_process.gen.ts new file mode 100644 index 0000000000000..6c5fb52525628 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/suspend_process/suspend_process.gen.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Suspend Process Schema + * version: 2023-10-31 + */ + +import type { z } from 'zod'; + +import { KillOrSuspendActionSchema, SuccessResponse } from '../../../model/schema/common.gen'; + +export type EndpointSuspendProcessActionRequestBody = z.infer< + typeof EndpointSuspendProcessActionRequestBody +>; +export const EndpointSuspendProcessActionRequestBody = KillOrSuspendActionSchema; +export type EndpointSuspendProcessActionRequestBodyInput = z.input< + typeof EndpointSuspendProcessActionRequestBody +>; + +export type EndpointSuspendProcessActionResponse = z.infer< + typeof EndpointSuspendProcessActionResponse +>; +export const EndpointSuspendProcessActionResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/suspend_process/suspend_process.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/suspend_process/suspend_process.schema.yaml new file mode 100644 index 0000000000000..f5f5b1e46ed2d --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/suspend_process/suspend_process.schema.yaml @@ -0,0 +1,25 @@ +openapi: 3.0.0 +info: + title: Suspend Process Schema + version: '2023-10-31' +paths: + /api/endpoint/action/suspend_process: + post: + summary: Suspend process Action + operationId: EndpointSuspendProcessAction + description: Suspend a running process on an endpoint + x-codegen-enabled: true + x-labels: [ess, serverless] + requestBody: + required: true + content: + application/json: + schema: + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/KillOrSuspendActionSchema' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/suspend_process_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/suspend_process/suspend_process.ts similarity index 73% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/suspend_process_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/suspend_process/suspend_process.ts index 81edb01197c69..d7cbbdc2b21e6 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/suspend_process_route.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/suspend_process/suspend_process.ts @@ -5,8 +5,9 @@ * 2.0. */ +import type { TypeOf } from '@kbn/config-schema'; import { schema } from '@kbn/config-schema'; -import { BaseActionRequestSchema } from './common/base'; +import { BaseActionRequestSchema } from '../../common/base'; export const SuspendProcessRouteRequestSchema = { body: schema.object({ @@ -17,3 +18,5 @@ export const SuspendProcessRouteRequestSchema = { ]), }), }; + +export type SuspendProcessRequestBody = TypeOf; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/unisolate_route.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/deprecated_unisolate.gen.ts similarity index 91% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/unisolate_route.gen.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/deprecated_unisolate.gen.ts index d4c68a439c172..42ed374c24fce 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/unisolate_route.gen.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/deprecated_unisolate.gen.ts @@ -16,7 +16,7 @@ import type { z } from 'zod'; -import { BaseActionSchema, SuccessResponse } from '../model/schema/common.gen'; +import { BaseActionSchema, SuccessResponse } from '../../../model/schema/common.gen'; export type EndpointUnisolateRedirectRequestBody = z.infer< typeof EndpointUnisolateRedirectRequestBody diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/unisolate_route.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/deprecated_unisolate.schema.yaml similarity index 76% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/unisolate_route.schema.yaml rename to x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/deprecated_unisolate.schema.yaml index 570854a054799..1d347f90fed44 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/unisolate_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/deprecated_unisolate.schema.yaml @@ -7,15 +7,15 @@ paths: post: summary: Permanently redirects to a new location operationId: EndpointUnisolateRedirect + deprecated: true x-codegen-enabled: true - x-labels: - - ess + x-labels: [ess] requestBody: required: true content: application/json: schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/BaseActionSchema' + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/BaseActionSchema' responses: '308': description: Permanent Redirect @@ -30,4 +30,4 @@ paths: content: application/json: schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/index.ts new file mode 100644 index 0000000000000..46e542a8d1ef1 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/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 './unisolate'; +export * from './unisolate.gen'; +export * from './deprecated_unisolate.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/unisolate.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/unisolate.gen.ts new file mode 100644 index 0000000000000..bf0fd2da1a605 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/unisolate.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Unisolate Schema + * version: 2023-10-31 + */ + +import type { z } from 'zod'; + +import { SuccessResponse, NoParametersRequestSchema } from '../../../model/schema/common.gen'; + +export type UnisolateRouteRequestBody = z.infer; +export const UnisolateRouteRequestBody = NoParametersRequestSchema; + +export type EndpointUnisolateActionRequestBody = z.infer; +export const EndpointUnisolateActionRequestBody = UnisolateRouteRequestBody; +export type EndpointUnisolateActionRequestBodyInput = z.input< + typeof EndpointUnisolateActionRequestBody +>; + +export type EndpointUnisolateActionResponse = z.infer; +export const EndpointUnisolateActionResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/unisolate.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/unisolate.schema.yaml new file mode 100644 index 0000000000000..6c12a21f3241a --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/unisolate.schema.yaml @@ -0,0 +1,30 @@ +openapi: 3.0.0 +info: + title: Unisolate Schema + version: '2023-10-31' +paths: + /api/endpoint/action/unisolate: + post: + summary: Unisolate Action + operationId: EndpointUnisolateAction + description: Release an endpoint + x-codegen-enabled: true + x-labels: [ess, serverless] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UnisolateRouteRequestBody' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + +components: + schemas: + UnisolateRouteRequestBody: + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/NoParametersRequestSchema' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/unisolate.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/unisolate.ts new file mode 100644 index 0000000000000..84d63886bb8fa --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/unisolate.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { TypeOf } from '@kbn/config-schema'; +import { NoParametersRequestSchema } from '../../common/base'; + +export const UnisolateRouteRequestSchema = NoParametersRequestSchema; +export type UnisolationRouteRequestBody = TypeOf; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/upload/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/upload/index.ts new file mode 100644 index 0000000000000..34071a503e98f --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/upload/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './upload'; +export * from './upload.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/upload/upload.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/upload/upload.gen.ts new file mode 100644 index 0000000000000..81a908d0b8728 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/upload/upload.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: File Upload Schema + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { SuccessResponse, BaseActionSchema } from '../../../model/schema/common.gen'; + +export type UploadRouteRequestBody = z.infer; +export const UploadRouteRequestBody = BaseActionSchema.merge( + z.object({ + parameters: z.object({ + overwrite: z.boolean().optional().default(false), + }), + file: z.string(), + }) +); + +export type EndpointUploadActionRequestBody = z.infer; +export const EndpointUploadActionRequestBody = UploadRouteRequestBody; +export type EndpointUploadActionRequestBodyInput = z.input; + +export type EndpointUploadActionResponse = z.infer; +export const EndpointUploadActionResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_upload.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/upload/upload.schema.yaml similarity index 68% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/file_upload.schema.yaml rename to x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/upload/upload.schema.yaml index fa9f0da1b1203..ff62065ae5403 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_upload.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/upload/upload.schema.yaml @@ -7,29 +7,28 @@ paths: post: summary: Upload Action operationId: EndpointUploadAction - x-codegen-enabled: false - x-labels: - - ess - - serverless + description: Upload a file to an endpoint + x-codegen-enabled: true + x-labels: [ess, serverless] requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/FileUploadActionRequestBody' + $ref: '#/components/schemas/UploadRouteRequestBody' responses: '200': description: OK content: application/json: schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' components: schemas: - FileUploadActionRequestBody: + UploadRouteRequestBody: allOf: - - $ref: '../model/schema/common.schema.yaml#/components/schemas/BaseActionSchema' + - $ref: '../../../model/schema/common.schema.yaml#/components/schemas/BaseActionSchema' - type: object required: - parameters diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_upload_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/upload/upload.ts similarity index 94% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/file_upload_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/upload/upload.ts index b5850a63ca8e0..5bebdfa58c0b0 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_upload_route.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/upload/upload.ts @@ -7,7 +7,7 @@ import type { TypeOf } from '@kbn/config-schema'; import { schema } from '@kbn/config-schema'; -import { BaseActionRequestSchema } from './common/base'; +import { BaseActionRequestSchema } from '../../common/base'; export const UploadActionRequestSchema = { body: schema.object({ diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/scan.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/scan.gen.ts deleted file mode 100644 index 0aa58c33b8ebf..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/scan.gen.ts +++ /dev/null @@ -1,28 +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. - */ - -/* - * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. - * - * info: - * title: Scan Schema - * version: 2023-10-31 - */ - -import { z } from 'zod'; - -import { BaseActionSchema } from '../model/schema/common.gen'; - -export type ScanActionRequestBody = z.infer; -export const ScanActionRequestBody = BaseActionSchema.merge( - z.object({ - parameters: z.object({ - path: z.string(), - }), - }) -); diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/state/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/state/index.ts new file mode 100644 index 0000000000000..38db865a0e2c7 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/state/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './state.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/details.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/state/state.gen.ts similarity index 59% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/details.gen.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/state/state.gen.ts index dcceb64d44a6b..758835f484034 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/details.gen.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/state/state.gen.ts @@ -10,13 +10,13 @@ * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. * * info: - * title: Details Schema + * title: Endpoint Action State Schema * version: 2023-10-31 */ -import { z } from 'zod'; +import type { z } from 'zod'; -export type DetailsRequestParams = z.infer; -export const DetailsRequestParams = z.object({ - action_id: z.string().optional(), -}); +import { SuccessResponse } from '../../model/schema/common.gen'; + +export type EndpointGetActionsStateResponse = z.infer; +export const EndpointGetActionsStateResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/state/state.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/state/state.schema.yaml new file mode 100644 index 0000000000000..a8d9187107cbe --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/state/state.schema.yaml @@ -0,0 +1,19 @@ +openapi: 3.0.0 +info: + title: Endpoint Action State Schema + version: '2023-10-31' +paths: + /api/endpoint/action/state: + get: + summary: Get Action State schema + operationId: EndpointGetActionsState + x-codegen-enabled: true + x-labels: [ess, serverless] + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/status/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/status/index.ts new file mode 100644 index 0000000000000..bcbee2a39039b --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/status/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './status'; +export * from './status.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/status/status.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/status/status.gen.ts new file mode 100644 index 0000000000000..69918d650e4a0 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/status/status.gen.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Get Action status schema + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { AgentIds, SuccessResponse } from '../../model/schema/common.gen'; + +export type EndpointGetActionsStatusRequestQuery = z.infer< + typeof EndpointGetActionsStatusRequestQuery +>; +export const EndpointGetActionsStatusRequestQuery = z.object({ + query: z.object({ + agent_ids: AgentIds.optional(), + }), +}); +export type EndpointGetActionsStatusRequestQueryInput = z.input< + typeof EndpointGetActionsStatusRequestQuery +>; + +export type EndpointGetActionsStatusResponse = z.infer; +export const EndpointGetActionsStatusResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/actions_status.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/status/status.schema.yaml similarity index 64% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/actions_status.schema.yaml rename to x-pack/plugins/security_solution/common/api/endpoint/actions/status/status.schema.yaml index 92438f6d1a9fc..ec8e3d386bcd2 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/actions_status.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/status/status.schema.yaml @@ -7,10 +7,9 @@ paths: get: summary: Get Actions status schema operationId: EndpointGetActionsStatus - x-codegen-enabled: false - x-labels: - - ess - - serverless + description: Get action status + x-codegen-enabled: true + x-labels: [ess, serverless] parameters: - name: query in: query @@ -19,12 +18,12 @@ paths: type: object properties: agent_ids: - $ref: '../model/schema/common.schema.yaml#/components/schemas/AgentIds' + $ref: '../../model/schema/common.schema.yaml#/components/schemas/AgentIds' responses: '200': description: OK content: application/json: schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + $ref: '../../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/action_status_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/status/status.ts similarity index 100% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/action_status_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/status/status.ts diff --git a/x-pack/plugins/security_solution/common/api/endpoint/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/index.ts index 6dfbcd20d12fb..5917101be93a1 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/index.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/index.ts @@ -5,28 +5,28 @@ * 2.0. */ -export * from './actions/audit_log_route'; -export * from './actions/action_status_route'; -export * from './actions/details_route'; -export * from './actions/file_download_route'; -export * from './actions/file_info_route'; -export * from './actions/file_upload_route'; -export * from './actions/list_route'; -export * from './actions/isolate_route'; -export * from './actions/unisolate_route'; -export * from './actions/kill_process_route'; -export * from './actions/suspend_process_route'; -export * from './actions/get_processes_route'; -export * from './actions/get_file_route'; -export * from './actions/execute_route'; -export * from './actions/scan_route'; export * from './actions/common/base'; export * from './actions/common/response_actions'; -export * from './metadata/list_metadata_route'; -export * from './metadata/get_metadata_route'; +export * from './actions/action_log'; +export * from './actions/status'; +export * from './actions/details'; +export * from './actions/file_download'; +export * from './actions/file_info'; +export * from './actions/list'; -export * from './policy/get_policy_response_route'; -export * from './policy/get_agent_policy_summary_route'; +export * from './actions/response_actions/isolate'; +export * from './actions/response_actions/unisolate'; +export * from './actions/response_actions/kill_process'; +export * from './actions/response_actions/suspend_process'; +export * from './actions/response_actions/running_procs'; +export * from './actions/response_actions/get_file'; +export * from './actions/response_actions/execute'; +export * from './actions/response_actions/upload'; +export * from './actions/response_actions/scan'; -export * from './suggestions/get_suggestions_route'; +export * from './metadata'; + +export * from './policy'; + +export * from './suggestions'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/metadata/get_metadata.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/metadata/get_metadata.gen.ts new file mode 100644 index 0000000000000..a520b56c07195 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/metadata/get_metadata.gen.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Endpoint Metadata Schema + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { ListRequestQuery } from './list_metadata.gen'; +import { SuccessResponse } from '../model/schema/common.gen'; + +export type GetEndpointMetadataListRequestQuery = z.infer< + typeof GetEndpointMetadataListRequestQuery +>; +export const GetEndpointMetadataListRequestQuery = z.object({ + query: ListRequestQuery, +}); +export type GetEndpointMetadataListRequestQueryInput = z.input< + typeof GetEndpointMetadataListRequestQuery +>; + +export type GetEndpointMetadataListResponse = z.infer; +export const GetEndpointMetadataListResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/metadata/metadata.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/metadata/get_metadata.schema.yaml similarity index 82% rename from x-pack/plugins/security_solution/common/api/endpoint/metadata/metadata.schema.yaml rename to x-pack/plugins/security_solution/common/api/endpoint/metadata/get_metadata.schema.yaml index a8af6101472d3..5ecea044053d2 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/metadata/metadata.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/endpoint/metadata/get_metadata.schema.yaml @@ -7,10 +7,8 @@ paths: get: summary: Get Metadata List schema operationId: GetEndpointMetadataList - x-codegen-enabled: false - x-labels: - - ess - - serverless + x-codegen-enabled: true + x-labels: [ess, serverless] parameters: - name: query in: query @@ -30,9 +28,7 @@ paths: summary: Get Metadata Transform schema operationId: GetEndpointMetadataTransform x-codegen-enabled: false - x-labels: - - ess - - serverless + x-labels: [ess, serverless] responses: '200': description: OK @@ -46,18 +42,13 @@ paths: summary: Get Metadata schema operationId: GetEndpointMetadata x-codegen-enabled: false - x-labels: - - ess - - serverless + x-labels: [ess, serverless] parameters: - - name: query + - name: id in: path required: true schema: - type: object - properties: - id: - type: string + type: string responses: '200': description: OK diff --git a/x-pack/plugins/security_solution/common/api/endpoint/metadata/get_metadata_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/metadata/get_metadata.ts similarity index 100% rename from x-pack/plugins/security_solution/common/api/endpoint/metadata/get_metadata_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/metadata/get_metadata.ts diff --git a/x-pack/plugins/security_solution/common/api/endpoint/metadata/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/metadata/index.ts new file mode 100644 index 0000000000000..d95135ef8aa90 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/metadata/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 './get_metadata'; +export * from './get_metadata.gen'; + +export * from './list_metadata'; +export * from './list_metadata.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/metadata/list_metadata_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/metadata/list_metadata.ts similarity index 100% rename from x-pack/plugins/security_solution/common/api/endpoint/metadata/list_metadata_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/metadata/list_metadata.ts diff --git a/x-pack/plugins/security_solution/common/api/endpoint/model/schema/common.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/model/schema/common.gen.ts index 4e3885391f241..57af5c334f4f0 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/model/schema/common.gen.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/model/schema/common.gen.ts @@ -70,6 +70,7 @@ export const Command = z.enum([ 'get-file', 'execute', 'upload', + 'scan', ]); export type CommandEnum = typeof Command.enum; export const CommandEnum = Command.enum; @@ -98,16 +99,22 @@ export type UserIds = z.infer; export const UserIds = z.union([z.array(z.string().min(1)).min(1), z.string().min(1)]); /** - * With Outputs + * Shows detailed outputs for an action response */ export type WithOutputs = z.infer; export const WithOutputs = z.union([z.array(z.string().min(1)).min(1), z.string().min(1)]); +/** + * Type of response action + */ export type Type = z.infer; export const Type = z.enum(['automated', 'manual']); export type TypeEnum = typeof Type.enum; export const TypeEnum = Type.enum; +/** + * List of types of response actions + */ export type Types = z.infer; export const Types = z.array(Type); @@ -135,17 +142,28 @@ export const Comment = z.string(); export type Parameters = z.infer; export const Parameters = z.object({}); +export type AgentTypes = z.infer; +export const AgentTypes = z.enum(['endpoint', 'sentinel_one', 'crowdstrike']); +export type AgentTypesEnum = typeof AgentTypes.enum; +export const AgentTypesEnum = AgentTypes.enum; + export type BaseActionSchema = z.infer; export const BaseActionSchema = z.object({ - endpoint_ids: EndpointIds.optional(), + endpoint_ids: EndpointIds, alert_ids: AlertIds.optional(), case_ids: CaseIds.optional(), comment: Comment.optional(), parameters: Parameters.optional(), + agent_type: AgentTypes.optional(), +}); + +export type NoParametersRequestSchema = z.infer; +export const NoParametersRequestSchema = z.object({ + body: BaseActionSchema, }); -export type ProcessActionSchemas = z.infer; -export const ProcessActionSchemas = BaseActionSchema.merge( +export type KillOrSuspendActionSchema = z.infer; +export const KillOrSuspendActionSchema = BaseActionSchema.merge( z.object({ parameters: z.union([ z.object({ diff --git a/x-pack/plugins/security_solution/common/api/endpoint/model/schema/common.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/model/schema/common.schema.yaml index 306bd31f4886b..00cf557b98496 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/model/schema/common.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/endpoint/model/schema/common.schema.yaml @@ -54,6 +54,7 @@ components: - get-file - execute - upload + - scan minLength: 1 description: The command to be executed (cannot be an empty string) @@ -101,16 +102,18 @@ components: minItems: 1 - type: string minLength: 1 - description: With Outputs + description: Shows detailed outputs for an action response Type: type: string + description: Type of response action enum: - automated - manual Types: type: array + description: List of types of response actions items: $ref: '#/components/schemas/Type' minLength: 1 @@ -136,6 +139,12 @@ components: Parameters: type: object description: Optional parameters object + AgentTypes: + type: string + enum: + - endpoint + - sentinel_one + - crowdstrike BaseActionSchema: x-inline: true @@ -151,8 +160,20 @@ components: $ref: '#/components/schemas/Comment' parameters: $ref: '#/components/schemas/Parameters' + agent_type: + $ref: '#/components/schemas/AgentTypes' + required: + - endpoint_ids + + NoParametersRequestSchema: + type: object + required: + - body + properties: + body: + $ref: '#/components/schemas/BaseActionSchema' - ProcessActionSchemas: + KillOrSuspendActionSchema: allOf: - $ref: '#/components/schemas/BaseActionSchema' - type: object diff --git a/x-pack/plugins/security_solution/common/api/endpoint/policy/policy.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/policy/deprecated_agent_policy_summary.gen.ts similarity index 66% rename from x-pack/plugins/security_solution/common/api/endpoint/policy/policy.gen.ts rename to x-pack/plugins/security_solution/common/api/endpoint/policy/deprecated_agent_policy_summary.gen.ts index 9cc37e874f6a2..9a20dcfc3b310 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/policy/policy.gen.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/policy/deprecated_agent_policy_summary.gen.ts @@ -16,7 +16,7 @@ import { z } from 'zod'; -import { SuccessResponse, AgentId } from '../model/schema/common.gen'; +import { SuccessResponse } from '../model/schema/common.gen'; export type GetAgentPolicySummaryRequestQuery = z.infer; export const GetAgentPolicySummaryRequestQuery = z.object({ @@ -31,13 +31,3 @@ export type GetAgentPolicySummaryRequestQueryInput = z.input< export type GetAgentPolicySummaryResponse = z.infer; export const GetAgentPolicySummaryResponse = SuccessResponse; -export type GetPolicyResponseRequestQuery = z.infer; -export const GetPolicyResponseRequestQuery = z.object({ - query: z.object({ - agentId: AgentId.optional(), - }), -}); -export type GetPolicyResponseRequestQueryInput = z.input; - -export type GetPolicyResponseResponse = z.infer; -export const GetPolicyResponseResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/policy/policy.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/policy/deprecated_agent_policy_summary.schema.yaml similarity index 50% rename from x-pack/plugins/security_solution/common/api/endpoint/policy/policy.schema.yaml rename to x-pack/plugins/security_solution/common/api/endpoint/policy/deprecated_agent_policy_summary.schema.yaml index 6084ae851bc17..803010a4e8268 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/policy/policy.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/endpoint/policy/deprecated_agent_policy_summary.schema.yaml @@ -7,10 +7,9 @@ paths: get: summary: Get Agent Policy Summary schema operationId: GetAgentPolicySummary + deprecated: true x-codegen-enabled: true - x-labels: - - ess - - serverless + x-labels: [ess, serverless] parameters: - name: query in: query @@ -31,28 +30,3 @@ paths: application/json: schema: $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' - - /api/endpoint/policy_response: - get: - summary: Get Policy Response schema - operationId: GetPolicyResponse - x-codegen-enabled: true - x-labels: - - ess - - serverless - parameters: - - name: query - in: query - required: true - schema: - type: object - properties: - agentId: - $ref: '../model/schema/common.schema.yaml#/components/schemas/AgentId' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/policy/get_agent_policy_summary_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/policy/deprecated_agent_policy_summary.ts similarity index 100% rename from x-pack/plugins/security_solution/common/api/endpoint/policy/get_agent_policy_summary_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/policy/deprecated_agent_policy_summary.ts diff --git a/x-pack/plugins/security_solution/common/api/endpoint/policy/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/policy/index.ts new file mode 100644 index 0000000000000..e27dc83a3b993 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/policy/index.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. + */ + +export * from './policy_response'; +export * from './deprecated_agent_policy_summary'; +export * from './policy_response.gen'; +export * from './deprecated_agent_policy_summary.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/policy/policy_response.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/policy/policy_response.gen.ts new file mode 100644 index 0000000000000..e2eb2595be662 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/policy/policy_response.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Endpoint Policy Schema + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { AgentId, SuccessResponse } from '../model/schema/common.gen'; + +export type GetPolicyResponseRequestQuery = z.infer; +export const GetPolicyResponseRequestQuery = z.object({ + query: z.object({ + agentId: AgentId.optional(), + }), +}); +export type GetPolicyResponseRequestQueryInput = z.input; + +export type GetPolicyResponseResponse = z.infer; +export const GetPolicyResponseResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/policy/policy_response.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/policy/policy_response.schema.yaml new file mode 100644 index 0000000000000..7acc39013da85 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/policy/policy_response.schema.yaml @@ -0,0 +1,27 @@ +openapi: 3.0.0 +info: + title: Endpoint Policy Schema + version: '2023-10-31' +paths: + /api/endpoint/policy_response: + get: + summary: Get Policy Response schema + operationId: GetPolicyResponse + x-codegen-enabled: true + x-labels: [ess, serverless] + parameters: + - name: query + in: query + required: true + schema: + type: object + properties: + agentId: + $ref: '../model/schema/common.schema.yaml#/components/schemas/AgentId' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/policy/get_policy_response_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/policy/policy_response.ts similarity index 100% rename from x-pack/plugins/security_solution/common/api/endpoint/policy/get_policy_response_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/policy/policy_response.ts diff --git a/x-pack/plugins/security_solution/common/api/endpoint/protection_updates_note/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/protection_updates_note/index.ts new file mode 100644 index 0000000000000..c49eecd853e2c --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/protection_updates_note/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './protection_updates_note'; +export * from './protection_updates_note.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/protection_updates_note/protection_updates_note.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/protection_updates_note/protection_updates_note.schema.yaml index ce2760780b627..44c02e417b185 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/protection_updates_note/protection_updates_note.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/endpoint/protection_updates_note/protection_updates_note.schema.yaml @@ -8,9 +8,7 @@ paths: summary: Get Protection Updates Note schema operationId: GetProtectionUpdatesNote x-codegen-enabled: true - x-labels: - - ess - - serverless + x-labels: [ess, serverless] parameters: - name: package_policy_id in: path @@ -28,9 +26,7 @@ paths: summary: Create Update Protection Updates Note schema operationId: CreateUpdateProtectionUpdatesNote x-codegen-enabled: true - x-labels: - - ess - - serverless + x-labels: [ess, serverless] requestBody: required: true content: diff --git a/x-pack/plugins/security_solution/common/api/endpoint/protection_updates_note/protection_updates_note_schema.ts b/x-pack/plugins/security_solution/common/api/endpoint/protection_updates_note/protection_updates_note.ts similarity index 100% rename from x-pack/plugins/security_solution/common/api/endpoint/protection_updates_note/protection_updates_note_schema.ts rename to x-pack/plugins/security_solution/common/api/endpoint/protection_updates_note/protection_updates_note.ts diff --git a/x-pack/plugins/security_solution/common/api/endpoint/suggestions/get_suggestions.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/suggestions/get_suggestions.schema.yaml index b7e1b0f34780e..573f9c0e3992f 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/suggestions/get_suggestions.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/endpoint/suggestions/get_suggestions.schema.yaml @@ -8,9 +8,7 @@ paths: summary: Get suggestions operationId: GetEndpointSuggestions x-codegen-enabled: true - x-labels: - - ess - - serverless + x-labels: [ess, serverless] requestBody: required: true content: diff --git a/x-pack/plugins/security_solution/common/api/endpoint/suggestions/get_suggestions_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/suggestions/get_suggestions.ts similarity index 100% rename from x-pack/plugins/security_solution/common/api/endpoint/suggestions/get_suggestions_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/suggestions/get_suggestions.ts diff --git a/x-pack/plugins/security_solution/common/api/endpoint/suggestions/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/suggestions/index.ts new file mode 100644 index 0000000000000..a4b3e85842ae9 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/suggestions/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './get_suggestions'; +export * from './get_suggestions.gen'; diff --git a/x-pack/plugins/security_solution/common/endpoint/schema/actions.test.ts b/x-pack/plugins/security_solution/common/endpoint/schema/actions.test.ts index 4ea9f59ea179d..c7ce775bb2129 100644 --- a/x-pack/plugins/security_solution/common/endpoint/schema/actions.test.ts +++ b/x-pack/plugins/security_solution/common/endpoint/schema/actions.test.ts @@ -19,10 +19,10 @@ import { KillProcessRouteRequestSchema, SuspendProcessRouteRequestSchema, UploadActionRequestSchema, + ExecuteActionRequestSchema, + ScanActionRequestSchema, + NoParametersRequestSchema, } from '../../api/endpoint'; -import { NoParametersRequestSchema } from '../../api/endpoint/actions/common/base'; -import { ExecuteActionRequestSchema } from '../../api/endpoint/actions/execute_route'; -import { ScanActionRequestSchema } from '../../api/endpoint/actions/scan_route'; // NOTE: Even though schemas are kept in common/api/endpoint - we keep tests here, because common/api should import from outside describe('actions schemas', () => { diff --git a/x-pack/plugins/security_solution/common/endpoint/types/actions.ts b/x-pack/plugins/security_solution/common/endpoint/types/actions.ts index 47556a966280a..061182d5075ac 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types/actions.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types/actions.ts @@ -9,13 +9,12 @@ import type { TypeOf } from '@kbn/config-schema'; import type { EcsError } from '@elastic/ecs'; import type { BaseFileMetadata, FileCompression, FileJSON } from '@kbn/files-plugin/common'; import type { - KillProcessRouteRequestSchema, - ResponseActionBodySchema, - SuspendProcessRouteRequestSchema, UploadActionApiRequestBody, + ActionStatusRequestSchema, + KillProcessRequestBody, + SuspendProcessRequestBody, } from '../../api/endpoint'; -import type { ActionStatusRequestSchema } from '../../api/endpoint/actions/action_status_route'; -import type { NoParametersRequestSchema } from '../../api/endpoint/actions/common/base'; + import type { ResponseActionAgentType, ResponseActionsApiCommandNames, @@ -358,14 +357,6 @@ export interface ActivityLog { data: ActivityLogEntry[]; } -export type HostIsolationRequestBody = TypeOf; - -export type ResponseActionRequestBody = TypeOf; - -export type KillProcessRequestBody = TypeOf; - -export type SuspendProcessRequestBody = TypeOf; - /** Note: this type should almost never be used. Use instead the response action specific types above */ export type KillOrSuspendProcessRequestBody = KillProcessRequestBody & SuspendProcessRequestBody; @@ -373,8 +364,6 @@ export interface HostIsolationResponse { action: string; } -export type ProcessesRequestBody = TypeOf; - export interface ResponseActionApiResponse< TOutputContent extends EndpointActionResponseDataOutput = EndpointActionResponseDataOutput > { diff --git a/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_endpoint_management_api_2023_10_31.bundled.schema.yaml b/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_endpoint_management_api_2023_10_31.bundled.schema.yaml index 93e0b5298c4aa..f291b2db216c0 100644 --- a/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_endpoint_management_api_2023_10_31.bundled.schema.yaml +++ b/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_endpoint_management_api_2023_10_31.bundled.schema.yaml @@ -13,13 +13,14 @@ servers: paths: /api/endpoint/action: get: + description: Get a list of action requests and their responses operationId: EndpointGetActionsList parameters: - in: query name: query required: true schema: - $ref: '#/components/schemas/EndpointActionListRequestQuery' + $ref: '#/components/schemas/GetEndpointActionListRouteQuery' responses: '200': content: @@ -32,18 +33,20 @@ paths: - Security Solution Endpoint Management API '/api/endpoint/action_log/{agent_id}': get: - operationId: EndpointGetActionAuditLog + deprecated: true + description: Get action requests log + operationId: EndpointGetActionLog parameters: - - in: query - name: query + - in: path + name: agent_id required: true schema: - $ref: '#/components/schemas/AuditLogRequestQuery' - - in: path + $ref: '#/components/schemas/AgentId' + - in: query name: query required: true schema: - $ref: '#/components/schemas/AuditLogRequestParams' + $ref: '#/components/schemas/ActionLogRequestQuery' responses: '200': content: @@ -51,11 +54,12 @@ paths: schema: $ref: '#/components/schemas/SuccessResponse' description: OK - summary: Get action audit log schema + summary: Get action requests log schema tags: - Security Solution Endpoint Management API /api/endpoint/action_status: get: + description: Get action status operationId: EndpointGetActionsStatus parameters: - in: query @@ -78,13 +82,14 @@ paths: - Security Solution Endpoint Management API '/api/endpoint/action/{action_id}': get: + description: Get action details operationId: EndpointGetActionsDetails parameters: - in: path - name: query + name: action_id required: true schema: - $ref: '#/components/schemas/DetailsRequestParams' + type: string responses: '200': content: @@ -97,13 +102,19 @@ paths: - Security Solution Endpoint Management API '/api/endpoint/action/{action_id}/file/{file_id}/download`': get: + description: Download a file from an endpoint operationId: EndpointFileDownload parameters: - in: path - name: query + name: action_id required: true schema: - $ref: '#/components/schemas/FileDownloadRequestParams' + type: string + - in: path + name: file_id + required: true + schema: + type: string responses: '200': content: @@ -116,13 +127,19 @@ paths: - Security Solution Endpoint Management API '/api/endpoint/action/{action_id}/file/{file_id}`': get: + description: Get file info operationId: EndpointFileInfo parameters: - in: path - name: query + name: action_id required: true schema: - $ref: '#/components/schemas/FileInfoRequestParams' + type: string + - in: path + name: file_id + required: true + schema: + type: string responses: '200': content: @@ -135,12 +152,13 @@ paths: - Security Solution Endpoint Management API /api/endpoint/action/execute: post: + description: Execute a given command on an endpoint operationId: EndpointExecuteAction requestBody: content: application/json: schema: - $ref: '#/components/schemas/ExecuteActionRequestBody' + $ref: '#/components/schemas/ExecuteRouteRequestBody' required: true responses: '200': @@ -154,12 +172,13 @@ paths: - Security Solution Endpoint Management API /api/endpoint/action/get_file: post: + description: Get a file from an endpoint operationId: EndpointGetFileAction requestBody: content: application/json: schema: - $ref: '#/components/schemas/GetFileActionRequestBody' + $ref: '#/components/schemas/GetFileRouteRequestBody' required: true responses: '200': @@ -173,23 +192,13 @@ paths: - Security Solution Endpoint Management API /api/endpoint/action/isolate: post: - operationId: EndpointIsolateHostAction + description: Isolate an endpoint + operationId: EndpointIsolateAction requestBody: content: application/json: schema: - type: object - properties: - alert_ids: - $ref: '#/components/schemas/AlertIds' - case_ids: - $ref: '#/components/schemas/CaseIds' - comment: - $ref: '#/components/schemas/Comment' - endpoint_ids: - $ref: '#/components/schemas/EndpointIds' - parameters: - $ref: '#/components/schemas/Parameters' + $ref: '#/components/schemas/IsolateRouteRequestBody' required: true responses: '200': @@ -198,17 +207,18 @@ paths: schema: $ref: '#/components/schemas/SuccessResponse' description: OK - summary: Isolate host Action + summary: Isolate Action tags: - Security Solution Endpoint Management API /api/endpoint/action/kill_process: post: + description: Kill a running process on an endpoint operationId: EndpointKillProcessAction requestBody: content: application/json: schema: - $ref: '#/components/schemas/ProcessActionSchemas' + $ref: '#/components/schemas/KillOrSuspendActionSchema' required: true responses: '200': @@ -222,23 +232,13 @@ paths: - Security Solution Endpoint Management API /api/endpoint/action/running_procs: post: - operationId: EndpointGetRunningProcessesAction + description: Get list of running processes on an endpoint + operationId: EndpointGetProcessesAction requestBody: content: application/json: schema: - type: object - properties: - alert_ids: - $ref: '#/components/schemas/AlertIds' - case_ids: - $ref: '#/components/schemas/CaseIds' - comment: - $ref: '#/components/schemas/Comment' - endpoint_ids: - $ref: '#/components/schemas/EndpointIds' - parameters: - $ref: '#/components/schemas/Parameters' + $ref: '#/components/schemas/GetProcessesRouteRequestBody' required: true responses: '200': @@ -252,12 +252,13 @@ paths: - Security Solution Endpoint Management API /api/endpoint/action/scan: post: + description: Scan a file or directory operationId: EndpointScanAction requestBody: content: application/json: schema: - $ref: '#/components/schemas/ScanActionRequestBody' + $ref: '#/components/schemas/ScanRouteRequestBody' required: true responses: '200': @@ -284,12 +285,13 @@ paths: - Security Solution Endpoint Management API /api/endpoint/action/suspend_process: post: + description: Suspend a running process on an endpoint operationId: EndpointSuspendProcessAction requestBody: content: application/json: schema: - $ref: '#/components/schemas/ProcessActionSchemas' + $ref: '#/components/schemas/KillOrSuspendActionSchema' required: true responses: '200': @@ -303,23 +305,13 @@ paths: - Security Solution Endpoint Management API /api/endpoint/action/unisolate: post: - operationId: EndpointUnisolateHostAction + description: Release an endpoint + operationId: EndpointUnisolateAction requestBody: content: application/json: schema: - type: object - properties: - alert_ids: - $ref: '#/components/schemas/AlertIds' - case_ids: - $ref: '#/components/schemas/CaseIds' - comment: - $ref: '#/components/schemas/Comment' - endpoint_ids: - $ref: '#/components/schemas/EndpointIds' - parameters: - $ref: '#/components/schemas/Parameters' + $ref: '#/components/schemas/UnisolateRouteRequestBody' required: true responses: '200': @@ -328,17 +320,18 @@ paths: schema: $ref: '#/components/schemas/SuccessResponse' description: OK - summary: Unisolate host Action + summary: Unisolate Action tags: - Security Solution Endpoint Management API /api/endpoint/action/upload: post: + description: Upload a file to an endpoint operationId: EndpointUploadAction requestBody: content: application/json: schema: - $ref: '#/components/schemas/FileUploadActionRequestBody' + $ref: '#/components/schemas/UploadRouteRequestBody' required: true responses: '200': @@ -352,6 +345,7 @@ paths: - Security Solution Endpoint Management API /api/endpoint/isolate: post: + deprecated: true operationId: EndpointIsolateRedirect requestBody: content: @@ -359,6 +353,8 @@ paths: schema: type: object properties: + agent_type: + $ref: '#/components/schemas/AgentTypes' alert_ids: $ref: '#/components/schemas/AlertIds' case_ids: @@ -369,6 +365,8 @@ paths: $ref: '#/components/schemas/EndpointIds' parameters: $ref: '#/components/schemas/Parameters' + required: + - endpoint_ids required: true responses: '200': @@ -412,13 +410,10 @@ paths: operationId: GetEndpointMetadata parameters: - in: path - name: query + name: id required: true schema: - type: object - properties: - id: - type: string + type: string responses: '200': content: @@ -466,6 +461,7 @@ paths: - Security Solution Endpoint Management API /api/endpoint/policy/summaries: get: + deprecated: true operationId: GetAgentPolicySummary parameters: - in: query @@ -573,6 +569,7 @@ paths: - Security Solution Endpoint Management API /api/endpoint/unisolate: post: + deprecated: true operationId: EndpointUnisolateRedirect requestBody: content: @@ -580,6 +577,8 @@ paths: schema: type: object properties: + agent_type: + $ref: '#/components/schemas/AgentTypes' alert_ids: $ref: '#/components/schemas/AlertIds' case_ids: @@ -590,6 +589,8 @@ paths: $ref: '#/components/schemas/EndpointIds' parameters: $ref: '#/components/schemas/Parameters' + required: + - endpoint_ids required: true responses: '200': @@ -611,6 +612,17 @@ paths: - Security Solution Endpoint Management API components: schemas: + ActionLogRequestQuery: + type: object + properties: + end_date: + $ref: '#/components/schemas/EndDate' + page: + $ref: '#/components/schemas/Page' + page_size: + $ref: '#/components/schemas/PageSize' + start_date: + $ref: '#/components/schemas/StartDate' AgentId: description: Agent ID type: string @@ -625,28 +637,18 @@ components: type: array - minLength: 1 type: string + AgentTypes: + enum: + - endpoint + - sentinel_one + - crowdstrike + type: string AlertIds: description: A list of alerts ids. items: $ref: '#/components/schemas/NonEmptyString' minItems: 1 type: array - AuditLogRequestParams: - type: object - properties: - agent_id: - $ref: '#/components/schemas/AgentId' - AuditLogRequestQuery: - type: object - properties: - end_date: - $ref: '#/components/schemas/EndDate' - page: - $ref: '#/components/schemas/Page' - page_size: - $ref: '#/components/schemas/PageSize' - start_date: - $ref: '#/components/schemas/StartDate' CaseIds: description: Case IDs to be updated (cannot contain empty strings) items: @@ -665,6 +667,7 @@ components: - get-file - execute - upload + - scan minLength: 1 type: string Commands: @@ -674,39 +677,9 @@ components: Comment: description: Optional comment type: string - DetailsRequestParams: - type: object - properties: - action_id: - type: string EndDate: description: End date type: string - EndpointActionListRequestQuery: - type: object - properties: - agentIds: - $ref: '#/components/schemas/AgentIds' - commands: - $ref: '#/components/schemas/Commands' - endDate: - $ref: '#/components/schemas/EndDate' - page: - $ref: '#/components/schemas/Page' - pageSize: - default: 10 - description: Number of items per page - maximum: 10000 - minimum: 1 - type: integer - startDate: - $ref: '#/components/schemas/StartDate' - types: - $ref: '#/components/schemas/Types' - userIds: - $ref: '#/components/schemas/UserIds' - withOutputs: - $ref: '#/components/schemas/WithOutputs' EndpointIds: description: List of endpoint IDs (cannot contain empty strings) items: @@ -714,10 +687,12 @@ components: type: string minItems: 1 type: array - ExecuteActionRequestBody: + ExecuteRouteRequestBody: allOf: - type: object properties: + agent_type: + $ref: '#/components/schemas/AgentTypes' alert_ids: $ref: '#/components/schemas/AlertIds' case_ids: @@ -728,6 +703,8 @@ components: $ref: '#/components/schemas/EndpointIds' parameters: $ref: '#/components/schemas/Parameters' + required: + - endpoint_ids - type: object properties: parameters: @@ -741,30 +718,39 @@ components: - command required: - parameters - FileDownloadRequestParams: + GetEndpointActionListRouteQuery: type: object properties: - action_id: - type: string - file_id: - type: string - required: - - action_id - - file_id - FileInfoRequestParams: - type: object - properties: - action_id: - type: string - file_id: - type: string - required: - - action_id - - file_id - FileUploadActionRequestBody: + agentIds: + $ref: '#/components/schemas/AgentIds' + agentTypes: + $ref: '#/components/schemas/AgentTypes' + commands: + $ref: '#/components/schemas/Commands' + endDate: + $ref: '#/components/schemas/EndDate' + page: + $ref: '#/components/schemas/Page' + pageSize: + default: 10 + description: Number of items per page + maximum: 10000 + minimum: 1 + type: integer + startDate: + $ref: '#/components/schemas/StartDate' + types: + $ref: '#/components/schemas/Types' + userIds: + $ref: '#/components/schemas/UserIds' + withOutputs: + $ref: '#/components/schemas/WithOutputs' + GetFileRouteRequestBody: allOf: - type: object properties: + agent_type: + $ref: '#/components/schemas/AgentTypes' alert_ids: $ref: '#/components/schemas/AlertIds' case_ids: @@ -775,24 +761,29 @@ components: $ref: '#/components/schemas/EndpointIds' parameters: $ref: '#/components/schemas/Parameters' + required: + - endpoint_ids - type: object properties: - file: - format: binary - type: string parameters: type: object properties: - overwrite: - default: false - type: boolean + path: + type: string + required: + - path required: - parameters - - file - GetFileActionRequestBody: + GetProcessesRouteRequestBody: + $ref: '#/components/schemas/NoParametersRequestSchema' + IsolateRouteRequestBody: + $ref: '#/components/schemas/NoParametersRequestSchema' + KillOrSuspendActionSchema: allOf: - type: object properties: + agent_type: + $ref: '#/components/schemas/AgentTypes' alert_ids: $ref: '#/components/schemas/AlertIds' case_ids: @@ -803,15 +794,22 @@ components: $ref: '#/components/schemas/EndpointIds' parameters: $ref: '#/components/schemas/Parameters' + required: + - endpoint_ids - type: object properties: parameters: - type: object - properties: - path: - type: string - required: - - path + oneOf: + - type: object + properties: + pid: + minimum: 1 + type: integer + - type: object + properties: + entity_id: + minLength: 1 + type: string required: - parameters ListRequestQuery: @@ -866,6 +864,28 @@ components: minLength: 1 pattern: ^(?! *$).+$ type: string + NoParametersRequestSchema: + type: object + properties: + body: + type: object + properties: + agent_type: + $ref: '#/components/schemas/AgentTypes' + alert_ids: + $ref: '#/components/schemas/AlertIds' + case_ids: + $ref: '#/components/schemas/CaseIds' + comment: + $ref: '#/components/schemas/Comment' + endpoint_ids: + $ref: '#/components/schemas/EndpointIds' + parameters: + $ref: '#/components/schemas/Parameters' + required: + - endpoint_ids + required: + - body Page: default: 1 description: Page number @@ -880,45 +900,17 @@ components: Parameters: description: Optional parameters object type: object - ProcessActionSchemas: - allOf: - - type: object - properties: - alert_ids: - $ref: '#/components/schemas/AlertIds' - case_ids: - $ref: '#/components/schemas/CaseIds' - comment: - $ref: '#/components/schemas/Comment' - endpoint_ids: - $ref: '#/components/schemas/EndpointIds' - parameters: - $ref: '#/components/schemas/Parameters' - - type: object - properties: - parameters: - oneOf: - - type: object - properties: - pid: - minimum: 1 - type: integer - - type: object - properties: - entity_id: - minLength: 1 - type: string - required: - - parameters ProtectionUpdatesNoteResponse: type: object properties: note: type: string - ScanActionRequestBody: + ScanRouteRequestBody: allOf: - type: object properties: + agent_type: + $ref: '#/components/schemas/AgentTypes' alert_ids: $ref: '#/components/schemas/AlertIds' case_ids: @@ -929,6 +921,8 @@ components: $ref: '#/components/schemas/EndpointIds' parameters: $ref: '#/components/schemas/Parameters' + required: + - endpoint_ids - type: object properties: parameters: @@ -951,16 +945,52 @@ components: minimum: 1 type: integer Type: + description: Type of response action enum: - automated - manual type: string Types: + description: List of types of response actions items: $ref: '#/components/schemas/Type' maxLength: 2 minLength: 1 type: array + UnisolateRouteRequestBody: + $ref: '#/components/schemas/NoParametersRequestSchema' + UploadRouteRequestBody: + allOf: + - type: object + properties: + agent_type: + $ref: '#/components/schemas/AgentTypes' + alert_ids: + $ref: '#/components/schemas/AlertIds' + case_ids: + $ref: '#/components/schemas/CaseIds' + comment: + $ref: '#/components/schemas/Comment' + endpoint_ids: + $ref: '#/components/schemas/EndpointIds' + parameters: + $ref: '#/components/schemas/Parameters' + required: + - endpoint_ids + - type: object + properties: + file: + format: binary + type: string + parameters: + type: object + properties: + overwrite: + default: false + type: boolean + required: + - parameters + - file UserIds: description: User IDs oneOf: @@ -972,7 +1002,7 @@ components: - minLength: 1 type: string WithOutputs: - description: With Outputs + description: Shows detailed outputs for an action response oneOf: - items: minLength: 1 diff --git a/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_endpoint_management_api_2023_10_31.bundled.schema.yaml b/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_endpoint_management_api_2023_10_31.bundled.schema.yaml index 76bf0dede41b7..82af84f6d4253 100644 --- a/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_endpoint_management_api_2023_10_31.bundled.schema.yaml +++ b/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_endpoint_management_api_2023_10_31.bundled.schema.yaml @@ -13,13 +13,14 @@ servers: paths: /api/endpoint/action: get: + description: Get a list of action requests and their responses operationId: EndpointGetActionsList parameters: - in: query name: query required: true schema: - $ref: '#/components/schemas/EndpointActionListRequestQuery' + $ref: '#/components/schemas/GetEndpointActionListRouteQuery' responses: '200': content: @@ -32,18 +33,20 @@ paths: - Security Solution Endpoint Management API '/api/endpoint/action_log/{agent_id}': get: - operationId: EndpointGetActionAuditLog + deprecated: true + description: Get action requests log + operationId: EndpointGetActionLog parameters: - - in: query - name: query + - in: path + name: agent_id required: true schema: - $ref: '#/components/schemas/AuditLogRequestQuery' - - in: path + $ref: '#/components/schemas/AgentId' + - in: query name: query required: true schema: - $ref: '#/components/schemas/AuditLogRequestParams' + $ref: '#/components/schemas/ActionLogRequestQuery' responses: '200': content: @@ -51,11 +54,12 @@ paths: schema: $ref: '#/components/schemas/SuccessResponse' description: OK - summary: Get action audit log schema + summary: Get action requests log schema tags: - Security Solution Endpoint Management API /api/endpoint/action_status: get: + description: Get action status operationId: EndpointGetActionsStatus parameters: - in: query @@ -78,13 +82,14 @@ paths: - Security Solution Endpoint Management API '/api/endpoint/action/{action_id}': get: + description: Get action details operationId: EndpointGetActionsDetails parameters: - in: path - name: query + name: action_id required: true schema: - $ref: '#/components/schemas/DetailsRequestParams' + type: string responses: '200': content: @@ -97,13 +102,19 @@ paths: - Security Solution Endpoint Management API '/api/endpoint/action/{action_id}/file/{file_id}/download`': get: + description: Download a file from an endpoint operationId: EndpointFileDownload parameters: - in: path - name: query + name: action_id required: true schema: - $ref: '#/components/schemas/FileDownloadRequestParams' + type: string + - in: path + name: file_id + required: true + schema: + type: string responses: '200': content: @@ -116,13 +127,19 @@ paths: - Security Solution Endpoint Management API '/api/endpoint/action/{action_id}/file/{file_id}`': get: + description: Get file info operationId: EndpointFileInfo parameters: - in: path - name: query + name: action_id required: true schema: - $ref: '#/components/schemas/FileInfoRequestParams' + type: string + - in: path + name: file_id + required: true + schema: + type: string responses: '200': content: @@ -135,12 +152,13 @@ paths: - Security Solution Endpoint Management API /api/endpoint/action/execute: post: + description: Execute a given command on an endpoint operationId: EndpointExecuteAction requestBody: content: application/json: schema: - $ref: '#/components/schemas/ExecuteActionRequestBody' + $ref: '#/components/schemas/ExecuteRouteRequestBody' required: true responses: '200': @@ -154,12 +172,13 @@ paths: - Security Solution Endpoint Management API /api/endpoint/action/get_file: post: + description: Get a file from an endpoint operationId: EndpointGetFileAction requestBody: content: application/json: schema: - $ref: '#/components/schemas/GetFileActionRequestBody' + $ref: '#/components/schemas/GetFileRouteRequestBody' required: true responses: '200': @@ -173,23 +192,13 @@ paths: - Security Solution Endpoint Management API /api/endpoint/action/isolate: post: - operationId: EndpointIsolateHostAction + description: Isolate an endpoint + operationId: EndpointIsolateAction requestBody: content: application/json: schema: - type: object - properties: - alert_ids: - $ref: '#/components/schemas/AlertIds' - case_ids: - $ref: '#/components/schemas/CaseIds' - comment: - $ref: '#/components/schemas/Comment' - endpoint_ids: - $ref: '#/components/schemas/EndpointIds' - parameters: - $ref: '#/components/schemas/Parameters' + $ref: '#/components/schemas/IsolateRouteRequestBody' required: true responses: '200': @@ -198,17 +207,18 @@ paths: schema: $ref: '#/components/schemas/SuccessResponse' description: OK - summary: Isolate host Action + summary: Isolate Action tags: - Security Solution Endpoint Management API /api/endpoint/action/kill_process: post: + description: Kill a running process on an endpoint operationId: EndpointKillProcessAction requestBody: content: application/json: schema: - $ref: '#/components/schemas/ProcessActionSchemas' + $ref: '#/components/schemas/KillOrSuspendActionSchema' required: true responses: '200': @@ -222,23 +232,13 @@ paths: - Security Solution Endpoint Management API /api/endpoint/action/running_procs: post: - operationId: EndpointGetRunningProcessesAction + description: Get list of running processes on an endpoint + operationId: EndpointGetProcessesAction requestBody: content: application/json: schema: - type: object - properties: - alert_ids: - $ref: '#/components/schemas/AlertIds' - case_ids: - $ref: '#/components/schemas/CaseIds' - comment: - $ref: '#/components/schemas/Comment' - endpoint_ids: - $ref: '#/components/schemas/EndpointIds' - parameters: - $ref: '#/components/schemas/Parameters' + $ref: '#/components/schemas/GetProcessesRouteRequestBody' required: true responses: '200': @@ -252,12 +252,13 @@ paths: - Security Solution Endpoint Management API /api/endpoint/action/scan: post: + description: Scan a file or directory operationId: EndpointScanAction requestBody: content: application/json: schema: - $ref: '#/components/schemas/ScanActionRequestBody' + $ref: '#/components/schemas/ScanRouteRequestBody' required: true responses: '200': @@ -284,12 +285,13 @@ paths: - Security Solution Endpoint Management API /api/endpoint/action/suspend_process: post: + description: Suspend a running process on an endpoint operationId: EndpointSuspendProcessAction requestBody: content: application/json: schema: - $ref: '#/components/schemas/ProcessActionSchemas' + $ref: '#/components/schemas/KillOrSuspendActionSchema' required: true responses: '200': @@ -303,23 +305,13 @@ paths: - Security Solution Endpoint Management API /api/endpoint/action/unisolate: post: - operationId: EndpointUnisolateHostAction + description: Release an endpoint + operationId: EndpointUnisolateAction requestBody: content: application/json: schema: - type: object - properties: - alert_ids: - $ref: '#/components/schemas/AlertIds' - case_ids: - $ref: '#/components/schemas/CaseIds' - comment: - $ref: '#/components/schemas/Comment' - endpoint_ids: - $ref: '#/components/schemas/EndpointIds' - parameters: - $ref: '#/components/schemas/Parameters' + $ref: '#/components/schemas/UnisolateRouteRequestBody' required: true responses: '200': @@ -328,17 +320,18 @@ paths: schema: $ref: '#/components/schemas/SuccessResponse' description: OK - summary: Unisolate host Action + summary: Unisolate Action tags: - Security Solution Endpoint Management API /api/endpoint/action/upload: post: + description: Upload a file to an endpoint operationId: EndpointUploadAction requestBody: content: application/json: schema: - $ref: '#/components/schemas/FileUploadActionRequestBody' + $ref: '#/components/schemas/UploadRouteRequestBody' required: true responses: '200': @@ -374,13 +367,10 @@ paths: operationId: GetEndpointMetadata parameters: - in: path - name: query + name: id required: true schema: - type: object - properties: - id: - type: string + type: string responses: '200': content: @@ -428,6 +418,7 @@ paths: - Security Solution Endpoint Management API /api/endpoint/policy/summaries: get: + deprecated: true operationId: GetAgentPolicySummary parameters: - in: query @@ -535,6 +526,17 @@ paths: - Security Solution Endpoint Management API components: schemas: + ActionLogRequestQuery: + type: object + properties: + end_date: + $ref: '#/components/schemas/EndDate' + page: + $ref: '#/components/schemas/Page' + page_size: + $ref: '#/components/schemas/PageSize' + start_date: + $ref: '#/components/schemas/StartDate' AgentId: description: Agent ID type: string @@ -549,28 +551,18 @@ components: type: array - minLength: 1 type: string + AgentTypes: + enum: + - endpoint + - sentinel_one + - crowdstrike + type: string AlertIds: description: A list of alerts ids. items: $ref: '#/components/schemas/NonEmptyString' minItems: 1 type: array - AuditLogRequestParams: - type: object - properties: - agent_id: - $ref: '#/components/schemas/AgentId' - AuditLogRequestQuery: - type: object - properties: - end_date: - $ref: '#/components/schemas/EndDate' - page: - $ref: '#/components/schemas/Page' - page_size: - $ref: '#/components/schemas/PageSize' - start_date: - $ref: '#/components/schemas/StartDate' CaseIds: description: Case IDs to be updated (cannot contain empty strings) items: @@ -589,6 +581,7 @@ components: - get-file - execute - upload + - scan minLength: 1 type: string Commands: @@ -598,39 +591,9 @@ components: Comment: description: Optional comment type: string - DetailsRequestParams: - type: object - properties: - action_id: - type: string EndDate: description: End date type: string - EndpointActionListRequestQuery: - type: object - properties: - agentIds: - $ref: '#/components/schemas/AgentIds' - commands: - $ref: '#/components/schemas/Commands' - endDate: - $ref: '#/components/schemas/EndDate' - page: - $ref: '#/components/schemas/Page' - pageSize: - default: 10 - description: Number of items per page - maximum: 10000 - minimum: 1 - type: integer - startDate: - $ref: '#/components/schemas/StartDate' - types: - $ref: '#/components/schemas/Types' - userIds: - $ref: '#/components/schemas/UserIds' - withOutputs: - $ref: '#/components/schemas/WithOutputs' EndpointIds: description: List of endpoint IDs (cannot contain empty strings) items: @@ -638,10 +601,12 @@ components: type: string minItems: 1 type: array - ExecuteActionRequestBody: + ExecuteRouteRequestBody: allOf: - type: object properties: + agent_type: + $ref: '#/components/schemas/AgentTypes' alert_ids: $ref: '#/components/schemas/AlertIds' case_ids: @@ -652,6 +617,8 @@ components: $ref: '#/components/schemas/EndpointIds' parameters: $ref: '#/components/schemas/Parameters' + required: + - endpoint_ids - type: object properties: parameters: @@ -665,30 +632,39 @@ components: - command required: - parameters - FileDownloadRequestParams: + GetEndpointActionListRouteQuery: type: object properties: - action_id: - type: string - file_id: - type: string - required: - - action_id - - file_id - FileInfoRequestParams: - type: object - properties: - action_id: - type: string - file_id: - type: string - required: - - action_id - - file_id - FileUploadActionRequestBody: + agentIds: + $ref: '#/components/schemas/AgentIds' + agentTypes: + $ref: '#/components/schemas/AgentTypes' + commands: + $ref: '#/components/schemas/Commands' + endDate: + $ref: '#/components/schemas/EndDate' + page: + $ref: '#/components/schemas/Page' + pageSize: + default: 10 + description: Number of items per page + maximum: 10000 + minimum: 1 + type: integer + startDate: + $ref: '#/components/schemas/StartDate' + types: + $ref: '#/components/schemas/Types' + userIds: + $ref: '#/components/schemas/UserIds' + withOutputs: + $ref: '#/components/schemas/WithOutputs' + GetFileRouteRequestBody: allOf: - type: object properties: + agent_type: + $ref: '#/components/schemas/AgentTypes' alert_ids: $ref: '#/components/schemas/AlertIds' case_ids: @@ -699,24 +675,29 @@ components: $ref: '#/components/schemas/EndpointIds' parameters: $ref: '#/components/schemas/Parameters' + required: + - endpoint_ids - type: object properties: - file: - format: binary - type: string parameters: type: object properties: - overwrite: - default: false - type: boolean + path: + type: string + required: + - path required: - parameters - - file - GetFileActionRequestBody: + GetProcessesRouteRequestBody: + $ref: '#/components/schemas/NoParametersRequestSchema' + IsolateRouteRequestBody: + $ref: '#/components/schemas/NoParametersRequestSchema' + KillOrSuspendActionSchema: allOf: - type: object properties: + agent_type: + $ref: '#/components/schemas/AgentTypes' alert_ids: $ref: '#/components/schemas/AlertIds' case_ids: @@ -727,15 +708,22 @@ components: $ref: '#/components/schemas/EndpointIds' parameters: $ref: '#/components/schemas/Parameters' + required: + - endpoint_ids - type: object properties: parameters: - type: object - properties: - path: - type: string - required: - - path + oneOf: + - type: object + properties: + pid: + minimum: 1 + type: integer + - type: object + properties: + entity_id: + minLength: 1 + type: string required: - parameters ListRequestQuery: @@ -790,6 +778,28 @@ components: minLength: 1 pattern: ^(?! *$).+$ type: string + NoParametersRequestSchema: + type: object + properties: + body: + type: object + properties: + agent_type: + $ref: '#/components/schemas/AgentTypes' + alert_ids: + $ref: '#/components/schemas/AlertIds' + case_ids: + $ref: '#/components/schemas/CaseIds' + comment: + $ref: '#/components/schemas/Comment' + endpoint_ids: + $ref: '#/components/schemas/EndpointIds' + parameters: + $ref: '#/components/schemas/Parameters' + required: + - endpoint_ids + required: + - body Page: default: 1 description: Page number @@ -804,45 +814,17 @@ components: Parameters: description: Optional parameters object type: object - ProcessActionSchemas: - allOf: - - type: object - properties: - alert_ids: - $ref: '#/components/schemas/AlertIds' - case_ids: - $ref: '#/components/schemas/CaseIds' - comment: - $ref: '#/components/schemas/Comment' - endpoint_ids: - $ref: '#/components/schemas/EndpointIds' - parameters: - $ref: '#/components/schemas/Parameters' - - type: object - properties: - parameters: - oneOf: - - type: object - properties: - pid: - minimum: 1 - type: integer - - type: object - properties: - entity_id: - minLength: 1 - type: string - required: - - parameters ProtectionUpdatesNoteResponse: type: object properties: note: type: string - ScanActionRequestBody: + ScanRouteRequestBody: allOf: - type: object properties: + agent_type: + $ref: '#/components/schemas/AgentTypes' alert_ids: $ref: '#/components/schemas/AlertIds' case_ids: @@ -853,6 +835,8 @@ components: $ref: '#/components/schemas/EndpointIds' parameters: $ref: '#/components/schemas/Parameters' + required: + - endpoint_ids - type: object properties: parameters: @@ -875,16 +859,52 @@ components: minimum: 1 type: integer Type: + description: Type of response action enum: - automated - manual type: string Types: + description: List of types of response actions items: $ref: '#/components/schemas/Type' maxLength: 2 minLength: 1 type: array + UnisolateRouteRequestBody: + $ref: '#/components/schemas/NoParametersRequestSchema' + UploadRouteRequestBody: + allOf: + - type: object + properties: + agent_type: + $ref: '#/components/schemas/AgentTypes' + alert_ids: + $ref: '#/components/schemas/AlertIds' + case_ids: + $ref: '#/components/schemas/CaseIds' + comment: + $ref: '#/components/schemas/Comment' + endpoint_ids: + $ref: '#/components/schemas/EndpointIds' + parameters: + $ref: '#/components/schemas/Parameters' + required: + - endpoint_ids + - type: object + properties: + file: + format: binary + type: string + parameters: + type: object + properties: + overwrite: + default: false + type: boolean + required: + - parameters + - file UserIds: description: User IDs oneOf: @@ -896,7 +916,7 @@ components: - minLength: 1 type: string WithOutputs: - description: With Outputs + description: Shows detailed outputs for an action response oneOf: - items: minLength: 1 diff --git a/x-pack/plugins/security_solution/public/common/lib/endpoint/endpoint_isolation/index.ts b/x-pack/plugins/security_solution/public/common/lib/endpoint/endpoint_isolation/index.ts index dc119de848dec..678c0d63e4be6 100644 --- a/x-pack/plugins/security_solution/public/common/lib/endpoint/endpoint_isolation/index.ts +++ b/x-pack/plugins/security_solution/public/common/lib/endpoint/endpoint_isolation/index.ts @@ -6,9 +6,10 @@ */ import type { - HostIsolationRequestBody, - ResponseActionApiResponse, -} from '../../../../../common/endpoint/types'; + IsolationRouteRequestBody, + UnisolationRouteRequestBody, +} from '../../../../../common/api/endpoint'; +import type { ResponseActionApiResponse } from '../../../../../common/endpoint/types'; import { KibanaServices } from '../../kibana'; import { ISOLATE_HOST_ROUTE_V2, @@ -19,7 +20,7 @@ import { /** Isolates a Host running either elastic endpoint or fleet agent */ export const isolateHost = async ( - params: HostIsolationRequestBody + params: IsolationRouteRequestBody ): Promise => { return KibanaServices.get().http.post(ISOLATE_HOST_ROUTE_V2, { body: JSON.stringify(params), @@ -29,7 +30,7 @@ export const isolateHost = async ( /** Un-isolates a Host running either elastic endpoint or fleet agent */ export const unIsolateHost = async ( - params: HostIsolationRequestBody + params: UnisolationRouteRequestBody ): Promise => { return KibanaServices.get().http.post(UNISOLATE_HOST_ROUTE_V2, { body: JSON.stringify(params), diff --git a/x-pack/plugins/security_solution/public/common/lib/endpoint/endpoint_isolation/mocks.ts b/x-pack/plugins/security_solution/public/common/lib/endpoint/endpoint_isolation/mocks.ts index 4881fc3f1569f..bd65a1de60612 100644 --- a/x-pack/plugins/security_solution/public/common/lib/endpoint/endpoint_isolation/mocks.ts +++ b/x-pack/plugins/security_solution/public/common/lib/endpoint/endpoint_isolation/mocks.ts @@ -5,10 +5,8 @@ * 2.0. */ -import type { - HostIsolationRequestBody, - HostIsolationResponse, -} from '../../../../../common/endpoint/types'; +import type { IsolationRouteRequestBody } from '../../../../../common/api/endpoint'; +import type { HostIsolationResponse } from '../../../../../common/endpoint/types'; import type { ResponseProvidersInterface } from '../../../mock/endpoint/http_handler_mock_factory'; import { httpHandlerMockFactory } from '../../../mock/endpoint/http_handler_mock_factory'; import { @@ -16,7 +14,7 @@ import { UNISOLATE_HOST_ROUTE_V2, } from '../../../../../common/endpoint/constants'; -export const hostIsolationRequestBodyMock = (): HostIsolationRequestBody => { +export const hostIsolationRequestBodyMock = (): IsolationRouteRequestBody => { return { endpoint_ids: ['88c04a90-b19c-11eb-b838-222'], alert_ids: ['88c04a90-b19c-11eb-b838-333'], diff --git a/x-pack/plugins/security_solution/public/common/lib/process_actions/index.ts b/x-pack/plugins/security_solution/public/common/lib/process_actions/index.ts index b8cb7c04f469c..960ec21f41ef4 100644 --- a/x-pack/plugins/security_solution/public/common/lib/process_actions/index.ts +++ b/x-pack/plugins/security_solution/public/common/lib/process_actions/index.ts @@ -6,10 +6,10 @@ */ import type { - ResponseActionApiResponse, KillProcessRequestBody, SuspendProcessRequestBody, -} from '../../../../common/endpoint/types'; +} from '../../../../common/api/endpoint'; +import type { ResponseActionApiResponse } from '../../../../common/endpoint/types'; import { KibanaServices } from '../kibana'; import { KILL_PROCESS_ROUTE, SUSPEND_PROCESS_ROUTE } from '../../../../common/endpoint/constants'; diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/get_processes_action.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/get_processes_action.tsx index 54dce8a6e4add..5086e1ac1fada 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/get_processes_action.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/get_processes_action.tsx @@ -6,12 +6,10 @@ */ import React, { memo, useMemo } from 'react'; +import type { GetProcessesRequestBody } from '../../../../../common/api/endpoint'; import { RunningProcessesActionResults } from '../../running_processes_action_results'; import { useConsoleActionSubmitter } from '../hooks/use_console_action_submitter'; -import type { - GetProcessesActionOutputContent, - ProcessesRequestBody, -} from '../../../../../common/endpoint/types'; +import type { GetProcessesActionOutputContent } from '../../../../../common/endpoint/types'; import { useSendGetEndpointProcessesRequest } from '../../../hooks/response_actions/use_send_get_endpoint_processes_request'; import type { ActionRequestComponentProps } from '../types'; @@ -32,7 +30,7 @@ export const GetProcessesActionResult = memo( }, [endpointId, comment, agentType]); const { result, actionDetails: completedActionDetails } = useConsoleActionSubmitter< - ProcessesRequestBody, + GetProcessesRequestBody, GetProcessesActionOutputContent >({ ResultComponent, diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/kill_process_action.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/kill_process_action.tsx index abfc48b78c791..e96f1f0028b7d 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/kill_process_action.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/kill_process_action.tsx @@ -6,11 +6,11 @@ */ import { memo, useMemo } from 'react'; +import type { KillProcessRequestBody } from '../../../../../common/api/endpoint'; import { parsedKillOrSuspendParameter } from '../lib/utils'; import { useSendKillProcessRequest } from '../../../hooks/response_actions/use_send_kill_process_endpoint_request'; import type { ActionRequestComponentProps } from '../types'; import { useConsoleActionSubmitter } from '../hooks/use_console_action_submitter'; -import type { KillProcessRequestBody } from '../../../../../common/endpoint/types'; export const KillProcessActionResult = memo< ActionRequestComponentProps<{ pid?: string[]; entityId?: string[]; processName?: string[] }> diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/suspend_process_action.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/suspend_process_action.tsx index 344ebe5da626e..b2a73b426aa27 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/suspend_process_action.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/suspend_process_action.tsx @@ -6,10 +6,10 @@ */ import { memo, useMemo } from 'react'; +import type { SuspendProcessRequestBody } from '../../../../../common/api/endpoint'; import { parsedKillOrSuspendParameter } from '../lib/utils'; import type { SuspendProcessActionOutputContent, - SuspendProcessRequestBody, ResponseActionParametersWithEntityId, ResponseActionParametersWithPid, } from '../../../../../common/endpoint/types'; diff --git a/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_get_endpoint_processes_request.ts b/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_get_endpoint_processes_request.ts index cf946db04ca37..849380714cd94 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_get_endpoint_processes_request.ts +++ b/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_get_endpoint_processes_request.ts @@ -8,8 +8,8 @@ import type { UseMutationOptions, UseMutationResult } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query'; import type { IHttpFetchError } from '@kbn/core-http-browser'; +import type { GetProcessesRequestBody } from '../../../../common/api/endpoint'; import type { - ProcessesRequestBody, ResponseActionApiResponse, GetProcessesActionOutputContent, } from '../../../../common/endpoint/types/actions'; @@ -24,18 +24,18 @@ export const useSendGetEndpointProcessesRequest = ( customOptions?: UseMutationOptions< ResponseActionApiResponse, IHttpFetchError, - ProcessesRequestBody + GetProcessesRequestBody > ): UseMutationResult< ResponseActionApiResponse, IHttpFetchError, - ProcessesRequestBody + GetProcessesRequestBody > => { return useMutation< ResponseActionApiResponse, IHttpFetchError, - ProcessesRequestBody - >((getRunningProcessesData: ProcessesRequestBody) => { + GetProcessesRequestBody + >((getRunningProcessesData: GetProcessesRequestBody) => { return KibanaServices.get().http.post< ResponseActionApiResponse >(GET_PROCESSES_ROUTE, { diff --git a/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_isolate_endpoint_request.ts b/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_isolate_endpoint_request.ts index b4cb131f6ae9c..a9efa834e97e5 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_isolate_endpoint_request.ts +++ b/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_isolate_endpoint_request.ts @@ -8,11 +8,9 @@ import type { UseMutationOptions, UseMutationResult } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query'; import type { IHttpFetchError } from '@kbn/core-http-browser'; +import type { IsolationRouteRequestBody } from '../../../../common/api/endpoint'; import { isolateHost } from '../../../common/lib/endpoint/endpoint_isolation'; -import type { - HostIsolationRequestBody, - ResponseActionApiResponse, -} from '../../../../common/endpoint/types'; +import type { ResponseActionApiResponse } from '../../../../common/endpoint/types'; /** * Create host isolation requests @@ -22,11 +20,11 @@ export const useSendIsolateEndpointRequest = ( customOptions?: UseMutationOptions< ResponseActionApiResponse, IHttpFetchError, - HostIsolationRequestBody + IsolationRouteRequestBody > -): UseMutationResult => { - return useMutation( - (isolateData: HostIsolationRequestBody) => { +): UseMutationResult => { + return useMutation( + (isolateData: IsolationRouteRequestBody) => { return isolateHost(isolateData); }, customOptions diff --git a/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_kill_process_endpoint_request.ts b/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_kill_process_endpoint_request.ts index b17d34ab4e463..1dd2b1ae2a8c7 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_kill_process_endpoint_request.ts +++ b/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_kill_process_endpoint_request.ts @@ -8,10 +8,8 @@ import type { UseMutationOptions, UseMutationResult } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query'; import type { IHttpFetchError } from '@kbn/core-http-browser'; -import type { - ResponseActionApiResponse, - KillProcessRequestBody, -} from '../../../../common/endpoint/types'; +import type { KillProcessRequestBody } from '../../../../common/api/endpoint'; +import type { ResponseActionApiResponse } from '../../../../common/endpoint/types'; import { killProcess } from '../../../common/lib/process_actions'; /** diff --git a/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_release_endpoint_request.ts b/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_release_endpoint_request.ts index b9e140dc3ba9d..ed0eaf68f90d4 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_release_endpoint_request.ts +++ b/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_release_endpoint_request.ts @@ -8,10 +8,8 @@ import type { UseMutationOptions, UseMutationResult } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query'; import type { IHttpFetchError } from '@kbn/core-http-browser'; -import type { - HostIsolationRequestBody, - ResponseActionApiResponse, -} from '../../../../common/endpoint/types'; +import type { UnisolationRouteRequestBody } from '../../../../common/api/endpoint'; +import type { ResponseActionApiResponse } from '../../../../common/endpoint/types'; import { unIsolateHost } from '../../../common/lib/endpoint/endpoint_isolation'; /** @@ -22,11 +20,11 @@ export const useSendReleaseEndpointRequest = ( customOptions?: UseMutationOptions< ResponseActionApiResponse, IHttpFetchError, - HostIsolationRequestBody + UnisolationRouteRequestBody > -): UseMutationResult => { - return useMutation( - (releaseData: HostIsolationRequestBody) => { +): UseMutationResult => { + return useMutation( + (releaseData: UnisolationRouteRequestBody) => { return unIsolateHost(releaseData); }, customOptions diff --git a/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_suspend_process_endpoint_request.ts b/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_suspend_process_endpoint_request.ts index 787344ffd54dc..f2e467e36073e 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_suspend_process_endpoint_request.ts +++ b/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_suspend_process_endpoint_request.ts @@ -8,10 +8,8 @@ import { useMutation } from '@tanstack/react-query'; import type { UseMutationOptions, UseMutationResult } from '@tanstack/react-query'; import type { IHttpFetchError } from '@kbn/core-http-browser'; -import type { - ResponseActionApiResponse, - SuspendProcessRequestBody, -} from '../../../../common/endpoint/types'; +import type { SuspendProcessRequestBody } from '../../../../common/api/endpoint'; +import type { ResponseActionApiResponse } from '../../../../common/endpoint/types'; import { suspendProcess } from '../../../common/lib/process_actions'; /** diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts index 01b9b3455dea9..43db8f92929e8 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts @@ -7,9 +7,9 @@ import type { Action } from 'redux'; import type { DataViewBase } from '@kbn/es-query'; +import type { IsolationRouteRequestBody } from '../../../../../common/api/endpoint'; import type { GetHostPolicyResponse, - HostIsolationRequestBody, ISOLATION_ACTIONS, MetadataListResponse, } from '../../../../../common/endpoint/types'; @@ -133,7 +133,7 @@ export interface ServerFailedToReturnEndpointsTotal { export type EndpointIsolationRequest = Action<'endpointIsolationRequest'> & { payload: { type: ISOLATION_ACTIONS; - data: HostIsolationRequestBody; + data: IsolationRouteRequestBody; }; }; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts index fce262052220e..ec27500a45e12 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts @@ -14,6 +14,10 @@ import type { IndexFieldsStrategyRequest, IndexFieldsStrategyResponse, } from '@kbn/timelines-plugin/common'; +import type { + IsolationRouteRequestBody, + UnisolationRouteRequestBody, +} from '../../../../../common/api/endpoint'; import { ENDPOINT_FIELDS_SEARCH_STRATEGY, HOST_METADATA_LIST_ROUTE, @@ -22,7 +26,6 @@ import { metadataCurrentIndexPattern, } from '../../../../../common/endpoint/constants'; import type { - HostIsolationRequestBody, HostResultList, Immutable, ImmutableObject, @@ -246,9 +249,9 @@ const handleIsolateEndpointHost = async ( let response: ResponseActionApiResponse; if (action.payload.type === 'unisolate') { - response = await unIsolateHost(action.payload.data as HostIsolationRequestBody); + response = await unIsolateHost(action.payload.data as UnisolationRouteRequestBody); } else { - response = await isolateHost(action.payload.data as HostIsolationRequestBody); + response = await isolateHost(action.payload.data as IsolationRouteRequestBody); } dispatch({ diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts index 47099ae060efa..2fdfa5143cfe2 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts @@ -42,7 +42,6 @@ import type { HostMetadata, LogsEndpointAction, ResponseActionApiResponse, - ResponseActionRequestBody, } from '../../../../common/endpoint/types'; import { EndpointDocGenerator } from '../../../../common/endpoint/generate_data'; import type { EndpointAuthz } from '../../../../common/endpoint/types/authz'; @@ -63,7 +62,10 @@ import * as ActionDetailsService from '../../services/actions/action_details_by_ import { CaseStatuses } from '@kbn/cases-components'; import { getEndpointAuthzInitialStateMock } from '../../../../common/endpoint/service/authz/mocks'; import { getResponseActionsClient as _getResponseActionsClient } from '../../services'; -import type { UploadActionApiRequestBody } from '../../../../common/api/endpoint'; +import type { + ResponseActionsRequestBody, + UploadActionApiRequestBody, +} from '../../../../common/api/endpoint'; import type { FleetToHostFileClientInterface } from '@kbn/fleet-plugin/server'; import type { HapiReadableStream, SecuritySolutionRequestHandlerContext } from '../../../types'; import { createHapiReadableStreamMock } from '../../services/actions/mocks'; @@ -92,7 +94,7 @@ jest.mock('../../services', () => { const getResponseActionsClientMock = _getResponseActionsClient; interface CallRouteInterface { - body?: ResponseActionRequestBody; + body?: ResponseActionsRequestBody; indexErrorResponse?: any; searchResponse?: HostMetadata; mockUser?: any; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.ts index 723daf576c38e..9f40852fec380 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.ts @@ -13,6 +13,10 @@ import { stringify } from '../../utils/stringify'; import { getResponseActionsClient, NormalizedExternalConnectorClient } from '../../services'; import type { ResponseActionsClient } from '../../services/actions/clients/lib/types'; import { CustomHttpRequestError } from '../../../utils/custom_http_request_error'; +import type { + KillProcessRequestBody, + SuspendProcessRequestBody, +} from '../../../../common/api/endpoint'; import { EndpointActionGetFileSchema, type ExecuteActionRequestBody, @@ -50,8 +54,6 @@ import type { ResponseActionParametersWithProcessData, ResponseActionsExecuteParameters, ResponseActionScanParameters, - KillProcessRequestBody, - SuspendProcessRequestBody, } from '../../../../common/endpoint/types'; import type { ResponseActionsApiCommandNames } from '../../../../common/endpoint/service/response_actions/constants'; import type { diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/protection_updates_note/handlers.ts b/x-pack/plugins/security_solution/server/endpoint/routes/protection_updates_note/handlers.ts index e1677451ff577..79eb38211387e 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/protection_updates_note/handlers.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/protection_updates_note/handlers.ts @@ -16,7 +16,7 @@ import { protectionUpdatesNoteSavedObjectType } from '../../lib/protection_updat import type { CreateUpdateProtectionUpdatesNoteSchema, GetProtectionUpdatesNoteSchema, -} from '../../../../common/api/endpoint/protection_updates_note/protection_updates_note_schema'; +} from '../../../../common/api/endpoint/protection_updates_note'; const getProtectionNote = async (SOClient: SavedObjectsClientContract, packagePolicyId: string) => { return SOClient.find<{ note: string }>({ diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/protection_updates_note/index.ts b/x-pack/plugins/security_solution/server/endpoint/routes/protection_updates_note/index.ts index 4d398bbe14e6e..7b28ccfcf9fe7 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/protection_updates_note/index.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/protection_updates_note/index.ts @@ -10,7 +10,7 @@ import { getProtectionUpdatesNoteHandler, postProtectionUpdatesNoteHandler } fro import { GetProtectionUpdatesNoteSchema, CreateUpdateProtectionUpdatesNoteSchema, -} from '../../../../common/api/endpoint/protection_updates_note/protection_updates_note_schema'; +} from '../../../../common/api/endpoint/protection_updates_note'; import { withEndpointAuthz } from '../with_endpoint_authz'; import { PROTECTION_UPDATES_NOTE_ROUTE } from '../../../../common/endpoint/constants'; import type { EndpointAppContext } from '../../types'; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/crowdstrike/crowdstrike_actions_client.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/crowdstrike/crowdstrike_actions_client.ts index 958c51014c6a0..b39c85726651f 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/crowdstrike/crowdstrike_actions_client.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/crowdstrike/crowdstrike_actions_client.ts @@ -27,7 +27,10 @@ import type { EndpointActionResponseDataOutput, LogsEndpointAction, } from '../../../../../../common/endpoint/types'; -import type { IsolationRouteRequestBody } from '../../../../../../common/api/endpoint'; +import type { + IsolationRouteRequestBody, + UnisolationRouteRequestBody, +} from '../../../../../../common/api/endpoint'; import type { ResponseActionsClientOptions, ResponseActionsClientWriteActionRequestToEndpointIndexOptions, @@ -238,7 +241,7 @@ export class CrowdstrikeActionsClient extends ResponseActionsClientImpl { } async release( - actionRequest: IsolationRouteRequestBody, + actionRequest: UnisolationRouteRequestBody, options: CommonResponseActionMethodOptions = {} ): Promise { const reqIndexOptions: ResponseActionsClientWriteActionRequestToEndpointIndexOptions = { diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/endpoint/endpoint_actions_client.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/endpoint/endpoint_actions_client.test.ts index eaaa5fa259927..a406397c2be19 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/endpoint/endpoint_actions_client.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/endpoint/endpoint_actions_client.test.ts @@ -11,20 +11,21 @@ import { EndpointActionsClient } from '../../..'; import { endpointActionClientMock } from './mocks'; import { responseActionsClientMock } from '../mocks'; import { ENDPOINT_ACTIONS_INDEX } from '../../../../../../common/endpoint/constants'; -import type { ResponseActionRequestBody } from '../../../../../../common/endpoint/types'; + import { DEFAULT_EXECUTE_ACTION_TIMEOUT } from '../../../../../../common/endpoint/service/response_actions/constants'; import { applyEsClientSearchMock } from '../../../../mocks/utils.mock'; import type { ElasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; import { BaseDataGenerator } from '../../../../../../common/endpoint/data_generators/base_data_generator'; import { Readable } from 'stream'; import { EndpointActionGenerator } from '../../../../../../common/endpoint/data_generators/endpoint_action_generator'; +import type { ResponseActionsRequestBody } from '../../../../../../common/api/endpoint'; describe('EndpointActionsClient', () => { let classConstructorOptions: ResponseActionsClientOptions; let endpointActionsClient: ResponseActionsClient; const getCommonResponseActionOptions = (): Pick< - ResponseActionRequestBody, + ResponseActionsRequestBody, 'endpoint_ids' | 'case_ids' > => { return { diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/endpoint/endpoint_actions_client.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/endpoint/endpoint_actions_client.ts index 59328beb46c12..df2b3c323ee08 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/endpoint/endpoint_actions_client.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/endpoint/endpoint_actions_client.ts @@ -24,6 +24,9 @@ import type { UploadActionApiRequestBody, ResponseActionsRequestBody, ScanActionRequestBody, + SuspendProcessRequestBody, + KillProcessRequestBody, + UnisolationRouteRequestBody, } from '../../../../../../common/api/endpoint'; import { ResponseActionsClientImpl } from '../lib/base_response_actions_client'; import type { @@ -44,8 +47,6 @@ import type { UploadedFileInfo, ResponseActionScanParameters, ResponseActionScanOutputContent, - KillProcessRequestBody, - SuspendProcessRequestBody, } from '../../../../../../common/endpoint/types'; import type { CommonResponseActionMethodOptions, @@ -236,10 +237,10 @@ export class EndpointActionsClient extends ResponseActionsClientImpl { } async release( - actionRequest: IsolationRouteRequestBody, + actionRequest: UnisolationRouteRequestBody, options: CommonResponseActionMethodOptions = {} ): Promise { - return this.handleResponseAction( + return this.handleResponseAction( 'unisolate', actionRequest, options diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/base_response_actions_client.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/base_response_actions_client.ts index 67aec8d861ce4..07ab63b77a312 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/base_response_actions_client.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/base_response_actions_client.ts @@ -66,16 +66,17 @@ import type { SuspendProcessActionOutputContent, UploadedFileInfo, WithAllKeys, - KillProcessRequestBody, - SuspendProcessRequestBody, } from '../../../../../../common/endpoint/types'; import type { ExecuteActionRequestBody, GetProcessesRequestBody, IsolationRouteRequestBody, + KillProcessRequestBody, ResponseActionGetFileRequestBody, ResponseActionsRequestBody, ScanActionRequestBody, + SuspendProcessRequestBody, + UnisolationRouteRequestBody, UploadActionApiRequestBody, } from '../../../../../../common/api/endpoint'; import { stringify } from '../../../../utils/stringify'; @@ -716,7 +717,7 @@ export abstract class ResponseActionsClientImpl implements ResponseActionsClient } public async release( - actionRequest: IsolationRouteRequestBody, + actionRequest: UnisolationRouteRequestBody, options?: CommonResponseActionMethodOptions ): Promise { throw new ResponseActionsNotSupportedError('unisolate'); diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/types.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/types.ts index 4a7b7efd4d4a5..e3407b5ba959a 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/types.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/types.ts @@ -23,17 +23,18 @@ import type { UploadedFileInfo, ResponseActionScanOutputContent, ResponseActionScanParameters, - KillProcessRequestBody, - SuspendProcessRequestBody, } from '../../../../../../common/endpoint/types'; import type { IsolationRouteRequestBody, + UnisolationRouteRequestBody, GetProcessesRequestBody, ResponseActionGetFileRequestBody, ExecuteActionRequestBody, UploadActionApiRequestBody, BaseActionRequestBody, ScanActionRequestBody, + KillProcessRequestBody, + SuspendProcessRequestBody, } from '../../../../../../common/api/endpoint'; type OmitUnsupportedAttributes = Omit< @@ -84,7 +85,7 @@ export interface ResponseActionsClient { ) => Promise; release: ( - actionRequest: OmitUnsupportedAttributes, + actionRequest: OmitUnsupportedAttributes, options?: CommonResponseActionMethodOptions ) => Promise; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/sentinel_one_actions_client.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/sentinel_one_actions_client.ts index 906125df4c2b8..2933f25cfd0ad 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/sentinel_one_actions_client.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/sentinel_one_actions_client.ts @@ -57,7 +57,6 @@ import type { EndpointActionResponseDataOutput, GetProcessesActionOutputContent, KillProcessActionOutputContent, - KillProcessRequestBody, LogsEndpointAction, LogsEndpointActionResponse, ResponseActionGetFileOutputContent, @@ -81,6 +80,8 @@ import type { GetProcessesRequestBody, IsolationRouteRequestBody, ResponseActionGetFileRequestBody, + KillProcessRequestBody, + UnisolationRouteRequestBody, } from '../../../../../../common/api/endpoint'; import type { ResponseActionsClientOptions, @@ -361,7 +362,7 @@ export class SentinelOneActionsClient extends ResponseActionsClientImpl { } async release( - actionRequest: IsolationRouteRequestBody, + actionRequest: UnisolationRouteRequestBody, options: CommonResponseActionMethodOptions = {} ): Promise { const reqIndexOptions: ResponseActionsClientWriteActionRequestToEndpointIndexOptions< diff --git a/x-pack/test/api_integration/services/security_solution_api.gen.ts b/x-pack/test/api_integration/services/security_solution_api.gen.ts index 582b7feea4d55..ef706d2aca2cd 100644 --- a/x-pack/test/api_integration/services/security_solution_api.gen.ts +++ b/x-pack/test/api_integration/services/security_solution_api.gen.ts @@ -41,8 +41,22 @@ import { DeleteNoteRequestBodyInput } from '@kbn/security-solution-plugin/common import { DeleteRuleRequestQueryInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/crud/delete_rule/delete_rule_route.gen'; import { DeleteTimelinesRequestBodyInput } from '@kbn/security-solution-plugin/common/api/timeline/delete_timelines/delete_timelines_route.gen'; import { DeprecatedTriggerRiskScoreCalculationRequestBodyInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/risk_engine/entity_calculation_route.gen'; -import { EndpointIsolateRedirectRequestBodyInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/isolate_route.gen'; -import { EndpointUnisolateRedirectRequestBodyInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/unisolate_route.gen'; +import { EndpointExecuteActionRequestBodyInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/response_actions/execute/execute.gen'; +import { EndpointFileDownloadRequestParamsInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/file_download/file_download.gen'; +import { EndpointFileInfoRequestParamsInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/file_info/file_info.gen'; +import { EndpointGetActionsDetailsRequestParamsInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/details/details.gen'; +import { EndpointGetActionsListRequestQueryInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/list/list.gen'; +import { EndpointGetActionsStatusRequestQueryInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/status/status.gen'; +import { EndpointGetFileActionRequestBodyInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/response_actions/get_file/get_file.gen'; +import { EndpointGetProcessesActionRequestBodyInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/response_actions/running_procs/running_procs.gen'; +import { EndpointIsolateActionRequestBodyInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/response_actions/isolate/isolate.gen'; +import { EndpointIsolateRedirectRequestBodyInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/response_actions/isolate/deprecated_isolate.gen'; +import { EndpointKillProcessActionRequestBodyInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/response_actions/kill_process/kill_process.gen'; +import { EndpointScanActionRequestBodyInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/response_actions/scan/scan.gen'; +import { EndpointSuspendProcessActionRequestBodyInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/response_actions/suspend_process/suspend_process.gen'; +import { EndpointUnisolateActionRequestBodyInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/response_actions/unisolate/unisolate.gen'; +import { EndpointUnisolateRedirectRequestBodyInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/response_actions/unisolate/deprecated_unisolate.gen'; +import { EndpointUploadActionRequestBodyInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/response_actions/upload/upload.gen'; import { ExportRulesRequestQueryInput, ExportRulesRequestBodyInput, @@ -54,15 +68,16 @@ import { import { FinalizeAlertsMigrationRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/signals_migration/finalize_signals_migration/finalize_signals_migration.gen'; import { FindAssetCriticalityRecordsRequestQueryInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/asset_criticality/list_asset_criticality.gen'; import { FindRulesRequestQueryInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/find_rules/find_rules_route.gen'; -import { GetAgentPolicySummaryRequestQueryInput } from '@kbn/security-solution-plugin/common/api/endpoint/policy/policy.gen'; +import { GetAgentPolicySummaryRequestQueryInput } from '@kbn/security-solution-plugin/common/api/endpoint/policy/deprecated_agent_policy_summary.gen'; import { GetAssetCriticalityRecordRequestQueryInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/asset_criticality/get_asset_criticality.gen'; import { GetDraftTimelinesRequestQueryInput } from '@kbn/security-solution-plugin/common/api/timeline/get_draft_timelines/get_draft_timelines_route.gen'; +import { GetEndpointMetadataListRequestQueryInput } from '@kbn/security-solution-plugin/common/api/endpoint/metadata/get_metadata.gen'; import { GetEndpointSuggestionsRequestParamsInput, GetEndpointSuggestionsRequestBodyInput, } from '@kbn/security-solution-plugin/common/api/endpoint/suggestions/get_suggestions.gen'; import { GetNotesRequestQueryInput } from '@kbn/security-solution-plugin/common/api/timeline/get_notes/get_notes_route.gen'; -import { GetPolicyResponseRequestQueryInput } from '@kbn/security-solution-plugin/common/api/endpoint/policy/policy.gen'; +import { GetPolicyResponseRequestQueryInput } from '@kbn/security-solution-plugin/common/api/endpoint/policy/policy_response.gen'; import { GetProtectionUpdatesNoteRequestParamsInput } from '@kbn/security-solution-plugin/common/api/endpoint/protection_updates_note/protection_updates_note.gen'; import { GetRuleExecutionEventsRequestQueryInput, @@ -341,6 +356,114 @@ Migrations are initiated per index. While the process is neither destructive nor .set(ELASTIC_HTTP_VERSION_HEADER, '1') .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); }, + /** + * Execute a given command on an endpoint + */ + endpointExecuteAction(props: EndpointExecuteActionProps) { + return supertest + .post('/api/endpoint/action/execute') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, + /** + * Download a file from an endpoint + */ + endpointFileDownload(props: EndpointFileDownloadProps) { + return supertest + .get( + replaceParams( + '/api/endpoint/action/{action_id}/file/{file_id}/download`', + props.params + ) + ) + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); + }, + /** + * Get file info + */ + endpointFileInfo(props: EndpointFileInfoProps) { + return supertest + .get(replaceParams('/api/endpoint/action/{action_id}/file/{file_id}`', props.params)) + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); + }, + /** + * Get action details + */ + endpointGetActionsDetails(props: EndpointGetActionsDetailsProps) { + return supertest + .get(replaceParams('/api/endpoint/action/{action_id}', props.params)) + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); + }, + /** + * Get a list of action requests and their responses + */ + endpointGetActionsList(props: EndpointGetActionsListProps) { + return supertest + .get('/api/endpoint/action') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .query(props.query); + }, + endpointGetActionsState() { + return supertest + .get('/api/endpoint/action/state') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); + }, + /** + * Get action status + */ + endpointGetActionsStatus(props: EndpointGetActionsStatusProps) { + return supertest + .get('/api/endpoint/action_status') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .query(props.query); + }, + /** + * Get a file from an endpoint + */ + endpointGetFileAction(props: EndpointGetFileActionProps) { + return supertest + .post('/api/endpoint/action/get_file') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, + /** + * Get list of running processes on an endpoint + */ + endpointGetProcessesAction(props: EndpointGetProcessesActionProps) { + return supertest + .post('/api/endpoint/action/running_procs') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, + /** + * Isolate an endpoint + */ + endpointIsolateAction(props: EndpointIsolateActionProps) { + return supertest + .post('/api/endpoint/action/isolate') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, endpointIsolateRedirect(props: EndpointIsolateRedirectProps) { return supertest .post('/api/endpoint/isolate') @@ -349,6 +472,50 @@ Migrations are initiated per index. While the process is neither destructive nor .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send(props.body as object); }, + /** + * Kill a running process on an endpoint + */ + endpointKillProcessAction(props: EndpointKillProcessActionProps) { + return supertest + .post('/api/endpoint/action/kill_process') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, + /** + * Scan a file or directory + */ + endpointScanAction(props: EndpointScanActionProps) { + return supertest + .post('/api/endpoint/action/scan') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, + /** + * Suspend a running process on an endpoint + */ + endpointSuspendProcessAction(props: EndpointSuspendProcessActionProps) { + return supertest + .post('/api/endpoint/action/suspend_process') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, + /** + * Release an endpoint + */ + endpointUnisolateAction(props: EndpointUnisolateActionProps) { + return supertest + .post('/api/endpoint/action/unisolate') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, endpointUnisolateRedirect(props: EndpointUnisolateRedirectProps) { return supertest .post('/api/endpoint/unisolate') @@ -357,6 +524,17 @@ Migrations are initiated per index. While the process is neither destructive nor .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send(props.body as object); }, + /** + * Upload a file to an endpoint + */ + endpointUploadAction(props: EndpointUploadActionProps) { + return supertest + .post('/api/endpoint/action/upload') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, /** * Export detection rules to an `.ndjson` file. The following configuration items are also included in the `.ndjson` file: - Actions @@ -447,6 +625,14 @@ finalize it. .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .query(props.query); }, + getEndpointMetadataList(props: GetEndpointMetadataListProps) { + return supertest + .get('/api/endpoint/metadata') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .query(props.query); + }, getEndpointSuggestions(props: GetEndpointSuggestionsProps) { return supertest .post(replaceParams('/api/endpoint/suggestions/{suggestion_type}', props.params)) @@ -899,12 +1085,54 @@ export interface DeleteTimelinesProps { export interface DeprecatedTriggerRiskScoreCalculationProps { body: DeprecatedTriggerRiskScoreCalculationRequestBodyInput; } +export interface EndpointExecuteActionProps { + body: EndpointExecuteActionRequestBodyInput; +} +export interface EndpointFileDownloadProps { + params: EndpointFileDownloadRequestParamsInput; +} +export interface EndpointFileInfoProps { + params: EndpointFileInfoRequestParamsInput; +} +export interface EndpointGetActionsDetailsProps { + params: EndpointGetActionsDetailsRequestParamsInput; +} +export interface EndpointGetActionsListProps { + query: EndpointGetActionsListRequestQueryInput; +} +export interface EndpointGetActionsStatusProps { + query: EndpointGetActionsStatusRequestQueryInput; +} +export interface EndpointGetFileActionProps { + body: EndpointGetFileActionRequestBodyInput; +} +export interface EndpointGetProcessesActionProps { + body: EndpointGetProcessesActionRequestBodyInput; +} +export interface EndpointIsolateActionProps { + body: EndpointIsolateActionRequestBodyInput; +} export interface EndpointIsolateRedirectProps { body: EndpointIsolateRedirectRequestBodyInput; } +export interface EndpointKillProcessActionProps { + body: EndpointKillProcessActionRequestBodyInput; +} +export interface EndpointScanActionProps { + body: EndpointScanActionRequestBodyInput; +} +export interface EndpointSuspendProcessActionProps { + body: EndpointSuspendProcessActionRequestBodyInput; +} +export interface EndpointUnisolateActionProps { + body: EndpointUnisolateActionRequestBodyInput; +} export interface EndpointUnisolateRedirectProps { body: EndpointUnisolateRedirectRequestBodyInput; } +export interface EndpointUploadActionProps { + body: EndpointUploadActionRequestBodyInput; +} export interface ExportRulesProps { query: ExportRulesRequestQueryInput; body: ExportRulesRequestBodyInput; @@ -931,6 +1159,9 @@ export interface GetAssetCriticalityRecordProps { export interface GetDraftTimelinesProps { query: GetDraftTimelinesRequestQueryInput; } +export interface GetEndpointMetadataListProps { + query: GetEndpointMetadataListRequestQueryInput; +} export interface GetEndpointSuggestionsProps { params: GetEndpointSuggestionsRequestParamsInput; body: GetEndpointSuggestionsRequestBodyInput; From 7df01e99c13ba4870c44d3268df2225a7fc8fb1c Mon Sep 17 00:00:00 2001 From: Dzmitry Lemechko Date: Wed, 7 Aug 2024 17:34:52 +0200 Subject: [PATCH 19/44] [FTR] support "deployment agnostic" api-integration tests (#189853) ## Summary ### This PR introduces a new type of API integration tests in FTR: deployment-agnostic ![8zcgq0 (1)](https://github.com/user-attachments/assets/17c6d4ee-7848-4a4c-a006-7ae54e523243) #### Test suite is considered deployment-agnostic when it fulfils the following criteria: **Functionality**: It tests Kibana APIs that are **logically identical in both stateful and serverless environments** for the same SAML roles. **Design**: The test design is **clean and does not require additional logic** to execute in either stateful or serverless environments. ### How It Works Most existing stateful tests use basic authentication for API testing. In contrast, serverless tests use SAML authentication with project-specific role mapping. Since stateful deployments also support SAML, deployment-agnostic tests **configure Elasticsearch and Kibana with SAML authentication in both cases**. For roles, stateful deployments define 'viewer', 'editor', and 'admin' roles with serverless-alike privileges. New `samlAuth` service has `AuthProvider` interface with 2 different implementations: depending on environment context (serverless or stateful) appropriate implementation is used. But it remains on service level and hidden in test suite. test example ``` export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { const samlAuth = getService('samlAuth'); const supertestWithoutAuth = getService('supertestWithoutAuth'); let roleAuthc: RoleCredentials; let internalHeaders: InternalRequestHeader; describe('GET /api/console/api_server', () => { before(async () => { roleAuthc = await samlAuth.createM2mApiKeyWithRoleScope('admin'); internalHeaders = samlAuth.getInternalRequestHeader(); }); after(async () => { await samlAuth.invalidateM2mApiKeyWithRoleScope(roleAuthc); }); it('returns autocomplete definitions', async () => { const { body } = await supertestWithoutAuth .get('/api/console/api_server') .set(roleAuthc.apiKeyHeader) .set(internalHeaders) .set('kbn-xsrf', 'true') .expect(200); expect(body.es).to.be.ok(); const { es: { name, globals, endpoints }, } = body; expect(name).to.be.ok(); expect(Object.keys(globals).length).to.be.above(0); expect(Object.keys(endpoints).length).to.be.above(0); }); }); } ``` Please read [readme](https://github.com/elastic/kibana/blob/966822ac872c71284258faf61682176251bcf2c2/x-pack/test/api_integration/deployment_agnostic/README.md) for more details and step-by-step guide. It should help migrating existing serverless tests to deployment-agnostic, assuming requirements are met. ### Examples Deployment-agnostic tests: ``` x-pack/test/api_integration/deployment_agnostic/apis/console/spec_definitions.ts x-pack/test/api_integration/deployment_agnostic/apis/core/compression.ts x-pack/test/api_integration/deployment_agnostic/apis/painless_lab/painless_lab.ts ``` Configs to run it: ``` node scripts/functional_tests --config x-pack/test/api_integration/deployment_agnostic/oblt.serverless.config.ts node scripts/functional_tests --config x-pack/test/api_integration/deployment_agnostic/search.serverless.config.ts node scripts/functional_tests --config x-pack/test/api_integration/deployment_agnostic/security.serverless.config.ts node scripts/functional_tests --config x-pack/test/api_integration/deployment_agnostic/stateful.config.ts ``` PR is a compact version of #188737 with reduced changes in existing serverless tests. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: elena-shostak <165678770+elena-shostak@users.noreply.github.com> Co-authored-by: Aleh Zasypkin --- .buildkite/ftr_base_serverless_configs.yml | 2 + .buildkite/ftr_oblt_serverless_configs.yml | 2 + .buildkite/ftr_platform_stateful_configs.yml | 5 +- .buildkite/ftr_search_serverless_configs.yml | 2 + .../ftr_security_serverless_configs.yml | 2 + .eslintrc.js | 2 +- .github/CODEOWNERS | 1 + packages/kbn-es/index.ts | 2 +- packages/kbn-es/src/paths.ts | 2 + .../kbn-es/src/stateful_resources/roles.yml | 130 ++++++++++ .../index.ts | 2 + .../services/all.ts | 2 + .../saml_auth/default_request_headers.ts | 33 +++ .../services/saml_auth/get_auth_provider.ts | 53 +++++ .../services/saml_auth/index.ts | 10 + .../services/saml_auth/saml_auth_provider.ts | 97 ++++---- .../saml_auth/serverless/auth_provider.ts | 67 ++++++ .../saml_auth/stateful/admin_mapping.json | 46 ++++ .../saml_auth/stateful/auth_provider.ts | 37 +++ .../saml_auth/stateful/create_role_mapping.ts | 52 ++++ .../tsconfig.json | 6 +- packages/kbn-test/src/auth/helper.ts | 5 +- packages/kbn-test/src/auth/session_manager.ts | 34 +-- .../kbn-test/src/auth/sesson_manager.test.ts | 58 +++-- .../lib/config/run_check_ftr_configs_cli.ts | 3 +- test/api_integration/apis/console/index.ts | 1 - .../apis/console/spec_definitions.ts | 30 --- test/api_integration/apis/core/compression.ts | 60 ----- test/api_integration/apis/core/index.ts | 1 - test/common/services/index.ts | 9 +- .../cypress/support/saml_authentication.ts | 19 +- .../common/roles_users/serverless/index.ts | 3 + .../apis/painless_lab/config.ts | 17 -- .../deployment_agnostic/README.md | 183 ++++++++++++++ .../deployment_agnostic/apis/console/index.ts | 14 ++ .../apis/console/spec_definitions.ts | 42 ++++ .../apis/core/compression.ts | 47 ++++ .../deployment_agnostic/apis/core/index.ts | 14 ++ .../apis/painless_lab/index.ts | 4 +- .../apis/painless_lab/painless_lab.ts | 29 ++- .../default_configs/serverless.config.base.ts | 90 +++++++ .../default_configs/stateful.config.base.ts | 95 ++++++++ .../ftr_provider_context.d.ts | 12 + .../deployment_agnostic/oblt.index.ts | 15 ++ .../oblt.serverless.config.ts | 16 ++ .../deployment_agnostic/search.index.ts | 14 ++ .../search.serverless.config.ts | 16 ++ .../deployment_agnostic/security.index.ts | 15 ++ .../security.serverless.config.ts | 16 ++ .../services/data_view_api.ts | 66 ++++++ .../services/deployment_agnostic_services.ts | 28 +++ .../deployment_agnostic/services/index.ts | 26 ++ .../deployment_agnostic/services/slo_api.ts | 203 ++++++++++++++++ .../deployment_agnostic/stateful.config.ts | 18 ++ .../deployment_agnostic/stateful.index.ts | 16 ++ x-pack/test/scalability/config.ts | 4 +- .../test/scalability/ftr_provider_context.ts | 4 +- x-pack/test/scalability/services.ts | 17 ++ .../helpers/saml/idp_metadata_mock_idp.xml | 41 ++++ .../serverless/services_edr_workflows.ts | 4 +- .../security_solution_serverless_utils.ts | 6 + .../cypress/support/saml_auth.ts | 31 ++- .../cypress/tsconfig.json | 1 + .../services/index.ts | 4 +- .../security_solution_endpoint/tsconfig.json | 1 + x-pack/test/tsconfig.json | 3 +- .../common/console/autocomplete_entities.ts | 3 +- .../export_transform.ts | 223 ++++++++---------- .../common/saved_objects_management/find.ts | 42 ++-- .../test_serverless/shared/services/index.ts | 11 +- .../shared/services/svl_common_api.ts | 3 +- .../shared/services/svl_reporting.ts | 4 +- 72 files changed, 1756 insertions(+), 420 deletions(-) create mode 100644 packages/kbn-es/src/stateful_resources/roles.yml create mode 100644 packages/kbn-ftr-common-functional-services/services/saml_auth/default_request_headers.ts create mode 100644 packages/kbn-ftr-common-functional-services/services/saml_auth/get_auth_provider.ts create mode 100644 packages/kbn-ftr-common-functional-services/services/saml_auth/index.ts rename x-pack/test_serverless/shared/services/svl_user_manager.ts => packages/kbn-ftr-common-functional-services/services/saml_auth/saml_auth_provider.ts (61%) create mode 100644 packages/kbn-ftr-common-functional-services/services/saml_auth/serverless/auth_provider.ts create mode 100644 packages/kbn-ftr-common-functional-services/services/saml_auth/stateful/admin_mapping.json create mode 100644 packages/kbn-ftr-common-functional-services/services/saml_auth/stateful/auth_provider.ts create mode 100644 packages/kbn-ftr-common-functional-services/services/saml_auth/stateful/create_role_mapping.ts delete mode 100644 test/api_integration/apis/console/spec_definitions.ts delete mode 100644 test/api_integration/apis/core/compression.ts delete mode 100644 x-pack/test/api_integration/apis/painless_lab/config.ts create mode 100644 x-pack/test/api_integration/deployment_agnostic/README.md create mode 100644 x-pack/test/api_integration/deployment_agnostic/apis/console/index.ts create mode 100644 x-pack/test/api_integration/deployment_agnostic/apis/console/spec_definitions.ts create mode 100644 x-pack/test/api_integration/deployment_agnostic/apis/core/compression.ts create mode 100644 x-pack/test/api_integration/deployment_agnostic/apis/core/index.ts rename x-pack/test/api_integration/{ => deployment_agnostic}/apis/painless_lab/index.ts (67%) rename x-pack/test/api_integration/{ => deployment_agnostic}/apis/painless_lab/painless_lab.ts (60%) create mode 100644 x-pack/test/api_integration/deployment_agnostic/default_configs/serverless.config.base.ts create mode 100644 x-pack/test/api_integration/deployment_agnostic/default_configs/stateful.config.base.ts create mode 100644 x-pack/test/api_integration/deployment_agnostic/ftr_provider_context.d.ts create mode 100644 x-pack/test/api_integration/deployment_agnostic/oblt.index.ts create mode 100644 x-pack/test/api_integration/deployment_agnostic/oblt.serverless.config.ts create mode 100644 x-pack/test/api_integration/deployment_agnostic/search.index.ts create mode 100644 x-pack/test/api_integration/deployment_agnostic/search.serverless.config.ts create mode 100644 x-pack/test/api_integration/deployment_agnostic/security.index.ts create mode 100644 x-pack/test/api_integration/deployment_agnostic/security.serverless.config.ts create mode 100644 x-pack/test/api_integration/deployment_agnostic/services/data_view_api.ts create mode 100644 x-pack/test/api_integration/deployment_agnostic/services/deployment_agnostic_services.ts create mode 100644 x-pack/test/api_integration/deployment_agnostic/services/index.ts create mode 100644 x-pack/test/api_integration/deployment_agnostic/services/slo_api.ts create mode 100644 x-pack/test/api_integration/deployment_agnostic/stateful.config.ts create mode 100644 x-pack/test/api_integration/deployment_agnostic/stateful.index.ts create mode 100644 x-pack/test/scalability/services.ts create mode 100644 x-pack/test/security_api_integration/packages/helpers/saml/idp_metadata_mock_idp.xml diff --git a/.buildkite/ftr_base_serverless_configs.yml b/.buildkite/ftr_base_serverless_configs.yml index 5e7baeb3c3aea..2ff9ba6678462 100644 --- a/.buildkite/ftr_base_serverless_configs.yml +++ b/.buildkite/ftr_base_serverless_configs.yml @@ -1,6 +1,8 @@ disabled: # Base config files, only necessary to inform config finding script + # Serverless deployment-agnostic default config for api-integration tests + - x-pack/test/api_integration/deployment_agnostic/default_configs/serverless.config.base.ts # Serverless base config files - x-pack/test_serverless/api_integration/config.base.ts - x-pack/test_serverless/functional/config.base.ts diff --git a/.buildkite/ftr_oblt_serverless_configs.yml b/.buildkite/ftr_oblt_serverless_configs.yml index 085c25f2d80a6..8fe505ff0e93e 100644 --- a/.buildkite/ftr_oblt_serverless_configs.yml +++ b/.buildkite/ftr_oblt_serverless_configs.yml @@ -26,3 +26,5 @@ enabled: - x-pack/test_serverless/functional/test_suites/observability/common_configs/config.group5.ts - x-pack/test_serverless/functional/test_suites/observability/common_configs/config.group6.ts - x-pack/test_serverless/functional/test_suites/observability/config.screenshots.ts + # serverless config files that run deployment-agnostic tests + - x-pack/test/api_integration/deployment_agnostic/oblt.serverless.config.ts diff --git a/.buildkite/ftr_platform_stateful_configs.yml b/.buildkite/ftr_platform_stateful_configs.yml index 71ba8932271e8..a984afc263170 100644 --- a/.buildkite/ftr_platform_stateful_configs.yml +++ b/.buildkite/ftr_platform_stateful_configs.yml @@ -1,4 +1,6 @@ disabled: + # Stateful base config for deployment-agnostic tests + - x-pack/test/api_integration/deployment_agnostic/default_configs/stateful.config.base.ts # Base config files, only necessary to inform config finding script - test/functional/config.base.js - test/functional/firefox/config.base.ts @@ -155,7 +157,6 @@ enabled: - x-pack/test/api_integration/apis/monitoring/config.ts - x-pack/test/api_integration/apis/monitoring_collection/config.ts - x-pack/test/api_integration/apis/osquery/config.ts - - x-pack/test/api_integration/apis/painless_lab/config.ts - x-pack/test/api_integration/apis/search/config.ts - x-pack/test/api_integration/apis/searchprofiler/config.ts - x-pack/test/api_integration/apis/security/config.ts @@ -359,3 +360,5 @@ enabled: - x-pack/performance/journeys_e2e/apm_service_inventory.ts - x-pack/performance/journeys_e2e/infra_hosts_view.ts - x-pack/test/custom_branding/config.ts + # stateful config files that run deployment-agnostic tests + - x-pack/test/api_integration/deployment_agnostic/stateful.config.ts diff --git a/.buildkite/ftr_search_serverless_configs.yml b/.buildkite/ftr_search_serverless_configs.yml index 73b6238027bce..9a5ce6798dbae 100644 --- a/.buildkite/ftr_search_serverless_configs.yml +++ b/.buildkite/ftr_search_serverless_configs.yml @@ -16,3 +16,5 @@ enabled: - x-pack/test_serverless/functional/test_suites/search/common_configs/config.group4.ts - x-pack/test_serverless/functional/test_suites/search/common_configs/config.group5.ts - x-pack/test_serverless/functional/test_suites/search/common_configs/config.group6.ts + # serverless config files that run deployment-agnostic tests + - x-pack/test/api_integration/deployment_agnostic/search.serverless.config.ts diff --git a/.buildkite/ftr_security_serverless_configs.yml b/.buildkite/ftr_security_serverless_configs.yml index 3880175623fdd..4c3b037ce9f8a 100644 --- a/.buildkite/ftr_security_serverless_configs.yml +++ b/.buildkite/ftr_security_serverless_configs.yml @@ -97,3 +97,5 @@ enabled: - x-pack/test/security_solution_api_integration/test_suites/edr_workflows/response_actions/trial_license_complete_tier/configs/serverless.config.ts - x-pack/test/security_solution_endpoint/configs/serverless.endpoint.config.ts - x-pack/test/security_solution_endpoint/configs/serverless.integrations.config.ts + # serverless config files that run deployment-agnostic tests + - x-pack/test/api_integration/deployment_agnostic/security.serverless.config.ts diff --git a/.eslintrc.js b/.eslintrc.js index 853b1549d2b93..2b8c6c819bb3e 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -618,7 +618,7 @@ module.exports = { 'test/*/*.config.ts', 'test/*/{tests,test_suites,apis,apps}/**/*', 'test/server_integration/**/*.ts', - 'x-pack/test/*/{tests,test_suites,apis,apps}/**/*', + 'x-pack/test/*/{tests,test_suites,apis,apps,deployment_agnostic}/**/*', 'x-pack/test/*/*config.*ts', 'x-pack/test/saved_object_api_integration/*/apis/**/*', 'x-pack/test/ui_capabilities/*/tests/**/*', diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 8c044f50bfc7d..7a6d772a9a880 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1274,6 +1274,7 @@ x-pack/test/observability_ai_assistant_functional @elastic/obs-ai-assistant /x-pack/test_serverless/functional/test_suites/security/ftr/ @elastic/appex-qa /x-pack/test_serverless/functional/test_suites/common/home_page/ @elastic/appex-qa /x-pack/test_serverless/**/services/ @elastic/appex-qa +/packages/kbn-es/src/stateful_resources/roles.yml @elastic/appex-qa # Core /config/ @elastic/kibana-core diff --git a/packages/kbn-es/index.ts b/packages/kbn-es/index.ts index 2f241828f400d..12bb617f3ecfb 100644 --- a/packages/kbn-es/index.ts +++ b/packages/kbn-es/index.ts @@ -21,4 +21,4 @@ export { readRolesDescriptorsFromResource, } from './src/utils'; export type { ArtifactLicense } from './src/artifact'; -export { SERVERLESS_ROLES_ROOT_PATH } from './src/paths'; +export { SERVERLESS_ROLES_ROOT_PATH, STATEFUL_ROLES_ROOT_PATH } from './src/paths'; diff --git a/packages/kbn-es/src/paths.ts b/packages/kbn-es/src/paths.ts index 2ee3ea7c7c205..b407630333c7e 100644 --- a/packages/kbn-es/src/paths.ts +++ b/packages/kbn-es/src/paths.ts @@ -25,6 +25,8 @@ export const ES_CONFIG = 'config/elasticsearch.yml'; export const ES_KEYSTORE_BIN = maybeUseBat('./bin/elasticsearch-keystore'); +export const STATEFUL_ROLES_ROOT_PATH = resolve(__dirname, './stateful_resources'); + export const SERVERLESS_OPERATOR_USERS_PATH = resolve( __dirname, './serverless_resources/operator_users.yml' diff --git a/packages/kbn-es/src/stateful_resources/roles.yml b/packages/kbn-es/src/stateful_resources/roles.yml new file mode 100644 index 0000000000000..49ae1fafad958 --- /dev/null +++ b/packages/kbn-es/src/stateful_resources/roles.yml @@ -0,0 +1,130 @@ +# ----- +# This file is for information purpose only. 'viewer' and 'editor' roles are defined in stateful Elasticsearch by default +# Source: https://github.com/elastic/elasticsearch/blob/4272164530807787d4d8b991e3095a6e79176dbf/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStore.java#L861-L952 +# Note: inconsistency between these roles definition and the same roles of serverless project may break FTR deployment-agnostic tests +# ----- +viewer: + cluster: [] + indices: + - names: + - '.alerts*' + - '.preview.alerts*' + privileges: + - 'read' + - 'view_index_metadata' + allow_restricted_indices: false + - names: + - '.items-*' + - '.lists-*' + - '.siem-signals*' + privileges: + - 'read' + - 'view_index_metadata' + allow_restricted_indices: false + - names: + - '/~(([.]|ilm-history-).*)/' + privileges: + - 'read' + - 'view_index_metadata' + allow_restricted_indices: false + - names: + - '.profiling-*' + - 'profiling-*' + privileges: + - 'read' + - 'view_index_metadata' + allow_restricted_indices: false + applications: + - application: 'kibana-.kibana' + privileges: + - 'read' + resources: + - '*' + run_as: [] + +editor: + cluster: [] + indices: + - names: + - 'observability-annotations' + privileges: + - 'read' + - 'view_index_metadata' + - 'write' + allow_restricted_indices: false + - names: + - '.items-*' + - '.lists-*' + - '.siem-signals*' + privileges: + - 'maintenance' + - 'read' + - 'view_index_metadata' + - 'write' + allow_restricted_indices: false + - names: + - '/~(([.]|ilm-history-).*)/' + privileges: + - 'read' + - 'view_index_metadata' + allow_restricted_indices: false + - names: + - '.profiling-*' + - 'profiling-*' + privileges: + - 'read' + - 'view_index_metadata' + allow_restricted_indices: false + - names: + - '.alerts*' + - '.internal.alerts*' + - '.internal.preview.alerts*' + - '.preview.alerts*' + privileges: + - 'maintenance' + - 'read' + - 'view_index_metadata' + - 'write' + allow_restricted_indices: false + applications: + - application: 'kibana-.kibana' + privileges: + - 'all' + resources: + - '*' + run_as: [] + +# Admin role without 'remote_indices' access definition +# There is no such built-in role in stateful, and it's a role "similar" to the built-in 'admin' role in serverless +admin: + # TODO: 'all' should be replaced with explicit list both here and serverless for deployment-agnostic tests with 'admin' role to be compatible + cluster: ['all'] + indices: + - names: ['*'] + privileges: ['all'] + allow_restricted_indices: false + - names: ['*'] + privileges: + - 'monitor' + - 'read' + - 'read_cross_cluster' + - 'view_index_metadata' + allow_restricted_indices: true + applications: + - application: '*' + privileges: ['*'] + resources: ['*'] + run_as: ['*'] + +# temporarily added for testing purpose +system_indices_superuser: + cluster: ['all'] + indices: + - names: ['*'] + privileges: ['all'] + allow_restricted_indices: true + applications: + - application: '*' + privileges: ['*'] + resources: ['*'] + run_as: ['*'] diff --git a/packages/kbn-ftr-common-functional-services/index.ts b/packages/kbn-ftr-common-functional-services/index.ts index 2fabcc12227c5..4bd3eca34c45c 100644 --- a/packages/kbn-ftr-common-functional-services/index.ts +++ b/packages/kbn-ftr-common-functional-services/index.ts @@ -23,4 +23,6 @@ export type Es = ProvidedType; import { SupertestWithoutAuthProvider } from './services/supertest_without_auth'; export type SupertestWithoutAuthProviderType = ProvidedType; +export type { InternalRequestHeader, RoleCredentials } from './services/saml_auth'; + export type { FtrProviderContext } from './services/ftr_provider_context'; diff --git a/packages/kbn-ftr-common-functional-services/services/all.ts b/packages/kbn-ftr-common-functional-services/services/all.ts index 128d50731081d..49308faeb3dd0 100644 --- a/packages/kbn-ftr-common-functional-services/services/all.ts +++ b/packages/kbn-ftr-common-functional-services/services/all.ts @@ -11,6 +11,7 @@ import { EsProvider } from './es'; import { KibanaServerProvider } from './kibana_server'; import { RetryService } from './retry'; import { SupertestWithoutAuthProvider } from './supertest_without_auth'; +import { SamlAuthProvider } from './saml_auth'; export const services = { es: EsProvider, @@ -18,4 +19,5 @@ export const services = { esArchiver: EsArchiverProvider, retry: RetryService, supertestWithoutAuth: SupertestWithoutAuthProvider, + samlAuth: SamlAuthProvider, }; diff --git a/packages/kbn-ftr-common-functional-services/services/saml_auth/default_request_headers.ts b/packages/kbn-ftr-common-functional-services/services/saml_auth/default_request_headers.ts new file mode 100644 index 0000000000000..95983647647f3 --- /dev/null +++ b/packages/kbn-ftr-common-functional-services/services/saml_auth/default_request_headers.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. + */ + +export const COMMON_REQUEST_HEADERS = { + 'kbn-xsrf': 'some-xsrf-token', +}; + +// possible change in 9.0 to match serverless +const STATEFUL_INTERNAL_REQUEST_HEADERS = { + ...COMMON_REQUEST_HEADERS, +}; + +const SERVERLESS_INTERNAL_REQUEST_HEADERS = { + ...COMMON_REQUEST_HEADERS, + 'x-elastic-internal-origin': 'kibana', +}; + +export type InternalRequestHeader = + | typeof STATEFUL_INTERNAL_REQUEST_HEADERS + | typeof SERVERLESS_INTERNAL_REQUEST_HEADERS; + +export const getServerlessInternalRequestHeaders = (): InternalRequestHeader => { + return SERVERLESS_INTERNAL_REQUEST_HEADERS; +}; + +export const getStatefulInternalRequestHeaders = (): InternalRequestHeader => { + return STATEFUL_INTERNAL_REQUEST_HEADERS; +}; diff --git a/packages/kbn-ftr-common-functional-services/services/saml_auth/get_auth_provider.ts b/packages/kbn-ftr-common-functional-services/services/saml_auth/get_auth_provider.ts new file mode 100644 index 0000000000000..3f1ec7af917ff --- /dev/null +++ b/packages/kbn-ftr-common-functional-services/services/saml_auth/get_auth_provider.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 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 fs from 'fs'; +import { type Config } from '@kbn/test'; +import { ToolingLog } from '@kbn/tooling-log'; +import { MOCK_IDP_REALM_NAME } from '@kbn/mock-idp-utils'; +import { KibanaServer } from '../..'; + +import { ServerlessAuthProvider } from './serverless/auth_provider'; +import { StatefulAuthProvider } from './stateful/auth_provider'; +import { createRole, createRoleMapping } from './stateful/create_role_mapping'; + +const STATEFUL_ADMIN_ROLE_MAPPING_PATH = './stateful/admin_mapping'; + +export interface AuthProvider { + getSupportedRoleDescriptors(): any; + getDefaultRole(): string; + getRolesDefinitionPath(): string; + getCommonRequestHeader(): { [key: string]: string }; + getInternalRequestHeader(): { [key: string]: string }; +} + +export interface AuthProviderProps { + config: Config; + kibanaServer: KibanaServer; + log: ToolingLog; +} + +export const getAuthProvider = async (props: AuthProviderProps) => { + const { config, log, kibanaServer } = props; + const isServerless = !!props.config.get('serverless'); + if (isServerless) { + return new ServerlessAuthProvider(config); + } + + const provider = new StatefulAuthProvider(); + // TODO: Move it to @kbn-es package, so that roles and its mapping are created before FTR services loading starts. + // 'viewer' and 'editor' roles are available by default, but we have to create 'admin' role + const adminRoleMapping = JSON.parse( + fs.readFileSync(require.resolve(STATEFUL_ADMIN_ROLE_MAPPING_PATH), 'utf8') + ); + await createRole({ roleName: 'admin', roleMapping: adminRoleMapping, kibanaServer, log }); + const roles = Object.keys(provider.getSupportedRoleDescriptors()); + // Creating roles mapping for mock-idp + await createRoleMapping({ name: MOCK_IDP_REALM_NAME, roles, config, log }); + return provider; +}; diff --git a/packages/kbn-ftr-common-functional-services/services/saml_auth/index.ts b/packages/kbn-ftr-common-functional-services/services/saml_auth/index.ts new file mode 100644 index 0000000000000..15735d4bffcbb --- /dev/null +++ b/packages/kbn-ftr-common-functional-services/services/saml_auth/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 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 { SamlAuthProvider } from './saml_auth_provider'; +export type { RoleCredentials } from './saml_auth_provider'; +export type { InternalRequestHeader } from './default_request_headers'; diff --git a/x-pack/test_serverless/shared/services/svl_user_manager.ts b/packages/kbn-ftr-common-functional-services/services/saml_auth/saml_auth_provider.ts similarity index 61% rename from x-pack/test_serverless/shared/services/svl_user_manager.ts rename to packages/kbn-ftr-common-functional-services/services/saml_auth/saml_auth_provider.ts index 2bb8cbc5fab40..4e79cad656197 100644 --- a/x-pack/test_serverless/shared/services/svl_user_manager.ts +++ b/packages/kbn-ftr-common-functional-services/services/saml_auth/saml_auth_provider.ts @@ -1,18 +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. + * 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 { ServerlessProjectType, SERVERLESS_ROLES_ROOT_PATH } from '@kbn/es'; import { SamlSessionManager } from '@kbn/test'; -import { readRolesDescriptorsFromResource } from '@kbn/es'; -import { resolve } from 'path'; -import { Role } from '@kbn/test/src/auth/types'; -import { isServerlessProjectType } from '@kbn/es/src/utils'; import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../functional/ftr_provider_context'; +import { REPO_ROOT } from '@kbn/repo-info'; +import { resolve } from 'path'; +import { FtrProviderContext } from '../ftr_provider_context'; +import { getAuthProvider } from './get_auth_provider'; +import { InternalRequestHeader } from './default_request_headers'; export interface RoleCredentials { apiKey: { id: string; name: string }; @@ -20,63 +20,41 @@ export interface RoleCredentials { cookieHeader: { Cookie: string }; } -export function SvlUserManagerProvider({ getService }: FtrProviderContext) { +export async function SamlAuthProvider({ getService }: FtrProviderContext) { const config = getService('config'); const log = getService('log'); - const svlCommonApi = getService('svlCommonApi'); + const kibanaServer = getService('kibanaServer'); const supertestWithoutAuth = getService('supertestWithoutAuth'); const isCloud = !!process.env.TEST_CLOUD; - const kbnServerArgs = config.get('kbnTestServer.serverArgs') as string[]; - const projectType = kbnServerArgs - .filter((arg) => arg.startsWith('--serverless')) - .reduce((acc, arg) => { - const match = arg.match(/--serverless[=\s](\w+)/); - return acc + (match ? match[1] : ''); - }, '') as ServerlessProjectType; - - if (!isServerlessProjectType(projectType)) { - throw new Error(`Unsupported serverless projectType: ${projectType}`); - } - - const supportedRoleDescriptors = readRolesDescriptorsFromResource( - resolve(SERVERLESS_ROLES_ROOT_PATH, projectType, 'roles.yml') - ); + const authRoleProvider = await getAuthProvider({ config, kibanaServer, log }); + const supportedRoleDescriptors = authRoleProvider.getSupportedRoleDescriptors(); const supportedRoles = Object.keys(supportedRoleDescriptors); - const defaultRolesToMap = new Map([ - ['es', 'developer'], - ['security', 'editor'], - ['oblt', 'editor'], - ]); - - const getDefaultRole = () => { - if (defaultRolesToMap.has(projectType)) { - return defaultRolesToMap.get(projectType)!; - } else { - throw new Error(`Default role is not defined for ${projectType} project`); - } - }; - const customRolesFileName: string | undefined = process.env.ROLES_FILENAME_OVERRIDE; + const cloudUsersFilePath = resolve(REPO_ROOT, '.ftr', customRolesFileName ?? 'role_users.json'); + // Sharing the instance within FTR config run means cookies are persistent for each role between tests. - const sessionManager = new SamlSessionManager( - { - hostOptions: { - protocol: config.get('servers.kibana.protocol'), - hostname: config.get('servers.kibana.hostname'), - port: isCloud ? undefined : config.get('servers.kibana.port'), - username: config.get('servers.kibana.username'), - password: config.get('servers.kibana.password'), - }, - log, - isCloud, - supportedRoles, + const sessionManager = new SamlSessionManager({ + hostOptions: { + protocol: config.get('servers.kibana.protocol'), + hostname: config.get('servers.kibana.hostname'), + port: isCloud ? undefined : config.get('servers.kibana.port'), + username: config.get('servers.kibana.username'), + password: config.get('servers.kibana.password'), }, - customRolesFileName - ); + log, + isCloud, + supportedRoles: { + roles: supportedRoles, + sourcePath: authRoleProvider.getRolesDefinitionPath(), + }, + cloudUsersFilePath, + }); - const DEFAULT_ROLE = getDefaultRole(); + const DEFAULT_ROLE = authRoleProvider.getDefaultRole(); + const COMMON_REQUEST_HEADERS = authRoleProvider.getCommonRequestHeader(); + const INTERNAL_REQUEST_HEADERS = authRoleProvider.getInternalRequestHeader(); return { async getInteractiveUserSessionCookieWithRoleScope(role: string) { @@ -119,7 +97,7 @@ export function SvlUserManagerProvider({ getService }: FtrProviderContext) { const { body, status } = await supertestWithoutAuth .post('/internal/security/api_key') - .set(svlCommonApi.getInternalRequestHeader()) + .set(INTERNAL_REQUEST_HEADERS) .set(adminCookieHeader) .send({ name: 'myTestApiKey', @@ -147,12 +125,19 @@ export function SvlUserManagerProvider({ getService }: FtrProviderContext) { const { status } = await supertestWithoutAuth .post('/internal/security/api_key/invalidate') - .set(svlCommonApi.getInternalRequestHeader()) + .set(INTERNAL_REQUEST_HEADERS) .set(roleCredentials.cookieHeader) .send(requestBody); expect(status).to.be(200); }, + getCommonRequestHeader() { + return COMMON_REQUEST_HEADERS; + }, + + getInternalRequestHeader(): InternalRequestHeader { + return INTERNAL_REQUEST_HEADERS; + }, DEFAULT_ROLE, }; } diff --git a/packages/kbn-ftr-common-functional-services/services/saml_auth/serverless/auth_provider.ts b/packages/kbn-ftr-common-functional-services/services/saml_auth/serverless/auth_provider.ts new file mode 100644 index 0000000000000..eecbe3a5862f2 --- /dev/null +++ b/packages/kbn-ftr-common-functional-services/services/saml_auth/serverless/auth_provider.ts @@ -0,0 +1,67 @@ +/* + * 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 { ServerlessProjectType, SERVERLESS_ROLES_ROOT_PATH } from '@kbn/es'; +import { type Config } from '@kbn/test'; +import { isServerlessProjectType, readRolesDescriptorsFromResource } from '@kbn/es/src/utils'; +import { resolve } from 'path'; +import { Role } from '@kbn/test/src/auth/types'; +import { + getServerlessInternalRequestHeaders, + COMMON_REQUEST_HEADERS, +} from '../default_request_headers'; +import { AuthProvider } from '../get_auth_provider'; + +const projectDefaultRoles = new Map([ + ['es', 'developer'], + ['security', 'editor'], + ['oblt', 'editor'], +]); + +const getDefaultServerlessRole = (projectType: string) => { + if (projectDefaultRoles.has(projectType)) { + return projectDefaultRoles.get(projectType)!; + } else { + throw new Error(`Default role is not defined for ${projectType} project`); + } +}; + +export class ServerlessAuthProvider implements AuthProvider { + private readonly projectType: string; + private readonly rolesDefinitionPath: string; + + constructor(config: Config) { + const kbnServerArgs = config.get('kbnTestServer.serverArgs') as string[]; + this.projectType = kbnServerArgs.reduce((acc, arg) => { + const match = arg.match(/--serverless[=\s](\w+)/); + return acc + (match ? match[1] : ''); + }, '') as ServerlessProjectType; + + if (!isServerlessProjectType(this.projectType)) { + throw new Error(`Unsupported serverless projectType: ${this.projectType}`); + } + + this.rolesDefinitionPath = resolve(SERVERLESS_ROLES_ROOT_PATH, this.projectType, 'roles.yml'); + } + + getSupportedRoleDescriptors(): any { + return readRolesDescriptorsFromResource(this.rolesDefinitionPath); + } + getDefaultRole(): string { + return getDefaultServerlessRole(this.projectType); + } + getRolesDefinitionPath(): string { + return this.rolesDefinitionPath; + } + getCommonRequestHeader() { + return COMMON_REQUEST_HEADERS; + } + getInternalRequestHeader() { + return getServerlessInternalRequestHeaders(); + } +} diff --git a/packages/kbn-ftr-common-functional-services/services/saml_auth/stateful/admin_mapping.json b/packages/kbn-ftr-common-functional-services/services/saml_auth/stateful/admin_mapping.json new file mode 100644 index 0000000000000..f8544f16bd239 --- /dev/null +++ b/packages/kbn-ftr-common-functional-services/services/saml_auth/stateful/admin_mapping.json @@ -0,0 +1,46 @@ +{ + "kibana":[ + { + "base":[ + "all" + ], + "feature":{ + + }, + "spaces":[ + "*" + ] + } + ], + "elasticsearch":{ + "cluster":[ + "all" + ], + "indices":[ + { + "names":[ + "*" + ], + "privileges":[ + "all" + ], + "allow_restricted_indices":false + }, + { + "names":[ + "*" + ], + "privileges":[ + "monitor", + "read", + "read_cross_cluster", + "view_index_metadata" + ], + "allow_restricted_indices":true + } + ], + "run_as":[ + + ] + } +} \ No newline at end of file diff --git a/packages/kbn-ftr-common-functional-services/services/saml_auth/stateful/auth_provider.ts b/packages/kbn-ftr-common-functional-services/services/saml_auth/stateful/auth_provider.ts new file mode 100644 index 0000000000000..cf27fd9d5d506 --- /dev/null +++ b/packages/kbn-ftr-common-functional-services/services/saml_auth/stateful/auth_provider.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { readRolesDescriptorsFromResource, STATEFUL_ROLES_ROOT_PATH } from '@kbn/es'; +import { REPO_ROOT } from '@kbn/repo-info'; +import { resolve } from 'path'; +import { AuthProvider } from '../get_auth_provider'; +import { + getStatefulInternalRequestHeaders, + COMMON_REQUEST_HEADERS, +} from '../default_request_headers'; + +export class StatefulAuthProvider implements AuthProvider { + private readonly rolesDefinitionPath = resolve(REPO_ROOT, STATEFUL_ROLES_ROOT_PATH, 'roles.yml'); + getSupportedRoleDescriptors(): any { + return readRolesDescriptorsFromResource(this.rolesDefinitionPath); + } + getDefaultRole() { + return 'editor'; + } + getRolesDefinitionPath() { + return this.rolesDefinitionPath; + } + + getCommonRequestHeader() { + return COMMON_REQUEST_HEADERS; + } + + getInternalRequestHeader() { + return getStatefulInternalRequestHeaders(); + } +} diff --git a/packages/kbn-ftr-common-functional-services/services/saml_auth/stateful/create_role_mapping.ts b/packages/kbn-ftr-common-functional-services/services/saml_auth/stateful/create_role_mapping.ts new file mode 100644 index 0000000000000..1e9b897362dc6 --- /dev/null +++ b/packages/kbn-ftr-common-functional-services/services/saml_auth/stateful/create_role_mapping.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { Config, createEsClientForFtrConfig } from '@kbn/test'; +import { ToolingLog } from '@kbn/tooling-log'; +import { KibanaServer } from '../../..'; + +export interface CreateRoleProps { + roleName: string; + roleMapping: string[]; + kibanaServer: KibanaServer; + log: ToolingLog; +} + +export interface CreateRoleMappingProps { + name: string; + roles: string[]; + config: Config; + log: ToolingLog; +} + +export async function createRole(props: CreateRoleProps) { + const { roleName, roleMapping, kibanaServer, log } = props; + log.debug(`Adding a role: ${roleName}`); + const { status, statusText } = await kibanaServer.request({ + path: `/api/security/role/${roleName}`, + method: 'PUT', + body: roleMapping, + retries: 0, + }); + if (status !== 204) { + throw new Error(`Expected status code of 204, received ${status} ${statusText}`); + } +} + +export async function createRoleMapping(props: CreateRoleMappingProps) { + const { name, roles, config, log } = props; + log.debug(`Creating a role mapping: {realm.name: ${name}, roles: ${roles}}`); + const esClient = createEsClientForFtrConfig(config); + await esClient.security.putRoleMapping({ + name, + roles, + enabled: true, + // @ts-ignore + rules: { field: { 'realm.name': name } }, + }); +} diff --git a/packages/kbn-ftr-common-functional-services/tsconfig.json b/packages/kbn-ftr-common-functional-services/tsconfig.json index 3641c807e4d6d..ff10ec6c9d5fb 100644 --- a/packages/kbn-ftr-common-functional-services/tsconfig.json +++ b/packages/kbn-ftr-common-functional-services/tsconfig.json @@ -14,7 +14,11 @@ "@kbn/core-saved-objects-server", "@kbn/tooling-log", "@kbn/es-archiver", - "@kbn/test" + "@kbn/test", + "@kbn/expect", + "@kbn/repo-info", + "@kbn/es", + "@kbn/mock-idp-utils" ], "exclude": [ "target/**/*", diff --git a/packages/kbn-test/src/auth/helper.ts b/packages/kbn-test/src/auth/helper.ts index fc32eab773c8e..3c8374911af8c 100644 --- a/packages/kbn-test/src/auth/helper.ts +++ b/packages/kbn-test/src/auth/helper.ts @@ -10,12 +10,13 @@ import * as fs from 'fs'; import { Role, User } from './types'; export const readCloudUsersFromFile = (filePath: string): Array<[Role, User]> => { + const defaultMessage = `Cannot read roles and email/password from ${filePath}`; if (!fs.existsSync(filePath)) { - throw new Error(`Please define user roles with email/password in ${filePath}`); + throw new Error(`${defaultMessage}: file does not exist`); } const data = fs.readFileSync(filePath, 'utf8'); if (data.length === 0) { - throw new Error(`'${filePath}' is empty: no roles are defined`); + throw new Error(`${defaultMessage}: file is empty`); } return Object.entries(JSON.parse(data)) as Array<[Role, User]>; diff --git a/packages/kbn-test/src/auth/session_manager.ts b/packages/kbn-test/src/auth/session_manager.ts index 40beccdfc3c6c..fb12e181e8a1e 100644 --- a/packages/kbn-test/src/auth/session_manager.ts +++ b/packages/kbn-test/src/auth/session_manager.ts @@ -6,10 +6,7 @@ * Side Public License, v 1. */ -import { SERVERLESS_ROLES_ROOT_PATH } from '@kbn/es'; -import { REPO_ROOT } from '@kbn/repo-info'; import { ToolingLog } from '@kbn/tooling-log'; -import { resolve } from 'path'; import Url from 'url'; import { KbnClient } from '../kbn_client'; import { readCloudUsersFromFile } from './helper'; @@ -32,31 +29,32 @@ export interface HostOptions { export interface SamlSessionManagerOptions { hostOptions: HostOptions; isCloud: boolean; - supportedRoles?: string[]; + supportedRoles?: SupportedRoles; + cloudUsersFilePath: string; log: ToolingLog; } +export interface SupportedRoles { + sourcePath: string; + roles: string[]; +} + /** * Manages cookies associated with user roles */ export class SamlSessionManager { - private readonly DEFAULT_ROLES_FILE_NAME: string = 'role_users.json'; private readonly isCloud: boolean; private readonly kbnHost: string; private readonly kbnClient: KbnClient; private readonly log: ToolingLog; private readonly roleToUserMap: Map; private readonly sessionCache: Map; - private readonly supportedRoles: string[]; - private readonly userRoleFilePath: string; + private readonly supportedRoles?: SupportedRoles; + private readonly cloudUsersFilePath: string; - constructor(options: SamlSessionManagerOptions, rolesFilename?: string) { + constructor(options: SamlSessionManagerOptions) { this.isCloud = options.isCloud; this.log = options.log; - // if the rolesFilename is provided, respect it. Otherwise use DEFAULT_ROLES_FILE_NAME. - const rolesFile = rolesFilename ? rolesFilename : this.DEFAULT_ROLES_FILE_NAME; - this.log.info(`Using the file ${rolesFile} for the role users`); - this.userRoleFilePath = resolve(REPO_ROOT, '.ftr', rolesFile); const hostOptionsWithoutAuth = { protocol: options.hostOptions.protocol, hostname: options.hostOptions.hostname, @@ -70,9 +68,10 @@ export class SamlSessionManager { auth: `${options.hostOptions.username}:${options.hostOptions.password}`, }), }); + this.cloudUsersFilePath = options.cloudUsersFilePath; this.sessionCache = new Map(); this.roleToUserMap = new Map(); - this.supportedRoles = options.supportedRoles ?? []; + this.supportedRoles = options.supportedRoles; } /** @@ -81,7 +80,8 @@ export class SamlSessionManager { */ private getCloudUsers = () => { if (this.roleToUserMap.size === 0) { - const data = readCloudUsersFromFile(this.userRoleFilePath); + this.log.info(`Reading cloud user credentials from ${this.cloudUsersFilePath}`); + const data = readCloudUsersFromFile(this.cloudUsersFilePath); for (const [roleName, user] of data) { this.roleToUserMap.set(roleName, user); } @@ -104,11 +104,11 @@ export class SamlSessionManager { } // Validate role before creating SAML session - if (this.supportedRoles.length && !this.supportedRoles.includes(role)) { + if (this.supportedRoles && !this.supportedRoles.roles.includes(role)) { throw new Error( - `Role '${role}' is not defined in the supported list: ${this.supportedRoles.join( + `Role '${role}' is not in the supported list: ${this.supportedRoles.roles.join( ', ' - )}. Update roles resource file in ${SERVERLESS_ROLES_ROOT_PATH} to enable it for testing` + )}. Add role descriptor in ${this.supportedRoles.sourcePath} to enable it for testing` ); } diff --git a/packages/kbn-test/src/auth/sesson_manager.test.ts b/packages/kbn-test/src/auth/sesson_manager.test.ts index 1f04620584507..929517e7c5a10 100644 --- a/packages/kbn-test/src/auth/sesson_manager.test.ts +++ b/packages/kbn-test/src/auth/sesson_manager.test.ts @@ -9,17 +9,23 @@ import { ToolingLog } from '@kbn/tooling-log'; import { Cookie } from 'tough-cookie'; import { Session } from './saml_auth'; -import { SamlSessionManager } from './session_manager'; +import { SamlSessionManager, SupportedRoles } from './session_manager'; import * as samlAuth from './saml_auth'; import * as helper from './helper'; import { Role, User, UserProfile } from './types'; import { SERVERLESS_ROLES_ROOT_PATH } from '@kbn/es'; +import { resolve } from 'path'; +import { REPO_ROOT } from '@kbn/repo-info'; const log = new ToolingLog(); -const supportedRoles = ['admin', 'editor', 'viewer']; +const supportedRoles: SupportedRoles = { + roles: ['admin', 'editor', 'viewer'], + sourcePath: 'test/roles.yml', +}; const roleViewer = 'viewer'; const roleEditor = 'editor'; +const cloudUsersFilePath = resolve(REPO_ROOT, SERVERLESS_ROLES_ROOT_PATH, 'role_users.json'); const createLocalSAMLSessionMock = jest.spyOn(samlAuth, 'createLocalSAMLSession'); const createCloudSAMLSessionMock = jest.spyOn(samlAuth, 'createCloudSAMLSession'); @@ -58,7 +64,7 @@ describe('SamlSessionManager', () => { hostOptions, isCloud, log, - supportedRoles, + cloudUsersFilePath, }; const testEmail = 'testuser@elastic.com'; const testFullname = 'Test User'; @@ -67,7 +73,7 @@ describe('SamlSessionManager', () => { )!; test('should create an instance of SamlSessionManager', () => { - const samlSessionManager = new SamlSessionManager({ hostOptions, log, isCloud }); + const samlSessionManager = new SamlSessionManager(samlSessionManagerOptions); expect(samlSessionManager).toBeInstanceOf(SamlSessionManager); }); @@ -118,10 +124,13 @@ describe('SamlSessionManager', () => { test(`throws error when role is not in 'supportedRoles'`, async () => { const nonExistingRole = 'tester'; - const expectedErrorMessage = `Role '${nonExistingRole}' is not defined in the supported list: ${supportedRoles.join( + const expectedErrorMessage = `Role '${nonExistingRole}' is not in the supported list: ${supportedRoles.roles.join( ', ' - )}. Update roles resource file in ${SERVERLESS_ROLES_ROOT_PATH} to enable it for testing`; - const samlSessionManager = new SamlSessionManager(samlSessionManagerOptions); + )}. Add role descriptor in ${supportedRoles.sourcePath} to enable it for testing`; + const samlSessionManager = new SamlSessionManager({ + ...samlSessionManagerOptions, + supportedRoles, + }); await expect( samlSessionManager.getInteractiveUserSessionCookieWithRoleScope(nonExistingRole) ).rejects.toThrow(expectedErrorMessage); @@ -145,11 +154,7 @@ describe('SamlSessionManager', () => { elastic_cloud_user: false, }; getSecurityProfileMock.mockResolvedValueOnce(testData); - const samlSessionManager = new SamlSessionManager({ - hostOptions, - log, - isCloud, - }); + const samlSessionManager = new SamlSessionManager(samlSessionManagerOptions); await samlSessionManager.getInteractiveUserSessionCookieWithRoleScope(nonExistingRole); await samlSessionManager.getApiCredentialsForRole(nonExistingRole); await samlSessionManager.getUserData(nonExistingRole); @@ -171,7 +176,7 @@ describe('SamlSessionManager', () => { hostOptions, isCloud, log, - supportedRoles, + cloudUsersFilePath, }; const cloudCookieInstance = Cookie.parse( 'sid=cloud_cookie_value; Path=/; Expires=Wed, 01 Oct 2023 07:00:00 GMT' @@ -195,11 +200,7 @@ describe('SamlSessionManager', () => { test('should throw error if TEST_CLOUD_HOST_NAME is not set', async () => { isValidHostnameMock.mockReturnValueOnce(false); - const samlSessionManager = new SamlSessionManager({ - hostOptions, - log, - isCloud, - }); + const samlSessionManager = new SamlSessionManager(samlSessionManagerOptions); await expect( samlSessionManager.getInteractiveUserSessionCookieWithRoleScope(roleViewer) ).rejects.toThrow( @@ -220,11 +221,7 @@ describe('SamlSessionManager', () => { }); test('should create an instance of SamlSessionManager', () => { - const samlSessionManager = new SamlSessionManager({ - hostOptions, - log, - isCloud, - }); + const samlSessionManager = new SamlSessionManager(samlSessionManagerOptions); expect(samlSessionManager).toBeInstanceOf(SamlSessionManager); }); @@ -276,10 +273,13 @@ describe('SamlSessionManager', () => { test(`throws error for non-existing role when 'supportedRoles' is defined`, async () => { const nonExistingRole = 'tester'; - const expectedErrorMessage = `Role '${nonExistingRole}' is not defined in the supported list: ${supportedRoles.join( + const expectedErrorMessage = `Role '${nonExistingRole}' is not in the supported list: ${supportedRoles.roles.join( ', ' - )}. Update roles resource file in ${SERVERLESS_ROLES_ROOT_PATH} to enable it for testing`; - const samlSessionManager = new SamlSessionManager(samlSessionManagerOptions); + )}. Add role descriptor in ${supportedRoles.sourcePath} to enable it for testing`; + const samlSessionManager = new SamlSessionManager({ + ...samlSessionManagerOptions, + supportedRoles, + }); await expect( samlSessionManager.getInteractiveUserSessionCookieWithRoleScope(nonExistingRole) ).rejects.toThrow(expectedErrorMessage); @@ -294,11 +294,7 @@ describe('SamlSessionManager', () => { test(`throws error for non-existing role when 'supportedRoles' is not defined`, async () => { const nonExistingRole = 'tester'; - const samlSessionManager = new SamlSessionManager({ - hostOptions, - log, - isCloud, - }); + const samlSessionManager = new SamlSessionManager(samlSessionManagerOptions); await expect( samlSessionManager.getInteractiveUserSessionCookieWithRoleScope(nonExistingRole) ).rejects.toThrow(`User with '${nonExistingRole}' role is not defined`); diff --git a/packages/kbn-test/src/functional_test_runner/lib/config/run_check_ftr_configs_cli.ts b/packages/kbn-test/src/functional_test_runner/lib/config/run_check_ftr_configs_cli.ts index 7176d21b8f0b3..309cac56ca30c 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/config/run_check_ftr_configs_cli.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/config/run_check_ftr_configs_cli.ts @@ -125,7 +125,8 @@ export async function runCheckFtrConfigsCli() { const invalid = possibleConfigs.filter((path) => !allFtrConfigs.includes(path)); if (invalid.length) { - const invalidList = invalid.map((path) => Path.relative(REPO_ROOT, path)).join('\n - '); + const invalidList = + ' - ' + invalid.map((path) => Path.relative(REPO_ROOT, path)).join('\n - '); log.error( `The following files look like FTR configs which are not listed in one of manifest files:\n${invalidList}\n Make sure to add your new FTR config to the correct manifest file.\n diff --git a/test/api_integration/apis/console/index.ts b/test/api_integration/apis/console/index.ts index f39cf6cabb129..4e4d97b8204c8 100644 --- a/test/api_integration/apis/console/index.ts +++ b/test/api_integration/apis/console/index.ts @@ -13,6 +13,5 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./proxy_route')); loadTestFile(require.resolve('./autocomplete_entities')); loadTestFile(require.resolve('./es_config')); - loadTestFile(require.resolve('./spec_definitions')); }); } diff --git a/test/api_integration/apis/console/spec_definitions.ts b/test/api_integration/apis/console/spec_definitions.ts deleted file mode 100644 index f8e56354f6319..0000000000000 --- a/test/api_integration/apis/console/spec_definitions.ts +++ /dev/null @@ -1,30 +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 expect from '@kbn/expect'; -import { FtrProviderContext } from '../../ftr_provider_context'; - -export default function ({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - - describe('GET /api/console/api_server', () => { - it('returns autocomplete definitions', async () => { - const { body } = await supertest - .get('/api/console/api_server') - .set('kbn-xsrf', 'true') - .expect(200); - expect(body.es).to.be.ok(); - const { - es: { name, globals, endpoints }, - } = body; - expect(name).to.be.ok(); - expect(Object.keys(globals).length).to.be.above(0); - expect(Object.keys(endpoints).length).to.be.above(0); - }); - }); -} diff --git a/test/api_integration/apis/core/compression.ts b/test/api_integration/apis/core/compression.ts deleted file mode 100644 index c4b119692f4bb..0000000000000 --- a/test/api_integration/apis/core/compression.ts +++ /dev/null @@ -1,60 +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 expect from '@kbn/expect'; -import { FtrProviderContext } from '../../ftr_provider_context'; - -export default function ({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - - const compressionSuite = (url: string) => { - it(`uses compression when there isn't a referer`, async () => { - await supertest - .get(url) - .set('accept-encoding', 'gzip') - .then((response) => { - expect(response.header).to.have.property('content-encoding', 'gzip'); - }); - }); - - it(`uses compression when there is a whitelisted referer`, async () => { - await supertest - .get(url) - .set('accept-encoding', 'gzip') - .set('referer', 'https://some-host.com') - .then((response) => { - expect(response.header).to.have.property('content-encoding', 'gzip'); - }); - }); - - it(`doesn't use compression when there is a non-whitelisted referer`, async () => { - await supertest - .get(url) - .set('accept-encoding', 'gzip') - .set('referer', 'https://other.some-host.com') - .then((response) => { - expect(response.header).not.to.have.property('content-encoding'); - }); - }); - - it(`supports brotli compression`, async () => { - await supertest - .get(url) - .set('accept-encoding', 'br') - .then((response) => { - expect(response.header).to.have.property('content-encoding', 'br'); - }); - }); - }; - - describe('compression', () => { - describe('against an application page', () => { - compressionSuite('/app/kibana'); - }); - }); -} diff --git a/test/api_integration/apis/core/index.ts b/test/api_integration/apis/core/index.ts index 65c8a5d5cb5c5..98010e01c8215 100644 --- a/test/api_integration/apis/core/index.ts +++ b/test/api_integration/apis/core/index.ts @@ -10,7 +10,6 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('core', () => { - loadTestFile(require.resolve('./compression')); loadTestFile(require.resolve('./translations')); loadTestFile(require.resolve('./capabilities')); }); diff --git a/test/common/services/index.ts b/test/common/services/index.ts index ad08829afd047..ccc786d4ccc6e 100644 --- a/test/common/services/index.ts +++ b/test/common/services/index.ts @@ -16,8 +16,15 @@ import { IndexPatternsService } from './index_patterns'; import { BsearchService } from './bsearch'; import { ConsoleProvider } from './console'; +// pick only services that work for any FTR config, e.g. 'samlAuth' requires SAML setup in config file +const { es, esArchiver, kibanaServer, retry, supertestWithoutAuth } = commonFunctionalServices; + export const services = { - ...commonFunctionalServices, + es, + esArchiver, + kibanaServer, + retry, + supertestWithoutAuth, deployment: DeploymentService, randomness: RandomnessService, security: SecurityServiceProvider, diff --git a/x-pack/plugins/security_solution/public/management/cypress/support/saml_authentication.ts b/x-pack/plugins/security_solution/public/management/cypress/support/saml_authentication.ts index a7085494ab6e8..526c430920ea6 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/support/saml_authentication.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/support/saml_authentication.ts @@ -10,6 +10,7 @@ import { ToolingLog } from '@kbn/tooling-log'; import type { HostOptions } from '@kbn/test'; import { SamlSessionManager } from '@kbn/test'; import type { SecurityRoleName } from '../../../../common/test'; +import { resolveCloudUsersFilePath } from '../../../../scripts/endpoint/common/roles_users/serverless'; export const samlAuthentication = async ( on: Cypress.PluginEvents, @@ -34,15 +35,15 @@ export const samlAuthentication = async ( role: string | SecurityRoleName ): Promise<{ cookie: string; username: string; password: string }> => { // If config.env.PROXY_ORG is set, it means that proxy service is used to create projects. Define the proxy org filename to override the roles. - const rolesFilename = config.env.PROXY_ORG ? `${config.env.PROXY_ORG}.json` : undefined; - const sessionManager = new SamlSessionManager( - { - hostOptions, - log, - isCloud: config.env.CLOUD_SERVERLESS, - }, - rolesFilename - ); + const rolesFilename = config.env.PROXY_ORG + ? `${config.env.PROXY_ORG}.json` + : 'role_users.json'; + const sessionManager = new SamlSessionManager({ + hostOptions, + log, + isCloud: config.env.CLOUD_SERVERLESS, + cloudUsersFilePath: resolveCloudUsersFilePath(rolesFilename), + }); return sessionManager.getInteractiveUserSessionCookieWithRoleScope(role).then((cookie) => { return { cookie, diff --git a/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/serverless/index.ts b/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/serverless/index.ts index 23a44df2d0808..f743f4e3db10c 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/serverless/index.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/serverless/index.ts @@ -7,6 +7,7 @@ import { resolve, join } from 'path'; import { readFileSync } from 'fs'; +import { REPO_ROOT } from '@kbn/repo-info'; const ES_RESOURCES_DIR = resolve(__dirname, 'es_serverless_resources'); @@ -16,6 +17,8 @@ export const ES_RESOURCES = Object.freeze({ users_roles: join(ES_RESOURCES_DIR, 'users_roles'), }); +export const resolveCloudUsersFilePath = (filename: string) => resolve(REPO_ROOT, '.ftr', filename); + export const ES_LOADED_USERS = readFileSync(ES_RESOURCES.users) .toString() .split(/\n/) diff --git a/x-pack/test/api_integration/apis/painless_lab/config.ts b/x-pack/test/api_integration/apis/painless_lab/config.ts deleted file mode 100644 index 5f335f116fefe..0000000000000 --- a/x-pack/test/api_integration/apis/painless_lab/config.ts +++ /dev/null @@ -1,17 +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'; - -export default async function ({ readConfigFile }: FtrConfigProviderContext) { - const baseIntegrationTestsConfig = await readConfigFile(require.resolve('../../config.ts')); - - return { - ...baseIntegrationTestsConfig.getAll(), - testFiles: [require.resolve('.')], - }; -} diff --git a/x-pack/test/api_integration/deployment_agnostic/README.md b/x-pack/test/api_integration/deployment_agnostic/README.md new file mode 100644 index 0000000000000..eff834b7b1db9 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/README.md @@ -0,0 +1,183 @@ +# Deployment-Agnostic Tests Guidelines + +## Definition +A deployment-agnostic API integration test is a test suite that fulfills the following criteria: + +**Functionality**: It tests Kibana APIs that are logically identical in both stateful and serverless environments for the same roles. + +**Design**: The test design is clean and does not require additional logic to execute in either stateful or serverless environments. + +## Tests Design Requirements +A deployment-agnostic test is contained within a single test file and always utilizes the [DeploymentAgnosticFtrProviderContext](https://github.com/elastic/kibana/blob/main/x-pack/test/api_integration/deployment_agnostic/ftr_provider_context.d.ts) to load compatible FTR services. A compatible FTR service must support: + +- **Serverless**: Both local environments and MKI (Managed Kubernetes Infrastructure). +- **Stateful**: Both local environments and Cloud deployments. + +To achieve this, services cannot use `supertest`, which employs an operator user for serverless and a system index superuser for stateful setups. Instead, services should use a combination of `supertestWithoutAuth` and `samlAuth` to generate an API key for user roles and make API calls. For example, see the [data_view_api.ts](https://github.com/elastic/kibana/blob/main/x-pack/test/api_integration/deployment_agnostic/services/data_view_api.ts) service. + +### How It Works +Most existing stateful tests use basic authentication for API testing. In contrast, serverless tests use SAML authentication with project-specific role mapping. + +Since both Elastic Cloud (ESS) and Serverless rely on SAML authentication by default, and stateful deployments also support SAML, *deployment-agnostic tests configure Elasticsearch and Kibana with SAML authentication to use the same authentication approach in all cases*. For roles, stateful deployments define 'viewer', 'editor', and 'admin' roles with serverless-alike permissions. + +### When to Create Separate Tests +While the deployment-agnostic testing approach is beneficial, it should not compromise the quality and simplicity of the tests. Here are some scenarios where separate test files are recommended: + +- **Role-Specific Logic**: If API access or logic depends on roles that differ across deployments. +- **Environment Constraints**: If a test can only run locally and not on MKI or Cloud deployments. +- **Complex Logic**: If the test logic requires splitting across multiple locations. + +## File Structure +We recommend following this structure to simplify maintenance and allow other teams to reuse code (e.g., FTR services) created by different teams: + +``` +x-pack/test/ +├─ deployment_agnostic +│ ├─ apis +│ │ ├─ +│ │ │ ├─ +│ │ │ ├─ +│ │ ├─ +│ │ │ ├─ +│ │ │ ├─ +│ ├─ services +│ │ ├─ index.ts // only services from 'x-pack/test/api_integration/deployment_agnostic/services' +│ │ ├─ .ts +│ │ ├─ .ts +│ ├─ ftr_provider_context.d.ts // with types of services from './services' +├─ stateful.index.ts +├─ stateful.config.ts +├─ .index.ts // e.g., oblt.index.ts +├─ .serverless.config.ts // e.g., oblt.serverless.config.ts +``` + +## Step-by-Step Guide +1. Define Deployment-Agnostic Services + +Under `x-pack/test//deployment_agnostic/services`, create `index.ts` and load default services like `samlAuth` and `superuserWithoutAuth`: + +```ts +import { commonFunctionalServices } from '@kbn/ftr-common-functional-services'; +import { services as deploymentAgnosticServices } from './../../api_integration/deployment_agnostic/services'; + +export type { + InternalRequestHeader, + RoleCredentials, + SupertestWithoutAuthProviderType, +} from '@kbn/ftr-common-functional-services'; + +export const services = { + ...deploymentAgnosticServices, + // create a new deployment-agnostic service and load here +}; +``` + +We suggest adding new services to `x-pack/test/api_integration/deployment_agnostic/services` so other teams can benefit from them. + +2. Create `DeploymentAgnosticFtrProviderContext` with Services Defined in Step 2 + +Create `ftr_provider_context.d.ts` and export `DeploymentAgnosticFtrProviderContext`: +```ts +import { GenericFtrProviderContext } from '@kbn/test'; +import { services } from './services'; + +export type DeploymentAgnosticFtrProviderContext = GenericFtrProviderContext; +``` + +3. Add Tests + +Add test files to `x-pack/test//deployment_agnostic/apis/`: + +test example +```ts +export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { + const samlAuth = getService('samlAuth'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + let roleAuthc: RoleCredentials; + let internalHeaders: InternalRequestHeader; + + describe('compression', () => { + before(async () => { + roleAuthc = await samlAuth.createM2mApiKeyWithRoleScope('admin'); + internalHeaders = samlAuth.getInternalRequestHeader(); + }); + after(async () => { + await samlAuth.invalidateM2mApiKeyWithRoleScope(roleAuthc); + }); + describe('against an application page', () => { + it(`uses compression when there isn't a referer`, async () => { + const response = await supertestWithoutAuth + .get('/app/kibana') + .set('accept-encoding', 'gzip') + .set(internalHeaders) + .set(roleAuthc.apiKeyHeader); + expect(response.header).to.have.property('content-encoding', 'gzip'); + }); + }); + }); +} +``` +Load all test files in `index.ts` under the same folder. + +4. Add Tests Entry File and FTR Config File for **Stateful** Deployment + +Create `stateful.index.ts` tests entry file and load tests: + +```ts +import { DeploymentAgnosticFtrProviderContext } from './ftr_provider_context'; + +export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { + describe('apis', () => { + loadTestFile(require.resolve('./apis/')); + }); +} +``` + +Create `stateful.config.ts` and link tests entry file: + +```ts +import { createStatefulTestConfig } from './../../api_integration/deployment_agnostic/default_configs/stateful.config.base'; + +export default createStatefulTestConfig({ + testFiles: [require.resolve('./stateful.index.ts')], + junit: { + reportName: 'Stateful - Deployment-agnostic API Integration Tests', + }, + // extra arguments + esServerArgs: [], + kbnServerArgs: [], +}); +``` +5. Add Tests Entry File and FTR Config File for Specific **Serverless** Project + +Example for Observability project: + +oblt.index.ts +```ts +import { DeploymentAgnosticFtrProviderContext } from './ftr_provider_context'; + +export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { + describe('Serverless Observability - Deployment-agnostic api integration tests', () => { + loadTestFile(require.resolve('./apis/')); + }); +} +``` + +oblt.serverless.config.ts +```ts +import { createServerlessTestConfig } from './../../api_integration/deployment_agnostic/default_configs/serverless.config.base'; + +export default createServerlessTestConfig({ + serverlessProject: 'oblt', + testFiles: [require.resolve('./oblt.index.ts')], + junit: { + reportName: 'Serverless Observability - Deployment-agnostic API Integration Tests', + }, +}); +``` + +ES and Kibana project-specific arguments are defined and loaded from `serverless.config.base`. These arguments are copied from the Elasticsearch and Kibana controller repositories. + +Note: The FTR (Functional Test Runner) does not have the capability to provision custom ES/Kibana server arguments into the serverless project. Any custom arguments listed explicitly in this config file will apply **only to a local environment**. + +6. Add FTR Configs Path to FTR Manifest Files Located in `.buildkite/` diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/console/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/console/index.ts new file mode 100644 index 0000000000000..4558f6818542f --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/console/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DeploymentAgnosticFtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { + describe('console', () => { + loadTestFile(require.resolve('./spec_definitions')); + }); +} diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/console/spec_definitions.ts b/x-pack/test/api_integration/deployment_agnostic/apis/console/spec_definitions.ts new file mode 100644 index 0000000000000..a2c8115e4ea0d --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/console/spec_definitions.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 expect from '@kbn/expect'; +import { RoleCredentials, InternalRequestHeader } from '@kbn/ftr-common-functional-services'; +import { DeploymentAgnosticFtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { + const samlAuth = getService('samlAuth'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + let roleAuthc: RoleCredentials; + let internalHeaders: InternalRequestHeader; + + describe('GET /api/console/api_server', () => { + before(async () => { + roleAuthc = await samlAuth.createM2mApiKeyWithRoleScope('admin'); + internalHeaders = samlAuth.getInternalRequestHeader(); + }); + after(async () => { + await samlAuth.invalidateM2mApiKeyWithRoleScope(roleAuthc); + }); + it('returns autocomplete definitions', async () => { + const { body } = await supertestWithoutAuth + .get('/api/console/api_server') + .set(roleAuthc.apiKeyHeader) + .set(internalHeaders) + .set('kbn-xsrf', 'true') + .expect(200); + expect(body.es).to.be.ok(); + const { + es: { name, globals, endpoints }, + } = body; + expect(name).to.be.ok(); + expect(Object.keys(globals).length).to.be.above(0); + expect(Object.keys(endpoints).length).to.be.above(0); + }); + }); +} diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/core/compression.ts b/x-pack/test/api_integration/deployment_agnostic/apis/core/compression.ts new file mode 100644 index 0000000000000..d1aa1cfb45153 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/core/compression.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { RoleCredentials, InternalRequestHeader } from '@kbn/ftr-common-functional-services'; +import { DeploymentAgnosticFtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { + const samlAuth = getService('samlAuth'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + let roleAuthc: RoleCredentials; + let internalHeaders: InternalRequestHeader; + + describe('compression', () => { + before(async () => { + roleAuthc = await samlAuth.createM2mApiKeyWithRoleScope('admin'); + internalHeaders = samlAuth.getInternalRequestHeader(); + }); + after(async () => { + await samlAuth.invalidateM2mApiKeyWithRoleScope(roleAuthc); + }); + describe('against an application page', () => { + it(`uses compression when there isn't a referer`, async () => { + const response = await supertestWithoutAuth + .get('/app/kibana') + .set('accept-encoding', 'gzip') + .set(internalHeaders) + .set(roleAuthc.apiKeyHeader); + expect(response.header).to.have.property('content-encoding', 'gzip'); + }); + + it(`uses compression when there is a whitelisted referer`, async () => { + const response = await supertestWithoutAuth + .get('/app/kibana') + .set('accept-encoding', 'gzip') + .set(internalHeaders) + .set('referer', 'https://some-host.com') + .set(roleAuthc.apiKeyHeader); + expect(response.header).to.have.property('content-encoding', 'gzip'); + }); + }); + }); +} diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/core/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/core/index.ts new file mode 100644 index 0000000000000..93e40d3d5b914 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/core/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DeploymentAgnosticFtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { + describe('core', () => { + loadTestFile(require.resolve('./compression')); + }); +} diff --git a/x-pack/test/api_integration/apis/painless_lab/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/painless_lab/index.ts similarity index 67% rename from x-pack/test/api_integration/apis/painless_lab/index.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/painless_lab/index.ts index 63744b77312d7..ff59037fc1f06 100644 --- a/x-pack/test/api_integration/apis/painless_lab/index.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/painless_lab/index.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { FtrProviderContext } from '../../ftr_provider_context'; +import { DeploymentAgnosticFtrProviderContext } from '../../ftr_provider_context'; -export default function ({ loadTestFile }: FtrProviderContext) { +export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { describe('Painless Lab', () => { loadTestFile(require.resolve('./painless_lab')); }); diff --git a/x-pack/test/api_integration/apis/painless_lab/painless_lab.ts b/x-pack/test/api_integration/deployment_agnostic/apis/painless_lab/painless_lab.ts similarity index 60% rename from x-pack/test/api_integration/apis/painless_lab/painless_lab.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/painless_lab/painless_lab.ts index b594220634d1d..7e7047ac9cb57 100644 --- a/x-pack/test/api_integration/apis/painless_lab/painless_lab.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/painless_lab/painless_lab.ts @@ -6,22 +6,34 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import { RoleCredentials, InternalRequestHeader } from '@kbn/ftr-common-functional-services'; +import { DeploymentAgnosticFtrProviderContext } from '../../ftr_provider_context'; const API_BASE_PATH = '/api/painless_lab'; -export default function ({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); +export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { + const samlAuth = getService('samlAuth'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + let roleAuthc: RoleCredentials; + let internalHeaders: InternalRequestHeader; - describe('Painless Lab', function () { + describe('Painless Lab Routes', function () { + before(async () => { + roleAuthc = await samlAuth.createM2mApiKeyWithRoleScope('admin'); + internalHeaders = samlAuth.getInternalRequestHeader(); + }); + after(async () => { + await samlAuth.invalidateM2mApiKeyWithRoleScope(roleAuthc); + }); describe('Execute', () => { it('should execute a valid painless script', async () => { const script = '"{\\n \\"script\\": {\\n \\"source\\": \\"return true;\\",\\n \\"params\\": {\\n \\"string_parameter\\": \\"string value\\",\\n \\"number_parameter\\": 1.5,\\n \\"boolean_parameter\\": true\\n}\\n }\\n}"'; - const { body } = await supertest + const { body } = await supertestWithoutAuth .post(`${API_BASE_PATH}/execute`) - .set('kbn-xsrf', 'xxx') + .set(internalHeaders) + .set(roleAuthc.apiKeyHeader) .set('Content-Type', 'application/json;charset=UTF-8') .send(script) .expect(200); @@ -35,10 +47,11 @@ export default function ({ getService }: FtrProviderContext) { const invalidScript = '"{\\n \\"script\\": {\\n \\"source\\": \\"foobar\\",\\n \\"params\\": {\\n \\"string_parameter\\": \\"string value\\",\\n \\"number_parameter\\": 1.5,\\n \\"boolean_parameter\\": true\\n}\\n }\\n}"'; - const { body } = await supertest + const { body } = await supertestWithoutAuth .post(`${API_BASE_PATH}/execute`) - .set('kbn-xsrf', 'xxx') + .set(internalHeaders) .set('Content-Type', 'application/json;charset=UTF-8') + .set(roleAuthc.apiKeyHeader) .send(invalidScript) .expect(200); diff --git a/x-pack/test/api_integration/deployment_agnostic/default_configs/serverless.config.base.ts b/x-pack/test/api_integration/deployment_agnostic/default_configs/serverless.config.base.ts new file mode 100644 index 0000000000000..606a71835b317 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/default_configs/serverless.config.base.ts @@ -0,0 +1,90 @@ +/* + * 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, Config } from '@kbn/test'; + +import { ServerlessProjectType } from '@kbn/es'; +import { services } from '../services'; + +interface CreateTestConfigOptions { + serverlessProject: ServerlessProjectType; + esServerArgs?: string[]; + kbnServerArgs?: string[]; + testFiles: string[]; + junit: { reportName: string }; + suiteTags?: { include?: string[]; exclude?: string[] }; +} + +// include settings from elasticsearch controller +// https://github.com/elastic/elasticsearch-controller/blob/main/helm/values.yaml +const esServerArgsFromController = { + es: [], + oblt: [ + 'xpack.apm_data.enabled=true', + // for ML, data frame analytics are not part of this project type + 'xpack.ml.dfa.enabled=false', + ], + security: [ + 'xpack.security.authc.api_key.cache.max_keys=70000', + 'data_streams.lifecycle.retention.factory_default=365d', + 'data_streams.lifecycle.retention.factory_max=365d', + ], +}; + +// include settings from kibana controller +// https://github.com/elastic/kibana-controller/blob/main/internal/controllers/kibana/config/config_settings.go +const kbnServerArgsFromController = { + es: [ + // useful for testing (also enabled in MKI QA) + '--coreApp.allowDynamicConfigOverrides=true', + ], + oblt: [ + '--coreApp.allowDynamicConfigOverrides=true', + // defined in MKI control plane + '--xpack.uptime.service.manifestUrl=mockDevUrl', + ], + security: [ + '--coreApp.allowDynamicConfigOverrides=true', + // 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'])}`, + ], +}; + +export function createServerlessTestConfig(options: CreateTestConfigOptions) { + return async ({ readConfigFile }: FtrConfigProviderContext): Promise => { + const svlSharedConfig = await readConfigFile( + require.resolve('@kbn/test-suites-serverless/shared/config.base') + ); + + return { + ...svlSharedConfig.getAll(), + + services: { + ...services, + }, + esTestCluster: { + ...svlSharedConfig.get('esTestCluster'), + serverArgs: [ + ...svlSharedConfig.get('esTestCluster.serverArgs'), + ...esServerArgsFromController[options.serverlessProject], + ...(options.esServerArgs ?? []), + ], + }, + kbnTestServer: { + ...svlSharedConfig.get('kbnTestServer'), + serverArgs: [ + ...svlSharedConfig.get('kbnTestServer.serverArgs'), + ...kbnServerArgsFromController[options.serverlessProject], + `--serverless=${options.serverlessProject}`, + ...(options.kbnServerArgs || []), + ], + }, + testFiles: options.testFiles, + junit: options.junit, + suiteTags: options.suiteTags, + }; + }; +} diff --git a/x-pack/test/api_integration/deployment_agnostic/default_configs/stateful.config.base.ts b/x-pack/test/api_integration/deployment_agnostic/default_configs/stateful.config.base.ts new file mode 100644 index 0000000000000..c784ff071895b --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/default_configs/stateful.config.base.ts @@ -0,0 +1,95 @@ +/* + * 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 { + MOCK_IDP_REALM_NAME, + MOCK_IDP_ENTITY_ID, + MOCK_IDP_ATTRIBUTE_PRINCIPAL, + MOCK_IDP_ATTRIBUTE_ROLES, + MOCK_IDP_ATTRIBUTE_EMAIL, + MOCK_IDP_ATTRIBUTE_NAME, +} from '@kbn/mock-idp-utils'; +import { + esTestConfig, + kbnTestConfig, + systemIndicesSuperuser, + FtrConfigProviderContext, +} from '@kbn/test'; +import { services } from '../services'; + +interface CreateTestConfigOptions { + esServerArgs?: string[]; + kbnServerArgs?: string[]; + testFiles: string[]; + junit: { reportName: string }; + suiteTags?: { include?: string[]; exclude?: string[] }; +} + +export function createStatefulTestConfig(options: CreateTestConfigOptions) { + return async ({ readConfigFile }: FtrConfigProviderContext) => { + const xPackAPITestsConfig = await readConfigFile(require.resolve('../../config.ts')); + + // TODO: move to kbn-es because currently metadata file has hardcoded entityID and Location + const idpPath = require.resolve( + '@kbn/security-api-integration-helpers/saml/idp_metadata_mock_idp.xml' + ); + + const servers = { + kibana: { + ...kbnTestConfig.getUrlParts(systemIndicesSuperuser), + protocol: process.env.TEST_CLOUD ? 'https' : 'http', + }, + elasticsearch: { + ...esTestConfig.getUrlParts(), + protocol: process.env.TEST_CLOUD ? 'https' : 'http', + }, + }; + + const kbnUrl = `${servers.kibana.protocol}://${servers.kibana.hostname}:${servers.kibana.port}`; + + return { + servers, + testFiles: options.testFiles, + security: { disableTestUser: true }, + services, + junit: options.junit, + suiteTags: options.suiteTags, + + esTestCluster: { + ...xPackAPITestsConfig.get('esTestCluster'), + serverArgs: [ + ...xPackAPITestsConfig.get('esTestCluster.serverArgs'), + ...(options.esServerArgs ?? []), + 'xpack.security.authc.token.enabled=true', + `xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.order=0`, + `xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.idp.metadata.path=${idpPath}`, + `xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.idp.entity_id=${MOCK_IDP_ENTITY_ID}`, + `xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.sp.entity_id=${kbnUrl}`, + `xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.sp.acs=${kbnUrl}/api/security/saml/callback`, + `xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.sp.logout=${kbnUrl}/logout`, + `xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.attributes.principal=${MOCK_IDP_ATTRIBUTE_PRINCIPAL}`, + `xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.attributes.groups=${MOCK_IDP_ATTRIBUTE_ROLES}`, + `xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.attributes.name=${MOCK_IDP_ATTRIBUTE_NAME}`, + `xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.attributes.mail=${MOCK_IDP_ATTRIBUTE_EMAIL}`, + ], + }, + + kbnTestServer: { + ...xPackAPITestsConfig.get('kbnTestServer'), + serverArgs: [ + ...xPackAPITestsConfig.get('kbnTestServer.serverArgs'), + ...(options.kbnServerArgs || []), + '--xpack.security.authc.selector.enabled=false', + `--xpack.security.authc.providers=${JSON.stringify({ + saml: { 'cloud-saml-kibana': { order: 0, realm: MOCK_IDP_REALM_NAME } }, + basic: { 'cloud-basic': { order: 1 } }, + })}`, + `--server.publicBaseUrl=${servers.kibana.protocol}://${servers.kibana.hostname}:${servers.kibana.port}`, + ], + }, + }; + }; +} diff --git a/x-pack/test/api_integration/deployment_agnostic/ftr_provider_context.d.ts b/x-pack/test/api_integration/deployment_agnostic/ftr_provider_context.d.ts new file mode 100644 index 0000000000000..81df490d79428 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/ftr_provider_context.d.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { GenericFtrProviderContext } from '@kbn/test'; + +import { services } from './services'; + +export type DeploymentAgnosticFtrProviderContext = GenericFtrProviderContext; diff --git a/x-pack/test/api_integration/deployment_agnostic/oblt.index.ts b/x-pack/test/api_integration/deployment_agnostic/oblt.index.ts new file mode 100644 index 0000000000000..d81415e0554dd --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/oblt.index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { DeploymentAgnosticFtrProviderContext } from './ftr_provider_context'; + +export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { + describe('Serverless Observability - Deployment-agnostic api integration tests', () => { + loadTestFile(require.resolve('./apis/console')); + loadTestFile(require.resolve('./apis/core')); + loadTestFile(require.resolve('./apis/painless_lab')); + }); +} diff --git a/x-pack/test/api_integration/deployment_agnostic/oblt.serverless.config.ts b/x-pack/test/api_integration/deployment_agnostic/oblt.serverless.config.ts new file mode 100644 index 0000000000000..52e1ba2d431ac --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/oblt.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 { createServerlessTestConfig } from './default_configs/serverless.config.base'; + +export default createServerlessTestConfig({ + serverlessProject: 'oblt', + testFiles: [require.resolve('./oblt.index.ts')], + junit: { + reportName: 'Serverless Observability - Deployment-agnostic API Integration Tests', + }, +}); diff --git a/x-pack/test/api_integration/deployment_agnostic/search.index.ts b/x-pack/test/api_integration/deployment_agnostic/search.index.ts new file mode 100644 index 0000000000000..740520f032f0f --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/search.index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { DeploymentAgnosticFtrProviderContext } from './ftr_provider_context'; + +export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { + describe('Serverless Search - Deployment-agnostic api integration tests', () => { + loadTestFile(require.resolve('./apis/console')); + loadTestFile(require.resolve('./apis/core')); + }); +} diff --git a/x-pack/test/api_integration/deployment_agnostic/search.serverless.config.ts b/x-pack/test/api_integration/deployment_agnostic/search.serverless.config.ts new file mode 100644 index 0000000000000..5e90b71d69550 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/search.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 { createServerlessTestConfig } from './default_configs/serverless.config.base'; + +export default createServerlessTestConfig({ + serverlessProject: 'es', + testFiles: [require.resolve('./search.index.ts')], + junit: { + reportName: 'Serverless Search - Deployment-agnostic API Integration Tests', + }, +}); diff --git a/x-pack/test/api_integration/deployment_agnostic/security.index.ts b/x-pack/test/api_integration/deployment_agnostic/security.index.ts new file mode 100644 index 0000000000000..9a9db972bcd5d --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/security.index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { DeploymentAgnosticFtrProviderContext } from './ftr_provider_context'; + +export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { + describe('Security Search - Deployment-agnostic api integration tests', () => { + loadTestFile(require.resolve('./apis/console')); + loadTestFile(require.resolve('./apis/core')); + loadTestFile(require.resolve('./apis/painless_lab')); + }); +} diff --git a/x-pack/test/api_integration/deployment_agnostic/security.serverless.config.ts b/x-pack/test/api_integration/deployment_agnostic/security.serverless.config.ts new file mode 100644 index 0000000000000..d3a30cc95d820 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/security.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 { createServerlessTestConfig } from './default_configs/serverless.config.base'; + +export default createServerlessTestConfig({ + serverlessProject: 'security', + testFiles: [require.resolve('./security.index.ts')], + junit: { + reportName: 'Serverless Security - Deployment-agnostic API Integration Tests', + }, +}); diff --git a/x-pack/test/api_integration/deployment_agnostic/services/data_view_api.ts b/x-pack/test/api_integration/deployment_agnostic/services/data_view_api.ts new file mode 100644 index 0000000000000..c22db40882b60 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/services/data_view_api.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 { RoleCredentials } from '@kbn/ftr-common-functional-services'; +import { DeploymentAgnosticFtrProviderContext } from '../ftr_provider_context'; + +export function DataViewApiProvider({ getService }: DeploymentAgnosticFtrProviderContext) { + const supertestWithoutAuth = getService('supertestWithoutAuth'); + const samlAuth = getService('samlAuth'); + + return { + async create({ + roleAuthc, + id, + name, + title, + }: { + roleAuthc: RoleCredentials; + id: string; + name: string; + title: string; + }) { + const { body } = await supertestWithoutAuth + .post(`/api/content_management/rpc/create`) + .set(roleAuthc.apiKeyHeader) + .set(samlAuth.getInternalRequestHeader()) + .set(samlAuth.getCommonRequestHeader()) + .send({ + contentTypeId: 'index-pattern', + data: { + fieldAttrs: '{}', + title, + timeFieldName: '@timestamp', + sourceFilters: '[]', + fields: '[]', + fieldFormatMap: '{}', + typeMeta: '{}', + runtimeFieldMap: '{}', + name, + }, + options: { id }, + version: 1, + }); + return body; + }, + + async delete({ roleAuthc, id }: { roleAuthc: RoleCredentials; id: string }) { + const { body } = await supertestWithoutAuth + .post(`/api/content_management/rpc/create`) + .set(roleAuthc.apiKeyHeader) + .set(samlAuth.getInternalRequestHeader()) + .set(samlAuth.getCommonRequestHeader()) + .send({ + contentTypeId: 'index-pattern', + id, + options: { force: true }, + version: 1, + }); + return body; + }, + }; +} diff --git a/x-pack/test/api_integration/deployment_agnostic/services/deployment_agnostic_services.ts b/x-pack/test/api_integration/deployment_agnostic/services/deployment_agnostic_services.ts new file mode 100644 index 0000000000000..38222c096bed0 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/services/deployment_agnostic_services.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 _ from 'lodash'; +import { services as apiIntegrationServices } from '../../services'; + +/** + * Load only services that support both stateful & serverless deployments (including Cloud/MKI), + * e.g. `randomness` or `retry` are deployment agnostic + */ +export const deploymentAgnosticServices = _.pick(apiIntegrationServices, [ + 'supertest', // TODO: review its behaviour + 'es', + 'esArchiver', + 'esSupertest', // TODO: review its behaviour + 'indexPatterns', + 'ingestPipelines', + 'kibanaServer', + // 'ml', depends on 'esDeleteAllIndices', can we make it deployment agnostic? + 'randomness', + 'retry', + 'security', + 'usageAPI', +]); diff --git a/x-pack/test/api_integration/deployment_agnostic/services/index.ts b/x-pack/test/api_integration/deployment_agnostic/services/index.ts new file mode 100644 index 0000000000000..c1e70466f1b3c --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/services/index.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { commonFunctionalServices } from '@kbn/ftr-common-functional-services'; +import { deploymentAgnosticServices } from './deployment_agnostic_services'; +import { DataViewApiProvider } from './data_view_api'; +import { SloApiProvider } from './slo_api'; + +export type { + InternalRequestHeader, + RoleCredentials, + SupertestWithoutAuthProviderType, +} from '@kbn/ftr-common-functional-services'; + +export const services = { + ...deploymentAgnosticServices, + supertestWithoutAuth: commonFunctionalServices.supertestWithoutAuth, + samlAuth: commonFunctionalServices.samlAuth, + dataViewApi: DataViewApiProvider, + sloApi: SloApiProvider, + // create a new deployment-agnostic service and load here +}; diff --git a/x-pack/test/api_integration/deployment_agnostic/services/slo_api.ts b/x-pack/test/api_integration/deployment_agnostic/services/slo_api.ts new file mode 100644 index 0000000000000..4c83a536ffb36 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/services/slo_api.ts @@ -0,0 +1,203 @@ +/* + * 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 { + fetchHistoricalSummaryParamsSchema, + FetchHistoricalSummaryResponse, +} from '@kbn/slo-schema'; +import * as t from 'io-ts'; +import { RoleCredentials } from '@kbn/ftr-common-functional-services'; +import { DeploymentAgnosticFtrProviderContext } from '../ftr_provider_context'; + +type DurationUnit = 'm' | 'h' | 'd' | 'w' | 'M'; + +interface Duration { + value: number; + unit: DurationUnit; +} + +interface WindowSchema { + id: string; + burnRateThreshold: number; + maxBurnRateThreshold: number; + longWindow: Duration; + shortWindow: Duration; + actionGroup: string; +} + +interface Dependency { + ruleId: string; + actionGroupsToSuppressOn: string[]; +} + +export interface SloBurnRateRuleParams { + sloId: string; + windows: WindowSchema[]; + dependencies?: Dependency[]; +} + +interface SloParams { + id?: string; + name: string; + description: string; + indicator: { + type: 'sli.kql.custom'; + params: { + index: string; + good: string; + total: string; + timestampField: string; + }; + }; + timeWindow: { + duration: string; + type: string; + }; + budgetingMethod: string; + objective: { + target: number; + }; + groupBy: string; +} + +type FetchHistoricalSummaryParams = t.OutputOf< + typeof fetchHistoricalSummaryParamsSchema.props.body +>; + +interface SloRequestParams { + id: string; + roleAuthc: RoleCredentials; +} + +export function SloApiProvider({ getService }: DeploymentAgnosticFtrProviderContext) { + const es = getService('es'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + const samlAuth = getService('samlAuth'); + const retry = getService('retry'); + const config = getService('config'); + const retryTimeout = config.get('timeouts.try'); + const requestTimeout = 30 * 1000; + + return { + async create(slo: SloParams, roleAuthc: RoleCredentials) { + const { body } = await supertestWithoutAuth + .post(`/api/observability/slos`) + .set(roleAuthc.apiKeyHeader) + .set(samlAuth.getInternalRequestHeader()) + .send(slo); + return body; + }, + + async delete({ id, roleAuthc }: SloRequestParams) { + const response = await supertestWithoutAuth + .delete(`/api/observability/slos/${id}`) + .set(roleAuthc.apiKeyHeader) + .set(samlAuth.getInternalRequestHeader()); + return response; + }, + + async fetchHistoricalSummary( + params: FetchHistoricalSummaryParams, + roleAuthc: RoleCredentials + ): Promise { + const { body } = await supertestWithoutAuth + .post(`/internal/observability/slos/_historical_summary`) + .set(roleAuthc.apiKeyHeader) + .set(samlAuth.getInternalRequestHeader()) + .send(params); + return body; + }, + + async waitForSloToBeDeleted({ id, roleAuthc }: SloRequestParams) { + return await retry.tryForTime(retryTimeout, async () => { + const response = await supertestWithoutAuth + .delete(`/api/observability/slos/${id}`) + .set(roleAuthc.apiKeyHeader) + .set(samlAuth.getInternalRequestHeader()) + .timeout(requestTimeout); + if (!response.ok) { + throw new Error(`SLO with id '${id}' was not deleted`); + } + return response; + }); + }, + + async waitForSloCreated({ id, roleAuthc }: SloRequestParams) { + return await retry.tryForTime(retryTimeout, async () => { + const response = await supertestWithoutAuth + .get(`/api/observability/slos/${id}`) + .set(roleAuthc.apiKeyHeader) + .set(samlAuth.getInternalRequestHeader()) + .timeout(requestTimeout); + if (response.body.id === undefined) { + throw new Error(`No SLO with id '${id}' found`); + } + return response.body; + }); + }, + + async waitForSloSummaryTempIndexToExist(index: string) { + return await retry.tryForTime(retryTimeout, async () => { + const indexExists = await es.indices.exists({ index, allow_no_indices: false }); + if (!indexExists) { + throw new Error(`SLO summary index '${index}' should exist`); + } + return indexExists; + }); + }, + + async getSloData({ sloId, indexName }: { sloId: string; indexName: string }) { + const response = await es.search({ + index: indexName, + body: { + query: { + bool: { + filter: [{ term: { 'slo.id': sloId } }], + }, + }, + }, + }); + return response; + }, + async waitForSloData({ id, indexName }: { id: string; indexName: string }) { + return await retry.tryForTime(retryTimeout, async () => { + const response = await es.search({ + index: indexName, + body: { + query: { + bool: { + filter: [{ term: { 'slo.id': id } }], + }, + }, + }, + }); + if (response.hits.hits.length === 0) { + throw new Error(`No hits found at index '${indexName}' for slo id='${id}'`); + } + return response; + }); + }, + async deleteAllSLOs(roleAuthc: RoleCredentials) { + const response = await supertestWithoutAuth + .get(`/api/observability/slos/_definitions`) + .set(roleAuthc.apiKeyHeader) + .set(samlAuth.getInternalRequestHeader()) + .send() + .expect(200); + await Promise.all( + response.body.results.map(({ id }: { id: string }) => { + return supertestWithoutAuth + .delete(`/api/observability/slos/${id}`) + .set(roleAuthc.apiKeyHeader) + .set(samlAuth.getInternalRequestHeader()) + .send() + .expect(204); + }) + ); + }, + }; +} diff --git a/x-pack/test/api_integration/deployment_agnostic/stateful.config.ts b/x-pack/test/api_integration/deployment_agnostic/stateful.config.ts new file mode 100644 index 0000000000000..b4c22e735925c --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/stateful.config.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 { createStatefulTestConfig } from './default_configs/stateful.config.base'; + +export default createStatefulTestConfig({ + testFiles: [require.resolve('./stateful.index.ts')], + junit: { + reportName: 'Stateful - Deployment-agnostic API Integration Tests', + }, + // extra arguments + esServerArgs: [], + kbnServerArgs: [], +}); diff --git a/x-pack/test/api_integration/deployment_agnostic/stateful.index.ts b/x-pack/test/api_integration/deployment_agnostic/stateful.index.ts new file mode 100644 index 0000000000000..3e46c4d5b0472 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/stateful.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. + */ + +import { DeploymentAgnosticFtrProviderContext } from './ftr_provider_context'; + +export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { + describe('apis', () => { + loadTestFile(require.resolve('./apis/console')); + loadTestFile(require.resolve('./apis/core')); + loadTestFile(require.resolve('./apis/painless_lab')); + }); +} diff --git a/x-pack/test/scalability/config.ts b/x-pack/test/scalability/config.ts index ebfa91d4ce1e2..3315276a688ca 100644 --- a/x-pack/test/scalability/config.ts +++ b/x-pack/test/scalability/config.ts @@ -11,8 +11,8 @@ import path from 'path'; // @ts-expect-error we have to check types with "allowJs: false" for now, causing this import to fail import { REPO_ROOT } from '@kbn/repo-info'; import { createFlagError } from '@kbn/dev-cli-errors'; -import { commonFunctionalServices } from '@kbn/ftr-common-functional-services'; import { v4 as uuidV4 } from 'uuid'; +import { services } from './services'; import { ScalabilityTestRunner } from './runner'; import { FtrProviderContext } from './ftr_provider_context'; import { ScalabilityJourney } from './types'; @@ -49,7 +49,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { return { ...baseConfig, - services: commonFunctionalServices, + services, pageObjects: {}, testRunner: (context: FtrProviderContext) => diff --git a/x-pack/test/scalability/ftr_provider_context.ts b/x-pack/test/scalability/ftr_provider_context.ts index 19c510a9ec8e7..82dba4367104b 100644 --- a/x-pack/test/scalability/ftr_provider_context.ts +++ b/x-pack/test/scalability/ftr_provider_context.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { commonFunctionalServices } from '@kbn/ftr-common-functional-services'; import { GenericFtrProviderContext, GenericFtrService } from '@kbn/test'; +import { services } from './services'; -export type FtrProviderContext = GenericFtrProviderContext; +export type FtrProviderContext = GenericFtrProviderContext; export class FtrService extends GenericFtrService {} diff --git a/x-pack/test/scalability/services.ts b/x-pack/test/scalability/services.ts new file mode 100644 index 0000000000000..5708fb19f7e57 --- /dev/null +++ b/x-pack/test/scalability/services.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. + */ + +import { commonFunctionalServices } from '@kbn/ftr-common-functional-services'; + +const { es, esArchiver, kibanaServer, retry, supertestWithoutAuth } = commonFunctionalServices; +export const services = { + es, + esArchiver, + kibanaServer, + retry, + supertestWithoutAuth, +}; diff --git a/x-pack/test/security_api_integration/packages/helpers/saml/idp_metadata_mock_idp.xml b/x-pack/test/security_api_integration/packages/helpers/saml/idp_metadata_mock_idp.xml new file mode 100644 index 0000000000000..1049f4392d9ba --- /dev/null +++ b/x-pack/test/security_api_integration/packages/helpers/saml/idp_metadata_mock_idp.xml @@ -0,0 +1,41 @@ + + + + + + + + MIIDYjCCAkqgAwIBAgIUZ2p8K7GMXGk6xwCS9S91BUl1JnAwDQYJKoZIhvcNAQEL +BQAwNDEyMDAGA1UEAxMpRWxhc3RpYyBDZXJ0aWZpY2F0ZSBUb29sIEF1dG9nZW5l +cmF0ZWQgQ0EwIBcNMjMwOTIzMTUyMDE0WhgPMjA3MzA5MTAxNTIwMTRaMBExDzAN +BgNVBAMTBmtpYmFuYTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMOU +r52dbZ5dY0BoP2p7CEnOpG+qHTNrOAqZO/OJfniPMtpGmwAMl3WZDca6u2XkV2KE +qQyevQ2ADk6G3o8S2RU8mO/+UweuCDF7LHuSdxEGTpucidZErmVhEGUOFosL5UeB +AtIDWxvWwgK+W9Yzt5IEN2HzNCZ6h0dOSk2r9EjVMG5yF4Q6kuqOYxBT7jxoaOtO +OCrgBRummtUga4T13WZ/ZIyyHpXj2+JD4YEmrDyoTa7NLaphv0hnVhHXYoYBI/c6 +2SwwAoBlmtDmlinwSACQ3o/8eLWk0tqkIP14rc3oFh3m7D2c3c2m2HXuyoSDMfGW +beG2IE1Q3idcGmeG3qsCAwEAAaOBjDCBiTAdBgNVHQ4EFgQUMOUM7w5jmIozDvnq +RpM779m5GigwHwYDVR0jBBgwFoAUMEwqwI5b0MYpNxwaHJ9Tw1Lp3p4wPAYDVR0R +BDUwM4IUaG9zdC5kb2NrZXIuaW50ZXJuYWyCCWxvY2FsaG9zdIIEZXMwM4IEZXMw +MoIEZXMwMTAJBgNVHRMEAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQCxqvQYXSKqgpdl +SP4gXgwipAnYsoW9qkgWQODTvSBEzUdOWme0d3j7i2l6Ur/nVSv5YjkqAv1hf/yJ +Hrk9h+j29ZO/aQ/KDh5i/gTEUnPw3Bxbw47dfn23tjMWO7NCU1fr5HNztRsa/gQr +e9s07g25u/gTfTi9Fyu0lcRe3bXOLS/mFVcuC5oxuS65R9OlbIsiORkZ2EfwuNUf +wAAYOGPIjM2VlQCvBitefsd/SzRKHdxSPy6KSjkO6MGEGo87fr7B7Nx1qp1DVrK7 +q9XeP1Cuygjg9WTcnsvWvNw8CssyuFM6X/3tGjpPasXwLvNUoG2AairK2AYTWhvS +foE31cFg + + + + + + + + + diff --git a/x-pack/test/security_solution_api_integration/config/serverless/services_edr_workflows.ts b/x-pack/test/security_solution_api_integration/config/serverless/services_edr_workflows.ts index 299d1a509f55e..6c50ff3500050 100644 --- a/x-pack/test/security_solution_api_integration/config/serverless/services_edr_workflows.ts +++ b/x-pack/test/security_solution_api_integration/config/serverless/services_edr_workflows.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { SvlUserManagerProvider } from '@kbn/test-suites-serverless/shared/services/svl_user_manager'; +import { commonFunctionalServices } from '@kbn/ftr-common-functional-services'; import { SvlCommonApiServiceProvider } from '@kbn/test-suites-serverless/shared/services/svl_common_api'; import { services as essServices } from '../ess/services_edr_workflows'; import { SecuritySolutionServerlessSuperTest } from '../services/security_solution_serverless_supertest'; @@ -15,6 +15,6 @@ export const svlServices = { ...essServices, supertest: SecuritySolutionServerlessSuperTest, securitySolutionUtils: SecuritySolutionServerlessUtils, - svlUserManager: SvlUserManagerProvider, + svlUserManager: commonFunctionalServices.samlAuth, svlCommonApi: SvlCommonApiServiceProvider, }; diff --git a/x-pack/test/security_solution_api_integration/config/services/security_solution_serverless_utils.ts b/x-pack/test/security_solution_api_integration/config/services/security_solution_serverless_utils.ts index da57ccf64860e..15b2699acbedb 100644 --- a/x-pack/test/security_solution_api_integration/config/services/security_solution_serverless_utils.ts +++ b/x-pack/test/security_solution_api_integration/config/services/security_solution_serverless_utils.ts @@ -32,6 +32,8 @@ export function SecuritySolutionServerlessUtils({ }); async function invalidateApiKey(credentials: RoleCredentials) { + // load service to call it outside mocha context + await svlUserManager.init(); await svlUserManager.invalidateM2mApiKeyWithRoleScope(credentials); } @@ -53,6 +55,8 @@ export function SecuritySolutionServerlessUtils({ const createSuperTest = async (role = 'admin') => { cleanCredentials(role); + // load service to call it outside mocha context + await svlUserManager.init(); const credentials = await svlUserManager.createM2mApiKeyWithRoleScope(role); rolesCredentials.set(role, credentials); @@ -62,6 +66,8 @@ export function SecuritySolutionServerlessUtils({ return { getUsername: async (role = 'admin') => { + // load service to call it outside mocha context + await svlUserManager.init(); const { username } = await svlUserManager.getUserData(role); return username; diff --git a/x-pack/test/security_solution_cypress/cypress/support/saml_auth.ts b/x-pack/test/security_solution_cypress/cypress/support/saml_auth.ts index b6701a8b4b553..1b5c2adcae455 100644 --- a/x-pack/test/security_solution_cypress/cypress/support/saml_auth.ts +++ b/x-pack/test/security_solution_cypress/cypress/support/saml_auth.ts @@ -9,6 +9,8 @@ import { ToolingLog } from '@kbn/tooling-log'; import { SecurityRoleName } from '@kbn/security-solution-plugin/common/test'; import { HostOptions, SamlSessionManager } from '@kbn/test'; +import { REPO_ROOT } from '@kbn/repo-info'; +import { resolve } from 'path'; import { DEFAULT_SERVERLESS_ROLE } from '../env_var_names_constants'; export const samlAuthentication = async ( @@ -31,30 +33,27 @@ export const samlAuthentication = async ( // If config.env.PROXY_ORG is set, it means that proxy service is used to create projects. Define the proxy org filename to override the roles. const rolesFilename = config.env.PROXY_ORG ? `${config.env.PROXY_ORG}.json` : undefined; + const cloudUsersFilePath = resolve(REPO_ROOT, '.ftr', rolesFilename ?? 'role_users.json'); on('task', { getSessionCookie: async (role: string | SecurityRoleName): Promise => { - const sessionManager = new SamlSessionManager( - { - hostOptions, - log, - isCloud: config.env.CLOUD_SERVERLESS, - }, - rolesFilename - ); + const sessionManager = new SamlSessionManager({ + hostOptions, + log, + isCloud: config.env.CLOUD_SERVERLESS, + cloudUsersFilePath, + }); return sessionManager.getInteractiveUserSessionCookieWithRoleScope(role); }, getFullname: async ( role: string | SecurityRoleName = DEFAULT_SERVERLESS_ROLE ): Promise => { - const sessionManager = new SamlSessionManager( - { - hostOptions, - log, - isCloud: config.env.CLOUD_SERVERLESS, - }, - rolesFilename - ); + const sessionManager = new SamlSessionManager({ + hostOptions, + log, + isCloud: config.env.CLOUD_SERVERLESS, + cloudUsersFilePath, + }); const { full_name: fullName } = await sessionManager.getUserData(role); return fullName; }, diff --git a/x-pack/test/security_solution_cypress/cypress/tsconfig.json b/x-pack/test/security_solution_cypress/cypress/tsconfig.json index d07d03536f7f4..b7223115bbcb6 100644 --- a/x-pack/test/security_solution_cypress/cypress/tsconfig.json +++ b/x-pack/test/security_solution_cypress/cypress/tsconfig.json @@ -42,5 +42,6 @@ "@kbn/actions-plugin", "@kbn/alerts-ui-shared", "@kbn/securitysolution-endpoint-exceptions-common", + "@kbn/repo-info", ] } diff --git a/x-pack/test/security_solution_endpoint/services/index.ts b/x-pack/test/security_solution_endpoint/services/index.ts index c9b9d59fa56f6..666378e4492c5 100644 --- a/x-pack/test/security_solution_endpoint/services/index.ts +++ b/x-pack/test/security_solution_endpoint/services/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { SvlUserManagerProvider } from '@kbn/test-suites-serverless/shared/services/svl_user_manager'; +import { commonFunctionalServices } from '@kbn/ftr-common-functional-services'; import { SvlCommonApiServiceProvider } from '@kbn/test-suites-serverless/shared/services/svl_common_api'; import { services as xPackFunctionalServices } from '../../functional/services'; import { IngestManagerProvider } from '../../common/services/ingest_manager'; @@ -43,7 +43,7 @@ export const svlServices = { supertestWithoutAuth: KibanaSupertestWithCertWithoutAuthProvider, svlCommonApi: SvlCommonApiServiceProvider, - svlUserManager: SvlUserManagerProvider, + svlUserManager: commonFunctionalServices.samlAuth, }; export type Services = typeof services | typeof svlServices; diff --git a/x-pack/test/security_solution_endpoint/tsconfig.json b/x-pack/test/security_solution_endpoint/tsconfig.json index 6b35306a0d693..e4ce04de12a59 100644 --- a/x-pack/test/security_solution_endpoint/tsconfig.json +++ b/x-pack/test/security_solution_endpoint/tsconfig.json @@ -27,5 +27,6 @@ "@kbn/ftr-common-functional-ui-services", "@kbn/test", "@kbn/test-subj-selector", + "@kbn/ftr-common-functional-services", ] } diff --git a/x-pack/test/tsconfig.json b/x-pack/test/tsconfig.json index 3449df8d6f23c..974a206d71e3f 100644 --- a/x-pack/test/tsconfig.json +++ b/x-pack/test/tsconfig.json @@ -176,6 +176,7 @@ "@kbn/osquery-plugin", "@kbn/entities-schema", "@kbn/actions-simulators-plugin", - "@kbn/cases-api-integration-test-plugin" + "@kbn/cases-api-integration-test-plugin", + "@kbn/mock-idp-utils" ] } diff --git a/x-pack/test_serverless/api_integration/test_suites/common/console/autocomplete_entities.ts b/x-pack/test_serverless/api_integration/test_suites/common/console/autocomplete_entities.ts index 64adeea57a9be..72640103c0ef9 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/console/autocomplete_entities.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/console/autocomplete_entities.ts @@ -7,8 +7,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; -import { RoleCredentials } from '../../../../shared/services'; -import { InternalRequestHeader } from '../../../../shared/services/svl_common_api'; +import { InternalRequestHeader, RoleCredentials } from '../../../../shared/services'; export default ({ getService }: FtrProviderContext) => { const svlCommonApi = getService('svlCommonApi'); diff --git a/x-pack/test_serverless/functional/test_suites/common/saved_objects_management/export_transform.ts b/x-pack/test_serverless/functional/test_suites/common/saved_objects_management/export_transform.ts index 80de679ca7b49..fa7de0335ff8d 100644 --- a/x-pack/test_serverless/functional/test_suites/common/saved_objects_management/export_transform.ts +++ b/x-pack/test_serverless/functional/test_suites/common/saved_objects_management/export_transform.ts @@ -42,7 +42,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it('allows to mutate the objects during an export', async () => { - await supertest + const resp = await supertest .post('/api/saved_objects/_export') .set(svlCommonApi.getCommonRequestHeader()) .set(svlCommonApi.getInternalRequestHeader()) @@ -50,24 +50,23 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { type: ['test-export-transform'], excludeExportDetails: true, }) - .expect(200) - .then((resp) => { - const objects = parseNdJson(resp.text); - expect(objects.map((obj) => ({ id: obj.id, enabled: obj.attributes.enabled }))).to.eql([ - { - id: 'type_1-obj_1', - enabled: false, - }, - { - id: 'type_1-obj_2', - enabled: false, - }, - ]); - }); + .expect(200); + + const objects = parseNdJson(resp.text); + expect(objects.map((obj) => ({ id: obj.id, enabled: obj.attributes.enabled }))).to.eql([ + { + id: 'type_1-obj_1', + enabled: false, + }, + { + id: 'type_1-obj_2', + enabled: false, + }, + ]); }); it('allows to add additional objects to an export', async () => { - await supertest + const resp = await supertest .post('/api/saved_objects/_export') .set(svlCommonApi.getCommonRequestHeader()) .set(svlCommonApi.getInternalRequestHeader()) @@ -80,15 +79,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { ], excludeExportDetails: true, }) - .expect(200) - .then((resp) => { - const objects = parseNdJson(resp.text); - expect(objects.map((obj) => obj.id)).to.eql(['type_2-obj_1', 'type_dep-obj_1']); - }); + .expect(200); + const objects = parseNdJson(resp.text); + expect(objects.map((obj) => obj.id)).to.eql(['type_2-obj_1', 'type_dep-obj_1']); }); it('allows to add additional objects to an export when exporting by type', async () => { - await supertest + const resp = await supertest .post('/api/saved_objects/_export') .set(svlCommonApi.getCommonRequestHeader()) .set(svlCommonApi.getInternalRequestHeader()) @@ -96,20 +93,18 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { type: ['test-export-add'], excludeExportDetails: true, }) - .expect(200) - .then((resp) => { - const objects = parseNdJson(resp.text); - expect(objects.map((obj) => obj.id)).to.eql([ - 'type_2-obj_1', - 'type_2-obj_2', - 'type_dep-obj_1', - 'type_dep-obj_2', - ]); - }); + .expect(200); + const objects = parseNdJson(resp.text); + expect(objects.map((obj) => obj.id)).to.eql([ + 'type_2-obj_1', + 'type_2-obj_2', + 'type_dep-obj_1', + 'type_dep-obj_2', + ]); }); it('returns a 400 when the type causes a transform error', async () => { - await supertest + const resp = await supertest .post('/api/saved_objects/_export') .set(svlCommonApi.getCommonRequestHeader()) .set(svlCommonApi.getInternalRequestHeader()) @@ -117,21 +112,19 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { type: ['test-export-transform-error'], excludeExportDetails: true, }) - .expect(400) - .then((resp) => { - const { attributes, ...error } = resp.body; - expect(error).to.eql({ - error: 'Bad Request', - message: 'Error transforming objects to export', - statusCode: 400, - }); - expect(attributes.cause).to.eql('Error during transform'); - expect(attributes.objects.map((obj: any) => obj.id)).to.eql(['type_4-obj_1']); - }); + .expect(400); + const { attributes, ...error } = resp.body; + expect(error).to.eql({ + error: 'Bad Request', + message: 'Error transforming objects to export', + statusCode: 400, + }); + expect(attributes.cause).to.eql('Error during transform'); + expect(attributes.objects.map((obj: any) => obj.id)).to.eql(['type_4-obj_1']); }); it('returns a 400 when the type causes an invalid transform', async () => { - await supertest + const resp = await supertest .post('/api/saved_objects/_export') .set(svlCommonApi.getCommonRequestHeader()) .set(svlCommonApi.getInternalRequestHeader()) @@ -139,17 +132,15 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { type: ['test-export-invalid-transform'], excludeExportDetails: true, }) - .expect(400) - .then((resp) => { - expect(resp.body).to.eql({ - error: 'Bad Request', - message: 'Invalid transform performed on objects to export', - statusCode: 400, - attributes: { - objectKeys: ['test-export-invalid-transform|type_3-obj_1'], - }, - }); - }); + .expect(400); + expect(resp.body).to.eql({ + error: 'Bad Request', + message: 'Invalid transform performed on objects to export', + statusCode: 400, + attributes: { + objectKeys: ['test-export-invalid-transform|type_3-obj_1'], + }, + }); }); }); @@ -172,7 +163,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it('execute export transforms for reference objects', async () => { - await supertest + const resp = await supertest .post('/api/saved_objects/_export') .set(svlCommonApi.getCommonRequestHeader()) .set(svlCommonApi.getInternalRequestHeader()) @@ -186,21 +177,17 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { includeReferencesDeep: true, excludeExportDetails: true, }) - .expect(200) - .then((resp) => { - const objects = parseNdJson(resp.text).sort((obj1, obj2) => - obj1.id.localeCompare(obj2.id) - ); - expect(objects.map((obj) => obj.id)).to.eql([ - 'type_1-obj_1', - 'type_1-obj_2', - 'type_2-obj_1', - 'type_dep-obj_1', - ]); + .expect(200); + const objects = parseNdJson(resp.text).sort((obj1, obj2) => obj1.id.localeCompare(obj2.id)); + expect(objects.map((obj) => obj.id)).to.eql([ + 'type_1-obj_1', + 'type_1-obj_2', + 'type_2-obj_1', + 'type_dep-obj_1', + ]); - expect(objects[0].attributes.enabled).to.eql(false); - expect(objects[1].attributes.enabled).to.eql(false); - }); + expect(objects[0].attributes.enabled).to.eql(false); + expect(objects[1].attributes.enabled).to.eql(false); }); }); @@ -223,7 +210,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it('should only export objects returning `true` for `isExportable`', async () => { - await supertest + const resp = await supertest .post('/api/saved_objects/_export') .set(svlCommonApi.getCommonRequestHeader()) .set(svlCommonApi.getInternalRequestHeader()) @@ -237,21 +224,17 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { includeReferencesDeep: true, excludeExportDetails: true, }) - .expect(200) - .then((resp) => { - const objects = parseNdJson(resp.text).sort((obj1, obj2) => - obj1.id.localeCompare(obj2.id) - ); - expect(objects.map((obj) => `${obj.type}:${obj.id}`)).to.eql([ - 'test-is-exportable:1', - 'test-is-exportable:3', - 'test-is-exportable:5', - ]); - }); + .expect(200); + const objects = parseNdJson(resp.text).sort((obj1, obj2) => obj1.id.localeCompare(obj2.id)); + expect(objects.map((obj) => `${obj.type}:${obj.id}`)).to.eql([ + 'test-is-exportable:1', + 'test-is-exportable:3', + 'test-is-exportable:5', + ]); }); it('lists objects that got filtered', async () => { - await supertest + const resp = await supertest .post('/api/saved_objects/_export') .set(svlCommonApi.getCommonRequestHeader()) .set(svlCommonApi.getInternalRequestHeader()) @@ -265,31 +248,29 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { includeReferencesDeep: true, excludeExportDetails: false, }) - .expect(200) - .then((resp) => { - const objects = parseNdJson(resp.text); - const exportDetails = objects[ - objects.length - 1 - ] as unknown as SavedObjectsExportResultDetails; + .expect(200); + const objects = parseNdJson(resp.text); + const exportDetails = objects[ + objects.length - 1 + ] as unknown as SavedObjectsExportResultDetails; - expect(exportDetails.excludedObjectsCount).to.eql(2); - expect(exportDetails.excludedObjects).to.eql([ - { - type: 'test-is-exportable', - id: '2', - reason: 'excluded', - }, - { - type: 'test-is-exportable', - id: '4', - reason: 'excluded', - }, - ]); - }); + expect(exportDetails.excludedObjectsCount).to.eql(2); + expect(exportDetails.excludedObjects).to.eql([ + { + type: 'test-is-exportable', + id: '2', + reason: 'excluded', + }, + { + type: 'test-is-exportable', + id: '4', + reason: 'excluded', + }, + ]); }); it('excludes objects if `isExportable` throws', async () => { - await supertest + const resp = await supertest .post('/api/saved_objects/_export') .set(svlCommonApi.getCommonRequestHeader()) .set(svlCommonApi.getInternalRequestHeader()) @@ -307,24 +288,20 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { includeReferencesDeep: true, excludeExportDetails: false, }) - .expect(200) - .then((resp) => { - const objects = parseNdJson(resp.text); - expect(objects.length).to.eql(2); - expect([objects[0]].map((obj) => `${obj.type}:${obj.id}`)).to.eql([ - 'test-is-exportable:5', - ]); - const exportDetails = objects[ - objects.length - 1 - ] as unknown as SavedObjectsExportResultDetails; - expect(exportDetails.excludedObjects).to.eql([ - { - type: 'test-is-exportable', - id: 'error', - reason: 'predicate_error', - }, - ]); - }); + .expect(200); + const objects = parseNdJson(resp.text); + expect(objects.length).to.eql(2); + expect([objects[0]].map((obj) => `${obj.type}:${obj.id}`)).to.eql(['test-is-exportable:5']); + const exportDetails = objects[ + objects.length - 1 + ] as unknown as SavedObjectsExportResultDetails; + expect(exportDetails.excludedObjects).to.eql([ + { + type: 'test-is-exportable', + id: 'error', + reason: 'predicate_error', + }, + ]); }); }); }); diff --git a/x-pack/test_serverless/functional/test_suites/common/saved_objects_management/find.ts b/x-pack/test_serverless/functional/test_suites/common/saved_objects_management/find.ts index 986479518bfd4..c792d0f7fdbc1 100644 --- a/x-pack/test_serverless/functional/test_suites/common/saved_objects_management/find.ts +++ b/x-pack/test_serverless/functional/test_suites/common/saved_objects_management/find.ts @@ -44,37 +44,35 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await kibanaServer.savedObjects.cleanStandardList(); }); - it('returns saved objects with importableAndExportable types', async () => - await supertest + it('returns saved objects with importableAndExportable types', async () => { + const resp = await supertest .get('/api/kibana/management/saved_objects/_find?type=test-hidden-importable-exportable') .set(svlCommonApi.getCommonRequestHeader()) .set(svlCommonApi.getInternalRequestHeader()) - .expect(200) - .then((resp) => { - expect( - resp.body.saved_objects.map((so: { id: string; type: string }) => ({ - id: so.id, - type: so.type, - })) - ).to.eql([ - { - type: 'test-hidden-importable-exportable', - id: 'ff3733a0-9fty-11e7-ahb3-3dcb94193fab', - }, - ]); - })); + .expect(200); + expect( + resp.body.saved_objects.map((so: { id: string; type: string }) => ({ + id: so.id, + type: so.type, + })) + ).to.eql([ + { + type: 'test-hidden-importable-exportable', + id: 'ff3733a0-9fty-11e7-ahb3-3dcb94193fab', + }, + ]); + }); - it('returns empty response for non importableAndExportable types', async () => - await supertest + it('returns empty response for non importableAndExportable types', async () => { + const resp = await supertest .get( '/api/kibana/management/saved_objects/_find?type=test-hidden-non-importable-exportable' ) .set(svlCommonApi.getCommonRequestHeader()) .set(svlCommonApi.getInternalRequestHeader()) - .expect(200) - .then((resp) => { - expect(resp.body.saved_objects).to.eql([]); - })); + .expect(200); + expect(resp.body.saved_objects).to.eql([]); + }); }); }); } diff --git a/x-pack/test_serverless/shared/services/index.ts b/x-pack/test_serverless/shared/services/index.ts index 71c048cdf772b..631d047d8fcfb 100644 --- a/x-pack/test_serverless/shared/services/index.ts +++ b/x-pack/test_serverless/shared/services/index.ts @@ -9,12 +9,13 @@ import { commonFunctionalServices } from '@kbn/ftr-common-functional-services'; import { SupertestProvider } from './supertest'; import { SvlCommonApiServiceProvider } from './svl_common_api'; import { SvlReportingServiceProvider } from './svl_reporting'; -import { SvlUserManagerProvider } from './svl_user_manager'; import { DataViewApiProvider } from './data_view_api'; -export type { RoleCredentials } from './svl_user_manager'; -export type { InternalRequestHeader } from './svl_common_api'; -export type { SupertestWithoutAuthProviderType } from '@kbn/ftr-common-functional-services'; +export type { + InternalRequestHeader, + RoleCredentials, + SupertestWithoutAuthProviderType, +} from '@kbn/ftr-common-functional-services'; const SupertestWithoutAuthProvider = commonFunctionalServices.supertestWithoutAuth; @@ -23,6 +24,6 @@ export const services = { supertestWithoutAuth: SupertestWithoutAuthProvider, svlCommonApi: SvlCommonApiServiceProvider, svlReportingApi: SvlReportingServiceProvider, - svlUserManager: SvlUserManagerProvider, + svlUserManager: commonFunctionalServices.samlAuth, dataViewApi: DataViewApiProvider, }; diff --git a/x-pack/test_serverless/shared/services/svl_common_api.ts b/x-pack/test_serverless/shared/services/svl_common_api.ts index 99ffe486dd4d7..f207c0ed3bb0e 100644 --- a/x-pack/test_serverless/shared/services/svl_common_api.ts +++ b/x-pack/test_serverless/shared/services/svl_common_api.ts @@ -22,10 +22,11 @@ export type InternalRequestHeader = typeof INTERNAL_REQUEST_HEADERS; export function SvlCommonApiServiceProvider({}: FtrProviderContext) { return { + // call it from 'samlAuth' service when tests are migrated to deployment-agnostic getCommonRequestHeader() { return COMMON_REQUEST_HEADERS; }, - + // call it from 'samlAuth' service when tests are migrated to deployment-agnostic getInternalRequestHeader(): InternalRequestHeader { return INTERNAL_REQUEST_HEADERS; }, diff --git a/x-pack/test_serverless/shared/services/svl_reporting.ts b/x-pack/test_serverless/shared/services/svl_reporting.ts index cd231ee8e77e1..9d3d7941ec503 100644 --- a/x-pack/test_serverless/shared/services/svl_reporting.ts +++ b/x-pack/test_serverless/shared/services/svl_reporting.ts @@ -11,8 +11,8 @@ import type { ReportingJobResponse } from '@kbn/reporting-plugin/server/types'; import { REPORTING_DATA_STREAM_WILDCARD_WITH_LEGACY } from '@kbn/reporting-server'; import rison from '@kbn/rison'; import { FtrProviderContext } from '../../functional/ftr_provider_context'; -import { RoleCredentials } from './svl_user_manager'; -import { InternalRequestHeader } from './svl_common_api'; +import { RoleCredentials } from '.'; +import { InternalRequestHeader } from '.'; const API_HEADER: [string, string] = ['kbn-xsrf', 'reporting']; From c645504d4f19d8671de8790388897ef84309d490 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 7 Aug 2024 17:41:11 +0100 Subject: [PATCH 20/44] skip flaky suites (#190063) --- .../rule_creation_ui/hooks/use_all_esql_rule_fields.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_all_esql_rule_fields.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_all_esql_rule_fields.test.ts index 996b3ca044864..377a1772a5ea0 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_all_esql_rule_fields.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_all_esql_rule_fields.test.ts @@ -47,7 +47,8 @@ const mockEsqlDatatable = { columns: [{ id: '_custom_field', name: '_custom_field', meta: { type: 'string' } }], }; -describe('useAllEsqlRuleFields', () => { +// FLAKY: https://github.com/elastic/kibana/issues/190063 +describe.skip('useAllEsqlRuleFields', () => { beforeEach(() => { jest.clearAllMocks(); getESQLQueryColumnsMock.mockImplementation(({ esqlQuery }) => From 5bce919cf11c618057093940157ca35e95fc4eb0 Mon Sep 17 00:00:00 2001 From: Colleen McGinnis Date: Wed, 7 Aug 2024 11:42:45 -0500 Subject: [PATCH 21/44] [DOCS] Add more APM UI redirects to the Observability guide (#190061) ## Summary In https://github.com/elastic/kibana/pull/179981 we moved the APM UI content to the Observability guide. When [backporting to the `8.14` branch](https://github.com/elastic/kibana/pull/190009) (which is the `current` branch at the time I'm writing this) the docs build failed because there are some links in the APM agent docs that are hard-coded to go to the `current` version of the docs, and those links were not properly redirected. When [`8.15` becomes the `current` branch](https://github.com/elastic/docs/pull/3036), I believe the docs build will fail unless we implement the AsciiDoc redirects in this PR. ### 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) --- docs/redirects.asciidoc | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/redirects.asciidoc b/docs/redirects.asciidoc index 85692a2583680..06a2fc7886b58 100644 --- a/docs/redirects.asciidoc +++ b/docs/redirects.asciidoc @@ -507,6 +507,12 @@ Refer to {observability-guide}/apm-service-maps.html#service-maps-supported[Supp Refer to {observability-guide}/apm-service-overview.html[Service overview]. +[float] +[[service-span-duration]] +=== Span types average duration and dependencies + +Refer to {observability-guide}/apm-service-overview.html#service-span-duration[Service overview]. + [role="exclude",id="mobile-service-overview"] == Mobile service overview @@ -598,6 +604,12 @@ Refer to {observability-guide}/apm-custom-links.html[Create custom links]. Refer to {observability-guide}/apm-filters.html[Filter data]. +[float] +[[environment-selector]] +=== Service environment filter + +Refer to {observability-guide}/apm-filters.html#environment-selector[Filter data]. + [role="exclude",id="correlations"] == Find transaction latency and failure correlations From 4c3dbbc711c68c165bd4d24f37b2f52d28f5f31e Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 7 Aug 2024 11:12:51 -0600 Subject: [PATCH 22/44] unskip unsaved changes tests (#189935) Closes https://github.com/elastic/kibana/issues/189811, https://github.com/elastic/kibana/issues/189823, and https://github.com/elastic/kibana/issues/189822 PR attempts resolves flaky tests by putting expects in `waitFor` --- .../children_unsaved_changes.test.ts | 85 +++++++++++++------ .../initialize_unsaved_changes.test.ts | 37 +++++--- 2 files changed, 80 insertions(+), 42 deletions(-) diff --git a/packages/presentation/presentation_containers/interfaces/unsaved_changes/children_unsaved_changes.test.ts b/packages/presentation/presentation_containers/interfaces/unsaved_changes/children_unsaved_changes.test.ts index 9f98aa798d6c7..d816ce835fa51 100644 --- a/packages/presentation/presentation_containers/interfaces/unsaved_changes/children_unsaved_changes.test.ts +++ b/packages/presentation/presentation_containers/interfaces/unsaved_changes/children_unsaved_changes.test.ts @@ -6,12 +6,11 @@ * Side Public License, v 1. */ -import { BehaviorSubject, skip } from 'rxjs'; +import { BehaviorSubject } from 'rxjs'; import { childrenUnsavedChanges$, DEBOUNCE_TIME } from './children_unsaved_changes'; +import { waitFor } from '@testing-library/react'; -// Failing: See https://github.com/elastic/kibana/issues/189823 -// FLAKY: https://github.com/elastic/kibana/issues/189822 -describe.skip('childrenUnsavedChanges$', () => { +describe('childrenUnsavedChanges$', () => { const child1Api = { unsavedChanges: new BehaviorSubject(undefined), resetUnsavedChanges: () => undefined, @@ -35,40 +34,64 @@ describe.skip('childrenUnsavedChanges$', () => { test('should emit on subscribe', async () => { const subscription = childrenUnsavedChanges$(children$).subscribe(onFireMock); - await new Promise((resolve) => setTimeout(resolve, DEBOUNCE_TIME + 1)); - expect(onFireMock).toHaveBeenCalledTimes(1); - const childUnsavedChanges = onFireMock.mock.calls[0][0]; - expect(childUnsavedChanges).toBeUndefined(); + await waitFor( + () => { + expect(onFireMock).toHaveBeenCalledTimes(1); + const childUnsavedChanges = onFireMock.mock.calls[0][0]; + expect(childUnsavedChanges).toBeUndefined(); + }, + { + interval: DEBOUNCE_TIME + 1, + } + ); subscription.unsubscribe(); }); test('should emit when child has new unsaved changes', async () => { - const subscription = childrenUnsavedChanges$(children$).pipe(skip(1)).subscribe(onFireMock); - await new Promise((resolve) => setTimeout(resolve, DEBOUNCE_TIME + 1)); - expect(onFireMock).toHaveBeenCalledTimes(0); + const subscription = childrenUnsavedChanges$(children$).subscribe(onFireMock); + await waitFor( + () => { + expect(onFireMock).toHaveBeenCalledTimes(1); + }, + { + interval: DEBOUNCE_TIME + 1, + } + ); child1Api.unsavedChanges.next({ key1: 'modified value', }); - await new Promise((resolve) => setTimeout(resolve, DEBOUNCE_TIME + 1)); - expect(onFireMock).toHaveBeenCalledTimes(1); - const childUnsavedChanges = onFireMock.mock.calls[0][0]; - expect(childUnsavedChanges).toEqual({ - child1: { - key1: 'modified value', + await waitFor( + () => { + expect(onFireMock).toHaveBeenCalledTimes(2); + const childUnsavedChanges = onFireMock.mock.calls[1][0]; + expect(childUnsavedChanges).toEqual({ + child1: { + key1: 'modified value', + }, + }); }, - }); + { + interval: DEBOUNCE_TIME + 1, + } + ); subscription.unsubscribe(); }); test('should emit when children changes', async () => { - const subscription = childrenUnsavedChanges$(children$).pipe(skip(1)).subscribe(onFireMock); - await new Promise((resolve) => setTimeout(resolve, DEBOUNCE_TIME + 1)); - expect(onFireMock).toHaveBeenCalledTimes(0); + const subscription = childrenUnsavedChanges$(children$).subscribe(onFireMock); + await waitFor( + () => { + expect(onFireMock).toHaveBeenCalledTimes(1); + }, + { + interval: DEBOUNCE_TIME + 1, + } + ); // add child children$.next({ @@ -78,15 +101,21 @@ describe.skip('childrenUnsavedChanges$', () => { resetUnsavedChanges: () => undefined, }, }); - await new Promise((resolve) => setTimeout(resolve, DEBOUNCE_TIME + 1)); - expect(onFireMock).toHaveBeenCalledTimes(1); - const childUnsavedChanges = onFireMock.mock.calls[0][0]; - expect(childUnsavedChanges).toEqual({ - child3: { - key1: 'modified value', + await waitFor( + () => { + expect(onFireMock).toHaveBeenCalledTimes(2); + const childUnsavedChanges = onFireMock.mock.calls[1][0]; + expect(childUnsavedChanges).toEqual({ + child3: { + key1: 'modified value', + }, + }); }, - }); + { + interval: DEBOUNCE_TIME + 1, + } + ); subscription.unsubscribe(); }); diff --git a/packages/presentation/presentation_containers/interfaces/unsaved_changes/initialize_unsaved_changes.test.ts b/packages/presentation/presentation_containers/interfaces/unsaved_changes/initialize_unsaved_changes.test.ts index f8a72e6add588..2a8f5a625d42a 100644 --- a/packages/presentation/presentation_containers/interfaces/unsaved_changes/initialize_unsaved_changes.test.ts +++ b/packages/presentation/presentation_containers/interfaces/unsaved_changes/initialize_unsaved_changes.test.ts @@ -12,14 +12,14 @@ import { initializeUnsavedChanges, } from './initialize_unsaved_changes'; import { PublishesUnsavedChanges, StateComparators } from '@kbn/presentation-publishing'; +import { waitFor } from '@testing-library/react'; interface TestState { key1: string; key2: string; } -// Failing: See https://github.com/elastic/kibana/issues/189811 -describe.skip('unsavedChanges api', () => { +describe('unsavedChanges api', () => { const lastSavedState = { key1: 'original key1 value', key2: 'original key2 value', @@ -47,33 +47,42 @@ describe.skip('unsavedChanges api', () => { test('should have unsaved changes when state changes', async () => { key1$.next('modified key1 value'); - await new Promise((resolve) => setTimeout(resolve, COMPARATOR_SUBJECTS_DEBOUNCE + 1)); - expect(api?.unsavedChanges.value).toEqual({ - key1: 'modified key1 value', - }); + await waitFor( + () => + expect(api?.unsavedChanges.value).toEqual({ + key1: 'modified key1 value', + }), + { + interval: COMPARATOR_SUBJECTS_DEBOUNCE + 1, + } + ); }); test('should have no unsaved changes after save', async () => { key1$.next('modified key1 value'); - await new Promise((resolve) => setTimeout(resolve, COMPARATOR_SUBJECTS_DEBOUNCE + 1)); - expect(api?.unsavedChanges.value).not.toBeUndefined(); + await waitFor(() => expect(api?.unsavedChanges.value).not.toBeUndefined(), { + interval: COMPARATOR_SUBJECTS_DEBOUNCE + 1, + }); // trigger save parentApi.saveNotification$.next(); - await new Promise((resolve) => setTimeout(resolve, 0)); - expect(api?.unsavedChanges.value).toBeUndefined(); + await waitFor(() => expect(api?.unsavedChanges.value).toBeUndefined(), { + interval: COMPARATOR_SUBJECTS_DEBOUNCE + 1, + }); }); test('should have no unsaved changes after reset', async () => { key1$.next('modified key1 value'); - await new Promise((resolve) => setTimeout(resolve, COMPARATOR_SUBJECTS_DEBOUNCE + 1)); - expect(api?.unsavedChanges.value).not.toBeUndefined(); + await waitFor(() => expect(api?.unsavedChanges.value).not.toBeUndefined(), { + interval: COMPARATOR_SUBJECTS_DEBOUNCE + 1, + }); // trigger reset api?.resetUnsavedChanges(); - await new Promise((resolve) => setTimeout(resolve, COMPARATOR_SUBJECTS_DEBOUNCE + 1)); - expect(api?.unsavedChanges.value).toBeUndefined(); + await waitFor(() => expect(api?.unsavedChanges.value).toBeUndefined(), { + interval: COMPARATOR_SUBJECTS_DEBOUNCE + 1, + }); }); }); From 7243a5c3b5dea3211667d67569b6b100639597f1 Mon Sep 17 00:00:00 2001 From: Kevin Delemme Date: Wed, 7 Aug 2024 13:26:11 -0400 Subject: [PATCH 23/44] chore(rca): remove revisions (#189983) Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Maryam Saeidi --- .../investigate/common/index.ts | 8 +- .../investigate/common/types.ts | 28 +- .../investigate/public/create_widget.ts | 3 +- .../public/hooks/use_investigate_widget.tsx | 7 +- .../create_new_investigation.ts | 14 +- .../public/hooks/use_investigation/index.tsx | 150 ++------- .../use_investigation/investigation_store.ts | 300 +++--------------- .../investigate/public/index.ts | 2 - .../investigate/public/types.ts | 7 +- .../get_es_filters_from_global_parameters.ts | 9 +- .../components/add_note_ui/index.stories.tsx | 1 - .../esql_widget_preview.tsx | 5 +- .../components/add_observation_ui/index.tsx | 3 +- .../components/grid_item/index.stories.tsx | 7 - .../public/components/grid_item/index.tsx | 86 +---- .../components/investigate_view/index.tsx | 101 ++---- .../investigate_widget_grid/index.stories.tsx | 2 - .../investigate_widget_grid/index.tsx | 79 +---- .../utils/get_es_filter_from_overrides.ts | 6 +- .../get_overrides_from_global_parameters.tsx | 72 ----- .../register_embeddable_widget.tsx | 17 +- .../esql_widget/register_esql_widget.tsx | 7 +- .../investigate_app/tsconfig.json | 1 - 23 files changed, 108 insertions(+), 807 deletions(-) delete mode 100644 x-pack/plugins/observability_solution/investigate_app/public/utils/get_overrides_from_global_parameters.tsx diff --git a/x-pack/plugins/observability_solution/investigate/common/index.ts b/x-pack/plugins/observability_solution/investigate/common/index.ts index 467a892ca55d1..d97a5c9e3c4d2 100644 --- a/x-pack/plugins/observability_solution/investigate/common/index.ts +++ b/x-pack/plugins/observability_solution/investigate/common/index.ts @@ -4,13 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -export type { - Investigation, - InvestigationRevision, - InvestigateWidget, - InvestigateWidgetCreate, - WorkflowBlock, -} from './types'; +export type { Investigation, InvestigateWidget, InvestigateWidgetCreate } from './types'; export { mergePlainObjects } from './utils/merge_plain_objects'; diff --git a/x-pack/plugins/observability_solution/investigate/common/types.ts b/x-pack/plugins/observability_solution/investigate/common/types.ts index 756e346ffe7af..e8ca999e8af7f 100644 --- a/x-pack/plugins/observability_solution/investigate/common/types.ts +++ b/x-pack/plugins/observability_solution/investigate/common/types.ts @@ -5,17 +5,14 @@ * 2.0. */ -import type { EuiThemeComputed } from '@elastic/eui'; -import type { Filter } from '@kbn/es-query'; -import type { DeepPartial, PickByValue } from 'utility-types'; import type { AuthenticatedUser } from '@kbn/core/public'; +import type { DeepPartial } from 'utility-types'; export interface GlobalWidgetParameters { timeRange: { from: string; to: string; }; - filters: Filter[]; } export enum InvestigateWidgetColumnSpan { @@ -25,19 +22,13 @@ export enum InvestigateWidgetColumnSpan { Four = 4, } -export interface InvestigationRevision { - id: string; - items: InvestigateWidget[]; - parameters: GlobalWidgetParameters; -} - export interface Investigation { id: string; '@timestamp': number; user: AuthenticatedUser; - revisions: InvestigationRevision[]; title: string; - revision: string; + items: InvestigateWidget[]; + parameters: GlobalWidgetParameters; } export interface InvestigateWidget< @@ -55,22 +46,11 @@ export interface InvestigateWidget< description?: string; columns: InvestigateWidgetColumnSpan; rows: number; - locked: boolean; } export type InvestigateWidgetCreate = {}> = Pick< InvestigateWidget, - 'title' | 'description' | 'columns' | 'rows' | 'type' | 'locked' + 'title' | 'description' | 'columns' | 'rows' | 'type' > & { parameters: DeepPartial & TParameters; }; - -export interface WorkflowBlock { - id: string; - content?: string; - description?: string; - loading: boolean; - onClick?: () => void; - color?: keyof PickByValue['colors'], string>; - children?: React.ReactNode; -} diff --git a/x-pack/plugins/observability_solution/investigate/public/create_widget.ts b/x-pack/plugins/observability_solution/investigate/public/create_widget.ts index 7528565d06165..29058298c674d 100644 --- a/x-pack/plugins/observability_solution/investigate/public/create_widget.ts +++ b/x-pack/plugins/observability_solution/investigate/public/create_widget.ts @@ -12,7 +12,7 @@ import { GlobalWidgetParameters } from '../common/types'; type MakePartial, K extends keyof T> = Omit & DeepPartial>; -type PredefinedKeys = 'rows' | 'columns' | 'locked' | 'type'; +type PredefinedKeys = 'rows' | 'columns' | 'type'; type AllowedDefaultKeys = 'rows' | 'columns'; @@ -31,7 +31,6 @@ export function createWidgetFactory>( return { rows: 12, columns: InvestigateWidgetColumnSpan.Four, - locked: false, type, ...defaults, ...widgetCreate, diff --git a/x-pack/plugins/observability_solution/investigate/public/hooks/use_investigate_widget.tsx b/x-pack/plugins/observability_solution/investigate/public/hooks/use_investigate_widget.tsx index 3ace787c71439..a29614f74782b 100644 --- a/x-pack/plugins/observability_solution/investigate/public/hooks/use_investigate_widget.tsx +++ b/x-pack/plugins/observability_solution/investigate/public/hooks/use_investigate_widget.tsx @@ -5,18 +5,13 @@ * 2.0. */ import { useContext, createContext } from 'react'; -import type { InvestigateWidgetCreate, WorkflowBlock } from '../../common'; - -type UnregisterBlocksFunction = () => void; +import type { InvestigateWidgetCreate } from '../../common'; export interface UseInvestigateWidgetApi< TParameters extends Record = {}, TData extends Record = {} > { onWidgetAdd: (create: InvestigateWidgetCreate) => Promise; - blocks: { - publish: (blocks: WorkflowBlock[]) => UnregisterBlocksFunction; - }; } const InvestigateWidgetApiContext = createContext(undefined); diff --git a/x-pack/plugins/observability_solution/investigate/public/hooks/use_investigation/create_new_investigation.ts b/x-pack/plugins/observability_solution/investigate/public/hooks/use_investigation/create_new_investigation.ts index 51c0a68c782c8..7eab4a192ca3c 100644 --- a/x-pack/plugins/observability_solution/investigate/public/hooks/use_investigation/create_new_investigation.ts +++ b/x-pack/plugins/observability_solution/investigate/public/hooks/use_investigation/create_new_investigation.ts @@ -8,7 +8,7 @@ import type { AuthenticatedUser } from '@kbn/security-plugin/common'; import { v4 } from 'uuid'; import { i18n } from '@kbn/i18n'; -import type { Investigation, InvestigationRevision } from '../../../common'; +import type { Investigation } from '../../../common'; import { GlobalWidgetParameters } from '../../../common/types'; export function createNewInvestigation({ @@ -20,14 +20,6 @@ export function createNewInvestigation({ user: AuthenticatedUser; globalWidgetParameters: GlobalWidgetParameters; }): Investigation { - const revisionId = v4(); - - const revision: InvestigationRevision = { - id: revisionId, - items: [], - parameters: globalWidgetParameters, - }; - return { '@timestamp': new Date().getTime(), user, @@ -35,7 +27,7 @@ export function createNewInvestigation({ title: i18n.translate('xpack.investigate.newInvestigationTitle', { defaultMessage: 'New investigation', }), - revision: revisionId, - revisions: [revision], + items: [], + parameters: globalWidgetParameters, }; } diff --git a/x-pack/plugins/observability_solution/investigate/public/hooks/use_investigation/index.tsx b/x-pack/plugins/observability_solution/investigate/public/hooks/use_investigation/index.tsx index 4bf2e10cad4be..069255e20e233 100644 --- a/x-pack/plugins/observability_solution/investigate/public/hooks/use_investigation/index.tsx +++ b/x-pack/plugins/observability_solution/investigate/public/hooks/use_investigation/index.tsx @@ -6,17 +6,12 @@ */ import type { AuthenticatedUser, NotificationsStart } from '@kbn/core/public'; import { i18n } from '@kbn/i18n'; -import { last, omit, pull } from 'lodash'; +import { pull } from 'lodash'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import useObservable from 'react-use/lib/useObservable'; import { v4 } from 'uuid'; import type { GlobalWidgetParameters } from '../..'; -import type { - InvestigateWidget, - InvestigateWidgetCreate, - Investigation, - WorkflowBlock, -} from '../../../common'; +import type { InvestigateWidget, InvestigateWidgetCreate, Investigation } from '../../../common'; import type { WidgetDefinition } from '../../types'; import { InvestigateWidgetApiContextProvider, @@ -24,56 +19,28 @@ import { } from '../use_investigate_widget'; import { useLocalStorage } from '../use_local_storage'; import { createNewInvestigation } from './create_new_investigation'; -import { - createInvestigationStore, - StatefulInvestigation, - StatefulInvestigationRevision, -} from './investigation_store'; +import { StatefulInvestigation, createInvestigationStore } from './investigation_store'; export type RenderableInvestigateWidget = InvestigateWidget & { loading: boolean; element: React.ReactNode; }; -export type RenderableInvestigationRevision = Omit & { +export type RenderableInvestigation = Omit & { items: RenderableInvestigateWidget[]; }; -export type RenderableInvestigateTimeline = Omit & { - revisions: RenderableInvestigationRevision[]; -}; - export interface UseInvestigationApi { startNewInvestigation: (id: string) => void; loadInvestigation: (id: string) => void; - investigation?: Omit; - revision?: RenderableInvestigationRevision; - isAtLatestRevision: boolean; - isAtEarliestRevision: boolean; - setItemPositions: ( - positions: Array<{ id: string; columns: number; rows: number }> - ) => Promise; - setItemTitle: (id: string, title: string) => Promise; - updateItem: ( - id: string, - cb: (item: InvestigateWidget) => Promise - ) => Promise; + investigations: Investigation[]; + investigation?: StatefulInvestigation; + renderableInvestigation?: RenderableInvestigation; copyItem: (id: string) => Promise; deleteItem: (id: string) => Promise; addItem: (options: InvestigateWidgetCreate) => Promise; - lockItem: (id: string) => Promise; - unlockItem: (id: string) => Promise; - setItemParameters: ( - id: string, - parameters: GlobalWidgetParameters & Record - ) => Promise; setGlobalParameters: (parameters: GlobalWidgetParameters) => Promise; - blocks: WorkflowBlock[]; - setRevision: (revisionId: string) => void; - gotoPreviousRevision: () => Promise; - gotoNextRevision: () => Promise; setTitle: (title: string) => Promise; - investigations: Investigation[]; deleteInvestigation: (id: string) => Promise; } @@ -98,7 +65,6 @@ function useInvestigationWithoutContext({ user, id: v4(), globalWidgetParameters: { - filters: [], timeRange: { from, to, @@ -109,22 +75,10 @@ function useInvestigationWithoutContext({ ); const investigation$ = investigationStore.asObservable(); - const investigation = useObservable(investigation$)?.investigation; - const currentRevision = useMemo(() => { - return investigation?.revisions.find((revision) => revision.id === investigation.revision); - }, [investigation?.revision, investigation?.revisions]); - - const isAtEarliestRevision = investigation?.revisions[0].id === currentRevision?.id; - - const isAtLatestRevision = last(investigation?.revisions)?.id === currentRevision?.id; - - const [blocksById, setBlocksById] = useState>({}); - const deleteItem = useCallback( async (id: string) => { - setBlocksById((prevBlocks) => omit(prevBlocks, id)); return investigationStore.deleteItem(id); }, [investigationStore] @@ -141,7 +95,7 @@ function useInvestigationWithoutContext({ const unusedComponentIds = Object.keys(widgetComponentsById); const nextItemsWithContext = - currentRevision?.items.map((item) => { + investigation?.items.map((item) => { let Component = widgetComponentsById.current[item.id]; if (!Component) { const id = item.id; @@ -149,21 +103,6 @@ function useInvestigationWithoutContext({ onWidgetAdd: async (create) => { return addItemRef.current(create); }, - blocks: { - publish: (nextBlocks) => { - const nextIds = nextBlocks.map((block) => block.id); - setBlocksById((prevBlocksById) => ({ - ...prevBlocksById, - [id]: (prevBlocksById[id] ?? []).concat(nextBlocks), - })); - return () => { - setBlocksById((prevBlocksById) => ({ - ...prevBlocksById, - [id]: (prevBlocksById[id] ?? []).filter((block) => !nextIds.includes(block.id)), - })); - }; - }, - }, }; const onDelete = () => { @@ -179,7 +118,6 @@ function useInvestigationWithoutContext({ {widgetDefinition ? widgetDefinition.render({ - blocks: api.blocks, onWidgetAdd: api.onWidgetAdd, onDelete, widget: props.widget, @@ -203,15 +141,13 @@ function useInvestigationWithoutContext({ }); return nextItemsWithContext; - }, [currentRevision?.items, widgetDefinitions, investigationStore]); + }, [investigation?.items, widgetDefinitions, investigationStore]); const addItem = useCallback( async (widget: InvestigateWidgetCreate) => { try { const id = v4(); - setBlocksById((prevBlocksById) => ({ ...prevBlocksById, [id]: [] })); - await investigationStore.addItem(id, widget); } catch (error) { notifications.showErrorDialog({ @@ -228,10 +164,10 @@ function useInvestigationWithoutContext({ const addItemRef = useRef(addItem); addItemRef.current = addItem; - const renderableRevision = useMemo(() => { - return currentRevision + const renderableInvestigation = useMemo(() => { + return investigation ? { - ...currentRevision, + ...investigation, items: itemsWithContext.map((item) => { const { Component, ...rest } = item; return { @@ -241,17 +177,15 @@ function useInvestigationWithoutContext({ }), } : undefined; - }, [currentRevision, itemsWithContext]); + }, [investigation, itemsWithContext]); const startNewInvestigation = useCallback( async (id: string) => { const prevInvestigation = await investigationStore.getInvestigation(); - const lastRevision = last(prevInvestigation.revisions)!; - const createdInvestigationStore = createInvestigationStore({ investigation: createNewInvestigation({ - globalWidgetParameters: lastRevision.parameters, + globalWidgetParameters: prevInvestigation.parameters, user, id, }) as StatefulInvestigation, @@ -260,38 +194,11 @@ function useInvestigationWithoutContext({ }); setInvestigationStore(createdInvestigationStore); - - setBlocksById({}); }, [user, widgetDefinitions, investigationStore] ); - const setItemParameters = useCallback( - async (id: string, nextGlobalWidgetParameters: GlobalWidgetParameters) => { - return investigationStore.setItemParameters(id, nextGlobalWidgetParameters); - }, - [investigationStore] - ); - - const lastItemId = last(currentRevision?.items)?.id; - - const blocks = useMemo(() => { - return lastItemId && blocksById[lastItemId] ? blocksById[lastItemId] : []; - }, [blocksById, lastItemId]); - - const { - copyItem, - gotoNextRevision, - gotoPreviousRevision, - lockItem, - setGlobalParameters, - setItemPositions, - setItemTitle, - setRevision, - setTitle, - unlockItem, - updateItem, - } = investigationStore; + const { copyItem, setGlobalParameters, setTitle } = investigationStore; const { storedItem: investigations, setStoredItem: setInvestigations } = useLocalStorage< Investigation[] @@ -337,7 +244,7 @@ function useInvestigationWithoutContext({ } const subscription = investigation$.subscribe(({ investigation: investigationFromStore }) => { - const isEmpty = investigationFromStore.revisions.length === 1; + const isEmpty = investigationFromStore.items.length === 0; if (isEmpty) { return; @@ -345,14 +252,9 @@ function useInvestigationWithoutContext({ const toSerialize = { ...investigationFromStore, - revisions: investigationFromStore.revisions.map((revision) => { - return { - ...revision, - items: revision.items.map((item) => { - const { loading, ...rest } = item; - return rest; - }), - }; + items: investigationFromStore.items.map((item) => { + const { loading, ...rest } = item; + return rest; }), }; @@ -403,26 +305,14 @@ function useInvestigationWithoutContext({ return { addItem, - blocks, copyItem, deleteItem, - gotoNextRevision, - gotoPreviousRevision, investigation, - isAtEarliestRevision, - isAtLatestRevision, loadInvestigation, - lockItem, - revision: renderableRevision, + renderableInvestigation, setGlobalParameters, - setItemParameters, - setItemPositions, - setItemTitle, - setRevision, setTitle, startNewInvestigation, - unlockItem, - updateItem, investigations, deleteInvestigation, }; diff --git a/x-pack/plugins/observability_solution/investigate/public/hooks/use_investigation/investigation_store.ts b/x-pack/plugins/observability_solution/investigate/public/hooks/use_investigation/investigation_store.ts index 54281b6347ea1..8502079a4b228 100644 --- a/x-pack/plugins/observability_solution/investigate/public/hooks/use_investigation/investigation_store.ts +++ b/x-pack/plugins/observability_solution/investigate/public/hooks/use_investigation/investigation_store.ts @@ -5,17 +5,15 @@ * 2.0. */ -import { BehaviorSubject, Observable } from 'rxjs'; import type { AuthenticatedUser } from '@kbn/security-plugin/common'; -import { v4 } from 'uuid'; import { MaybePromise } from '@kbn/utility-types'; -import { keyBy } from 'lodash'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { v4 } from 'uuid'; import { InvestigateWidget, mergePlainObjects } from '../../../common'; import { GlobalWidgetParameters, InvestigateWidgetCreate, Investigation, - InvestigationRevision, } from '../../../common/types'; import { WidgetDefinition } from '../../types'; @@ -23,37 +21,19 @@ export type StatefulInvestigateWidget = InvestigateWidget & { loading: boolean; }; -export type StatefulInvestigation = Omit & { - revisions: StatefulInvestigationRevision[]; -}; - -export type StatefulInvestigationRevision = Omit & { +export type StatefulInvestigation = Omit & { items: StatefulInvestigateWidget[]; }; interface InvestigationStore { - setItemPositions: ( - positions: Array<{ id: string; columns: number; rows: number }> - ) => Promise; copyItem: (id: string) => Promise; deleteItem: (id: string) => Promise; addItem: (id: string, item: InvestigateWidgetCreate) => Promise; - updateItem: ( - id: string, - cb: (prevItem: InvestigateWidget) => Promise - ) => Promise; - lockItem: (id: string) => Promise; - unlockItem: (id: string) => Promise; - setItemTitle: (id: string, title: string) => Promise; - setRevision: (revisionId: string) => Promise; - gotoPreviousRevision: () => Promise; - gotoNextRevision: () => Promise; asObservable: () => Observable<{ investigation: StatefulInvestigation; }>; getInvestigation: () => Promise>; setGlobalParameters: (globalWidgetParameters: GlobalWidgetParameters) => Promise; - setItemParameters: (id: string, parameters: GlobalWidgetParameters) => Promise; setTitle: (title: string) => Promise; destroy: () => void; } @@ -81,11 +61,7 @@ async function regenerateItem({ throw new Error(`Definition for widget ${widget.type} not found`); } - const nextParameters = mergePlainObjects( - globalWidgetParameters, - widget.parameters, - widget.locked ? {} : globalWidgetParameters - ); + const nextParameters = mergePlainObjects(widget.parameters, globalWidgetParameters); const widgetData = await definition.generate({ parameters: nextParameters, @@ -117,12 +93,7 @@ export function createInvestigationStore({ const observable$ = new BehaviorSubject<{ investigation: StatefulInvestigation }>({ investigation: { ...investigation, - revisions: investigation.revisions.map((revision) => { - return { - ...revision, - items: revision.items.map((item) => ({ ...item, loading: false })), - }; - }), + items: investigation.items.map((item) => ({ ...item, loading: false })), }, }); @@ -132,137 +103,14 @@ export function createInvestigationStore({ observable$.next({ investigation: await cb(observable$.value.investigation) }); } - async function updateRevisionInPlace( - cb: (prevRevision: StatefulInvestigationRevision) => MaybePromise - ) { - return updateInvestigationInPlace(async (prevInvestigation) => { - const currentRevision = prevInvestigation.revisions.find((revision) => { - return revision.id === prevInvestigation.revision; - })!; - - const newRevision = await cb(currentRevision); - - return { - ...prevInvestigation, - revisions: prevInvestigation.revisions.map((revision) => { - return revision.id === currentRevision.id ? newRevision : revision; - }), - }; - }); - } - - async function nextRevision( - cb: (prevRevision: StatefulInvestigationRevision) => MaybePromise - ) { - return updateInvestigationInPlace(async (prevInvestigation) => { - const indexOfCurrentRevision = prevInvestigation.revisions.findIndex( - (revision) => revision.id === prevInvestigation.revision - ); - - const currentRevision = prevInvestigation.revisions[indexOfCurrentRevision]; - - const newRevision = { - ...(await cb(currentRevision)), - id: v4(), - }; - - return { - ...prevInvestigation, - revisions: prevInvestigation.revisions - .slice(0, indexOfCurrentRevision + 1) - .concat(newRevision) - .slice(-10), - revision: newRevision.id, - }; - }); - } - - async function updateItem( - itemId: string, - cb: (prevItem: InvestigateWidget) => MaybePromise - ) { - await updateRevisionInPlace(async (prevRevision) => { - const prevItem = prevRevision.items.find((item) => item.id === itemId); - if (!prevItem) { - throw new Error('Could not find item by id ' + itemId); - } - return { - ...prevRevision, - items: prevRevision.items.map((item) => { - if (item === prevItem) { - return { ...prevItem, loading: true }; - } - return item; - }), - }; - }); - - await nextRevision(async (prevRevision) => { - const prevItem = prevRevision.items.find((item) => item.id === itemId); - if (!prevItem) { - throw new Error('Could not find item by id ' + itemId); - } - const nextItem = await cb(prevItem); - return { - ...prevRevision, - items: prevRevision.items.map((item) => { - if (item === prevItem) { - return { ...nextItem, loading: false }; - } - return item; - }), - }; - }); - } - - const regenerateItemAndUpdateRevision = async ( - itemId: string, - partials: Partial - ) => { - await updateRevisionInPlace((prevRevision) => { - return { - ...prevRevision, - items: prevRevision.items.map((item) => { - if (item.id === itemId) { - return { ...item, loading: true, ...partials }; - } - return item; - }), - }; - }); - - await nextRevision(async (prevRevision) => { - return { - ...prevRevision, - items: await Promise.all( - prevRevision.items.map(async (item) => { - if (item.id === itemId) { - return { - ...(await regenerateItem({ - user, - globalWidgetParameters: prevRevision.parameters, - signal: controller.signal, - widget: item, - widgetDefinitions, - })), - loading: false, - }; - } - return item; - }) - ), - }; - }); - }; - const asObservable = observable$.asObservable(); return { addItem: (itemId, item) => { - return nextRevision(async (prevRevision) => { + return updateInvestigationInPlace(async (prevInvestigation) => { return { - ...prevRevision, - items: prevRevision.items.concat({ + ...prevInvestigation, + items: prevInvestigation.items.concat({ ...(await regenerateItem({ user, widgetDefinitions, @@ -271,25 +119,22 @@ export function createInvestigationStore({ ...item, id: itemId, }, - globalWidgetParameters: prevRevision.parameters, + globalWidgetParameters: prevInvestigation.parameters, })), loading: false, }), }; }); }, - updateItem: async (itemId, cb) => { - return updateItem(itemId, cb); - }, copyItem: (itemId) => { - return nextRevision((prevRevision) => { - const itemToCopy = prevRevision.items.find((item) => item.id === itemId); + return updateInvestigationInPlace((prevInvestigation) => { + const itemToCopy = prevInvestigation.items.find((item) => item.id === itemId); if (!itemToCopy) { throw new Error('Cannot find item for id ' + itemId); } return { - ...prevRevision, - items: prevRevision.items.concat({ + ...prevInvestigation, + items: prevInvestigation.items.concat({ ...itemToCopy, id: v4(), }), @@ -297,72 +142,17 @@ export function createInvestigationStore({ }); }, deleteItem: (itemId) => { - return nextRevision((prevRevision) => { - const itemToDelete = prevRevision.items.find((item) => item.id === itemId); - if (!itemToDelete) { - return prevRevision; - } - return { - ...prevRevision, - items: prevRevision.items.filter((itemAtIndex) => itemAtIndex.id !== itemToDelete.id), - }; - }); - }, - setItemPositions: (positions) => { - return nextRevision((prevRevision) => { - const positionsById = keyBy(positions, (position) => position.id); - return { - ...prevRevision, - items: prevRevision.items.map((item) => { - const position = positionsById[item.id]; - - return { - ...item, - ...position, - }; - }), - }; - }); - }, - setRevision: (revision) => { - return updateInvestigationInPlace((prevInvestigation) => { - return { - ...prevInvestigation, - revision, - }; - }); - }, - gotoPreviousRevision: () => { return updateInvestigationInPlace((prevInvestigation) => { - const indexOfCurrentRevision = prevInvestigation.revisions.findIndex( - (revision) => revision.id === prevInvestigation.revision - ); - - const targetRevision = prevInvestigation.revisions[indexOfCurrentRevision - 1]; - if (!targetRevision) { - throw new Error('Could not find previous revision'); - } - - return { - ...prevInvestigation, - revision: targetRevision.id, - }; - }); - }, - gotoNextRevision: () => { - return updateInvestigationInPlace((prevInvestigation) => { - const indexOfCurrentRevision = prevInvestigation.revisions.findIndex( - (revision) => revision.id === prevInvestigation.revision - ); - - const targetRevision = prevInvestigation.revisions[indexOfCurrentRevision + 1]; - if (!targetRevision) { - throw new Error('Could not find previous revision'); + const itemToDelete = prevInvestigation.items.find((item) => item.id === itemId); + if (!itemToDelete) { + return prevInvestigation; } return { ...prevInvestigation, - revision: targetRevision.id, + items: prevInvestigation.items.filter( + (itemAtIndex) => itemAtIndex.id !== itemToDelete.id + ), }; }); }, @@ -372,54 +162,40 @@ export function createInvestigationStore({ return controller.abort(); }, setGlobalParameters: async (parameters) => { - await updateRevisionInPlace((prevRevision) => { + await updateInvestigationInPlace((prevInvestigation) => { return { - ...prevRevision, - items: prevRevision.items.map((item) => { - return { ...item, loading: !item.locked }; + ...prevInvestigation, + items: prevInvestigation.items.map((item) => { + return { ...item, loading: true }; }), }; }); - await nextRevision(async (prevRevision) => { + await updateInvestigationInPlace(async (prevInvestigation) => { return { - ...prevRevision, + ...prevInvestigation, parameters, items: await Promise.all( - prevRevision.items.map(async (item) => { - return item.locked - ? item - : { - ...(await regenerateItem({ - widget: item, - globalWidgetParameters: parameters, - signal: controller.signal, - user, - widgetDefinitions, - })), - loading: false, - }; + prevInvestigation.items.map(async (item) => { + return { + ...(await regenerateItem({ + widget: item, + globalWidgetParameters: parameters, + signal: controller.signal, + user, + widgetDefinitions, + })), + loading: false, + }; }) ), }; }); }, - setItemTitle: async (itemId, title) => { - return updateItem(itemId, (prev) => ({ ...prev, title })); - }, - lockItem: async (itemId) => { - return updateItem(itemId, (prev) => ({ ...prev, locked: true })); - }, - unlockItem: async (itemId) => { - await regenerateItemAndUpdateRevision(itemId, { locked: false }); - }, setTitle: async (title: string) => { - return nextRevision((prevRevision) => { - return { ...prevRevision, title }; + return updateInvestigationInPlace((prevInvestigation) => { + return { ...prevInvestigation, title }; }); }, - setItemParameters: async (itemId, nextParameters) => { - await regenerateItemAndUpdateRevision(itemId, { parameters: nextParameters }); - }, }; } diff --git a/x-pack/plugins/observability_solution/investigate/public/index.ts b/x-pack/plugins/observability_solution/investigate/public/index.ts index b830aebc89947..8d296a321c94d 100644 --- a/x-pack/plugins/observability_solution/investigate/public/index.ts +++ b/x-pack/plugins/observability_solution/investigate/public/index.ts @@ -21,12 +21,10 @@ export type { InvestigatePublicSetup, InvestigatePublicStart, OnWidgetAdd, Widge export { type Investigation, - type InvestigationRevision, type InvestigateWidget, type InvestigateWidgetCreate, InvestigateWidgetColumnSpan, type GlobalWidgetParameters, - type WorkflowBlock, } from '../common/types'; export { mergePlainObjects } from '../common/utils/merge_plain_objects'; diff --git a/x-pack/plugins/observability_solution/investigate/public/types.ts b/x-pack/plugins/observability_solution/investigate/public/types.ts index fc6ca0376d20f..8951781be99f2 100644 --- a/x-pack/plugins/observability_solution/investigate/public/types.ts +++ b/x-pack/plugins/observability_solution/investigate/public/types.ts @@ -9,7 +9,7 @@ import type { FromSchema } from 'json-schema-to-ts'; import type { CompatibleJSONSchema } from '@kbn/observability-ai-assistant-plugin/public'; import type { AuthenticatedUser } from '@kbn/core/public'; -import type { InvestigateWidget, WorkflowBlock } from '../common'; +import type { InvestigateWidget } from '../common'; import type { GlobalWidgetParameters, InvestigateWidgetCreate } from '../common/types'; import type { UseInvestigationApi } from './hooks/use_investigation'; import type { UseInvestigateWidgetApi } from './hooks/use_investigate_widget'; @@ -22,14 +22,9 @@ export enum ChromeOption { export type OnWidgetAdd = (create: InvestigateWidgetCreate) => Promise; -type UnregisterFunction = () => void; - export interface WidgetRenderAPI { onDelete: () => void; onWidgetAdd: OnWidgetAdd; - blocks: { - publish: (blocks: WorkflowBlock[]) => UnregisterFunction; - }; } type WidgetRenderOptions = { diff --git a/x-pack/plugins/observability_solution/investigate/public/util/get_es_filters_from_global_parameters.ts b/x-pack/plugins/observability_solution/investigate/public/util/get_es_filters_from_global_parameters.ts index 8d8592bf09a9a..10d3c76a86d03 100644 --- a/x-pack/plugins/observability_solution/investigate/public/util/get_es_filters_from_global_parameters.ts +++ b/x-pack/plugins/observability_solution/investigate/public/util/get_es_filters_from_global_parameters.ts @@ -8,11 +8,10 @@ import { type BoolQuery, buildEsQuery } from '@kbn/es-query'; import type { GlobalWidgetParameters } from '../../common/types'; -export function getEsFilterFromGlobalParameters({ - filters, - timeRange, -}: Partial): { bool: BoolQuery } { - const esFilter = buildEsQuery(undefined, [], filters ?? []); +export function getEsFilterFromGlobalParameters({ timeRange }: Partial): { + bool: BoolQuery; +} { + const esFilter = buildEsQuery(undefined, [], []); if (timeRange) { esFilter.bool.filter.push({ diff --git a/x-pack/plugins/observability_solution/investigate_app/public/components/add_note_ui/index.stories.tsx b/x-pack/plugins/observability_solution/investigate_app/public/components/add_note_ui/index.stories.tsx index edb714c6d388e..0db8b3b67ee35 100644 --- a/x-pack/plugins/observability_solution/investigate_app/public/components/add_note_ui/index.stories.tsx +++ b/x-pack/plugins/observability_solution/investigate_app/public/components/add_note_ui/index.stories.tsx @@ -34,7 +34,6 @@ const defaultStory: Story = { username: 'johndoe', full_name: 'John Doe', }, - filters: [], timeRange: { from: moment().subtract(15, 'minutes').toISOString(), to: moment().toISOString(), diff --git a/x-pack/plugins/observability_solution/investigate_app/public/components/add_observation_ui/esql_widget_preview.tsx b/x-pack/plugins/observability_solution/investigate_app/public/components/add_observation_ui/esql_widget_preview.tsx index 2bd84639e67dc..a661e6fdc8eae 100644 --- a/x-pack/plugins/observability_solution/investigate_app/public/components/add_observation_ui/esql_widget_preview.tsx +++ b/x-pack/plugins/observability_solution/investigate_app/public/components/add_observation_ui/esql_widget_preview.tsx @@ -52,7 +52,6 @@ function getWidgetFromSuggestion({ }, columns: makeItWide ? InvestigateWidgetColumnSpan.Four : InvestigateWidgetColumnSpan.One, rows, - locked: false, }); } @@ -79,7 +78,6 @@ function PreviewContainer({ children }: { children: React.ReactNode }) { export function EsqlWidgetPreview({ esqlQuery, onWidgetAdd, - filters, timeRange, }: { esqlQuery: string; @@ -91,10 +89,9 @@ export function EsqlWidgetPreview({ const filter = useMemo(() => { return getEsFilterFromOverrides({ - filters, timeRange, }); - }, [filters, timeRange]); + }, [timeRange]); const [selectedSuggestion, setSelectedSuggestion] = useState(undefined); diff --git a/x-pack/plugins/observability_solution/investigate_app/public/components/add_observation_ui/index.tsx b/x-pack/plugins/observability_solution/investigate_app/public/components/add_observation_ui/index.tsx index 7d5424f9005d5..69f43ef515146 100644 --- a/x-pack/plugins/observability_solution/investigate_app/public/components/add_observation_ui/index.tsx +++ b/x-pack/plugins/observability_solution/investigate_app/public/components/add_observation_ui/index.tsx @@ -21,7 +21,7 @@ const emptyPreview = css` padding: 36px 0px 36px 0px; `; -export function AddObservationUI({ onWidgetAdd, timeRange, filters }: Props) { +export function AddObservationUI({ onWidgetAdd, timeRange }: Props) { const [isOpen, setIsOpen] = React.useState(false); const [query, setQuery] = React.useState({ esql: '' }); @@ -111,7 +111,6 @@ export function AddObservationUI({ onWidgetAdd, timeRange, filters }: Props) { ) : ( { diff --git a/x-pack/plugins/observability_solution/investigate_app/public/components/grid_item/index.stories.tsx b/x-pack/plugins/observability_solution/investigate_app/public/components/grid_item/index.stories.tsx index aff1eab514351..0f66c5403e172 100644 --- a/x-pack/plugins/observability_solution/investigate_app/public/components/grid_item/index.stories.tsx +++ b/x-pack/plugins/observability_solution/investigate_app/public/components/grid_item/index.stories.tsx @@ -40,18 +40,11 @@ const defaultProps: Story = { return (
      {}} onDelete={() => {}} - onLockToggle={() => {}} - onOverrideRemove={async () => {}} - onTitleChange={() => {}} - overrides={[]} title="My visualization" description="A long description" - onEditClick={() => {}} {...props} />
      diff --git a/x-pack/plugins/observability_solution/investigate_app/public/components/grid_item/index.tsx b/x-pack/plugins/observability_solution/investigate_app/public/components/grid_item/index.tsx index df418271fac22..1068d59ce304c 100644 --- a/x-pack/plugins/observability_solution/investigate_app/public/components/grid_item/index.tsx +++ b/x-pack/plugins/observability_solution/investigate_app/public/components/grid_item/index.tsx @@ -4,14 +4,11 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiPanel, EuiText } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiText } from '@elastic/eui'; import { css } from '@emotion/css'; -import classNames from 'classnames'; import React from 'react'; -import { i18n } from '@kbn/i18n'; import { useTheme } from '../../hooks/use_theme'; import { InvestigateTextButton } from '../investigate_text_button'; -import { InvestigateWidgetGridItemOverride } from '../investigate_widget_grid'; export const GRID_ITEM_HEADER_HEIGHT = 40; @@ -20,16 +17,9 @@ interface GridItemProps { title: string; description: string; children: React.ReactNode; - locked: boolean; onCopy: () => void; - onTitleChange: (title: string) => void; onDelete: () => void; - onLockToggle: () => void; loading: boolean; - faded: boolean; - onOverrideRemove: (override: InvestigateWidgetGridItemOverride) => Promise; - onEditClick: () => void; - overrides: InvestigateWidgetGridItemOverride[]; } const editTitleButtonClassName = `investigateGridItemTitleEditButton`; @@ -46,17 +36,6 @@ const titleItemClassName = css` } `; -const fadedClassName = css` - opacity: 0.5 !important; -`; - -const lockedControlClassName = css` - opacity: 0.9 !important; - &:hover { - opacity: 1 !important; - } -`; - const panelContainerClassName = css` overflow: clip; overflow-clip-margin: 20px; @@ -78,28 +57,14 @@ const headerClassName = css` height: ${GRID_ITEM_HEADER_HEIGHT}px; `; -const changeBadgeClassName = css` - max-width: 96px; - .euiText { - text-overflow: ellipsis; - overflow: hidden; - } -`; - export function GridItem({ id, title, description, children, - locked, - onLockToggle, onDelete, onCopy, loading, - faded, - overrides, - onOverrideRemove, - onEditClick, }: GridItemProps) { const theme = useTheme(); @@ -118,7 +83,7 @@ export function GridItem({ @@ -133,33 +98,6 @@ export function GridItem({ {title} - - {overrides.length ? ( - - {overrides.map((override) => ( - - { - onOverrideRemove(override); - }} - iconOnClickAriaLabel={i18n.translate( - 'xpack.investigateApp.gridItem.removeOverrideButtonAriaLabel', - { - defaultMessage: 'Remove filter', - } - )} - > - {override.label} - - - ))} - - ) : null} - - - { - onEditClick(); - }} - disabled={loading} - /> - - - { - onLockToggle(); - }} - disabled={loading} - /> - diff --git a/x-pack/plugins/observability_solution/investigate_app/public/components/investigate_view/index.tsx b/x-pack/plugins/observability_solution/investigate_app/public/components/investigate_view/index.tsx index a562c3647fcd4..37ed996162ed2 100644 --- a/x-pack/plugins/observability_solution/investigate_app/public/components/investigate_view/index.tsx +++ b/x-pack/plugins/observability_solution/investigate_app/public/components/investigate_view/index.tsx @@ -6,15 +6,13 @@ */ import datemath from '@elastic/datemath'; import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; -import type { InvestigateWidget, InvestigateWidgetCreate } from '@kbn/investigate-plugin/public'; -import { DATE_FORMAT_ID } from '@kbn/management-settings-ids'; +import type { InvestigateWidgetCreate } from '@kbn/investigate-plugin/public'; import { AuthenticatedUser } from '@kbn/security-plugin/common'; -import { keyBy, omit, pick } from 'lodash'; -import React, { useEffect, useMemo, useRef, useState } from 'react'; +import { keyBy, noop } from 'lodash'; +import React, { useEffect, useMemo, useRef } from 'react'; import useAsync from 'react-use/lib/useAsync'; import { useDateRange } from '../../hooks/use_date_range'; import { useKibana } from '../../hooks/use_kibana'; -import { getOverridesFromGlobalParameters } from '../../utils/get_overrides_from_global_parameters'; import { AddNoteUI } from '../add_note_ui'; import { AddObservationUI } from '../add_observation_ui'; import { InvestigateSearchBar } from '../investigate_search_bar'; @@ -22,35 +20,26 @@ import { InvestigateWidgetGrid } from '../investigate_widget_grid'; function InvestigateViewWithUser({ user }: { user: AuthenticatedUser }) { const { - core: { uiSettings }, dependencies: { start: { investigate }, }, } = useKibana(); const widgetDefinitions = useMemo(() => investigate.getWidgetDefinitions(), [investigate]); const [range, setRange] = useDateRange(); - const [searchBarFocused, setSearchBarFocused] = useState(false); const { addItem, - setItemPositions, - setItemTitle, copyItem, deleteItem, investigation, - lockItem, - setItemParameters, setGlobalParameters, - unlockItem, - revision, + renderableInvestigation, } = investigate.useInvestigation({ user, from: range.start.toISOString(), to: range.end.toISOString(), }); - const [_editingItem, setEditingItem] = useState(undefined); - const createWidget = (widgetCreate: InvestigateWidgetCreate) => { return addItem(widgetCreate); }; @@ -58,31 +47,21 @@ function InvestigateViewWithUser({ user }: { user: AuthenticatedUser }) { const createWidgetRef = useRef(createWidget); createWidgetRef.current = createWidget; - useEffect(() => { - const itemIds = revision?.items.map((item) => item.id) ?? []; - setEditingItem((prevEditingItem) => { - if (prevEditingItem && !itemIds.includes(prevEditingItem.id)) { - return undefined; - } - return prevEditingItem; - }); - }, [revision]); - useEffect(() => { if ( - revision?.parameters.timeRange.from && - revision?.parameters.timeRange.to && - range.start.toISOString() !== revision.parameters.timeRange.from && - range.end.toISOString() !== revision.parameters.timeRange.to + renderableInvestigation?.parameters.timeRange.from && + renderableInvestigation?.parameters.timeRange.to && + range.start.toISOString() !== renderableInvestigation.parameters.timeRange.from && + range.end.toISOString() !== renderableInvestigation.parameters.timeRange.to ) { setRange({ - from: revision.parameters.timeRange.from, - to: revision.parameters.timeRange.to, + from: renderableInvestigation.parameters.timeRange.from, + to: renderableInvestigation.parameters.timeRange.to, }); } }, [ - revision?.parameters.timeRange.from, - revision?.parameters.timeRange.to, + renderableInvestigation?.parameters.timeRange.from, + renderableInvestigation?.parameters.timeRange.to, range.start, range.end, setRange, @@ -91,7 +70,7 @@ function InvestigateViewWithUser({ user }: { user: AuthenticatedUser }) { const gridItems = useMemo(() => { const widgetDefinitionsByType = keyBy(widgetDefinitions, 'type'); - return revision?.items.map((item) => { + return renderableInvestigation?.items.map((item) => { const definitionForType = widgetDefinitionsByType[item.type]; return ( @@ -103,21 +82,13 @@ function InvestigateViewWithUser({ user }: { user: AuthenticatedUser }) { columns: item.columns, rows: item.rows, chrome: definitionForType.chrome, - locked: item.locked, loading: item.loading, - overrides: item.locked - ? getOverridesFromGlobalParameters( - pick(item.parameters, 'filters', 'timeRange'), - revision.parameters, - uiSettings.get(DATE_FORMAT_ID) ?? 'Browser' - ) - : [], } ?? [] ); }); - }, [revision, widgetDefinitions, uiSettings]); + }, [renderableInvestigation, widgetDefinitions]); - if (!investigation || !revision || !gridItems) { + if (!investigation || !renderableInvestigation || !gridItems) { return ; } @@ -136,18 +107,12 @@ function InvestigateViewWithUser({ user }: { user: AuthenticatedUser }) { to: datemath.parse(dateRange.to)!.toISOString(), }; await setGlobalParameters({ - ...revision.parameters, + ...renderableInvestigation.parameters, timeRange: nextDateRange, }); setRange(nextDateRange); }} - onFocus={() => { - setSearchBarFocused(true); - }} - onBlur={() => { - setSearchBarFocused(false); - }} /> @@ -155,16 +120,7 @@ function InvestigateViewWithUser({ user }: { user: AuthenticatedUser }) { { - return setItemPositions( - nextGridItems.map((gridItem) => ({ - columns: gridItem.columns, - rows: gridItem.rows, - id: gridItem.id, - })) - ); - }} - onItemTitleChange={async (item, title) => { - return setItemTitle(item.id, title); + noop(); }} onItemCopy={async (copiedItem) => { return copyItem(copiedItem.id); @@ -172,30 +128,12 @@ function InvestigateViewWithUser({ user }: { user: AuthenticatedUser }) { onItemDelete={async (deletedItem) => { return deleteItem(deletedItem.id); }} - onItemLockToggle={async (toggledItem) => { - return toggledItem.locked ? unlockItem(toggledItem.id) : lockItem(toggledItem.id); - }} - fadeLockedItems={searchBarFocused} - onItemOverrideRemove={async (updatedItem, override) => { - // TODO: remove filters - const itemToUpdate = revision.items.find((item) => item.id === updatedItem.id); - if (itemToUpdate) { - return setItemParameters(updatedItem.id, { - ...revision.parameters, - ...omit(itemToUpdate.parameters, override.id), - }); - } - }} - onItemEditClick={(itemToEdit) => { - setEditingItem(revision.items.find((item) => item.id === itemToEdit.id)); - }} /> { return createWidgetRef.current(widget); }} @@ -206,8 +144,7 @@ function InvestigateViewWithUser({ user }: { user: AuthenticatedUser }) { { return createWidgetRef.current(widget); }} diff --git a/x-pack/plugins/observability_solution/investigate_app/public/components/investigate_widget_grid/index.stories.tsx b/x-pack/plugins/observability_solution/investigate_app/public/components/investigate_widget_grid/index.stories.tsx index f4436051d76c2..5962a55fa22f0 100644 --- a/x-pack/plugins/observability_solution/investigate_app/public/components/investigate_widget_grid/index.stories.tsx +++ b/x-pack/plugins/observability_solution/investigate_app/public/components/investigate_widget_grid/index.stories.tsx @@ -62,7 +62,6 @@ function createItem>(overrides: T) columns: 4, rows: 2, description: '', - locked: false, loading: false, overrides: [], }; @@ -96,7 +95,6 @@ export const InvestigateWidgetGridStory: ComponentStoryObj = { ), columns: 4, rows: 12, - locked: true, }), createItem({ title: '5', diff --git a/x-pack/plugins/observability_solution/investigate_app/public/components/investigate_widget_grid/index.tsx b/x-pack/plugins/observability_solution/investigate_app/public/components/investigate_widget_grid/index.tsx index eea40cdca391d..053433b83383e 100644 --- a/x-pack/plugins/observability_solution/investigate_app/public/components/investigate_widget_grid/index.tsx +++ b/x-pack/plugins/observability_solution/investigate_app/public/components/investigate_widget_grid/index.tsx @@ -12,9 +12,9 @@ import React, { useCallback, useMemo, useRef } from 'react'; import { ItemCallback, Layout, Responsive, WidthProvider } from 'react-grid-layout'; import 'react-grid-layout/css/styles.css'; import 'react-resizable/css/styles.css'; -import { EuiBreakpoint, EUI_BREAKPOINTS, useBreakpoints } from '../../hooks/use_breakpoints'; +import { EUI_BREAKPOINTS, EuiBreakpoint, useBreakpoints } from '../../hooks/use_breakpoints'; import { useTheme } from '../../hooks/use_theme'; -import { GridItem, GRID_ITEM_HEADER_HEIGHT } from '../grid_item'; +import { GRID_ITEM_HEADER_HEIGHT, GridItem } from '../grid_item'; import './styles.scss'; const gridContainerClassName = css` @@ -36,11 +36,6 @@ interface GridSection { type Section = SingleComponentSection | GridSection; -export interface InvestigateWidgetGridItemOverride { - id: string; - label: React.ReactNode; -} - export interface InvestigateWidgetGridItem { title: string; description: string; @@ -48,10 +43,8 @@ export interface InvestigateWidgetGridItem { id: string; columns: number; rows: number; - locked: boolean; chrome?: ChromeOption; loading: boolean; - overrides: InvestigateWidgetGridItemOverride[]; } interface InvestigateWidgetGridProps { @@ -59,14 +52,6 @@ interface InvestigateWidgetGridProps { onItemsChange: (items: InvestigateWidgetGridItem[]) => Promise; onItemCopy: (item: InvestigateWidgetGridItem) => Promise; onItemDelete: (item: InvestigateWidgetGridItem) => Promise; - onItemLockToggle: (item: InvestigateWidgetGridItem) => Promise; - onItemOverrideRemove: ( - item: InvestigateWidgetGridItem, - override: InvestigateWidgetGridItemOverride - ) => Promise; - onItemTitleChange: (item: InvestigateWidgetGridItem, title: string) => Promise; - onItemEditClick: (item: InvestigateWidgetGridItem) => void; - fadeLockedItems: boolean; } const ROW_HEIGHT = 32; @@ -130,11 +115,6 @@ function GridSectionRenderer({ onItemsChange, onItemDelete, onItemCopy, - onItemLockToggle, - onItemOverrideRemove, - onItemTitleChange, - onItemEditClick, - fadeLockedItems, }: InvestigateWidgetGridProps) { const WithFixedWidth = useMemo(() => WidthProvider(Responsive), []); @@ -144,14 +124,9 @@ function GridSectionRenderer({ onItemsChange, onItemCopy, onItemDelete, - onItemLockToggle, - onItemOverrideRemove, - onItemTitleChange, - onItemEditClick, }; const itemCallbacksRef = useRef(callbacks); - itemCallbacksRef.current = callbacks; const { currentBreakpoint } = useBreakpoints(); @@ -167,34 +142,19 @@ function GridSectionRenderer({ id={item.id} title={item.title} description={item.description} - onTitleChange={(title) => { - return itemCallbacksRef.current.onItemTitleChange(item, title); - }} onCopy={() => { return itemCallbacksRef.current.onItemCopy(item); }} onDelete={() => { return itemCallbacksRef.current.onItemDelete(item); }} - locked={item.locked} - onLockToggle={() => { - itemCallbacksRef.current.onItemLockToggle(item); - }} - onOverrideRemove={(override) => { - return itemCallbacksRef.current.onItemOverrideRemove(item, override); - }} - onEditClick={() => { - return itemCallbacksRef.current.onItemEditClick(item); - }} - overrides={item.overrides} loading={item.loading} - faded={fadeLockedItems && item.locked} > {item.element} )); - }, [items, fadeLockedItems]); + }, [items]); // react-grid calls `onLayoutChange` every time // `layouts` changes, except when on mount. So... @@ -273,11 +233,6 @@ export function InvestigateWidgetGrid({ onItemsChange, onItemDelete, onItemCopy, - onItemLockToggle, - fadeLockedItems, - onItemOverrideRemove, - onItemTitleChange, - onItemEditClick, }: InvestigateWidgetGridProps) { const sections = useMemo(() => { let currentGrid: GridSection = { items: [] }; @@ -317,9 +272,6 @@ export function InvestigateWidgetGrid({ onItemDelete={(deletedItem) => { return onItemDelete(deletedItem); }} - onItemLockToggle={(toggledItem) => { - return onItemLockToggle(toggledItem); - }} onItemsChange={(itemsInSection) => { const nextItems = sections.flatMap((sectionAtIndex) => { if ('item' in sectionAtIndex) { @@ -333,16 +285,6 @@ export function InvestigateWidgetGrid({ return onItemsChange(nextItems); }} - onItemOverrideRemove={(item, override) => { - return onItemOverrideRemove(item, override); - }} - onItemTitleChange={(item, title) => { - return onItemTitleChange(item, title); - }} - onItemEditClick={(item) => { - return onItemEditClick(item); - }} - fadeLockedItems={fadeLockedItems} /> ); @@ -356,28 +298,13 @@ export function InvestigateWidgetGrid({ id={section.item.id} title={section.item.title} description={section.item.description} - faded={section.item.locked && fadeLockedItems} loading={section.item.loading} - locked={section.item.locked} - overrides={section.item.overrides} onCopy={() => { return onItemCopy(section.item); }} onDelete={() => { return onItemDelete(section.item); }} - onOverrideRemove={(override) => { - return onItemOverrideRemove(section.item, override); - }} - onTitleChange={(nextTitle) => { - return onItemTitleChange(section.item, nextTitle); - }} - onLockToggle={() => { - return onItemLockToggle(section.item); - }} - onEditClick={() => { - return onItemEditClick(section.item); - }} > {section.item.element} diff --git a/x-pack/plugins/observability_solution/investigate_app/public/utils/get_es_filter_from_overrides.ts b/x-pack/plugins/observability_solution/investigate_app/public/utils/get_es_filter_from_overrides.ts index ccc385701fb0f..18b28770772eb 100644 --- a/x-pack/plugins/observability_solution/investigate_app/public/utils/get_es_filter_from_overrides.ts +++ b/x-pack/plugins/observability_solution/investigate_app/public/utils/get_es_filter_from_overrides.ts @@ -5,19 +5,17 @@ * 2.0. */ -import { type BoolQuery, buildEsQuery, type Filter } from '@kbn/es-query'; +import { buildEsQuery, type BoolQuery } from '@kbn/es-query'; export function getEsFilterFromOverrides({ - filters, timeRange, }: { - filters?: Filter[]; timeRange?: { from: string; to: string; }; }): { bool: BoolQuery } { - const esFilter = buildEsQuery(undefined, [], filters ?? []); + const esFilter = buildEsQuery(undefined, [], []); if (timeRange) { esFilter.bool.filter.push({ diff --git a/x-pack/plugins/observability_solution/investigate_app/public/utils/get_overrides_from_global_parameters.tsx b/x-pack/plugins/observability_solution/investigate_app/public/utils/get_overrides_from_global_parameters.tsx deleted file mode 100644 index 8da9daf040e36..0000000000000 --- a/x-pack/plugins/observability_solution/investigate_app/public/utils/get_overrides_from_global_parameters.tsx +++ /dev/null @@ -1,72 +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 { isEqual } from 'lodash'; -import type { GlobalWidgetParameters } from '@kbn/investigate-plugin/public'; -import type { Filter } from '@kbn/es-query'; -import objectHash from 'object-hash'; -import { i18n } from '@kbn/i18n'; -import { PrettyDuration } from '@elastic/eui'; -import type { InvestigateWidgetGridItemOverride } from '../components/investigate_widget_grid'; - -enum OverrideType { - timeRange = 'timeRange', - filters = 'filters', -} - -function getIdForFilter(filter: Filter) { - return objectHash({ meta: filter.meta, query: filter.query }); -} - -function getLabelForFilter(filter: Filter) { - return ( - filter.meta.alias ?? - filter.meta.key ?? - JSON.stringify({ meta: filter.meta, query: filter.query }) - ); -} - -export function getOverridesFromGlobalParameters( - itemParameters: GlobalWidgetParameters, - globalParameters: GlobalWidgetParameters, - uiSettingsDateFormat: string -) { - const overrides: InvestigateWidgetGridItemOverride[] = []; - - if (!isEqual(itemParameters.timeRange, globalParameters.timeRange)) { - overrides.push({ - id: OverrideType.timeRange, - label: ( - - ), - }); - } - - if (!isEqual(itemParameters.filters, globalParameters.filters)) { - if (!itemParameters.filters.length) { - overrides.push({ - id: OverrideType.filters, - label: i18n.translate('xpack.investigateApp.overrides.noFilters', { - defaultMessage: 'No filters', - }), - }); - } - - itemParameters.filters.forEach((filter) => { - overrides.push({ - id: `${OverrideType.filters}_${getIdForFilter(filter)}`, - label: getLabelForFilter(filter), - }); - }); - } - - return overrides; -} diff --git a/x-pack/plugins/observability_solution/investigate_app/public/widgets/embeddable_widget/register_embeddable_widget.tsx b/x-pack/plugins/observability_solution/investigate_app/public/widgets/embeddable_widget/register_embeddable_widget.tsx index e4a1ea7e3396d..779fb9c5301b9 100644 --- a/x-pack/plugins/observability_solution/investigate_app/public/widgets/embeddable_widget/register_embeddable_widget.tsx +++ b/x-pack/plugins/observability_solution/investigate_app/public/widgets/embeddable_widget/register_embeddable_widget.tsx @@ -28,17 +28,16 @@ type Props = EmbeddableWidgetParameters & GlobalWidgetParameters; type ParentApi = ReturnType['getParentApi']>; -function ReactEmbeddable({ type, config, filters, timeRange: { from, to }, savedObjectId }: Props) { +function ReactEmbeddable({ type, config, timeRange: { from, to }, savedObjectId }: Props) { const configWithOverrides = useMemo(() => { return { ...config, - filters, timeRange: { from, to, }, }; - }, [config, filters, from, to]); + }, [config, from, to]); const configWithOverridesRef = useRef(configWithOverrides); @@ -66,13 +65,7 @@ function ReactEmbeddable({ type, config, filters, timeRange: { from, to }, saved ); } -function LegacyEmbeddable({ - type, - config, - filters, - timeRange: { from, to }, - savedObjectId, -}: Props) { +function LegacyEmbeddable({ type, config, timeRange: { from, to }, savedObjectId }: Props) { const { dependencies: { start: { embeddable }, @@ -95,7 +88,6 @@ function LegacyEmbeddable({ const configWithOverrides = { ...configWithId, - filters, timeRange: { from, to, @@ -109,7 +101,7 @@ function LegacyEmbeddable({ const instance = await factory.create(configWithOverrides); return instance; - }, [type, savedObjectId, config, from, to, embeddable, filters]); + }, [type, savedObjectId, config, from, to, embeddable]); const embeddableInstance = embeddableInstanceAsync.value; @@ -193,7 +185,6 @@ export function registerEmbeddableWidget({ registerWidget }: RegisterWidgetOptio config: widget.parameters.config, savedObjectId: widget.parameters.savedObjectId, timeRange: widget.parameters.timeRange, - filters: widget.parameters.filters, query: widget.parameters.query, }; diff --git a/x-pack/plugins/observability_solution/investigate_app/public/widgets/esql_widget/register_esql_widget.tsx b/x-pack/plugins/observability_solution/investigate_app/public/widgets/esql_widget/register_esql_widget.tsx index ea990d4e4ac4c..14d18302ae516 100644 --- a/x-pack/plugins/observability_solution/investigate_app/public/widgets/esql_widget/register_esql_widget.tsx +++ b/x-pack/plugins/observability_solution/investigate_app/public/widgets/esql_widget/register_esql_widget.tsx @@ -82,7 +82,7 @@ export function EsqlWidget({ const timestampColumn = datatable.columns.find((column) => column.name === '@timestamp'); const messageColumn = datatable.columns.find((column) => column.name === 'message'); - if (datatable.columns.length > 100 && timestampColumn && messageColumn) { + if (datatable.columns.length > 20 && timestampColumn && messageColumn) { const hasDataForBothColumns = datatable.rows.every((row) => { const timestampValue = row['@timestamp']; const messageValue = row.message; @@ -183,6 +183,7 @@ export function EsqlWidget({ query={memoizedQueryObject} flyoutType="overlay" initialColumns={initialColumns} + initialRowHeight={1} /> @@ -217,7 +218,6 @@ export function registerEsqlWidget({ async ({ parameters, signal }) => { const { esql: esqlQuery, - filters, timeRange, suggestion: suggestionFromParameters, } = parameters as EsqlWidgetParameters & GlobalWidgetParameters; @@ -226,7 +226,6 @@ export function registerEsqlWidget({ const esFilters = [ getEsFilterFromOverrides({ - filters, timeRange, }), ]; @@ -265,7 +264,7 @@ export function registerEsqlWidget({ dateHistogram: dateHistoResponse, }; }, - ({ widget, blocks }) => { + ({ widget }) => { const { main: { dataView, columns, values, suggestion }, dateHistogram, diff --git a/x-pack/plugins/observability_solution/investigate_app/tsconfig.json b/x-pack/plugins/observability_solution/investigate_app/tsconfig.json index 7ad57bf2c01a7..e5a4e73d4990b 100644 --- a/x-pack/plugins/observability_solution/investigate_app/tsconfig.json +++ b/x-pack/plugins/observability_solution/investigate_app/tsconfig.json @@ -47,7 +47,6 @@ "@kbn/unified-search-plugin", "@kbn/es-query", "@kbn/server-route-repository", - "@kbn/management-settings-ids", "@kbn/security-plugin", "@kbn/ui-actions-plugin", "@kbn/esql-datagrid", From e2fdced90e8fa03aa301911c8a38d15f44dc7bc3 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 7 Aug 2024 11:37:26 -0600 Subject: [PATCH 24/44] [maps] Revert "Revert of fix EMS vector file manifest is loaded in Discover (#190073) Re-instate https://github.com/elastic/kibana/pull/189550. PR makes one small clean-up in that getSuggestionsLazy promise will not reject when modules fail to load. https://github.com/elastic/kibana/pull/189984 confirmed that [the fix did](https://github.com/elastic/kibana/pull/189550) had a large impact on performance. --- .../choropleth_chart/region_key_editor.tsx | 78 +++++++++++++------ .../public/lens/choropleth_chart/setup.ts | 13 ---- .../lens/choropleth_chart/suggestions_lazy.ts | 53 +++++++++++++ .../lens/choropleth_chart/visualization.tsx | 23 +++--- 4 files changed, 119 insertions(+), 48 deletions(-) create mode 100644 x-pack/plugins/maps/public/lens/choropleth_chart/suggestions_lazy.ts diff --git a/x-pack/plugins/maps/public/lens/choropleth_chart/region_key_editor.tsx b/x-pack/plugins/maps/public/lens/choropleth_chart/region_key_editor.tsx index 6d0f516e5642c..e0d0f93c7967a 100644 --- a/x-pack/plugins/maps/public/lens/choropleth_chart/region_key_editor.tsx +++ b/x-pack/plugins/maps/public/lens/choropleth_chart/region_key_editor.tsx @@ -5,22 +5,50 @@ * 2.0. */ -import React from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiComboBox, EuiComboBoxOptionOption, EuiFormRow } from '@elastic/eui'; +import { EuiComboBox, EuiComboBoxOptionOption, EuiFormRow, EuiSelect } from '@elastic/eui'; import type { FileLayer } from '@elastic/ems-client'; import { ChoroplethChartState } from './types'; import { EMSFileSelect } from '../../components/ems_file_select'; +import { getEmsFileLayers } from '../../util'; interface Props { - emsFileLayers: FileLayer[]; state: ChoroplethChartState; setState: (state: ChoroplethChartState) => void; } export function RegionKeyEditor(props: Props) { + const [emsFileLayers, setEmsFileLayers] = useState([]); + const [isLoading, setIsLoading] = useState(false); + + useEffect(() => { + let ignore = false; + setIsLoading(true); + getEmsFileLayers() + .then((fileLayers) => { + if (!ignore) { + setEmsFileLayers(fileLayers); + setIsLoading(false); + } + }) + .catch(() => { + if (!ignore) { + // eslint-disable-next-line no-console + console.warn( + `Lens region map is unable to access administrative boundaries from Elastic Maps Service (EMS). To avoid unnecessary EMS requests, set 'map.includeElasticMapsService: false' in 'kibana.yml'.` + ); + setIsLoading(false); + } + }); + + return () => { + ignore = true; + }; + }, []); + function onEmsLayerSelect(emsLayerId: string) { - const emsFields = getEmsFields(props.emsFileLayers, emsLayerId); + const emsFields = getEmsFields(emsFileLayers, emsLayerId); props.setState({ ...props.state, emsLayerId, @@ -28,27 +56,30 @@ export function RegionKeyEditor(props: Props) { }); } - function onEmsFieldSelect(selectedOptions: Array>) { - if (selectedOptions.length === 0) { - return; + const emsFieldSelect = useMemo(() => { + const emsFields = getEmsFields(emsFileLayers, props.state.emsLayerId); + if (emsFields.length === 0) { + return null; } - props.setState({ - ...props.state, - emsField: selectedOptions[0].value, - }); - } + const selectedOption = props.state.emsField + ? emsFields.find((option: EuiComboBoxOptionOption) => { + return props.state.emsField === option.value; + }) + : undefined; + + function onEmsFieldSelect(selectedOptions: Array>) { + if (selectedOptions.length === 0) { + return; + } - let emsFieldSelect; - const emsFields = getEmsFields(props.emsFileLayers, props.state.emsLayerId); - if (emsFields.length) { - let selectedOption; - if (props.state.emsField) { - selectedOption = emsFields.find((option: EuiComboBoxOptionOption) => { - return props.state.emsField === option.value; + props.setState({ + ...props.state, + emsField: selectedOptions[0].value, }); } - emsFieldSelect = ( + + return ( ); - } - return ( + }, [emsFileLayers, props]); + + return isLoading ? ( + + ) : ( <> { const [coreStart, plugins]: [CoreStart, MapsPluginStartDependencies, unknown] = await coreSetup.getStartServices(); - const { getEmsFileLayers } = await import('../../util'); const { getVisualization } = await import('./visualization'); - let emsFileLayers: FileLayer[] = []; - try { - emsFileLayers = await getEmsFileLayers(); - } catch (error) { - // eslint-disable-next-line no-console - console.warn( - `Lens region map setup is unable to access administrative boundaries from Elastic Maps Service (EMS). To avoid unnecessary EMS requests, set 'map.includeElasticMapsService: false' in 'kibana.yml'. For more details please visit ${coreStart.docLinks.links.maps.connectToEms}` - ); - } - return getVisualization({ theme: coreStart.theme, - emsFileLayers, paletteService: await plugins.charts.palettes.getPalettes(), }); }); diff --git a/x-pack/plugins/maps/public/lens/choropleth_chart/suggestions_lazy.ts b/x-pack/plugins/maps/public/lens/choropleth_chart/suggestions_lazy.ts new file mode 100644 index 0000000000000..64ba7ea4af831 --- /dev/null +++ b/x-pack/plugins/maps/public/lens/choropleth_chart/suggestions_lazy.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 { SuggestionRequest, VisualizationSuggestion } from '@kbn/lens-plugin/public'; +import type { FileLayer } from '@elastic/ems-client'; +import type { ChoroplethChartState } from './types'; + +let emsFileLayers: FileLayer[] | undefined; +let getSuggestionsActual: + | (( + suggestionRequest: SuggestionRequest, + emsFileLayers: FileLayer[] + ) => Array>) + | undefined; +let promise: undefined | Promise; + +/** + * Avoid loading file layers during plugin setup + * Instead, load file layers when getSuggestions is called + * Since getSuggestions is sync, the trade off is that + * getSuggestions will return no suggestions until file layers load + */ +export function getSuggestionsLazy( + suggestionRequest: SuggestionRequest +): Array> { + if (!promise) { + promise = new Promise((resolve) => { + Promise.all([import('./suggestions'), import('../../util')]) + .then(async ([{ getSuggestions }, { getEmsFileLayers }]) => { + getSuggestionsActual = getSuggestions; + try { + emsFileLayers = await getEmsFileLayers(); + } catch (error) { + // eslint-disable-next-line no-console + console.warn( + `Lens region map is unable to access administrative boundaries from Elastic Maps Service (EMS). To avoid unnecessary EMS requests, set 'map.includeElasticMapsService: false' in 'kibana.yml'.` + ); + } + resolve(); + }) + .catch(resolve); + }); + return []; + } + + return emsFileLayers && getSuggestionsActual + ? getSuggestionsActual(suggestionRequest, emsFileLayers) + : []; +} diff --git a/x-pack/plugins/maps/public/lens/choropleth_chart/visualization.tsx b/x-pack/plugins/maps/public/lens/choropleth_chart/visualization.tsx index 6b4fef07867f2..fd7ff91b78f8b 100644 --- a/x-pack/plugins/maps/public/lens/choropleth_chart/visualization.tsx +++ b/x-pack/plugins/maps/public/lens/choropleth_chart/visualization.tsx @@ -7,15 +7,14 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import type { FileLayer } from '@elastic/ems-client'; +import { dynamic } from '@kbn/shared-ux-utility'; import type { PaletteRegistry } from '@kbn/coloring'; import { ThemeServiceStart } from '@kbn/core/public'; import { layerTypes } from '@kbn/lens-plugin/public'; import type { OperationMetadata, SuggestionRequest, Visualization } from '@kbn/lens-plugin/public'; import { IconRegionMap } from '@kbn/chart-icons'; -import { getSuggestions } from './suggestions'; +import { getSuggestionsLazy } from './suggestions_lazy'; import type { ChoroplethChartState } from './types'; -import { RegionKeyEditor } from './region_key_editor'; const REGION_KEY_GROUP_ID = 'region_key'; const METRIC_GROUP_ID = 'metric'; @@ -27,11 +26,9 @@ const CHART_LABEL = i18n.translate('xpack.maps.lens.choropleth.label', { export const getVisualization = ({ paletteService, theme, - emsFileLayers, }: { paletteService: PaletteRegistry; theme: ThemeServiceStart; - emsFileLayers: FileLayer[]; }): Visualization => ({ id: 'lnsChoropleth', @@ -73,7 +70,7 @@ export const getVisualization = ({ }, getSuggestions(suggestionRequest: SuggestionRequest) { - return getSuggestions(suggestionRequest, emsFileLayers); + return getSuggestionsLazy(suggestionRequest); }, initialize(addNewLayer, state) { @@ -194,13 +191,13 @@ export const getVisualization = ({ DimensionEditorComponent(props) { if (props.groupId === REGION_KEY_GROUP_ID) { - return ( - - ); + const DimensionEditor = dynamic(async () => { + const { RegionKeyEditor } = await import('./region_key_editor'); + return { + default: RegionKeyEditor, + }; + }); + return ; } return null; }, From 5a86b0523ee94cf71d94cb0c456855150c888fd5 Mon Sep 17 00:00:00 2001 From: Bryce Buchanan <75274611+bryce-b@users.noreply.github.com> Date: Wed, 7 Aug 2024 10:49:28 -0700 Subject: [PATCH 25/44] fixes blank storage explorer summary when filter string is active (#189760) ## Summary I've opted to ignore the filters, because the tooltip describes this behavior. ### Checklist Delete any items that are not applicable to this PR. - [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 - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [x] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [x] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [x] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [x] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) --- .../profiling_views/storage_explorer.cy.ts | 35 +++++++++++++++++++ .../public/views/storage_explorer/index.tsx | 5 ++- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/observability_solution/profiling/e2e/cypress/e2e/profiling_views/storage_explorer.cy.ts b/x-pack/plugins/observability_solution/profiling/e2e/cypress/e2e/profiling_views/storage_explorer.cy.ts index 5f9f69180e449..db752969f14dd 100644 --- a/x-pack/plugins/observability_solution/profiling/e2e/cypress/e2e/profiling_views/storage_explorer.cy.ts +++ b/x-pack/plugins/observability_solution/profiling/e2e/cypress/e2e/profiling_views/storage_explorer.cy.ts @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ + describe('Storage explorer page', () => { const rangeFrom = '2023-04-18T00:00:00.000Z'; const rangeTo = '2023-04-18T00:05:00.000Z'; @@ -59,6 +60,40 @@ describe('Storage explorer page', () => { }); }); + describe('summary stats', () => { + it('will still load with kuery', () => { + cy.intercept('GET', '/internal/profiling/storage_explorer/summary?*', { + fixture: 'storage_explorer_summary.json', + }).as('summaryStats'); + cy.visitKibana('/app/profiling/storage-explorer', { + rangeFrom, + rangeTo, + kuery: 'host.id : "1234"', + }); + cy.wait('@summaryStats').then(({ request, response }) => { + const { + dailyDataGenerationBytes, + diskSpaceUsedPct, + totalNumberOfDistinctProbabilisticValues, + totalNumberOfHosts, + totalProfilingSizeBytes, + totalSymbolsSizeBytes, + } = response?.body; + + const { kuery } = request.query; + + expect(parseFloat(dailyDataGenerationBytes)).to.be.gt(0); + expect(parseFloat(diskSpaceUsedPct)).to.be.gt(0); + expect(parseFloat(totalNumberOfDistinctProbabilisticValues)).to.be.gt(0); + expect(parseFloat(totalNumberOfHosts)).to.be.gt(0); + expect(parseFloat(totalProfilingSizeBytes)).to.be.gt(0); + expect(parseFloat(totalSymbolsSizeBytes)).to.be.gt(0); + /* eslint-disable @typescript-eslint/no-unused-expressions */ + expect(kuery).to.be.empty; + }); + }); + }); + describe('Data breakdown', () => { it('displays correct values per index', () => { cy.intercept('GET', '/internal/profiling/storage_explorer/indices_storage_details?*').as( diff --git a/x-pack/plugins/observability_solution/profiling/public/views/storage_explorer/index.tsx b/x-pack/plugins/observability_solution/profiling/public/views/storage_explorer/index.tsx index 8969a389df01d..ff111c4bebcd0 100644 --- a/x-pack/plugins/observability_solution/profiling/public/views/storage_explorer/index.tsx +++ b/x-pack/plugins/observability_solution/profiling/public/views/storage_explorer/index.tsx @@ -30,7 +30,7 @@ import { Summary } from './summary'; export function StorageExplorerView() { const { query } = useProfilingParams('/storage-explorer'); - const { rangeFrom, rangeTo, kuery, indexLifecyclePhase } = query; + const { rangeFrom, rangeTo, indexLifecyclePhase } = query; const timeRange = useTimeRange({ rangeFrom, rangeTo }); const [selectedTab, setSelectedTab] = useState<'host_breakdown' | 'data_breakdown'>( @@ -47,7 +47,7 @@ export function StorageExplorerView() { http, timeFrom: timeRange.inSeconds.start, timeTo: timeRange.inSeconds.end, - kuery, + kuery: '', indexLifecyclePhase, }); }, @@ -55,7 +55,6 @@ export function StorageExplorerView() { fetchStorageExplorerSummary, timeRange.inSeconds.start, timeRange.inSeconds.end, - kuery, indexLifecyclePhase, ] ); From 2328efba99a5f68d6efa30e6d5a92a792da53500 Mon Sep 17 00:00:00 2001 From: Janki Salvi <117571355+js-jankisalvi@users.noreply.github.com> Date: Wed, 7 Aug 2024 19:08:24 +0100 Subject: [PATCH 26/44] [ResponseOps][Cases] Fix multi select filter flaky test (#190079) ## Summary Fixes https://github.com/elastic/kibana/issues/183663 Updated all `getBy` to `findBy` and added `await waitFor ` for checking method calls --- .../all_cases/multi_select_filter.test.tsx | 58 +++++++++++-------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/x-pack/plugins/cases/public/components/all_cases/multi_select_filter.test.tsx b/x-pack/plugins/cases/public/components/all_cases/multi_select_filter.test.tsx index 10bdd185ef9f1..c2575b146b9f8 100644 --- a/x-pack/plugins/cases/public/components/all_cases/multi_select_filter.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/multi_select_filter.test.tsx @@ -6,12 +6,11 @@ */ import React from 'react'; import { MultiSelectFilter } from './multi_select_filter'; -import { render, screen } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl'; -// FLAKY: https://github.com/elastic/kibana/issues/183663 -describe.skip('multi select filter', () => { +describe('multi select filter', () => { it('should render the amount of options available', async () => { const onChange = jest.fn(); const props = { @@ -29,10 +28,10 @@ describe.skip('multi select filter', () => { render(); - userEvent.click(screen.getByRole('button', { name: 'Tags' })); + userEvent.click(await screen.findByRole('button', { name: 'Tags' })); await waitForEuiPopoverOpen(); - expect(screen.getByText('4 options')).toBeInTheDocument(); + expect(await screen.findByText('4 options')).toBeInTheDocument(); }); it('hides the limit reached warning when a selected tag is removed', async () => { @@ -53,14 +52,17 @@ describe.skip('multi select filter', () => { const { rerender } = render(); - userEvent.click(screen.getByRole('button', { name: 'Tags' })); + userEvent.click(await screen.findByRole('button', { name: 'Tags' })); await waitForEuiPopoverOpen(); - expect(screen.getByText('Limit reached')).toBeInTheDocument(); + expect(await screen.findByText('Limit reached')).toBeInTheDocument(); - userEvent.click(screen.getByRole('option', { name: 'tag a' })); + userEvent.click(await screen.findByRole('option', { name: 'tag a' })); + + await waitFor(() => { + expect(onChange).toHaveBeenCalledWith({ filterId: 'tags', selectedOptionKeys: [] }); + }); - expect(onChange).toHaveBeenCalledWith({ filterId: 'tags', selectedOptionKeys: [] }); rerender(); expect(screen.queryByText('Limit reached')).not.toBeInTheDocument(); @@ -84,20 +86,23 @@ describe.skip('multi select filter', () => { const { rerender } = render(); - userEvent.click(screen.getByRole('button', { name: 'Tags' })); + userEvent.click(await screen.findByRole('button', { name: 'Tags' })); await waitForEuiPopoverOpen(); expect(screen.queryByText('Limit reached')).not.toBeInTheDocument(); - userEvent.click(screen.getByRole('option', { name: 'tag b' })); + userEvent.click(await screen.findByRole('option', { name: 'tag b' })); - expect(onChange).toHaveBeenCalledWith({ - filterId: 'tags', - selectedOptionKeys: ['tag a', 'tag b'], + await waitFor(() => { + expect(onChange).toHaveBeenCalledWith({ + filterId: 'tags', + selectedOptionKeys: ['tag a', 'tag b'], + }); }); + rerender(); - expect(screen.getByText('Limit reached')).toBeInTheDocument(); + expect(await screen.findByText('Limit reached')).toBeInTheDocument(); }); it('should not call onChange when the limit has been reached', async () => { @@ -118,14 +123,16 @@ describe.skip('multi select filter', () => { render(); - userEvent.click(screen.getByRole('button', { name: 'Tags' })); + userEvent.click(await screen.findByRole('button', { name: 'Tags' })); await waitForEuiPopoverOpen(); - expect(screen.getByText('Limit reached')).toBeInTheDocument(); + expect(await screen.findByText('Limit reached')).toBeInTheDocument(); - userEvent.click(screen.getByRole('option', { name: 'tag b' })); + userEvent.click(await screen.findByRole('option', { name: 'tag b' })); - expect(onChange).not.toHaveBeenCalled(); + await waitFor(() => { + expect(onChange).not.toHaveBeenCalled(); + }); }); it('should remove selected option if it suddenly disappeared from the list', async () => { @@ -144,7 +151,10 @@ describe.skip('multi select filter', () => { const { rerender } = render(); rerender(); - expect(onChange).toHaveBeenCalledWith({ filterId: 'tags', selectedOptionKeys: [] }); + + await waitFor(() => { + expect(onChange).toHaveBeenCalledWith({ filterId: 'tags', selectedOptionKeys: [] }); + }); }); it('activates custom renderOption when set', async () => { @@ -164,12 +174,12 @@ describe.skip('multi select filter', () => { }; render(); - userEvent.click(screen.getByRole('button', { name: 'Tags' })); + userEvent.click(await screen.findByRole('button', { name: 'Tags' })); await waitForEuiPopoverOpen(); - expect(screen.getAllByTestId(TEST_ID).length).toBe(2); + expect((await screen.findAllByTestId(TEST_ID)).length).toBe(2); }); - it('should not show the amount of options if hideActiveOptionsNumber is active', () => { + it('should not show the amount of options if hideActiveOptionsNumber is active', async () => { const onChange = jest.fn(); const props = { id: 'tags', @@ -184,7 +194,7 @@ describe.skip('multi select filter', () => { }; const { rerender } = render(); - expect(screen.queryByLabelText('1 active filters')).toBeInTheDocument(); + expect(await screen.findByLabelText('1 active filters')).toBeInTheDocument(); rerender(); expect(screen.queryByLabelText('1 active filters')).not.toBeInTheDocument(); }); From d3d5a7c7fe4325b64c98d8d57bb17d553a261fe8 Mon Sep 17 00:00:00 2001 From: Ievgen Sorokopud Date: Wed, 7 Aug 2024 20:27:57 +0200 Subject: [PATCH 27/44] [Security Solution] Use AST utils from @kbn/esql-ast for ES|QL rule type query parsing (#9282) (#189780) ## Summary With these changes we utilise AST based utils to do ES|QL query validation. This allows us to recognise and display syntax errors. Syntax errors have higher priority than the rest of the validation errors. Validation errors priorities from top to bottom: 1. Syntax error 2. Missing metadata for non-aggregating queries 3. Missing data source and/or data fields 4. Missing `_id` column requested for non-aggregating queries via metadata operator These priorities define the sequence in which we display errors to the user. If there are several errors detected, that the one with higher priority will be shown. https://github.com/user-attachments/assets/cef88c60-b0a4-413e-885a-b619773fd853 ### Checklist Delete any items that are not applicable to this PR. - [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] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed * [Integration tests](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/6681) (100 ESS, 100 Serverless) * [Cypress DE tests](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/6712) (100 ESS, 100 Serverless) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../esql/compute_if_esql_query_aggregating.ts | 9 +- .../kbn-securitysolution-utils/tsconfig.json | 3 +- .../logic/esql_validator.test.ts | 85 +++++++++++++------ .../rule_creation/logic/esql_validator.ts | 65 ++++++++++++-- .../hooks/use_all_esql_rule_fields.test.ts | 16 ++-- .../hooks/use_all_esql_rule_fields.ts | 6 +- .../plugins/security_solution/tsconfig.json | 2 + .../rule_creation/esql_rule.cy.ts | 11 +++ 8 files changed, 152 insertions(+), 45 deletions(-) diff --git a/packages/kbn-securitysolution-utils/src/esql/compute_if_esql_query_aggregating.ts b/packages/kbn-securitysolution-utils/src/esql/compute_if_esql_query_aggregating.ts index 44deada7cd155..251c00d4a75b4 100644 --- a/packages/kbn-securitysolution-utils/src/esql/compute_if_esql_query_aggregating.ts +++ b/packages/kbn-securitysolution-utils/src/esql/compute_if_esql_query_aggregating.ts @@ -6,11 +6,18 @@ * Side Public License, v 1. */ +import { ESQLAst, getAstAndSyntaxErrors } from '@kbn/esql-ast'; + +export const isAggregatingQuery = (ast: ESQLAst): boolean => { + return ast.some((astItem) => astItem.type === 'command' && astItem.name === 'stats'); +}; + /** * compute if esqlQuery is aggregating/grouping, i.e. using STATS...BY command * @param esqlQuery * @returns boolean */ export const computeIsESQLQueryAggregating = (esqlQuery: string): boolean => { - return /\|\s+stats\s/i.test(esqlQuery); + const { ast } = getAstAndSyntaxErrors(esqlQuery); + return isAggregatingQuery(ast); }; diff --git a/packages/kbn-securitysolution-utils/tsconfig.json b/packages/kbn-securitysolution-utils/tsconfig.json index c734b5c153fb0..5b9520c487e31 100644 --- a/packages/kbn-securitysolution-utils/tsconfig.json +++ b/packages/kbn-securitysolution-utils/tsconfig.json @@ -12,7 +12,8 @@ ], "kbn_references": [ "@kbn/i18n", - "@kbn/esql-utils" + "@kbn/esql-utils", + "@kbn/esql-ast" ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/logic/esql_validator.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/logic/esql_validator.test.ts index 07f14830d6a71..2fdd8cf8120d7 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/logic/esql_validator.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/logic/esql_validator.test.ts @@ -5,82 +5,117 @@ * 2.0. */ +import { getAstAndSyntaxErrors } from '@kbn/esql-ast'; import { parseEsqlQuery, computeHasMetadataOperator } from './esql_validator'; -import { computeIsESQLQueryAggregating } from '@kbn/securitysolution-utils'; +import { isAggregatingQuery } from '@kbn/securitysolution-utils'; -jest.mock('@kbn/securitysolution-utils', () => ({ computeIsESQLQueryAggregating: jest.fn() })); +jest.mock('@kbn/securitysolution-utils', () => ({ isAggregatingQuery: jest.fn() })); -const computeIsESQLQueryAggregatingMock = computeIsESQLQueryAggregating as jest.Mock; +const isAggregatingQueryMock = isAggregatingQuery as jest.Mock; + +const getQeryAst = (query: string) => { + const { ast } = getAstAndSyntaxErrors(query); + return ast; +}; describe('computeHasMetadataOperator', () => { it('should be false if query does not have operator', () => { - expect(computeHasMetadataOperator('from test*')).toBe(false); - expect(computeHasMetadataOperator('from test* [metadata]')).toBe(false); - expect(computeHasMetadataOperator('from test* [metadata id]')).toBe(false); - expect(computeHasMetadataOperator('from metadata*')).toBe(false); - expect(computeHasMetadataOperator('from test* | keep metadata')).toBe(false); - expect(computeHasMetadataOperator('from test* | eval x="[metadata _id]"')).toBe(false); + expect(computeHasMetadataOperator(getQeryAst('from test*'))).toBe(false); + expect(computeHasMetadataOperator(getQeryAst('from test* [metadata]'))).toBe(false); + expect(computeHasMetadataOperator(getQeryAst('from test* [metadata id]'))).toBe(false); + expect(computeHasMetadataOperator(getQeryAst('from metadata*'))).toBe(false); + expect(computeHasMetadataOperator(getQeryAst('from test* | keep metadata'))).toBe(false); + expect(computeHasMetadataOperator(getQeryAst('from test* | eval x="[metadata _id]"'))).toBe( + false + ); }); it('should be true if query has operator', () => { - expect(computeHasMetadataOperator('from test* metadata _id')).toBe(true); - expect(computeHasMetadataOperator('from test* metadata _id, _index')).toBe(true); - expect(computeHasMetadataOperator('from test* metadata _index, _id')).toBe(true); - expect(computeHasMetadataOperator('from test* metadata _id ')).toBe(true); - expect(computeHasMetadataOperator('from test* metadata _id | limit 10')).toBe(true); + expect(computeHasMetadataOperator(getQeryAst('from test* metadata _id'))).toBe(true); + expect(computeHasMetadataOperator(getQeryAst('from test* metadata _id, _index'))).toBe(true); + expect(computeHasMetadataOperator(getQeryAst('from test* metadata _index, _id'))).toBe(true); + expect(computeHasMetadataOperator(getQeryAst('from test* metadata _id '))).toBe(true); + expect(computeHasMetadataOperator(getQeryAst('from test* metadata _id | limit 10'))).toBe( + true + ); expect( - computeHasMetadataOperator(`from packetbeat* metadata + computeHasMetadataOperator( + getQeryAst(`from packetbeat* metadata _id | limit 100`) + ) ).toBe(true); // still validates deprecated square bracket syntax - expect(computeHasMetadataOperator('from test* [metadata _id]')).toBe(true); - expect(computeHasMetadataOperator('from test* [metadata _id, _index]')).toBe(true); - expect(computeHasMetadataOperator('from test* [metadata _index, _id]')).toBe(true); - expect(computeHasMetadataOperator('from test* [ metadata _id ]')).toBe(true); - expect(computeHasMetadataOperator('from test* [ metadata _id] ')).toBe(true); - expect(computeHasMetadataOperator('from test* [ metadata _id] | limit 10')).toBe(true); + expect(computeHasMetadataOperator(getQeryAst('from test* [metadata _id]'))).toBe(true); + expect(computeHasMetadataOperator(getQeryAst('from test* [metadata _id, _index]'))).toBe(true); + expect(computeHasMetadataOperator(getQeryAst('from test* [metadata _index, _id]'))).toBe(true); + expect(computeHasMetadataOperator(getQeryAst('from test* [ metadata _id ]'))).toBe(true); + expect(computeHasMetadataOperator(getQeryAst('from test* [ metadata _id] '))).toBe(true); + expect(computeHasMetadataOperator(getQeryAst('from test* [ metadata _id] | limit 10'))).toBe( + true + ); expect( - computeHasMetadataOperator(`from packetbeat* [metadata + computeHasMetadataOperator( + getQeryAst(`from packetbeat* [metadata _id ] | limit 100`) + ) ).toBe(true); }); }); describe('parseEsqlQuery', () => { it('returns isMissingMetadataOperator true when query is not aggregating and does not have metadata operator', () => { - computeIsESQLQueryAggregatingMock.mockReturnValueOnce(false); + isAggregatingQueryMock.mockReturnValueOnce(false); expect(parseEsqlQuery('from test*')).toEqual({ + errors: [], isEsqlQueryAggregating: false, isMissingMetadataOperator: true, }); }); it('returns isMissingMetadataOperator false when query is not aggregating and has metadata operator', () => { - computeIsESQLQueryAggregatingMock.mockReturnValueOnce(false); + isAggregatingQueryMock.mockReturnValueOnce(false); expect(parseEsqlQuery('from test* metadata _id')).toEqual({ + errors: [], isEsqlQueryAggregating: false, isMissingMetadataOperator: false, }); }); it('returns isMissingMetadataOperator false when query is aggregating', () => { - computeIsESQLQueryAggregatingMock.mockReturnValue(true); + isAggregatingQueryMock.mockReturnValue(true); expect(parseEsqlQuery('from test*')).toEqual({ + errors: [], isEsqlQueryAggregating: true, isMissingMetadataOperator: false, }); expect(parseEsqlQuery('from test* metadata _id')).toEqual({ + errors: [], isEsqlQueryAggregating: true, isMissingMetadataOperator: false, }); }); + + it('returns error when query is syntactically invalid', () => { + isAggregatingQueryMock.mockReturnValueOnce(false); + + expect(parseEsqlQuery('aaa bbbb ssdasd')).toEqual({ + errors: expect.arrayContaining([ + expect.objectContaining({ + message: + "SyntaxError: mismatched input 'aaa' expecting {'explain', 'from', 'meta', 'metrics', 'row', 'show'}", + }), + ]), + isEsqlQueryAggregating: false, + isMissingMetadataOperator: true, + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/logic/esql_validator.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/logic/esql_validator.ts index 484f78c53f0e0..869e379c21aed 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/logic/esql_validator.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/logic/esql_validator.ts @@ -7,8 +7,11 @@ import { isEmpty } from 'lodash'; import type { QueryClient } from '@tanstack/react-query'; -import { computeIsESQLQueryAggregating } from '@kbn/securitysolution-utils'; +import { isAggregatingQuery } from '@kbn/securitysolution-utils'; +import type { ESQLAst } from '@kbn/esql-ast'; +import { getAstAndSyntaxErrors } from '@kbn/esql-ast'; +import { isColumnItem, isOptionItem } from '@kbn/esql-validation-autocomplete'; import { KibanaServices } from '../../../common/lib/kibana'; import type { ValidationError, ValidationFunc } from '../../../shared_imports'; @@ -21,6 +24,7 @@ export type FieldType = 'string'; export enum ERROR_CODES { INVALID_ESQL = 'ERR_INVALID_ESQL', + INVALID_SYNTAX = 'ERR_INVALID_SYNTAX', ERR_MISSING_ID_FIELD_FROM_RESULT = 'ERR_MISSING_ID_FIELD_FROM_RESULT', } @@ -34,11 +38,52 @@ const constructValidationError = (error: Error) => { }; }; +const constructSyntaxError = (error: Error) => { + return { + code: ERROR_CODES.INVALID_SYNTAX, + message: error?.message + ? i18n.esqlValidationErrorMessage(error.message) + : i18n.ESQL_VALIDATION_UNKNOWN_ERROR, + error, + }; +}; + +const getMetadataOption = (ast: ESQLAst) => { + const fromCommand = ast.find((astItem) => astItem.type === 'command' && astItem.name === 'from'); + + if (!fromCommand?.args) { + return undefined; + } + + // Check whether the `from` command has `metadata` operator + for (const fromArg of fromCommand.args) { + if (isOptionItem(fromArg) && fromArg.name === 'metadata') { + return fromArg; + } + } + + return undefined; +}; + /** * checks whether query has metadata _id operator */ -export const computeHasMetadataOperator = (esqlQuery: string) => { - return /(? { + // Check whether the `from` command has `metadata` operator + const metadataOption = getMetadataOption(ast); + if (!metadataOption) { + return false; + } + + // Check whether the `metadata` operator has `_id` argument + const idColumnItem = metadataOption.args.find( + (fromArg) => isColumnItem(fromArg) && fromArg.name === '_id' + ); + if (!idColumnItem) { + return false; + } + + return true; }; /** @@ -61,7 +106,12 @@ export const esqlValidator = async ( const queryClient = (customData.value as { queryClient: QueryClient | undefined })?.queryClient; const services = KibanaServices.get(); - const { isEsqlQueryAggregating, isMissingMetadataOperator } = parseEsqlQuery(query); + const { isEsqlQueryAggregating, isMissingMetadataOperator, errors } = parseEsqlQuery(query); + + // Check if there are any syntax errors + if (errors.length) { + return constructSyntaxError(new Error(errors[0].message)); + } if (isMissingMetadataOperator) { return { @@ -97,11 +147,14 @@ export const esqlValidator = async ( * - if it's non aggregation query it must have metadata operator */ export const parseEsqlQuery = (query: string) => { - const isEsqlQueryAggregating = computeIsESQLQueryAggregating(query); + const { ast, errors } = getAstAndSyntaxErrors(query); + + const isEsqlQueryAggregating = isAggregatingQuery(ast); return { + errors, isEsqlQueryAggregating, // non-aggregating query which does not have [metadata], is not a valid one - isMissingMetadataOperator: !isEsqlQueryAggregating && !computeHasMetadataOperator(query), + isMissingMetadataOperator: !isEsqlQueryAggregating && !computeHasMetadataOperator(ast), }; }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_all_esql_rule_fields.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_all_esql_rule_fields.test.ts index 377a1772a5ea0..1a13f0ff8e3a2 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_all_esql_rule_fields.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_all_esql_rule_fields.test.ts @@ -12,11 +12,9 @@ import { getESQLQueryColumns } from '@kbn/esql-utils'; import { useAllEsqlRuleFields } from './use_all_esql_rule_fields'; import { createQueryWrapperMock } from '../../../common/__mocks__/query_wrapper'; -import { parseEsqlQuery } from '../../rule_creation/logic/esql_validator'; +import { computeIsESQLQueryAggregating } from '@kbn/securitysolution-utils'; -jest.mock('../../rule_creation/logic/esql_validator', () => ({ - parseEsqlQuery: jest.fn(), -})); +jest.mock('@kbn/securitysolution-utils', () => ({ computeIsESQLQueryAggregating: jest.fn() })); jest.mock('@kbn/esql-utils', () => { return { @@ -25,7 +23,7 @@ jest.mock('@kbn/esql-utils', () => { }; }); -const parseEsqlQueryMock = parseEsqlQuery as jest.Mock; +const computeIsESQLQueryAggregatingMock = computeIsESQLQueryAggregating as jest.Mock; const getESQLQueryColumnsMock = getESQLQueryColumns as jest.Mock; const { wrapper } = createQueryWrapperMock(); @@ -61,7 +59,7 @@ describe.skip('useAllEsqlRuleFields', () => { : mockEsqlDatatable.columns ) ); - parseEsqlQueryMock.mockReturnValue({ isEsqlQueryAggregating: false }); + computeIsESQLQueryAggregatingMock.mockReturnValue(false); }); it('should return loading true when esql fields still loading', () => { @@ -104,7 +102,7 @@ describe.skip('useAllEsqlRuleFields', () => { }); it('should return index pattern fields concatenated with ES|QL fields when ES|QL query is non-aggregating', async () => { - parseEsqlQueryMock.mockReturnValue({ isEsqlQueryAggregating: false }); + computeIsESQLQueryAggregatingMock.mockReturnValue(false); const { result, waitFor } = renderHook( () => @@ -127,7 +125,7 @@ describe.skip('useAllEsqlRuleFields', () => { }); it('should return only ES|QL fields when ES|QL query is aggregating', async () => { - parseEsqlQueryMock.mockReturnValue({ isEsqlQueryAggregating: true }); + computeIsESQLQueryAggregatingMock.mockReturnValue(true); const { result, waitFor } = renderHook( () => @@ -149,7 +147,7 @@ describe.skip('useAllEsqlRuleFields', () => { it('should deduplicate index pattern fields and ES|QL fields when fields have same name', async () => { // getESQLQueryColumnsMock.mockClear(); - parseEsqlQueryMock.mockReturnValue({ isEsqlQueryAggregating: false }); + computeIsESQLQueryAggregatingMock.mockReturnValue(false); const { result, waitFor } = renderHook( () => diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_all_esql_rule_fields.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_all_esql_rule_fields.ts index a67b990c88b80..80bfc364e5b51 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_all_esql_rule_fields.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_all_esql_rule_fields.ts @@ -13,7 +13,7 @@ import useDebounce from 'react-use/lib/useDebounce'; import { useQuery } from '@tanstack/react-query'; import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { parseEsqlQuery } from '../../rule_creation/logic/esql_validator'; +import { computeIsESQLQueryAggregating } from '@kbn/securitysolution-utils'; import { getEsqlQueryConfig } from '../../rule_creation/logic/get_esql_query_config'; @@ -89,8 +89,8 @@ export const useAllEsqlRuleFields: UseAllEsqlRuleFields = ({ esqlQuery, indexPat const [debouncedEsqlQuery, setDebouncedEsqlQuery] = useState(undefined); const { fields: esqlFields, isLoading } = useEsqlFields(debouncedEsqlQuery); - const { isEsqlQueryAggregating } = useMemo( - () => parseEsqlQuery(debouncedEsqlQuery ?? ''), + const isEsqlQueryAggregating = useMemo( + () => computeIsESQLQueryAggregating(debouncedEsqlQuery ?? ''), [debouncedEsqlQuery] ); diff --git a/x-pack/plugins/security_solution/tsconfig.json b/x-pack/plugins/security_solution/tsconfig.json index 3258167eb50b7..bdaf656b9c986 100644 --- a/x-pack/plugins/security_solution/tsconfig.json +++ b/x-pack/plugins/security_solution/tsconfig.json @@ -208,6 +208,8 @@ "@kbn/core-theme-browser", "@kbn/integration-assistant-plugin", "@kbn/avc-banner", + "@kbn/esql-ast", + "@kbn/esql-validation-autocomplete", "@kbn/config", ] } diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/esql_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/esql_rule.cy.ts index 1dad7edc63ab6..348133b1d2802 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/esql_rule.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/esql_rule.cy.ts @@ -180,6 +180,17 @@ describe( cy.get(ESQL_QUERY_BAR).contains('Error validating ES|QL'); }); + + it('shows syntax error when query is syntactically invalid - prioritizing it over missing metadata operator error', function () { + const invalidNonAggregatingQuery = 'from auditbeat* | limit 5 test'; + selectEsqlRuleType(); + fillEsqlQueryBar(invalidNonAggregatingQuery); + getDefineContinueButton().click(); + + cy.get(ESQL_QUERY_BAR).contains( + `Error validating ES|QL: "SyntaxError: extraneous input 'test' expecting "` + ); + }); }); describe('ES|QL investigation fields', () => { From 0a01fdd591de9139c96097ac3980c523d439c6d3 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Wed, 7 Aug 2024 20:52:41 +0200 Subject: [PATCH 28/44] [ML] AIOps: Tweak function API for fetchTopCategories/fetchTopTerms (#189863) ## Summary Follow up to #187669. Part of #187684. Fixes #176387. (Ran the flaky test runner on AIOps functional tests) - Fixes the `size: 0` option to be properly nested for `createCategoryRequest()`. - Changes the arguments structure for `fetchTopCategories` and `fetchTopTerms` from individual arguments to an options object to be more in line with the other functions used for log rate analysis. - Adds jest unit test for `fetchTopCategories` and `fetchTopTerms`. ### 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] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [x] 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) --- .../create_category_request.ts | 2 +- .../__mocks__/top_categories_result.ts | 37 + .../top_categories_search_response.ts | 75 + .../queries/__mocks__/top_terms_result.ts | 1311 +++++++++++++++++ .../__mocks__/top_terms_search_response.ts | 232 +++ .../queries/fetch_categories.test.ts | 2 +- .../queries/fetch_categories.ts | 1 + .../queries/fetch_top_categories.test.ts | 91 ++ .../queries/fetch_top_categories.ts | 23 +- .../queries/fetch_top_terms.test.ts | 62 + .../queries/fetch_top_terms.ts | 22 +- .../queries/fetch_top_types.ts | 22 + .../analysis_handlers/top_items_handler.ts | 32 +- 13 files changed, 1872 insertions(+), 40 deletions(-) create mode 100644 x-pack/packages/ml/aiops_log_rate_analysis/queries/__mocks__/top_categories_result.ts create mode 100644 x-pack/packages/ml/aiops_log_rate_analysis/queries/__mocks__/top_categories_search_response.ts create mode 100644 x-pack/packages/ml/aiops_log_rate_analysis/queries/__mocks__/top_terms_result.ts create mode 100644 x-pack/packages/ml/aiops_log_rate_analysis/queries/__mocks__/top_terms_search_response.ts create mode 100644 x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_top_categories.test.ts create mode 100644 x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_top_terms.test.ts create mode 100644 x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_top_types.ts diff --git a/x-pack/packages/ml/aiops_log_pattern_analysis/create_category_request.ts b/x-pack/packages/ml/aiops_log_pattern_analysis/create_category_request.ts index efb6eb2c3e23f..c3556803745a7 100644 --- a/x-pack/packages/ml/aiops_log_pattern_analysis/create_category_request.ts +++ b/x-pack/packages/ml/aiops_log_pattern_analysis/create_category_request.ts @@ -112,11 +112,11 @@ export function createCategoryRequest( return { params: { index, - size: 0, body: { query, aggs: wrap(aggs), ...(isPopulatedObject(runtimeMappings) ? { runtime_mappings: runtimeMappings } : {}), + size: 0, }, }, }; diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/queries/__mocks__/top_categories_result.ts b/x-pack/packages/ml/aiops_log_rate_analysis/queries/__mocks__/top_categories_result.ts new file mode 100644 index 0000000000000..4b233c4d67bce --- /dev/null +++ b/x-pack/packages/ml/aiops_log_rate_analysis/queries/__mocks__/top_categories_result.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const topCategoriesResultMock = [ + { + bg_count: 0, + doc_count: 1642, + fieldName: 'message', + fieldValue: + '71.231.222.196 - - [2018-08-13T05:04:08.731Z] "GET /kibana/kibana-6.3.2-windows-x86_64.zip HTTP/1.1" 200 15139 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:6.0a1) Gecko/20110421 Firefox/6.0a1"', + key: 'GET HTTP/1.1 Mozilla/5.0 X11 Linux x86_64 rv Gecko/20110421 Firefox/6.0a1', + normalizedScore: 0, + pValue: 1, + score: 0, + total_bg_count: 0, + total_doc_count: 0, + type: 'log_pattern', + }, + { + bg_count: 0, + doc_count: 1488, + fieldName: 'message', + fieldValue: + '7.210.210.41 - - [2018-08-13T04:20:49.558Z] "GET /elasticsearch/elasticsearch-6.3.2.deb HTTP/1.1" 404 6699 "-" "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.50 Safari/534.24"', + key: 'GET HTTP/1.1 Mozilla/5.0 X11 Linux i686 AppleWebKit/534.24 KHTML like Gecko Chrome/11.0.696.50 Safari/534.24', + normalizedScore: 0, + pValue: 1, + score: 0, + total_bg_count: 0, + total_doc_count: 0, + type: 'log_pattern', + }, +]; diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/queries/__mocks__/top_categories_search_response.ts b/x-pack/packages/ml/aiops_log_rate_analysis/queries/__mocks__/top_categories_search_response.ts new file mode 100644 index 0000000000000..4114620a1b5c6 --- /dev/null +++ b/x-pack/packages/ml/aiops_log_rate_analysis/queries/__mocks__/top_categories_search_response.ts @@ -0,0 +1,75 @@ +/* + * 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 topCategoriesSearchResponseMock = { + took: 98, + responses: [ + { + took: 98, + timed_out: false, + _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, + hits: { total: { value: 4413, relation: 'eq' }, max_score: null, hits: [] }, + aggregations: { + categories: { + buckets: [ + { + doc_count: 1642, + key: 'GET HTTP/1.1 Mozilla/5.0 X11 Linux x86_64 rv Gecko/20110421 Firefox/6.0a1', + regex: + '.*?GET.+?HTTP/1\\.1.+?Mozilla/5\\.0.+?X11.+?Linux.+?x86_64.+?rv.+?Gecko/20110421.+?Firefox/6\\.0a1.*?', + max_matching_length: 233, + examples: { + hits: { + total: { value: 1642, relation: 'eq' }, + max_score: null, + hits: [ + { + _index: '.ds-kibana_sample_data_logs-2024.07.08-000001', + _id: 'zpkLk5AB4oRN3GwDmOW1', + _score: null, + _source: { + message: + '71.231.222.196 - - [2018-08-13T05:04:08.731Z] "GET /kibana/kibana-6.3.2-windows-x86_64.zip HTTP/1.1" 200 15139 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:6.0a1) Gecko/20110421 Firefox/6.0a1"', + }, + sort: [1721624648731], + }, + ], + }, + }, + }, + { + doc_count: 1488, + key: 'GET HTTP/1.1 Mozilla/5.0 X11 Linux i686 AppleWebKit/534.24 KHTML like Gecko Chrome/11.0.696.50 Safari/534.24', + regex: + '.*?GET.+?HTTP/1\\.1.+?Mozilla/5\\.0.+?X11.+?Linux.+?i686.+?AppleWebKit/534\\.24.+?KHTML.+?like.+?Gecko.+?Chrome/11\\.0\\.696\\.50.+?Safari/534\\.24.*?', + max_matching_length: 266, + examples: { + hits: { + total: { value: 1488, relation: 'eq' }, + max_score: null, + hits: [ + { + _index: '.ds-kibana_sample_data_logs-2024.07.08-000001', + _id: 'VpkLk5AB4oRN3GwDmOW1', + _score: null, + _source: { + message: + '7.210.210.41 - - [2018-08-13T04:20:49.558Z] "GET /elasticsearch/elasticsearch-6.3.2.deb HTTP/1.1" 404 6699 "-" "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.50 Safari/534.24"', + }, + sort: [1721622049558], + }, + ], + }, + }, + }, + ], + }, + }, + status: 200, + }, + ], +}; diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/queries/__mocks__/top_terms_result.ts b/x-pack/packages/ml/aiops_log_rate_analysis/queries/__mocks__/top_terms_result.ts new file mode 100644 index 0000000000000..e3290941646b0 --- /dev/null +++ b/x-pack/packages/ml/aiops_log_rate_analysis/queries/__mocks__/top_terms_result.ts @@ -0,0 +1,1311 @@ +/* + * 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 topTermsResult = [ + { + key: 'agent.keyword:Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.50 Safari/534.24', + type: 'keyword', + fieldName: 'agent.keyword', + fieldValue: + 'Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.50 Safari/534.24', + doc_count: 179, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'agent.keyword:Mozilla/5.0 (X11; Linux x86_64; rv:6.0a1) Gecko/20110421 Firefox/6.0a1', + type: 'keyword', + fieldName: 'agent.keyword', + fieldValue: 'Mozilla/5.0 (X11; Linux x86_64; rv:6.0a1) Gecko/20110421 Firefox/6.0a1', + doc_count: 87, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'agent.keyword:Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)', + type: 'keyword', + fieldName: 'agent.keyword', + fieldValue: 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)', + doc_count: 63, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'clientip:30.156.16.164', + type: 'keyword', + fieldName: 'clientip', + fieldValue: '30.156.16.164', + doc_count: 100, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'clientip:107.152.89.90', + type: 'keyword', + fieldName: 'clientip', + fieldValue: '107.152.89.90', + doc_count: 3, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'clientip:112.106.69.227', + type: 'keyword', + fieldName: 'clientip', + fieldValue: '112.106.69.227', + doc_count: 3, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'clientip:160.20.100.193', + type: 'keyword', + fieldName: 'clientip', + fieldValue: '160.20.100.193', + doc_count: 3, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'clientip:186.153.168.71', + type: 'keyword', + fieldName: 'clientip', + fieldValue: '186.153.168.71', + doc_count: 3, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'clientip:16.241.165.21', + type: 'keyword', + fieldName: 'clientip', + fieldValue: '16.241.165.21', + doc_count: 2, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'clientip:20.129.3.8', + type: 'keyword', + fieldName: 'clientip', + fieldValue: '20.129.3.8', + doc_count: 2, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'clientip:24.42.142.201', + type: 'keyword', + fieldName: 'clientip', + fieldValue: '24.42.142.201', + doc_count: 2, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'clientip:43.86.71.5', + type: 'keyword', + fieldName: 'clientip', + fieldValue: '43.86.71.5', + doc_count: 2, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'clientip:50.184.59.162', + type: 'keyword', + fieldName: 'clientip', + fieldValue: '50.184.59.162', + doc_count: 2, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'event.dataset:sample_web_logs', + type: 'keyword', + fieldName: 'event.dataset', + fieldValue: 'sample_web_logs', + doc_count: 329, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'extension.keyword:', + type: 'keyword', + fieldName: 'extension.keyword', + fieldValue: '', + doc_count: 196, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'extension.keyword:gz', + type: 'keyword', + fieldName: 'extension.keyword', + fieldValue: 'gz', + doc_count: 42, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'extension.keyword:zip', + type: 'keyword', + fieldName: 'extension.keyword', + fieldValue: 'zip', + doc_count: 28, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'extension.keyword:css', + type: 'keyword', + fieldName: 'extension.keyword', + fieldValue: 'css', + doc_count: 27, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'extension.keyword:deb', + type: 'keyword', + fieldName: 'extension.keyword', + fieldValue: 'deb', + doc_count: 26, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'extension.keyword:rpm', + type: 'keyword', + fieldName: 'extension.keyword', + fieldValue: 'rpm', + doc_count: 10, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'geo.dest:IN', + type: 'keyword', + fieldName: 'geo.dest', + fieldValue: 'IN', + doc_count: 135, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'geo.dest:CN', + type: 'keyword', + fieldName: 'geo.dest', + fieldValue: 'CN', + doc_count: 38, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'geo.dest:US', + type: 'keyword', + fieldName: 'geo.dest', + fieldValue: 'US', + doc_count: 18, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'geo.dest:ID', + type: 'keyword', + fieldName: 'geo.dest', + fieldValue: 'ID', + doc_count: 10, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'geo.dest:BD', + type: 'keyword', + fieldName: 'geo.dest', + fieldValue: 'BD', + doc_count: 7, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'geo.dest:BR', + type: 'keyword', + fieldName: 'geo.dest', + fieldValue: 'BR', + doc_count: 7, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'geo.dest:NG', + type: 'keyword', + fieldName: 'geo.dest', + fieldValue: 'NG', + doc_count: 7, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'geo.dest:AR', + type: 'keyword', + fieldName: 'geo.dest', + fieldValue: 'AR', + doc_count: 4, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'geo.dest:DE', + type: 'keyword', + fieldName: 'geo.dest', + fieldValue: 'DE', + doc_count: 4, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'geo.dest:ET', + type: 'keyword', + fieldName: 'geo.dest', + fieldValue: 'ET', + doc_count: 4, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'geo.src:US', + type: 'keyword', + fieldName: 'geo.src', + fieldValue: 'US', + doc_count: 329, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'geo.srcdest:US:IN', + type: 'keyword', + fieldName: 'geo.srcdest', + fieldValue: 'US:IN', + doc_count: 135, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'geo.srcdest:US:CN', + type: 'keyword', + fieldName: 'geo.srcdest', + fieldValue: 'US:CN', + doc_count: 38, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'geo.srcdest:US:US', + type: 'keyword', + fieldName: 'geo.srcdest', + fieldValue: 'US:US', + doc_count: 18, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'geo.srcdest:US:ID', + type: 'keyword', + fieldName: 'geo.srcdest', + fieldValue: 'US:ID', + doc_count: 10, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'geo.srcdest:US:BD', + type: 'keyword', + fieldName: 'geo.srcdest', + fieldValue: 'US:BD', + doc_count: 7, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'geo.srcdest:US:BR', + type: 'keyword', + fieldName: 'geo.srcdest', + fieldValue: 'US:BR', + doc_count: 7, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'geo.srcdest:US:NG', + type: 'keyword', + fieldName: 'geo.srcdest', + fieldValue: 'US:NG', + doc_count: 7, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'geo.srcdest:US:AR', + type: 'keyword', + fieldName: 'geo.srcdest', + fieldValue: 'US:AR', + doc_count: 4, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'geo.srcdest:US:DE', + type: 'keyword', + fieldName: 'geo.srcdest', + fieldValue: 'US:DE', + doc_count: 4, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'geo.srcdest:US:ET', + type: 'keyword', + fieldName: 'geo.srcdest', + fieldValue: 'US:ET', + doc_count: 4, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'host.keyword:elastic-elastic-elastic.org', + type: 'keyword', + fieldName: 'host.keyword', + fieldValue: 'elastic-elastic-elastic.org', + doc_count: 112, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'host.keyword:artifacts.elastic.co', + type: 'keyword', + fieldName: 'host.keyword', + fieldValue: 'artifacts.elastic.co', + doc_count: 106, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'host.keyword:www.elastic.co', + type: 'keyword', + fieldName: 'host.keyword', + fieldValue: 'www.elastic.co', + doc_count: 84, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'host.keyword:cdn.elastic-elastic-elastic.org', + type: 'keyword', + fieldName: 'host.keyword', + fieldValue: 'cdn.elastic-elastic-elastic.org', + doc_count: 27, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'index.keyword:kibana_sample_data_logs', + type: 'keyword', + fieldName: 'index.keyword', + fieldValue: 'kibana_sample_data_logs', + doc_count: 329, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'ip:30.156.16.163', + type: 'keyword', + fieldName: 'ip', + fieldValue: '30.156.16.163', + doc_count: 101, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'ip:107.152.89.90', + type: 'keyword', + fieldName: 'ip', + fieldValue: '107.152.89.90', + doc_count: 3, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'ip:112.106.69.227', + type: 'keyword', + fieldName: 'ip', + fieldValue: '112.106.69.227', + doc_count: 3, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'ip:160.20.100.193', + type: 'keyword', + fieldName: 'ip', + fieldValue: '160.20.100.193', + doc_count: 3, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'ip:186.153.168.71', + type: 'keyword', + fieldName: 'ip', + fieldValue: '186.153.168.71', + doc_count: 3, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'ip:16.241.165.21', + type: 'keyword', + fieldName: 'ip', + fieldValue: '16.241.165.21', + doc_count: 2, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'ip:20.129.3.8', + type: 'keyword', + fieldName: 'ip', + fieldValue: '20.129.3.8', + doc_count: 2, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'ip:24.42.142.201', + type: 'keyword', + fieldName: 'ip', + fieldValue: '24.42.142.201', + doc_count: 2, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'ip:43.86.71.5', + type: 'keyword', + fieldName: 'ip', + fieldValue: '43.86.71.5', + doc_count: 2, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'ip:50.184.59.162', + type: 'keyword', + fieldName: 'ip', + fieldValue: '50.184.59.162', + doc_count: 2, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'machine.os.keyword:win xp', + type: 'keyword', + fieldName: 'machine.os.keyword', + fieldValue: 'win xp', + doc_count: 148, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'machine.os.keyword:osx', + type: 'keyword', + fieldName: 'machine.os.keyword', + fieldValue: 'osx', + doc_count: 50, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'machine.os.keyword:ios', + type: 'keyword', + fieldName: 'machine.os.keyword', + fieldValue: 'ios', + doc_count: 44, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'machine.os.keyword:win 7', + type: 'keyword', + fieldName: 'machine.os.keyword', + fieldValue: 'win 7', + doc_count: 44, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'machine.os.keyword:win 8', + type: 'keyword', + fieldName: 'machine.os.keyword', + fieldValue: 'win 8', + doc_count: 43, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'referer:http://www.elastic-elastic-elastic.com/success/timothy-l-kopra', + type: 'keyword', + fieldName: 'referer', + fieldValue: 'http://www.elastic-elastic-elastic.com/success/timothy-l-kopra', + doc_count: 101, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'referer:http://facebook.com/success/daniel-barry', + type: 'keyword', + fieldName: 'referer', + fieldValue: 'http://facebook.com/success/daniel-barry', + doc_count: 2, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'referer:http://facebook.com/success/mark-kelly', + type: 'keyword', + fieldName: 'referer', + fieldValue: 'http://facebook.com/success/mark-kelly', + doc_count: 2, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'referer:http://facebook.com/success/pavel-popovich', + type: 'keyword', + fieldName: 'referer', + fieldValue: 'http://facebook.com/success/pavel-popovich', + doc_count: 2, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'referer:http://facebook.com/success/scott-altman', + type: 'keyword', + fieldName: 'referer', + fieldValue: 'http://facebook.com/success/scott-altman', + doc_count: 2, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'referer:http://twitter.com/success/dafydd-williams', + type: 'keyword', + fieldName: 'referer', + fieldValue: 'http://twitter.com/success/dafydd-williams', + doc_count: 2, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'referer:http://twitter.com/success/valentin-lebedev', + type: 'keyword', + fieldName: 'referer', + fieldValue: 'http://twitter.com/success/valentin-lebedev', + doc_count: 2, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'referer:http://twitter.com/success/viktor-m-afanasyev', + type: 'keyword', + fieldName: 'referer', + fieldValue: 'http://twitter.com/success/viktor-m-afanasyev', + doc_count: 2, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'referer:http://twitter.com/success/y-ng-l-w-i', + type: 'keyword', + fieldName: 'referer', + fieldValue: 'http://twitter.com/success/y-ng-l-w-i', + doc_count: 2, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'referer:http://www.elastic-elastic-elastic.com/success/georgi-dobrovolski', + type: 'keyword', + fieldName: 'referer', + fieldValue: 'http://www.elastic-elastic-elastic.com/success/georgi-dobrovolski', + doc_count: 2, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'request.keyword:/apm', + type: 'keyword', + fieldName: 'request.keyword', + fieldValue: '/apm', + doc_count: 19, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'request.keyword:/beats', + type: 'keyword', + fieldName: 'request.keyword', + fieldValue: '/beats', + doc_count: 13, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'request.keyword:/beats/filebeat', + type: 'keyword', + fieldName: 'request.keyword', + fieldValue: '/beats/filebeat', + doc_count: 11, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'request.keyword:/elasticsearch/elasticsearch-6.3.2.tar.gz', + type: 'keyword', + fieldName: 'request.keyword', + fieldValue: '/elasticsearch/elasticsearch-6.3.2.tar.gz', + doc_count: 11, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'request.keyword:/kibana/kibana-6.3.2-darwin-x86_64.tar.gz', + type: 'keyword', + fieldName: 'request.keyword', + fieldValue: '/kibana/kibana-6.3.2-darwin-x86_64.tar.gz', + doc_count: 11, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'request.keyword:/', + type: 'keyword', + fieldName: 'request.keyword', + fieldValue: '/', + doc_count: 10, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'request.keyword:/apm-server/apm-server-6.3.2-windows-x86.zip', + type: 'keyword', + fieldName: 'request.keyword', + fieldValue: '/apm-server/apm-server-6.3.2-windows-x86.zip', + doc_count: 10, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'request.keyword:/beats/metricbeat', + type: 'keyword', + fieldName: 'request.keyword', + fieldValue: '/beats/metricbeat', + doc_count: 10, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'request.keyword:/beats/metricbeat/metricbeat-6.3.2-i686.rpm', + type: 'keyword', + fieldName: 'request.keyword', + fieldValue: '/beats/metricbeat/metricbeat-6.3.2-i686.rpm', + doc_count: 10, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'request.keyword:/elasticsearch/elasticsearch-6.3.2.deb', + type: 'keyword', + fieldName: 'request.keyword', + fieldValue: '/elasticsearch/elasticsearch-6.3.2.deb', + doc_count: 10, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'response.keyword:200', + type: 'keyword', + fieldName: 'response.keyword', + fieldValue: '200', + doc_count: 210, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'response.keyword:404', + type: 'keyword', + fieldName: 'response.keyword', + fieldValue: '404', + doc_count: 110, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'response.keyword:503', + type: 'keyword', + fieldName: 'response.keyword', + fieldValue: '503', + doc_count: 9, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'tags.keyword:success', + type: 'keyword', + fieldName: 'tags.keyword', + fieldValue: 'success', + doc_count: 295, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'tags.keyword:info', + type: 'keyword', + fieldName: 'tags.keyword', + fieldValue: 'info', + doc_count: 279, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'tags.keyword:security', + type: 'keyword', + fieldName: 'tags.keyword', + fieldValue: 'security', + doc_count: 37, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'tags.keyword:warning', + type: 'keyword', + fieldName: 'tags.keyword', + fieldValue: 'warning', + doc_count: 23, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'tags.keyword:login', + type: 'keyword', + fieldName: 'tags.keyword', + fieldValue: 'login', + doc_count: 13, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'tags.keyword:error', + type: 'keyword', + fieldName: 'tags.keyword', + fieldValue: 'error', + doc_count: 11, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'url.keyword:https://www.elastic.co/downloads/apm', + type: 'keyword', + fieldName: 'url.keyword', + fieldValue: 'https://www.elastic.co/downloads/apm', + doc_count: 16, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'url.keyword:https://www.elastic.co/downloads/beats', + type: 'keyword', + fieldName: 'url.keyword', + fieldValue: 'https://www.elastic.co/downloads/beats', + doc_count: 13, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'url.keyword:https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.3.2.tar.gz', + type: 'keyword', + fieldName: 'url.keyword', + fieldValue: 'https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.3.2.tar.gz', + doc_count: 11, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'url.keyword:https://artifacts.elastic.co/downloads/kibana/kibana-6.3.2-darwin-x86_64.tar.gz', + type: 'keyword', + fieldName: 'url.keyword', + fieldValue: 'https://artifacts.elastic.co/downloads/kibana/kibana-6.3.2-darwin-x86_64.tar.gz', + doc_count: 11, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'url.keyword:https://www.elastic.co/downloads/beats/filebeat', + type: 'keyword', + fieldName: 'url.keyword', + fieldValue: 'https://www.elastic.co/downloads/beats/filebeat', + doc_count: 11, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'url.keyword:https://artifacts.elastic.co/downloads/apm-server/apm-server-6.3.2-windows-x86.zip', + type: 'keyword', + fieldName: 'url.keyword', + fieldValue: + 'https://artifacts.elastic.co/downloads/apm-server/apm-server-6.3.2-windows-x86.zip', + doc_count: 10, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'url.keyword:https://artifacts.elastic.co/downloads/beats/metricbeat/metricbeat-6.3.2-i686.rpm', + type: 'keyword', + fieldName: 'url.keyword', + fieldValue: 'https://artifacts.elastic.co/downloads/beats/metricbeat/metricbeat-6.3.2-i686.rpm', + doc_count: 10, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'url.keyword:https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.3.2.deb', + type: 'keyword', + fieldName: 'url.keyword', + fieldValue: 'https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.3.2.deb', + doc_count: 10, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'url.keyword:https://www.elastic.co/downloads/beats/metricbeat', + type: 'keyword', + fieldName: 'url.keyword', + fieldValue: 'https://www.elastic.co/downloads/beats/metricbeat', + doc_count: 10, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'url.keyword:https://www.elastic.co/downloads/enterprise', + type: 'keyword', + fieldName: 'url.keyword', + fieldValue: 'https://www.elastic.co/downloads/enterprise', + doc_count: 10, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, +]; diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/queries/__mocks__/top_terms_search_response.ts b/x-pack/packages/ml/aiops_log_rate_analysis/queries/__mocks__/top_terms_search_response.ts new file mode 100644 index 0000000000000..ef2f0f9eb6a26 --- /dev/null +++ b/x-pack/packages/ml/aiops_log_rate_analysis/queries/__mocks__/top_terms_search_response.ts @@ -0,0 +1,232 @@ +/* + * 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 topTermsSearchResponseMock = { + took: 8, + timed_out: false, + _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, + hits: { total: { value: 329, relation: 'eq' }, max_score: null, hits: [] }, + aggregations: { + top_terms_0: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.50 Safari/534.24', + doc_count: 179, + }, + { + key: 'Mozilla/5.0 (X11; Linux x86_64; rv:6.0a1) Gecko/20110421 Firefox/6.0a1', + doc_count: 87, + }, + { + key: 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)', + doc_count: 63, + }, + ], + }, + top_terms_1: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 207, + buckets: [ + { key: '30.156.16.164', doc_count: 100 }, + { key: '107.152.89.90', doc_count: 3 }, + { key: '112.106.69.227', doc_count: 3 }, + { key: '160.20.100.193', doc_count: 3 }, + { key: '186.153.168.71', doc_count: 3 }, + { key: '16.241.165.21', doc_count: 2 }, + { key: '20.129.3.8', doc_count: 2 }, + { key: '24.42.142.201', doc_count: 2 }, + { key: '43.86.71.5', doc_count: 2 }, + { key: '50.184.59.162', doc_count: 2 }, + ], + }, + top_terms_2: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [{ key: 'sample_web_logs', doc_count: 329 }], + }, + top_terms_3: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { key: '', doc_count: 196 }, + { key: 'gz', doc_count: 42 }, + { key: 'zip', doc_count: 28 }, + { key: 'css', doc_count: 27 }, + { key: 'deb', doc_count: 26 }, + { key: 'rpm', doc_count: 10 }, + ], + }, + top_terms_4: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 95, + buckets: [ + { key: 'IN', doc_count: 135 }, + { key: 'CN', doc_count: 38 }, + { key: 'US', doc_count: 18 }, + { key: 'ID', doc_count: 10 }, + { key: 'BD', doc_count: 7 }, + { key: 'BR', doc_count: 7 }, + { key: 'NG', doc_count: 7 }, + { key: 'AR', doc_count: 4 }, + { key: 'DE', doc_count: 4 }, + { key: 'ET', doc_count: 4 }, + ], + }, + top_terms_5: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [{ key: 'US', doc_count: 329 }], + }, + top_terms_6: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 95, + buckets: [ + { key: 'US:IN', doc_count: 135 }, + { key: 'US:CN', doc_count: 38 }, + { key: 'US:US', doc_count: 18 }, + { key: 'US:ID', doc_count: 10 }, + { key: 'US:BD', doc_count: 7 }, + { key: 'US:BR', doc_count: 7 }, + { key: 'US:NG', doc_count: 7 }, + { key: 'US:AR', doc_count: 4 }, + { key: 'US:DE', doc_count: 4 }, + { key: 'US:ET', doc_count: 4 }, + ], + }, + top_terms_7: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { key: 'elastic-elastic-elastic.org', doc_count: 112 }, + { key: 'artifacts.elastic.co', doc_count: 106 }, + { key: 'www.elastic.co', doc_count: 84 }, + { key: 'cdn.elastic-elastic-elastic.org', doc_count: 27 }, + ], + }, + top_terms_8: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [{ key: 'kibana_sample_data_logs', doc_count: 329 }], + }, + top_terms_9: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 206, + buckets: [ + { key: '30.156.16.163', doc_count: 101 }, + { key: '107.152.89.90', doc_count: 3 }, + { key: '112.106.69.227', doc_count: 3 }, + { key: '160.20.100.193', doc_count: 3 }, + { key: '186.153.168.71', doc_count: 3 }, + { key: '16.241.165.21', doc_count: 2 }, + { key: '20.129.3.8', doc_count: 2 }, + { key: '24.42.142.201', doc_count: 2 }, + { key: '43.86.71.5', doc_count: 2 }, + { key: '50.184.59.162', doc_count: 2 }, + ], + }, + top_terms_10: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { key: 'win xp', doc_count: 148 }, + { key: 'osx', doc_count: 50 }, + { key: 'ios', doc_count: 44 }, + { key: 'win 7', doc_count: 44 }, + { key: 'win 8', doc_count: 43 }, + ], + }, + top_terms_11: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 210, + buckets: [ + { key: 'http://www.elastic-elastic-elastic.com/success/timothy-l-kopra', doc_count: 101 }, + { key: 'http://facebook.com/success/daniel-barry', doc_count: 2 }, + { key: 'http://facebook.com/success/mark-kelly', doc_count: 2 }, + { key: 'http://facebook.com/success/pavel-popovich', doc_count: 2 }, + { key: 'http://facebook.com/success/scott-altman', doc_count: 2 }, + { key: 'http://twitter.com/success/dafydd-williams', doc_count: 2 }, + { key: 'http://twitter.com/success/valentin-lebedev', doc_count: 2 }, + { key: 'http://twitter.com/success/viktor-m-afanasyev', doc_count: 2 }, + { key: 'http://twitter.com/success/y-ng-l-w-i', doc_count: 2 }, + { + key: 'http://www.elastic-elastic-elastic.com/success/georgi-dobrovolski', + doc_count: 2, + }, + ], + }, + top_terms_12: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 214, + buckets: [ + { key: '/apm', doc_count: 19 }, + { key: '/beats', doc_count: 13 }, + { key: '/beats/filebeat', doc_count: 11 }, + { key: '/elasticsearch/elasticsearch-6.3.2.tar.gz', doc_count: 11 }, + { key: '/kibana/kibana-6.3.2-darwin-x86_64.tar.gz', doc_count: 11 }, + { key: '/', doc_count: 10 }, + { key: '/apm-server/apm-server-6.3.2-windows-x86.zip', doc_count: 10 }, + { key: '/beats/metricbeat', doc_count: 10 }, + { key: '/beats/metricbeat/metricbeat-6.3.2-i686.rpm', doc_count: 10 }, + { key: '/elasticsearch/elasticsearch-6.3.2.deb', doc_count: 10 }, + ], + }, + top_terms_13: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { key: '200', doc_count: 210 }, + { key: '404', doc_count: 110 }, + { key: '503', doc_count: 9 }, + ], + }, + top_terms_14: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { key: 'success', doc_count: 295 }, + { key: 'info', doc_count: 279 }, + { key: 'security', doc_count: 37 }, + { key: 'warning', doc_count: 23 }, + { key: 'login', doc_count: 13 }, + { key: 'error', doc_count: 11 }, + ], + }, + top_terms_15: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 217, + buckets: [ + { key: 'https://www.elastic.co/downloads/apm', doc_count: 16 }, + { key: 'https://www.elastic.co/downloads/beats', doc_count: 13 }, + { + key: 'https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.3.2.tar.gz', + doc_count: 11, + }, + { + key: 'https://artifacts.elastic.co/downloads/kibana/kibana-6.3.2-darwin-x86_64.tar.gz', + doc_count: 11, + }, + { key: 'https://www.elastic.co/downloads/beats/filebeat', doc_count: 11 }, + { + key: 'https://artifacts.elastic.co/downloads/apm-server/apm-server-6.3.2-windows-x86.zip', + doc_count: 10, + }, + { + key: 'https://artifacts.elastic.co/downloads/beats/metricbeat/metricbeat-6.3.2-i686.rpm', + doc_count: 10, + }, + { + key: 'https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.3.2.deb', + doc_count: 10, + }, + { key: 'https://www.elastic.co/downloads/beats/metricbeat', doc_count: 10 }, + { key: 'https://www.elastic.co/downloads/enterprise', doc_count: 10 }, + ], + }, + }, +}; diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_categories.test.ts b/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_categories.test.ts index 86896e1c33a01..64477ab64aff9 100644 --- a/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_categories.test.ts +++ b/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_categories.test.ts @@ -67,7 +67,6 @@ describe('getCategoryRequest', () => { // time range filter whatsoever, for example for start/end (0,50). expect(query).toEqual({ index: 'the-index', - size: 0, body: { query: { bool: { @@ -117,6 +116,7 @@ describe('getCategoryRequest', () => { }, }, }, + size: 0, }, }); }); diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_categories.ts b/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_categories.ts index 0b1c580d93a01..2a06b36a9fac4 100644 --- a/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_categories.ts +++ b/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_categories.ts @@ -84,6 +84,7 @@ export const getCategoryRequest = ( wrap, undefined, undefined, + false, false ); diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_top_categories.test.ts b/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_top_categories.test.ts new file mode 100644 index 0000000000000..e7bdd6c7944d7 --- /dev/null +++ b/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_top_categories.test.ts @@ -0,0 +1,91 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { paramsMock } from './__mocks__/params_match_all'; + +import type { ElasticsearchClient } from '@kbn/core/server'; +import type { Logger } from '@kbn/logging'; + +import { topCategoriesSearchResponseMock } from './__mocks__/top_categories_search_response'; +import { topCategoriesResultMock } from './__mocks__/top_categories_result'; +import { fetchTopCategories } from './fetch_top_categories'; + +const esClientMock = { + msearch: jest.fn().mockImplementation(() => topCategoriesSearchResponseMock), +} as unknown as ElasticsearchClient; + +const loggerMock = {} as unknown as Logger; + +describe('fetchTopCategories', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + test('should fetch top categories successfully', async () => { + const abortSignal = new AbortController().signal; + + const result = await fetchTopCategories({ + esClient: esClientMock, + logger: loggerMock, + emitError: jest.fn(), + abortSignal, + arguments: { ...paramsMock, fieldNames: ['message'] }, + }); + expect(result).toEqual(topCategoriesResultMock); + expect(esClientMock.msearch).toHaveBeenCalledWith( + { + searches: [ + { index: 'the-index' }, + { + aggs: { + categories: { + aggs: { + examples: { + top_hits: { _source: 'message', size: 4, sort: ['the-time-field-name'] }, + }, + }, + categorize_text: { field: 'message', size: 1000 }, + }, + }, + query: { + bool: { + filter: [ + { + bool: { + should: [ + { + range: { + 'the-time-field-name': { + format: 'epoch_millis', + gte: 10, + lte: 20, + }, + }, + }, + { + range: { + 'the-time-field-name': { + format: 'epoch_millis', + gte: 30, + lte: 40, + }, + }, + }, + ], + }, + }, + ], + }, + }, + size: 0, + }, + ], + }, + { maxRetries: 0, signal: abortSignal } + ); + }); +}); diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_top_categories.ts b/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_top_categories.ts index a9fb020c10c51..2806c21834405 100644 --- a/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_top_categories.ts +++ b/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_top_categories.ts @@ -7,24 +7,21 @@ import { uniq } from 'lodash'; -import type { ElasticsearchClient } from '@kbn/core/server'; -import type { Logger } from '@kbn/logging'; import { type SignificantItem, SIGNIFICANT_ITEM_TYPE } from '@kbn/ml-agg-utils'; -import type { AiopsLogRateAnalysisSchema } from '../api/schema'; - import { fetchCategories } from './fetch_categories'; +import type { FetchTopOptions } from './fetch_top_types'; -export const fetchTopCategories = async ( - esClient: ElasticsearchClient, - params: AiopsLogRateAnalysisSchema, - fieldNames: string[], - logger: Logger, +export const fetchTopCategories = async ({ + esClient, + abortSignal, + emitError, + logger, + arguments: args, +}: FetchTopOptions) => { // The default value of 1 means no sampling will be used - sampleProbability: number = 1, - emitError: (m: string) => void, - abortSignal?: AbortSignal -) => { + const { fieldNames, sampleProbability = 1, ...params } = args; + const categoriesOverall = await fetchCategories( esClient, params, diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_top_terms.test.ts b/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_top_terms.test.ts new file mode 100644 index 0000000000000..761c39c00e74a --- /dev/null +++ b/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_top_terms.test.ts @@ -0,0 +1,62 @@ +/* + * 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 { paramsMock } from './__mocks__/params_match_all'; + +import type { ElasticsearchClient } from '@kbn/core/server'; +import type { Logger } from '@kbn/logging'; + +import { topTermsSearchResponseMock } from './__mocks__/top_terms_search_response'; +import { topTermsResult } from './__mocks__/top_terms_result'; +import { fetchTopTerms } from './fetch_top_terms'; + +const esClientMock = { + search: jest.fn().mockImplementation(() => topTermsSearchResponseMock), +} as unknown as ElasticsearchClient; + +const loggerMock = {} as unknown as Logger; + +describe('fetchTopTerms', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + test('should fetch top terms successfully', async () => { + const abortSignal = new AbortController().signal; + + const result = await fetchTopTerms({ + esClient: esClientMock, + logger: loggerMock, + emitError: jest.fn(), + abortSignal, + arguments: { + ...paramsMock, + fieldNames: [ + 'agent.keyword', + 'clientip', + 'event.dataset', + 'extension.keyword', + 'geo.dest', + 'geo.src', + 'geo.srcdest', + 'host.keyword', + 'index.keyword', + 'ip', + 'machine.os.keyword', + 'referer', + 'request.keyword', + 'response.keyword', + 'tags.keyword', + 'url.keyword', + ], + }, + }); + + expect(esClientMock.search).toHaveBeenCalledTimes(1); + expect(result).toEqual(topTermsResult); + }); +}); diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_top_terms.ts b/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_top_terms.ts index 8c5688d542083..ed39c37cbbb97 100644 --- a/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_top_terms.ts +++ b/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_top_terms.ts @@ -5,10 +5,9 @@ * 2.0. */ import { uniqBy } from 'lodash'; + import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import type { ElasticsearchClient } from '@kbn/core/server'; -import type { Logger } from '@kbn/logging'; import { type SignificantItem, SIGNIFICANT_ITEM_TYPE } from '@kbn/ml-agg-utils'; import { createRandomSamplerWrapper, @@ -21,6 +20,7 @@ import { LOG_RATE_ANALYSIS_SETTINGS, RANDOM_SAMPLER_SEED } from '../constants'; import { getQueryWithParams } from './get_query_with_params'; import { getRequestBase } from './get_request_base'; +import type { FetchTopOptions } from './fetch_top_types'; // TODO Consolidate with duplicate `fetchDurationFieldCandidates` in // `x-pack/plugins/observability_solution/apm/server/routes/correlations/queries/fetch_failed_events_correlation_p_values.ts` @@ -87,16 +87,16 @@ interface Aggs extends estypes.AggregationsLongTermsAggregate { buckets: estypes.AggregationsLongTermsBucket[]; } -export const fetchTopTerms = async ( - esClient: ElasticsearchClient, - params: AiopsLogRateAnalysisSchema, - fieldNames: string[], - logger: Logger, +export const fetchTopTerms = async ({ + esClient, + abortSignal, + emitError, + logger, + arguments: args, +}: FetchTopOptions): Promise => { // The default value of 1 means no sampling will be used - sampleProbability: number = 1, - emitError: (m: string) => void, - abortSignal?: AbortSignal -): Promise => { + const { fieldNames, sampleProbability = 1, ...params } = args; + const randomSamplerWrapper = createRandomSamplerWrapper({ probability: sampleProbability, seed: RANDOM_SAMPLER_SEED, diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_top_types.ts b/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_top_types.ts new file mode 100644 index 0000000000000..26d743caa9347 --- /dev/null +++ b/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_top_types.ts @@ -0,0 +1,22 @@ +/* + * 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/server'; +import type { Logger } from '@kbn/logging'; + +import type { AiopsLogRateAnalysisSchema } from '../api/schema'; + +export interface FetchTopOptions { + esClient: ElasticsearchClient; + logger: Logger; + emitError: (m: string) => void; + abortSignal?: AbortSignal; + arguments: AiopsLogRateAnalysisSchema & { + fieldNames: string[]; + sampleProbability?: number; + }; +} diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/top_items_handler.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/top_items_handler.ts index b33682a627ca4..a59b46887b5ff 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/top_items_handler.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/top_items_handler.ts @@ -133,15 +133,17 @@ export const topItemsHandlerFactory = let fetchedTopTerms: Awaited>; try { - fetchedTopTerms = await fetchTopTerms( + fetchedTopTerms = await fetchTopTerms({ esClient, - requestBody, - fieldNames, logger, - stateHandler.sampleProbability(), - responseStream.pushError, - abortSignal - ); + emitError: responseStream.pushError, + abortSignal, + arguments: { + ...requestBody, + fieldNames, + sampleProbability: stateHandler.sampleProbability(), + }, + }); } catch (e) { if (!isRequestAbortedError(e)) { logger.error( @@ -183,15 +185,17 @@ export const topItemsHandlerFactory = } else if (isTextFieldCandidates(payload)) { const { textFieldCandidates: fieldNames } = payload; - const topCategoriesForField = await await fetchTopCategories( + const topCategoriesForField = await fetchTopCategories({ esClient, - requestBody, - fieldNames, logger, - stateHandler.sampleProbability(), - responseStream.pushError, - abortSignal - ); + emitError: responseStream.pushError, + abortSignal, + arguments: { + ...requestBody, + fieldNames, + sampleProbability: stateHandler.sampleProbability(), + }, + }); if (topCategoriesForField.length > 0) { topCategories.push(...topCategoriesForField); From e46e54a18c552d40fb86655a79fdcd580c647f9c Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Wed, 7 Aug 2024 15:27:52 -0400 Subject: [PATCH 29/44] [Response Ops][Task Manager] Resource based task scheduling - 2nd attempt (#189626) ## Summary Redoing the resource based task claim PR: https://github.com/elastic/kibana/pull/187999 and followup PRs https://github.com/elastic/kibana/pull/189220 and https://github.com/elastic/kibana/pull/189117. Please see the descriptions of those PRs for more details. This was original reverted because unregistered task types in serverless caused the task manager health aggregation to fail. This PR includes an additional commit to exclude unregistered task types from the health report: https://github.com/elastic/kibana/pull/189626/commits/58eb2b1db7ae6d6b48fe3178fe5a4e57cf82b51d. To verify this, make sure you're using the `default` claim strategy, start up Kibana so that the default set of tasks get created. Then either disable a bunch of plugins via config: ``` # remove security and o11y enterpriseSearch.enabled: false xpack.apm.enabled: false xpack.cloudSecurityPosture.enabled: false xpack.fleet.enabled: false xpack.infra.enabled: false xpack.observability.enabled: false xpack.observabilityAIAssistant.enabled: false xpack.observabilityLogsExplorer.enabled: false xpack.search.notebooks.enabled: false xpack.securitySolution.enabled: false xpack.uptime.enabled: false ``` or comment out the task registration of a task that was previously scheduled (I'm using the observability AI assistant) ``` --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/index.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/index.ts @@ -89,24 +89,24 @@ export class ObservabilityAIAssistantService { this.allowInit(); - taskManager.registerTaskDefinitions({ - [INDEX_QUEUED_DOCUMENTS_TASK_TYPE]: { - title: 'Index queued KB articles', - description: - 'Indexes previously registered entries into the knowledge base when it is ready', - timeout: '30m', - maxAttempts: 2, - createTaskRunner: (context) => { - return { - run: async () => { - if (this.kbService) { - await this.kbService.processQueue(); - } - }, - }; - }, - }, - }); + // taskManager.registerTaskDefinitions({ + // [INDEX_QUEUED_DOCUMENTS_TASK_TYPE]: { + // title: 'Index queued KB articles', + // description: + // 'Indexes previously registered entries into the knowledge base when it is ready', + // timeout: '30m', + // maxAttempts: 2, + // createTaskRunner: (context) => { + // return { + // run: async () => { + // if (this.kbService) { + // await this.kbService.processQueue(); + // } + // }, + // }; + // }, + // }, + // }); } ``` and restart Kibana. You should still be able to access the TM health report with the workload field and if you update the background health logging so it always logs and more frequently, you should see the logging succeed with no errors: Below, I've made changes to always log the background health at a 15 second interval: ``` --- a/x-pack/plugins/task_manager/server/plugin.ts +++ b/x-pack/plugins/task_manager/server/plugin.ts @@ -236,6 +236,7 @@ export class TaskManagerPlugin if (this.isNodeBackgroundTasksOnly()) { setupIntervalLogging(monitoredHealth$, this.logger, LogHealthForBackgroundTasksOnlyMinutes); } + setupIntervalLogging(monitoredHealth$, this.logger, LogHealthForBackgroundTasksOnlyMinutes); reduce the logging interval --- a/x-pack/plugins/task_manager/server/lib/log_health_metrics.ts +++ b/x-pack/plugins/task_manager/server/lib/log_health_metrics.ts @@ -35,7 +35,8 @@ export function setupIntervalLogging( monitoredHealth = m; }); - setInterval(onInterval, 1000 * 60 * minutes); + // setInterval(onInterval, 1000 * 60 * minutes); + setInterval(onInterval, 1000 * 15); function onInterval() { ``` --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Elastic Machine --- .../server/rule_type_registry.test.ts | 33 + .../alerting/server/rule_type_registry.ts | 7 + x-pack/plugins/task_manager/kibana.jsonc | 1 + .../plugins/task_manager/server/MONITORING.md | 6 +- .../task_manager/server/config.test.ts | 3 - x-pack/plugins/task_manager/server/config.ts | 16 +- .../server/ephemeral_task_lifecycle.test.ts | 29 +- .../server/ephemeral_task_lifecycle.ts | 6 +- x-pack/plugins/task_manager/server/index.ts | 7 +- .../task_cost_check.test.ts.snap | 10 + .../managed_configuration.test.ts | 484 ++++-- .../integration_tests/task_cost_check.test.ts | 63 + .../lib/calculate_health_status.test.ts | 9 +- .../lib/create_managed_configuration.test.ts | 280 +++- .../lib/create_managed_configuration.ts | 108 +- .../task_manager/server/lib/fill_pool.test.ts | 1 - .../server/lib/get_default_capacity.test.ts | 185 +++ .../server/lib/get_default_capacity.ts | 51 + .../server/lib/log_health_metrics.test.ts | 12 +- .../server/metrics/create_aggregator.test.ts | 1 - .../background_task_utilization_statistics.ts | 2 +- .../monitoring/capacity_estimation.test.ts | 40 +- .../server/monitoring/capacity_estimation.ts | 12 +- .../configuration_statistics.test.ts | 27 +- .../monitoring/configuration_statistics.ts | 30 +- .../ephemeral_task_statistics.test.ts | 10 +- .../monitoring/ephemeral_task_statistics.ts | 6 +- .../task_manager/server/monitoring/index.ts | 36 +- .../monitoring_stats_stream.test.ts | 53 +- .../monitoring/monitoring_stats_stream.ts | 43 +- ...s.test.ts => task_run_calculators.test.ts} | 2 +- ..._calcultors.ts => task_run_calculators.ts} | 0 .../server/monitoring/task_run_statistics.ts | 2 +- .../monitoring/workload_statistics.test.ts | 622 ++++---- .../server/monitoring/workload_statistics.ts | 190 ++- .../task_manager/server/plugin.test.ts | 10 +- x-pack/plugins/task_manager/server/plugin.ts | 69 +- .../polling/delay_on_claim_conflicts.test.ts | 26 +- .../polling/delay_on_claim_conflicts.ts | 12 +- .../server/polling_lifecycle.test.ts | 147 +- .../task_manager/server/polling_lifecycle.ts | 56 +- .../server/queries/task_claiming.test.ts | 4 +- .../server/queries/task_claiming.ts | 10 +- .../task_manager/server/routes/health.test.ts | 12 +- x-pack/plugins/task_manager/server/task.ts | 18 +- .../server/task_claimers/index.ts | 4 +- .../task_claimers/strategy_default.test.ts | 18 +- .../task_claimers/strategy_mget.test.ts | 1297 ++++++++++++++++- .../server/task_claimers/strategy_mget.ts | 115 +- .../task_manager/server/task_pool.test.ts | 471 ------ .../server/task_pool/capacity.mock.ts | 21 + .../server/task_pool/cost_capacity.test.ts | 171 +++ .../server/task_pool/cost_capacity.ts | 111 ++ .../task_manager/server/task_pool/index.ts | 9 + .../server/{ => task_pool}/task_pool.mock.ts | 31 +- .../server/task_pool/task_pool.test.ts | 867 +++++++++++ .../server/{ => task_pool}/task_pool.ts | 116 +- .../server/task_pool/test_utils.ts | 53 + .../task_manager/server/task_pool/types.ts | 31 + .../task_manager/server/task_pool/utils.ts | 16 + .../server/task_pool/worker_capacity.test.ts | 176 +++ .../server/task_pool/worker_capacity.ts | 95 ++ .../task_manager/server/task_store.test.ts | 146 +- .../plugins/task_manager/server/task_store.ts | 56 +- .../server/task_type_dictionary.test.ts | 42 +- .../server/task_type_dictionary.ts | 12 +- .../task_manager_usage_collector.test.ts | 12 +- x-pack/plugins/task_manager/tsconfig.json | 3 +- .../test_suites/task_manager/health_route.ts | 7 +- .../test_suites/task_manager/health_route.ts | 7 +- 70 files changed, 5092 insertions(+), 1546 deletions(-) create mode 100644 x-pack/plugins/task_manager/server/integration_tests/__snapshots__/task_cost_check.test.ts.snap create mode 100644 x-pack/plugins/task_manager/server/integration_tests/task_cost_check.test.ts create mode 100644 x-pack/plugins/task_manager/server/lib/get_default_capacity.test.ts create mode 100644 x-pack/plugins/task_manager/server/lib/get_default_capacity.ts rename x-pack/plugins/task_manager/server/monitoring/{task_run_calcultors.test.ts => task_run_calculators.test.ts} (98%) rename x-pack/plugins/task_manager/server/monitoring/{task_run_calcultors.ts => task_run_calculators.ts} (100%) delete mode 100644 x-pack/plugins/task_manager/server/task_pool.test.ts create mode 100644 x-pack/plugins/task_manager/server/task_pool/capacity.mock.ts create mode 100644 x-pack/plugins/task_manager/server/task_pool/cost_capacity.test.ts create mode 100644 x-pack/plugins/task_manager/server/task_pool/cost_capacity.ts create mode 100644 x-pack/plugins/task_manager/server/task_pool/index.ts rename x-pack/plugins/task_manager/server/{ => task_pool}/task_pool.mock.ts (58%) create mode 100644 x-pack/plugins/task_manager/server/task_pool/task_pool.test.ts rename x-pack/plugins/task_manager/server/{ => task_pool}/task_pool.ts (73%) create mode 100644 x-pack/plugins/task_manager/server/task_pool/test_utils.ts create mode 100644 x-pack/plugins/task_manager/server/task_pool/types.ts create mode 100644 x-pack/plugins/task_manager/server/task_pool/utils.ts create mode 100644 x-pack/plugins/task_manager/server/task_pool/worker_capacity.test.ts create mode 100644 x-pack/plugins/task_manager/server/task_pool/worker_capacity.ts diff --git a/x-pack/plugins/alerting/server/rule_type_registry.test.ts b/x-pack/plugins/alerting/server/rule_type_registry.test.ts index 3ee3551a301d5..e678228660e51 100644 --- a/x-pack/plugins/alerting/server/rule_type_registry.test.ts +++ b/x-pack/plugins/alerting/server/rule_type_registry.test.ts @@ -564,6 +564,39 @@ describe('Create Lifecycle', () => { }); }); + test('injects custom cost for certain rule types', () => { + const ruleType: RuleType = { + id: 'siem.indicatorRule', + name: 'Test', + actionGroups: [ + { + id: 'default', + name: 'Default', + }, + ], + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + isExportable: true, + executor: jest.fn(), + category: 'test', + producer: 'alerts', + ruleTaskTimeout: '20m', + validate: { + params: { validate: (params) => params }, + }, + }; + const registry = new RuleTypeRegistry(ruleTypeRegistryParams); + registry.register(ruleType); + expect(taskManager.registerTaskDefinitions).toHaveBeenCalledTimes(1); + expect(taskManager.registerTaskDefinitions.mock.calls[0][0]).toMatchObject({ + 'alerting:siem.indicatorRule': { + timeout: '20m', + title: 'Test', + cost: 10, + }, + }); + }); + test('shallow clones the given rule type', () => { const ruleType: RuleType = { id: 'test', diff --git a/x-pack/plugins/alerting/server/rule_type_registry.ts b/x-pack/plugins/alerting/server/rule_type_registry.ts index d1ffe59df3b6f..bc7a10d767ff0 100644 --- a/x-pack/plugins/alerting/server/rule_type_registry.ts +++ b/x-pack/plugins/alerting/server/rule_type_registry.ts @@ -14,6 +14,7 @@ import { Logger } from '@kbn/core/server'; import { LicensingPluginSetup } from '@kbn/licensing-plugin/server'; import { RunContext, TaskManagerSetupContract } from '@kbn/task-manager-plugin/server'; import { stateSchemaByVersion } from '@kbn/alerting-state-types'; +import { TaskCost } from '@kbn/task-manager-plugin/server/task'; import { TaskRunnerFactory } from './task_runner'; import { RuleType, @@ -40,6 +41,9 @@ import { AlertsService } from './alerts_service/alerts_service'; import { getRuleTypeIdValidLegacyConsumers } from './rule_type_registry_deprecated_consumers'; import { AlertingConfig } from './config'; +const RULE_TYPES_WITH_CUSTOM_COST: Record = { + 'siem.indicatorRule': TaskCost.ExtraLarge, +}; export interface ConstructorOptions { config: AlertingConfig; logger: Logger; @@ -289,6 +293,8 @@ export class RuleTypeRegistry { normalizedRuleType as unknown as UntypedNormalizedRuleType ); + const taskCost: TaskCost | undefined = RULE_TYPES_WITH_CUSTOM_COST[ruleType.id]; + this.taskManager.registerTaskDefinitions({ [`alerting:${ruleType.id}`]: { title: ruleType.name, @@ -310,6 +316,7 @@ export class RuleTypeRegistry { spaceId: schema.string(), consumer: schema.maybe(schema.string()), }), + ...(taskCost ? { cost: taskCost } : {}), }, }); diff --git a/x-pack/plugins/task_manager/kibana.jsonc b/x-pack/plugins/task_manager/kibana.jsonc index e1141bbc58377..33edc225e42c1 100644 --- a/x-pack/plugins/task_manager/kibana.jsonc +++ b/x-pack/plugins/task_manager/kibana.jsonc @@ -11,6 +11,7 @@ "task_manager" ], "optionalPlugins": [ + "cloud", "usageCollection" ] } diff --git a/x-pack/plugins/task_manager/server/MONITORING.md b/x-pack/plugins/task_manager/server/MONITORING.md index 02946b9b3e53f..c4e66ab92bad5 100644 --- a/x-pack/plugins/task_manager/server/MONITORING.md +++ b/x-pack/plugins/task_manager/server/MONITORING.md @@ -50,7 +50,7 @@ The root `timestamp` is the time in which the summary was exposed (either to the Follow this step-by-step guide to make sense of the stats: https://www.elastic.co/guide/en/kibana/master/task-manager-troubleshooting.html#task-manager-diagnosing-root-cause #### The Configuration Section -The `configuration` section summarizes Task Manager's current configuration, including dynamic configurations which change over time, such as `poll_interval` and `max_workers` which adjust in reaction to changing load on the system. +The `configuration` section summarizes Task Manager's current configuration, including dynamic configurations which change over time, such as `poll_interval` and `capacity` which adjust in reaction to changing load on the system. These are "Hot" stats which are updated whenever a change happens in the configuration. @@ -69,8 +69,8 @@ The `runtime` tracks Task Manager's performance as it runs, making note of task These include: - The time it takes a task to run (p50, p90, p95 & p99, using a configurable running average window, `50` by default) - The average _drift_ that tasks experience (p50, p90, p95 & p99, using the same configurable running average window as above). Drift tells us how long after a task's scheduled a task typically executes. - - The average _load_ (p50, p90, p95 & p99, using the same configurable running average window as above). Load tells us what percentage of workers is in use at the end of each polling cycle. - - The polling rate (the timestamp of the last time a polling cycle completed), the polling health stats (number of version clashes and mismatches) and the result [`No tasks | Filled task pool | Unexpectedly ran out of workers`] frequency the past 50 polling cycles (using the same window size as the one used for running averages) + - The average _load_ (p50, p90, p95 & p99, using the same configurable running average window as above). Load tells us what percentage of capacity is in use at the end of each polling cycle. + - The polling rate (the timestamp of the last time a polling cycle completed), the polling health stats (number of version clashes and mismatches) and the result [`No tasks | Filled task pool | Unexpectedly ran out of capacity`] frequency the past 50 polling cycles (using the same window size as the one used for running averages) - The `Success | Retry | Failure ratio` by task type. This is different than the workload stats which tell you what's in the queue, but ca't keep track of retries and of non recurring tasks as they're wiped off the index when completed. These are "Hot" stats which are updated reactively as Tasks are executed and interacted with. diff --git a/x-pack/plugins/task_manager/server/config.test.ts b/x-pack/plugins/task_manager/server/config.test.ts index bb59a73a305d6..81e9e24ea4586 100644 --- a/x-pack/plugins/task_manager/server/config.test.ts +++ b/x-pack/plugins/task_manager/server/config.test.ts @@ -23,7 +23,6 @@ describe('config validation', () => { "warn_threshold": 5000, }, "max_attempts": 3, - "max_workers": 10, "metrics_reset_interval": 30000, "monitored_aggregated_stats_refresh_rate": 60000, "monitored_stats_health_verbose_log": Object { @@ -81,7 +80,6 @@ describe('config validation', () => { "warn_threshold": 5000, }, "max_attempts": 3, - "max_workers": 10, "metrics_reset_interval": 30000, "monitored_aggregated_stats_refresh_rate": 60000, "monitored_stats_health_verbose_log": Object { @@ -137,7 +135,6 @@ describe('config validation', () => { "warn_threshold": 5000, }, "max_attempts": 3, - "max_workers": 10, "metrics_reset_interval": 30000, "monitored_aggregated_stats_refresh_rate": 60000, "monitored_stats_health_verbose_log": Object { diff --git a/x-pack/plugins/task_manager/server/config.ts b/x-pack/plugins/task_manager/server/config.ts index eec63c5be489c..f0f4031a4c8ac 100644 --- a/x-pack/plugins/task_manager/server/config.ts +++ b/x-pack/plugins/task_manager/server/config.ts @@ -8,6 +8,9 @@ import { schema, TypeOf } from '@kbn/config-schema'; export const MAX_WORKERS_LIMIT = 100; +export const DEFAULT_CAPACITY = 10; +export const MAX_CAPACITY = 50; +export const MIN_CAPACITY = 5; export const DEFAULT_MAX_WORKERS = 10; export const DEFAULT_POLL_INTERVAL = 3000; export const DEFAULT_VERSION_CONFLICT_THRESHOLD = 80; @@ -64,6 +67,8 @@ const requestTimeoutsConfig = schema.object({ export const configSchema = schema.object( { allow_reading_invalid_state: schema.boolean({ defaultValue: true }), + /* The number of normal cost tasks that this Kibana instance will run simultaneously */ + capacity: schema.maybe(schema.number({ min: MIN_CAPACITY, max: MAX_CAPACITY })), ephemeral_tasks: schema.object({ enabled: schema.boolean({ defaultValue: false }), /* How many requests can Task Manager buffer before it rejects new requests. */ @@ -81,11 +86,12 @@ export const configSchema = schema.object( min: 1, }), /* The maximum number of tasks that this Kibana instance will run simultaneously. */ - max_workers: schema.number({ - defaultValue: DEFAULT_MAX_WORKERS, - // disable the task manager rather than trying to specify it with 0 workers - min: 1, - }), + max_workers: schema.maybe( + schema.number({ + // disable the task manager rather than trying to specify it with 0 workers + min: 1, + }) + ), /* The interval at which monotonically increasing metrics counters will reset */ metrics_reset_interval: schema.number({ defaultValue: DEFAULT_METRICS_RESET_INTERVAL, diff --git a/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.test.ts b/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.test.ts index 19cfa2943502c..2a6f1bf8c33b8 100644 --- a/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.test.ts +++ b/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.test.ts @@ -18,7 +18,7 @@ import { v4 as uuidv4 } from 'uuid'; import { asTaskPollingCycleEvent, asTaskRunEvent, TaskPersistence } from './task_events'; import { TaskRunResult } from './task_running'; import { TaskPoolRunResult } from './task_pool'; -import { TaskPoolMock } from './task_pool.mock'; +import { TaskPoolMock } from './task_pool/task_pool.mock'; import { executionContextServiceMock } from '@kbn/core/server/mocks'; import { taskManagerMock } from './mocks'; @@ -45,7 +45,6 @@ describe('EphemeralTaskLifecycle', () => { definitions: new TaskTypeDictionary(taskManagerLogger), executionContext, config: { - max_workers: 10, max_attempts: 9, poll_interval: 6000000, version_conflict_threshold: 80, @@ -156,7 +155,7 @@ describe('EphemeralTaskLifecycle', () => { expect(ephemeralTaskLifecycle.attemptToRun(task)).toMatchObject(asOk(task)); poolCapacity.mockReturnValue({ - availableWorkers: 10, + availableCapacity: 10, }); lifecycleEvent$.next( @@ -179,7 +178,7 @@ describe('EphemeralTaskLifecycle', () => { expect(ephemeralTaskLifecycle.attemptToRun(task)).toMatchObject(asOk(task)); poolCapacity.mockReturnValue({ - availableWorkers: 10, + availableCapacity: 10, }); lifecycleEvent$.next( @@ -216,7 +215,7 @@ describe('EphemeralTaskLifecycle', () => { expect(ephemeralTaskLifecycle.attemptToRun(tasks[2])).toMatchObject(asOk(tasks[2])); poolCapacity.mockReturnValue({ - availableWorkers: 2, + availableCapacity: 2, }); lifecycleEvent$.next( @@ -256,9 +255,9 @@ describe('EphemeralTaskLifecycle', () => { // pool has capacity for both poolCapacity.mockReturnValue({ - availableWorkers: 10, + availableCapacity: 10, }); - pool.getOccupiedWorkersByType.mockReturnValue(0); + pool.getUsedCapacityByType.mockReturnValue(0); lifecycleEvent$.next( asTaskPollingCycleEvent(asOk({ result: FillPoolResult.NoTasksClaimed })) @@ -296,10 +295,10 @@ describe('EphemeralTaskLifecycle', () => { // pool has capacity in general poolCapacity.mockReturnValue({ - availableWorkers: 2, + availableCapacity: 2, }); // but when we ask how many it has occupied by type - wee always have one worker already occupied by that type - pool.getOccupiedWorkersByType.mockReturnValue(1); + pool.getUsedCapacityByType.mockReturnValue(1); lifecycleEvent$.next( asTaskPollingCycleEvent(asOk({ result: FillPoolResult.NoTasksClaimed })) @@ -308,7 +307,7 @@ describe('EphemeralTaskLifecycle', () => { expect(pool.run).toHaveBeenCalledTimes(0); // now we release the worker in the pool and cause another cycle in the epheemral queue - pool.getOccupiedWorkersByType.mockReturnValue(0); + pool.getUsedCapacityByType.mockReturnValue(0); lifecycleEvent$.next( asTaskPollingCycleEvent(asOk({ result: FillPoolResult.NoTasksClaimed })) ); @@ -356,9 +355,9 @@ describe('EphemeralTaskLifecycle', () => { // pool has capacity for all poolCapacity.mockReturnValue({ - availableWorkers: 10, + availableCapacity: 10, }); - pool.getOccupiedWorkersByType.mockReturnValue(0); + pool.getUsedCapacityByType.mockReturnValue(0); lifecycleEvent$.next(asTaskPollingCycleEvent(asOk({ result: FillPoolResult.NoTasksClaimed }))); @@ -389,19 +388,19 @@ describe('EphemeralTaskLifecycle', () => { expect(ephemeralTaskLifecycle.queuedTasks).toBe(3); poolCapacity.mockReturnValue({ - availableWorkers: 1, + availableCapacity: 1, }); lifecycleEvent$.next(asTaskPollingCycleEvent(asOk({ result: FillPoolResult.NoTasksClaimed }))); expect(ephemeralTaskLifecycle.queuedTasks).toBe(2); poolCapacity.mockReturnValue({ - availableWorkers: 1, + availableCapacity: 1, }); lifecycleEvent$.next(asTaskPollingCycleEvent(asOk({ result: FillPoolResult.NoTasksClaimed }))); expect(ephemeralTaskLifecycle.queuedTasks).toBe(1); poolCapacity.mockReturnValue({ - availableWorkers: 1, + availableCapacity: 1, }); lifecycleEvent$.next(asTaskPollingCycleEvent(asOk({ result: FillPoolResult.NoTasksClaimed }))); expect(ephemeralTaskLifecycle.queuedTasks).toBe(0); diff --git a/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.ts b/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.ts index 37cc166ece211..c7ee267b848e5 100644 --- a/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.ts +++ b/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.ts @@ -143,13 +143,13 @@ export class EphemeralTaskLifecycle { taskType && this.definitions.get(taskType)?.maxConcurrency ? Math.max( Math.min( - this.pool.availableWorkers, + this.pool.availableCapacity(), this.definitions.get(taskType)!.maxConcurrency! - - this.pool.getOccupiedWorkersByType(taskType) + this.pool.getUsedCapacityByType(taskType) ), 0 ) - : this.pool.availableWorkers; + : this.pool.availableCapacity(); private emitEvent = (event: TaskLifecycleEvent) => { this.events$.next(event); diff --git a/x-pack/plugins/task_manager/server/index.ts b/x-pack/plugins/task_manager/server/index.ts index 8d50c37adda0b..965df090911fd 100644 --- a/x-pack/plugins/task_manager/server/index.ts +++ b/x-pack/plugins/task_manager/server/index.ts @@ -55,9 +55,6 @@ export type { export const config: PluginConfigDescriptor = { schema: configSchema, - exposeToUsage: { - max_workers: true, - }, deprecations: ({ deprecate }) => { return [ deprecate('ephemeral_tasks.enabled', 'a future version', { @@ -68,6 +65,10 @@ export const config: PluginConfigDescriptor = { level: 'warning', message: `Configuring "xpack.task_manager.ephemeral_tasks.request_capacity" is deprecated and will be removed in a future version. Remove this setting to increase task execution resiliency.`, }), + deprecate('max_workers', 'a future version', { + level: 'warning', + message: `Configuring "xpack.task_manager.max_workers" is deprecated and will be removed in a future version. Remove this setting and use "xpack.task_manager.capacity" instead.`, + }), (settings, fromPath, addDeprecation) => { const taskManager = get(settings, fromPath); if (taskManager?.index) { diff --git a/x-pack/plugins/task_manager/server/integration_tests/__snapshots__/task_cost_check.test.ts.snap b/x-pack/plugins/task_manager/server/integration_tests/__snapshots__/task_cost_check.test.ts.snap new file mode 100644 index 0000000000000..e59912ed91905 --- /dev/null +++ b/x-pack/plugins/task_manager/server/integration_tests/__snapshots__/task_cost_check.test.ts.snap @@ -0,0 +1,10 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Task cost checks detects tasks with cost definitions 1`] = ` +Array [ + Object { + "cost": 10, + "taskType": "alerting:siem.indicatorRule", + }, +] +`; diff --git a/x-pack/plugins/task_manager/server/integration_tests/managed_configuration.test.ts b/x-pack/plugins/task_manager/server/integration_tests/managed_configuration.test.ts index c0939b5b31667..cc16b8d0544cf 100644 --- a/x-pack/plugins/task_manager/server/integration_tests/managed_configuration.test.ts +++ b/x-pack/plugins/task_manager/server/integration_tests/managed_configuration.test.ts @@ -35,164 +35,362 @@ describe('managed configuration', () => { }, }; - beforeEach(async () => { - jest.resetAllMocks(); - clock = sinon.useFakeTimers(); - - const context = coreMock.createPluginInitializerContext({ - max_workers: 10, - max_attempts: 9, - poll_interval: 3000, - allow_reading_invalid_state: false, - version_conflict_threshold: 80, - monitored_aggregated_stats_refresh_rate: 60000, - monitored_stats_health_verbose_log: { - enabled: false, - level: 'debug' as const, - warn_delayed_task_start_in_seconds: 60, - }, - monitored_stats_required_freshness: 4000, - monitored_stats_running_average_window: 50, - request_capacity: 1000, - monitored_task_execution_thresholds: { - default: { - error_threshold: 90, - warn_threshold: 80, - }, - custom: {}, - }, - ephemeral_tasks: { - enabled: true, - request_capacity: 10, - }, - unsafe: { - exclude_task_types: [], - authenticate_background_task_utilization: true, - }, - event_loop_delay: { - monitor: true, - warn_threshold: 5000, - }, - worker_utilization_running_average_window: 5, - metrics_reset_interval: 3000, - claim_strategy: 'default', - request_timeouts: { - update_by_query: 1000, - }, + afterEach(() => clock.restore()); + + describe('managed poll interval', () => { + beforeEach(async () => { + jest.resetAllMocks(); + clock = sinon.useFakeTimers(); + + const context = coreMock.createPluginInitializerContext({ + capacity: 10, + max_attempts: 9, + poll_interval: 3000, + allow_reading_invalid_state: false, + version_conflict_threshold: 80, + monitored_aggregated_stats_refresh_rate: 60000, + monitored_stats_health_verbose_log: { + enabled: false, + level: 'debug' as const, + warn_delayed_task_start_in_seconds: 60, + }, + monitored_stats_required_freshness: 4000, + monitored_stats_running_average_window: 50, + request_capacity: 1000, + monitored_task_execution_thresholds: { + default: { + error_threshold: 90, + warn_threshold: 80, + }, + custom: {}, + }, + ephemeral_tasks: { + enabled: true, + request_capacity: 10, + }, + unsafe: { + exclude_task_types: [], + authenticate_background_task_utilization: true, + }, + event_loop_delay: { + monitor: true, + warn_threshold: 5000, + }, + worker_utilization_running_average_window: 5, + metrics_reset_interval: 3000, + claim_strategy: 'default', + request_timeouts: { + update_by_query: 1000, + }, + }); + logger = context.logger.get('taskManager'); + + const taskManager = new TaskManagerPlugin(context); + ( + await taskManager.setup(coreMock.createSetup(), { usageCollection: undefined }) + ).registerTaskDefinitions({ + foo: { + title: 'Foo', + createTaskRunner: jest.fn(), + }, + }); + + const coreStart = coreMock.createStart(); + coreStart.elasticsearch = esStart; + esStart.client.asInternalUser.child.mockReturnValue( + esStart.client.asInternalUser as unknown as Client + ); + coreStart.savedObjects.createInternalRepository.mockReturnValue(savedObjectsClient); + taskManagerStart = await taskManager.start(coreStart, {}); + + // force rxjs timers to fire when they are scheduled for setTimeout(0) as the + // sinon fake timers cause them to stall + clock.tick(0); }); - logger = context.logger.get('taskManager'); - - const taskManager = new TaskManagerPlugin(context); - ( - await taskManager.setup(coreMock.createSetup(), { usageCollection: undefined }) - ).registerTaskDefinitions({ - foo: { - title: 'Foo', - createTaskRunner: jest.fn(), - }, + + test('should increase poll interval when Elasticsearch returns 429 error', async () => { + savedObjectsClient.create.mockRejectedValueOnce( + SavedObjectsErrorHelpers.createTooManyRequestsError('a', 'b') + ); + + // Cause "too many requests" error to be thrown + await expect( + taskManagerStart.schedule({ + taskType: 'foo', + state: {}, + params: {}, + }) + ).rejects.toThrowErrorMatchingInlineSnapshot(`"Too Many Requests"`); + clock.tick(ADJUST_THROUGHPUT_INTERVAL); + + expect(logger.warn).toHaveBeenCalledWith( + 'Poll interval configuration is temporarily increased after Elasticsearch returned 1 "too many request" and/or "execute [inline] script" error(s).' + ); + expect(logger.debug).toHaveBeenCalledWith( + 'Poll interval configuration changing from 3000 to 3600 after seeing 1 "too many request" and/or "execute [inline] script" error(s)' + ); + expect(logger.debug).toHaveBeenCalledWith('Task poller now using interval of 3600ms'); }); - const coreStart = coreMock.createStart(); - coreStart.elasticsearch = esStart; - esStart.client.asInternalUser.child.mockReturnValue( - esStart.client.asInternalUser as unknown as Client - ); - coreStart.savedObjects.createInternalRepository.mockReturnValue(savedObjectsClient); - taskManagerStart = await taskManager.start(coreStart); - - // force rxjs timers to fire when they are scheduled for setTimeout(0) as the - // sinon fake timers cause them to stall - clock.tick(0); - }); + test('should increase poll interval when Elasticsearch returns "cannot execute [inline] scripts" error', async () => { + const childEsClient = esStart.client.asInternalUser.child({}) as jest.Mocked; + childEsClient.search.mockImplementationOnce(async () => { + throw inlineScriptError; + }); - afterEach(() => clock.restore()); + await expect(taskManagerStart.fetch({})).rejects.toThrowErrorMatchingInlineSnapshot( + `"cannot execute [inline] scripts\\" error"` + ); - test('should lower max workers when Elasticsearch returns 429 error', async () => { - savedObjectsClient.create.mockRejectedValueOnce( - SavedObjectsErrorHelpers.createTooManyRequestsError('a', 'b') - ); - - // Cause "too many requests" error to be thrown - await expect( - taskManagerStart.schedule({ - taskType: 'foo', - state: {}, - params: {}, - }) - ).rejects.toThrowErrorMatchingInlineSnapshot(`"Too Many Requests"`); - clock.tick(ADJUST_THROUGHPUT_INTERVAL); - - expect(logger.warn).toHaveBeenCalledWith( - 'Max workers configuration is temporarily reduced after Elasticsearch returned 1 "too many request" and/or "execute [inline] script" error(s).' - ); - expect(logger.debug).toHaveBeenCalledWith( - 'Max workers configuration changing from 10 to 8 after seeing 1 "too many request" and/or "execute [inline] script" error(s)' - ); - expect(logger.debug).toHaveBeenCalledWith('Task pool now using 10 as the max worker value'); - }); + clock.tick(ADJUST_THROUGHPUT_INTERVAL); - test('should increase poll interval when Elasticsearch returns 429 error', async () => { - savedObjectsClient.create.mockRejectedValueOnce( - SavedObjectsErrorHelpers.createTooManyRequestsError('a', 'b') - ); - - // Cause "too many requests" error to be thrown - await expect( - taskManagerStart.schedule({ - taskType: 'foo', - state: {}, - params: {}, - }) - ).rejects.toThrowErrorMatchingInlineSnapshot(`"Too Many Requests"`); - clock.tick(ADJUST_THROUGHPUT_INTERVAL); - - expect(logger.warn).toHaveBeenCalledWith( - 'Poll interval configuration is temporarily increased after Elasticsearch returned 1 "too many request" and/or "execute [inline] script" error(s).' - ); - expect(logger.debug).toHaveBeenCalledWith( - 'Poll interval configuration changing from 3000 to 3600 after seeing 1 "too many request" and/or "execute [inline] script" error(s)' - ); - expect(logger.debug).toHaveBeenCalledWith('Task poller now using interval of 3600ms'); + expect(logger.warn).toHaveBeenCalledWith( + 'Poll interval configuration is temporarily increased after Elasticsearch returned 1 "too many request" and/or "execute [inline] script" error(s).' + ); + expect(logger.debug).toHaveBeenCalledWith( + 'Poll interval configuration changing from 3000 to 3600 after seeing 1 "too many request" and/or "execute [inline] script" error(s)' + ); + expect(logger.debug).toHaveBeenCalledWith('Task poller now using interval of 3600ms'); + }); }); - test('should lower max workers when Elasticsearch returns "cannot execute [inline] scripts" error', async () => { - const childEsClient = esStart.client.asInternalUser.child({}) as jest.Mocked; - childEsClient.search.mockImplementationOnce(async () => { - throw inlineScriptError; + describe('managed capacity with default claim strategy', () => { + beforeEach(async () => { + jest.resetAllMocks(); + clock = sinon.useFakeTimers(); + + const context = coreMock.createPluginInitializerContext({ + capacity: 10, + max_attempts: 9, + poll_interval: 3000, + allow_reading_invalid_state: false, + version_conflict_threshold: 80, + monitored_aggregated_stats_refresh_rate: 60000, + monitored_stats_health_verbose_log: { + enabled: false, + level: 'debug' as const, + warn_delayed_task_start_in_seconds: 60, + }, + monitored_stats_required_freshness: 4000, + monitored_stats_running_average_window: 50, + request_capacity: 1000, + monitored_task_execution_thresholds: { + default: { + error_threshold: 90, + warn_threshold: 80, + }, + custom: {}, + }, + ephemeral_tasks: { + enabled: true, + request_capacity: 10, + }, + unsafe: { + exclude_task_types: [], + authenticate_background_task_utilization: true, + }, + event_loop_delay: { + monitor: true, + warn_threshold: 5000, + }, + worker_utilization_running_average_window: 5, + metrics_reset_interval: 3000, + claim_strategy: 'default', + request_timeouts: { + update_by_query: 1000, + }, + }); + logger = context.logger.get('taskManager'); + + const taskManager = new TaskManagerPlugin(context); + ( + await taskManager.setup(coreMock.createSetup(), { usageCollection: undefined }) + ).registerTaskDefinitions({ + foo: { + title: 'Foo', + createTaskRunner: jest.fn(), + }, + }); + + const coreStart = coreMock.createStart(); + coreStart.elasticsearch = esStart; + esStart.client.asInternalUser.child.mockReturnValue( + esStart.client.asInternalUser as unknown as Client + ); + coreStart.savedObjects.createInternalRepository.mockReturnValue(savedObjectsClient); + taskManagerStart = await taskManager.start(coreStart, {}); + + // force rxjs timers to fire when they are scheduled for setTimeout(0) as the + // sinon fake timers cause them to stall + clock.tick(0); }); - await expect(taskManagerStart.fetch({})).rejects.toThrowErrorMatchingInlineSnapshot( - `"cannot execute [inline] scripts\\" error"` - ); - clock.tick(ADJUST_THROUGHPUT_INTERVAL); - - expect(logger.warn).toHaveBeenCalledWith( - 'Max workers configuration is temporarily reduced after Elasticsearch returned 1 "too many request" and/or "execute [inline] script" error(s).' - ); - expect(logger.debug).toHaveBeenCalledWith( - 'Max workers configuration changing from 10 to 8 after seeing 1 "too many request" and/or "execute [inline] script" error(s)' - ); - expect(logger.debug).toHaveBeenCalledWith('Task pool now using 10 as the max worker value'); + test('should lower capacity when Elasticsearch returns 429 error', async () => { + savedObjectsClient.create.mockRejectedValueOnce( + SavedObjectsErrorHelpers.createTooManyRequestsError('a', 'b') + ); + + // Cause "too many requests" error to be thrown + await expect( + taskManagerStart.schedule({ + taskType: 'foo', + state: {}, + params: {}, + }) + ).rejects.toThrowErrorMatchingInlineSnapshot(`"Too Many Requests"`); + clock.tick(ADJUST_THROUGHPUT_INTERVAL); + + expect(logger.warn).toHaveBeenCalledWith( + 'Capacity configuration is temporarily reduced after Elasticsearch returned 1 "too many request" and/or "execute [inline] script" error(s).' + ); + expect(logger.debug).toHaveBeenCalledWith( + 'Capacity configuration changing from 10 to 8 after seeing 1 "too many request" and/or "execute [inline] script" error(s)' + ); + expect(logger.debug).toHaveBeenCalledWith( + 'Task pool now using 10 as the max worker value which is based on a capacity of 10' + ); + }); + + test('should lower capacity when Elasticsearch returns "cannot execute [inline] scripts" error', async () => { + const childEsClient = esStart.client.asInternalUser.child({}) as jest.Mocked; + childEsClient.search.mockImplementationOnce(async () => { + throw inlineScriptError; + }); + + await expect(taskManagerStart.fetch({})).rejects.toThrowErrorMatchingInlineSnapshot( + `"cannot execute [inline] scripts\\" error"` + ); + clock.tick(ADJUST_THROUGHPUT_INTERVAL); + + expect(logger.warn).toHaveBeenCalledWith( + 'Capacity configuration is temporarily reduced after Elasticsearch returned 1 "too many request" and/or "execute [inline] script" error(s).' + ); + expect(logger.debug).toHaveBeenCalledWith( + 'Capacity configuration changing from 10 to 8 after seeing 1 "too many request" and/or "execute [inline] script" error(s)' + ); + expect(logger.debug).toHaveBeenCalledWith( + 'Task pool now using 10 as the max worker value which is based on a capacity of 10' + ); + }); }); - test('should increase poll interval when Elasticsearch returns "cannot execute [inline] scripts" error', async () => { - const childEsClient = esStart.client.asInternalUser.child({}) as jest.Mocked; - childEsClient.search.mockImplementationOnce(async () => { - throw inlineScriptError; + describe('managed capacity with mget claim strategy', () => { + beforeEach(async () => { + jest.resetAllMocks(); + clock = sinon.useFakeTimers(); + + const context = coreMock.createPluginInitializerContext({ + capacity: 10, + max_attempts: 9, + poll_interval: 3000, + allow_reading_invalid_state: false, + version_conflict_threshold: 80, + monitored_aggregated_stats_refresh_rate: 60000, + monitored_stats_health_verbose_log: { + enabled: false, + level: 'debug' as const, + warn_delayed_task_start_in_seconds: 60, + }, + monitored_stats_required_freshness: 4000, + monitored_stats_running_average_window: 50, + request_capacity: 1000, + monitored_task_execution_thresholds: { + default: { + error_threshold: 90, + warn_threshold: 80, + }, + custom: {}, + }, + ephemeral_tasks: { + enabled: true, + request_capacity: 10, + }, + unsafe: { + exclude_task_types: [], + authenticate_background_task_utilization: true, + }, + event_loop_delay: { + monitor: true, + warn_threshold: 5000, + }, + worker_utilization_running_average_window: 5, + metrics_reset_interval: 3000, + claim_strategy: 'unsafe_mget', + request_timeouts: { + update_by_query: 1000, + }, + }); + logger = context.logger.get('taskManager'); + + const taskManager = new TaskManagerPlugin(context); + ( + await taskManager.setup(coreMock.createSetup(), { usageCollection: undefined }) + ).registerTaskDefinitions({ + foo: { + title: 'Foo', + createTaskRunner: jest.fn(), + }, + }); + + const coreStart = coreMock.createStart(); + coreStart.elasticsearch = esStart; + esStart.client.asInternalUser.child.mockReturnValue( + esStart.client.asInternalUser as unknown as Client + ); + coreStart.savedObjects.createInternalRepository.mockReturnValue(savedObjectsClient); + taskManagerStart = await taskManager.start(coreStart, {}); + + // force rxjs timers to fire when they are scheduled for setTimeout(0) as the + // sinon fake timers cause them to stall + clock.tick(0); }); - await expect(taskManagerStart.fetch({})).rejects.toThrowErrorMatchingInlineSnapshot( - `"cannot execute [inline] scripts\\" error"` - ); + test('should lower capacity when Elasticsearch returns 429 error', async () => { + savedObjectsClient.create.mockRejectedValueOnce( + SavedObjectsErrorHelpers.createTooManyRequestsError('a', 'b') + ); - clock.tick(ADJUST_THROUGHPUT_INTERVAL); + // Cause "too many requests" error to be thrown + await expect( + taskManagerStart.schedule({ + taskType: 'foo', + state: {}, + params: {}, + }) + ).rejects.toThrowErrorMatchingInlineSnapshot(`"Too Many Requests"`); + clock.tick(ADJUST_THROUGHPUT_INTERVAL); + + expect(logger.warn).toHaveBeenCalledWith( + 'Capacity configuration is temporarily reduced after Elasticsearch returned 1 "too many request" and/or "execute [inline] script" error(s).' + ); + expect(logger.debug).toHaveBeenCalledWith( + 'Capacity configuration changing from 10 to 8 after seeing 1 "too many request" and/or "execute [inline] script" error(s)' + ); + expect(logger.debug).toHaveBeenCalledWith( + 'Task pool now using 20 as the max allowed cost which is based on a capacity of 10' + ); + }); - expect(logger.warn).toHaveBeenCalledWith( - 'Poll interval configuration is temporarily increased after Elasticsearch returned 1 "too many request" and/or "execute [inline] script" error(s).' - ); - expect(logger.debug).toHaveBeenCalledWith( - 'Poll interval configuration changing from 3000 to 3600 after seeing 1 "too many request" and/or "execute [inline] script" error(s)' - ); - expect(logger.debug).toHaveBeenCalledWith('Task poller now using interval of 3600ms'); + test('should lower capacity when Elasticsearch returns "cannot execute [inline] scripts" error', async () => { + const childEsClient = esStart.client.asInternalUser.child({}) as jest.Mocked; + childEsClient.search.mockImplementationOnce(async () => { + throw inlineScriptError; + }); + + await expect(taskManagerStart.fetch({})).rejects.toThrowErrorMatchingInlineSnapshot( + `"cannot execute [inline] scripts\\" error"` + ); + clock.tick(ADJUST_THROUGHPUT_INTERVAL); + + expect(logger.warn).toHaveBeenCalledWith( + 'Capacity configuration is temporarily reduced after Elasticsearch returned 1 "too many request" and/or "execute [inline] script" error(s).' + ); + expect(logger.debug).toHaveBeenCalledWith( + 'Capacity configuration changing from 10 to 8 after seeing 1 "too many request" and/or "execute [inline] script" error(s)' + ); + expect(logger.debug).toHaveBeenCalledWith( + 'Task pool now using 20 as the max allowed cost which is based on a capacity of 10' + ); + }); }); }); diff --git a/x-pack/plugins/task_manager/server/integration_tests/task_cost_check.test.ts b/x-pack/plugins/task_manager/server/integration_tests/task_cost_check.test.ts new file mode 100644 index 0000000000000..96678f714ac69 --- /dev/null +++ b/x-pack/plugins/task_manager/server/integration_tests/task_cost_check.test.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + type TestElasticsearchUtils, + type TestKibanaUtils, +} from '@kbn/core-test-helpers-kbn-server'; +import { TaskCost, TaskDefinition } from '../task'; +import { setupTestServers } from './lib'; +import { TaskTypeDictionary } from '../task_type_dictionary'; + +jest.mock('../task_type_dictionary', () => { + const actual = jest.requireActual('../task_type_dictionary'); + return { + ...actual, + TaskTypeDictionary: jest.fn().mockImplementation((opts) => { + return new actual.TaskTypeDictionary(opts); + }), + }; +}); + +// Notify response-ops if a task sets a cost to something other than `Normal` +describe('Task cost checks', () => { + let esServer: TestElasticsearchUtils; + let kibanaServer: TestKibanaUtils; + let taskTypeDictionary: TaskTypeDictionary; + + beforeAll(async () => { + const setupResult = await setupTestServers(); + esServer = setupResult.esServer; + kibanaServer = setupResult.kibanaServer; + + const mockedTaskTypeDictionary = jest.requireMock('../task_type_dictionary'); + expect(mockedTaskTypeDictionary.TaskTypeDictionary).toHaveBeenCalledTimes(1); + taskTypeDictionary = mockedTaskTypeDictionary.TaskTypeDictionary.mock.results[0].value; + }); + + afterAll(async () => { + if (kibanaServer) { + await kibanaServer.stop(); + } + if (esServer) { + await esServer.stop(); + } + }); + + it('detects tasks with cost definitions', async () => { + const taskTypes = taskTypeDictionary.getAllDefinitions(); + const taskTypesWithCost = taskTypes + .map((taskType: TaskDefinition) => + !!taskType.cost ? { taskType: taskType.type, cost: taskType.cost } : null + ) + .filter( + (tt: { taskType: string; cost: TaskCost } | null) => + null != tt && tt.cost !== TaskCost.Normal + ); + expect(taskTypesWithCost).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/task_manager/server/lib/calculate_health_status.test.ts b/x-pack/plugins/task_manager/server/lib/calculate_health_status.test.ts index fc2f34701e3c1..49c68459982ba 100644 --- a/x-pack/plugins/task_manager/server/lib/calculate_health_status.test.ts +++ b/x-pack/plugins/task_manager/server/lib/calculate_health_status.test.ts @@ -16,7 +16,6 @@ Date.now = jest.fn().mockReturnValue(new Date(now)); const logger = loggingSystemMock.create().get(); const config = { enabled: true, - max_workers: 10, index: 'foo', max_attempts: 9, poll_interval: 3000, @@ -73,6 +72,8 @@ const getStatsWithTimestamp = ({ configuration: { timestamp, value: { + capacity: { config: 10, as_cost: 20, as_workers: 10 }, + claim_strategy: 'default', request_capacity: 1000, monitored_aggregated_stats_refresh_rate: 5000, monitored_stats_running_average_window: 50, @@ -84,7 +85,6 @@ const getStatsWithTimestamp = ({ }, }, poll_interval: 3000, - max_workers: 10, }, status: HealthStatus.OK, }, @@ -213,24 +213,29 @@ const getStatsWithTimestamp = ({ timestamp, value: { count: 2, + cost: 4, task_types: { taskType1: { count: 1, + cost: 2, status: { idle: 1, }, }, taskType2: { count: 1, + cost: 2, status: { idle: 1, }, }, }, non_recurring: 2, + non_recurring_cost: 4, owner_ids: 0, schedule: [['5m', 2]], overdue: 0, + overdue_cost: 0, overdue_non_recurring: 0, estimated_schedule_density: [ 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, diff --git a/x-pack/plugins/task_manager/server/lib/create_managed_configuration.test.ts b/x-pack/plugins/task_manager/server/lib/create_managed_configuration.test.ts index b1d6ce92c323a..a93da63ae693a 100644 --- a/x-pack/plugins/task_manager/server/lib/create_managed_configuration.test.ts +++ b/x-pack/plugins/task_manager/server/lib/create_managed_configuration.test.ts @@ -13,6 +13,7 @@ import { ADJUST_THROUGHPUT_INTERVAL, } from './create_managed_configuration'; import { mockLogger } from '../test_utils'; +import { CLAIM_STRATEGY_DEFAULT, CLAIM_STRATEGY_MGET, TaskManagerConfig } from '../config'; describe('createManagedConfiguration()', () => { let clock: sinon.SinonFakeTimers; @@ -26,51 +27,141 @@ describe('createManagedConfiguration()', () => { afterEach(() => clock.restore()); test('returns observables with initialized values', async () => { - const maxWorkersSubscription = jest.fn(); + const capacitySubscription = jest.fn(); const pollIntervalSubscription = jest.fn(); - const { maxWorkersConfiguration$, pollIntervalConfiguration$ } = createManagedConfiguration({ + const { capacityConfiguration$, pollIntervalConfiguration$ } = createManagedConfiguration({ logger, errors$: new Subject(), - startingMaxWorkers: 1, - startingPollInterval: 2, + config: { + capacity: 20, + poll_interval: 2, + } as TaskManagerConfig, }); - maxWorkersConfiguration$.subscribe(maxWorkersSubscription); + capacityConfiguration$.subscribe(capacitySubscription); pollIntervalConfiguration$.subscribe(pollIntervalSubscription); - expect(maxWorkersSubscription).toHaveBeenCalledTimes(1); - expect(maxWorkersSubscription).toHaveBeenNthCalledWith(1, 1); + expect(capacitySubscription).toHaveBeenCalledTimes(1); + expect(capacitySubscription).toHaveBeenNthCalledWith(1, 20); expect(pollIntervalSubscription).toHaveBeenCalledTimes(1); expect(pollIntervalSubscription).toHaveBeenNthCalledWith(1, 2); }); + test('uses max_workers config as capacity if only max workers is defined', async () => { + const capacitySubscription = jest.fn(); + const pollIntervalSubscription = jest.fn(); + const { capacityConfiguration$, pollIntervalConfiguration$ } = createManagedConfiguration({ + logger, + errors$: new Subject(), + config: { + max_workers: 10, + poll_interval: 2, + } as TaskManagerConfig, + }); + capacityConfiguration$.subscribe(capacitySubscription); + pollIntervalConfiguration$.subscribe(pollIntervalSubscription); + expect(capacitySubscription).toHaveBeenCalledTimes(1); + expect(capacitySubscription).toHaveBeenNthCalledWith(1, 10); + expect(pollIntervalSubscription).toHaveBeenCalledTimes(1); + expect(pollIntervalSubscription).toHaveBeenNthCalledWith(1, 2); + }); + + test('uses max_workers config as capacity but does not exceed MAX_CAPACITY', async () => { + const capacitySubscription = jest.fn(); + const pollIntervalSubscription = jest.fn(); + const { capacityConfiguration$, pollIntervalConfiguration$ } = createManagedConfiguration({ + logger, + errors$: new Subject(), + config: { + max_workers: 1000, + poll_interval: 2, + } as TaskManagerConfig, + }); + capacityConfiguration$.subscribe(capacitySubscription); + pollIntervalConfiguration$.subscribe(pollIntervalSubscription); + expect(capacitySubscription).toHaveBeenCalledTimes(1); + expect(capacitySubscription).toHaveBeenNthCalledWith(1, 50); + expect(pollIntervalSubscription).toHaveBeenCalledTimes(1); + expect(pollIntervalSubscription).toHaveBeenNthCalledWith(1, 2); + }); + + test('uses provided defaultCapacity if neither capacity nor max_workers is defined', async () => { + const capacitySubscription = jest.fn(); + const pollIntervalSubscription = jest.fn(); + const { capacityConfiguration$, pollIntervalConfiguration$ } = createManagedConfiguration({ + defaultCapacity: 500, + logger, + errors$: new Subject(), + config: { + poll_interval: 2, + } as TaskManagerConfig, + }); + capacityConfiguration$.subscribe(capacitySubscription); + pollIntervalConfiguration$.subscribe(pollIntervalSubscription); + expect(capacitySubscription).toHaveBeenCalledTimes(1); + expect(capacitySubscription).toHaveBeenNthCalledWith(1, 500); + expect(pollIntervalSubscription).toHaveBeenCalledTimes(1); + expect(pollIntervalSubscription).toHaveBeenNthCalledWith(1, 2); + }); + + test('logs warning and uses capacity config if both capacity and max_workers is defined', async () => { + const capacitySubscription = jest.fn(); + const pollIntervalSubscription = jest.fn(); + const { capacityConfiguration$, pollIntervalConfiguration$ } = createManagedConfiguration({ + logger, + errors$: new Subject(), + config: { + capacity: 30, + max_workers: 10, + poll_interval: 2, + } as TaskManagerConfig, + }); + capacityConfiguration$.subscribe(capacitySubscription); + pollIntervalConfiguration$.subscribe(pollIntervalSubscription); + expect(capacitySubscription).toHaveBeenCalledTimes(1); + expect(capacitySubscription).toHaveBeenNthCalledWith(1, 30); + expect(pollIntervalSubscription).toHaveBeenCalledTimes(1); + expect(pollIntervalSubscription).toHaveBeenNthCalledWith(1, 2); + expect(logger.warn).toHaveBeenCalledWith( + `Both \"xpack.task_manager.capacity\" and \"xpack.task_manager.max_workers\" configs are set, max_workers will be ignored in favor of capacity and the setting should be removed.` + ); + }); + test(`skips errors that aren't about too many requests`, async () => { - const maxWorkersSubscription = jest.fn(); + const capacitySubscription = jest.fn(); const pollIntervalSubscription = jest.fn(); const errors$ = new Subject(); - const { maxWorkersConfiguration$, pollIntervalConfiguration$ } = createManagedConfiguration({ + const { capacityConfiguration$, pollIntervalConfiguration$ } = createManagedConfiguration({ errors$, logger, - startingMaxWorkers: 100, - startingPollInterval: 100, + config: { + capacity: 10, + poll_interval: 100, + } as TaskManagerConfig, }); - maxWorkersConfiguration$.subscribe(maxWorkersSubscription); + capacityConfiguration$.subscribe(capacitySubscription); pollIntervalConfiguration$.subscribe(pollIntervalSubscription); errors$.next(new Error('foo')); clock.tick(ADJUST_THROUGHPUT_INTERVAL); - expect(maxWorkersSubscription).toHaveBeenCalledTimes(1); + expect(capacitySubscription).toHaveBeenCalledTimes(1); expect(pollIntervalSubscription).toHaveBeenCalledTimes(1); }); - describe('maxWorker configuration', () => { - function setupScenario(startingMaxWorkers: number) { + describe('capacity configuration', () => { + function setupScenario( + startingCapacity: number, + claimStrategy: string = CLAIM_STRATEGY_DEFAULT + ) { const errors$ = new Subject(); const subscription = jest.fn(); - const { maxWorkersConfiguration$ } = createManagedConfiguration({ + const { capacityConfiguration$ } = createManagedConfiguration({ errors$, - startingMaxWorkers, logger, - startingPollInterval: 1, + config: { + capacity: startingCapacity, + poll_interval: 1, + claim_strategy: claimStrategy, + } as TaskManagerConfig, }); - maxWorkersConfiguration$.subscribe(subscription); + capacityConfiguration$.subscribe(subscription); return { subscription, errors$ }; } @@ -81,66 +172,103 @@ describe('createManagedConfiguration()', () => { afterEach(() => clock.restore()); - test('should decrease configuration at the next interval when an error is emitted', async () => { - const { subscription, errors$ } = setupScenario(100); - errors$.next(SavedObjectsErrorHelpers.createTooManyRequestsError('a', 'b')); - clock.tick(ADJUST_THROUGHPUT_INTERVAL - 1); - expect(subscription).toHaveBeenCalledTimes(1); - clock.tick(1); - expect(subscription).toHaveBeenCalledTimes(2); - expect(subscription).toHaveBeenNthCalledWith(2, 80); - }); + describe('default claim strategy', () => { + test('should decrease configuration at the next interval when an error is emitted', async () => { + const { subscription, errors$ } = setupScenario(10); + errors$.next(SavedObjectsErrorHelpers.createTooManyRequestsError('a', 'b')); + clock.tick(ADJUST_THROUGHPUT_INTERVAL - 1); + expect(subscription).toHaveBeenCalledTimes(1); + expect(subscription).toHaveBeenNthCalledWith(1, 10); + clock.tick(1); + expect(subscription).toHaveBeenCalledTimes(2); + expect(subscription).toHaveBeenNthCalledWith(2, 8); + }); - test('should log a warning when the configuration changes from the starting value', async () => { - const { errors$ } = setupScenario(100); - errors$.next(SavedObjectsErrorHelpers.createTooManyRequestsError('a', 'b')); - clock.tick(ADJUST_THROUGHPUT_INTERVAL); - expect(logger.warn).toHaveBeenCalledWith( - 'Max workers configuration is temporarily reduced after Elasticsearch returned 1 "too many request" and/or "execute [inline] script" error(s).' - ); - }); + test('should log a warning when the configuration changes from the starting value', async () => { + const { errors$ } = setupScenario(10); + errors$.next(SavedObjectsErrorHelpers.createTooManyRequestsError('a', 'b')); + clock.tick(ADJUST_THROUGHPUT_INTERVAL); + expect(logger.warn).toHaveBeenCalledWith( + 'Capacity configuration is temporarily reduced after Elasticsearch returned 1 "too many request" and/or "execute [inline] script" error(s).' + ); + }); - test('should increase configuration back to normal incrementally after an error is emitted', async () => { - const { subscription, errors$ } = setupScenario(100); - errors$.next(SavedObjectsErrorHelpers.createTooManyRequestsError('a', 'b')); - clock.tick(ADJUST_THROUGHPUT_INTERVAL * 10); - expect(subscription).toHaveBeenNthCalledWith(2, 80); - expect(subscription).toHaveBeenNthCalledWith(3, 84); - // 88.2- > 89 from Math.ceil - expect(subscription).toHaveBeenNthCalledWith(4, 89); - expect(subscription).toHaveBeenNthCalledWith(5, 94); - expect(subscription).toHaveBeenNthCalledWith(6, 99); - // 103.95 -> 100 from Math.min with starting value - expect(subscription).toHaveBeenNthCalledWith(7, 100); - // No new calls due to value not changing and usage of distinctUntilChanged() - expect(subscription).toHaveBeenCalledTimes(7); + test('should increase configuration back to normal incrementally after an error is emitted', async () => { + const { subscription, errors$ } = setupScenario(10); + errors$.next(SavedObjectsErrorHelpers.createTooManyRequestsError('a', 'b')); + clock.tick(ADJUST_THROUGHPUT_INTERVAL * 10); + expect(subscription).toHaveBeenNthCalledWith(1, 10); + expect(subscription).toHaveBeenNthCalledWith(2, 8); + expect(subscription).toHaveBeenNthCalledWith(3, 9); + expect(subscription).toHaveBeenNthCalledWith(4, 10); + // No new calls due to value not changing and usage of distinctUntilChanged() + expect(subscription).toHaveBeenCalledTimes(4); + }); + + test('should keep reducing configuration when errors keep emitting until it reaches minimum', async () => { + const { subscription, errors$ } = setupScenario(10); + for (let i = 0; i < 20; i++) { + errors$.next(SavedObjectsErrorHelpers.createTooManyRequestsError('a', 'b')); + clock.tick(ADJUST_THROUGHPUT_INTERVAL); + } + expect(subscription).toHaveBeenNthCalledWith(1, 10); + expect(subscription).toHaveBeenNthCalledWith(2, 8); + expect(subscription).toHaveBeenNthCalledWith(3, 6); + expect(subscription).toHaveBeenNthCalledWith(4, 4); + expect(subscription).toHaveBeenNthCalledWith(5, 3); + expect(subscription).toHaveBeenNthCalledWith(6, 2); + expect(subscription).toHaveBeenNthCalledWith(7, 1); + // No new calls due to value not changing and usage of distinctUntilChanged() + expect(subscription).toHaveBeenCalledTimes(7); + }); }); - test('should keep reducing configuration when errors keep emitting', async () => { - const { subscription, errors$ } = setupScenario(100); - for (let i = 0; i < 20; i++) { + describe('mget claim strategy', () => { + test('should decrease configuration at the next interval when an error is emitted', async () => { + const { subscription, errors$ } = setupScenario(10, CLAIM_STRATEGY_MGET); + errors$.next(SavedObjectsErrorHelpers.createTooManyRequestsError('a', 'b')); + clock.tick(ADJUST_THROUGHPUT_INTERVAL - 1); + expect(subscription).toHaveBeenCalledTimes(1); + expect(subscription).toHaveBeenNthCalledWith(1, 10); + clock.tick(1); + expect(subscription).toHaveBeenCalledTimes(2); + expect(subscription).toHaveBeenNthCalledWith(2, 8); + }); + + test('should log a warning when the configuration changes from the starting value', async () => { + const { errors$ } = setupScenario(10, CLAIM_STRATEGY_MGET); errors$.next(SavedObjectsErrorHelpers.createTooManyRequestsError('a', 'b')); clock.tick(ADJUST_THROUGHPUT_INTERVAL); - } - expect(subscription).toHaveBeenNthCalledWith(2, 80); - expect(subscription).toHaveBeenNthCalledWith(3, 64); - // 51.2 -> 51 from Math.floor - expect(subscription).toHaveBeenNthCalledWith(4, 51); - expect(subscription).toHaveBeenNthCalledWith(5, 40); - expect(subscription).toHaveBeenNthCalledWith(6, 32); - expect(subscription).toHaveBeenNthCalledWith(7, 25); - expect(subscription).toHaveBeenNthCalledWith(8, 20); - expect(subscription).toHaveBeenNthCalledWith(9, 16); - expect(subscription).toHaveBeenNthCalledWith(10, 12); - expect(subscription).toHaveBeenNthCalledWith(11, 9); - expect(subscription).toHaveBeenNthCalledWith(12, 7); - expect(subscription).toHaveBeenNthCalledWith(13, 5); - expect(subscription).toHaveBeenNthCalledWith(14, 4); - expect(subscription).toHaveBeenNthCalledWith(15, 3); - expect(subscription).toHaveBeenNthCalledWith(16, 2); - expect(subscription).toHaveBeenNthCalledWith(17, 1); - // No new calls due to value not changing and usage of distinctUntilChanged() - expect(subscription).toHaveBeenCalledTimes(17); + expect(logger.warn).toHaveBeenCalledWith( + 'Capacity configuration is temporarily reduced after Elasticsearch returned 1 "too many request" and/or "execute [inline] script" error(s).' + ); + }); + + test('should increase configuration back to normal incrementally after an error is emitted', async () => { + const { subscription, errors$ } = setupScenario(10, CLAIM_STRATEGY_MGET); + errors$.next(SavedObjectsErrorHelpers.createTooManyRequestsError('a', 'b')); + clock.tick(ADJUST_THROUGHPUT_INTERVAL * 10); + expect(subscription).toHaveBeenNthCalledWith(1, 10); + expect(subscription).toHaveBeenNthCalledWith(2, 8); + expect(subscription).toHaveBeenNthCalledWith(3, 9); + expect(subscription).toHaveBeenNthCalledWith(4, 10); + // No new calls due to value not changing and usage of distinctUntilChanged() + expect(subscription).toHaveBeenCalledTimes(4); + }); + + test('should keep reducing configuration when errors keep emitting until it reaches minimum', async () => { + const { subscription, errors$ } = setupScenario(10, CLAIM_STRATEGY_MGET); + for (let i = 0; i < 20; i++) { + errors$.next(SavedObjectsErrorHelpers.createTooManyRequestsError('a', 'b')); + clock.tick(ADJUST_THROUGHPUT_INTERVAL); + } + expect(subscription).toHaveBeenNthCalledWith(1, 10); + expect(subscription).toHaveBeenNthCalledWith(2, 8); + expect(subscription).toHaveBeenNthCalledWith(3, 6); + expect(subscription).toHaveBeenNthCalledWith(4, 5); + // No new calls due to value not changing and usage of distinctUntilChanged() + expect(subscription).toHaveBeenCalledTimes(4); + }); }); }); @@ -151,8 +279,10 @@ describe('createManagedConfiguration()', () => { const { pollIntervalConfiguration$ } = createManagedConfiguration({ logger, errors$, - startingPollInterval, - startingMaxWorkers: 1, + config: { + poll_interval: startingPollInterval, + capacity: 20, + } as TaskManagerConfig, }); pollIntervalConfiguration$.subscribe(subscription); return { subscription, errors$ }; diff --git a/x-pack/plugins/task_manager/server/lib/create_managed_configuration.ts b/x-pack/plugins/task_manager/server/lib/create_managed_configuration.ts index 5c7b1a16a4308..3036eb2008de6 100644 --- a/x-pack/plugins/task_manager/server/lib/create_managed_configuration.ts +++ b/x-pack/plugins/task_manager/server/lib/create_managed_configuration.ts @@ -10,17 +10,26 @@ import { filter, mergeScan, map, scan, distinctUntilChanged, startWith } from 'r import { SavedObjectsErrorHelpers } from '@kbn/core/server'; import { Logger } from '@kbn/core/server'; import { isEsCannotExecuteScriptError } from './identify_es_error'; +import { CLAIM_STRATEGY_MGET, DEFAULT_CAPACITY, MAX_CAPACITY, TaskManagerConfig } from '../config'; +import { TaskCost } from '../task'; const FLUSH_MARKER = Symbol('flush'); export const ADJUST_THROUGHPUT_INTERVAL = 10 * 1000; export const PREFERRED_MAX_POLL_INTERVAL = 60 * 1000; + +// Capacity is measured in number of normal cost tasks that can be run +// At a minimum, we need to be able to run a single task with the greatest cost +// so we should convert the greatest cost to normal cost +export const MIN_COST = TaskCost.ExtraLarge / TaskCost.Normal; + +// For default claim strategy export const MIN_WORKERS = 1; -// When errors occur, reduce maxWorkers by MAX_WORKERS_DECREASE_PERCENTAGE -// When errors no longer occur, start increasing maxWorkers by MAX_WORKERS_INCREASE_PERCENTAGE +// When errors occur, reduce capacity by CAPACITY_DECREASE_PERCENTAGE +// When errors no longer occur, start increasing capacity by CAPACITY_INCREASE_PERCENTAGE // until starting value is reached -const MAX_WORKERS_DECREASE_PERCENTAGE = 0.8; -const MAX_WORKERS_INCREASE_PERCENTAGE = 1.05; +const CAPACITY_DECREASE_PERCENTAGE = 0.8; +const CAPACITY_INCREASE_PERCENTAGE = 1.05; // When errors occur, increase pollInterval by POLL_INTERVAL_INCREASE_PERCENTAGE // When errors no longer occur, start decreasing pollInterval by POLL_INTERVAL_DECREASE_PERCENTAGE @@ -29,28 +38,32 @@ const POLL_INTERVAL_DECREASE_PERCENTAGE = 0.95; const POLL_INTERVAL_INCREASE_PERCENTAGE = 1.2; interface ManagedConfigurationOpts { - logger: Logger; - startingMaxWorkers: number; - startingPollInterval: number; + config: TaskManagerConfig; + defaultCapacity?: number; errors$: Observable; + logger: Logger; } export interface ManagedConfiguration { - maxWorkersConfiguration$: Observable; + startingCapacity: number; + capacityConfiguration$: Observable; pollIntervalConfiguration$: Observable; } export function createManagedConfiguration({ + config, + defaultCapacity = DEFAULT_CAPACITY, logger, - startingMaxWorkers, - startingPollInterval, errors$, }: ManagedConfigurationOpts): ManagedConfiguration { const errorCheck$ = countErrors(errors$, ADJUST_THROUGHPUT_INTERVAL); + const startingCapacity = calculateStartingCapacity(config, logger, defaultCapacity); + const startingPollInterval = config.poll_interval; return { - maxWorkersConfiguration$: errorCheck$.pipe( - createMaxWorkersScan(logger, startingMaxWorkers), - startWith(startingMaxWorkers), + startingCapacity, + capacityConfiguration$: errorCheck$.pipe( + createCapacityScan(config, logger, startingCapacity), + startWith(startingCapacity), distinctUntilChanged() ), pollIntervalConfiguration$: errorCheck$.pipe( @@ -61,37 +74,39 @@ export function createManagedConfiguration({ }; } -function createMaxWorkersScan(logger: Logger, startingMaxWorkers: number) { - return scan((previousMaxWorkers: number, errorCount: number) => { - let newMaxWorkers: number; +function createCapacityScan(config: TaskManagerConfig, logger: Logger, startingCapacity: number) { + return scan((previousCapacity: number, errorCount: number) => { + let newCapacity: number; if (errorCount > 0) { - // Decrease max workers by MAX_WORKERS_DECREASE_PERCENTAGE while making sure it doesn't go lower than 1. + const minCapacity = getMinCapacity(config); + // Decrease capacity by CAPACITY_DECREASE_PERCENTAGE while making sure it doesn't go lower than minCapacity. // Using Math.floor to make sure the number is different than previous while not being a decimal value. - newMaxWorkers = Math.max( - Math.floor(previousMaxWorkers * MAX_WORKERS_DECREASE_PERCENTAGE), - MIN_WORKERS + newCapacity = Math.max( + Math.floor(previousCapacity * CAPACITY_DECREASE_PERCENTAGE), + minCapacity ); } else { - // Increase max workers by MAX_WORKERS_INCREASE_PERCENTAGE while making sure it doesn't go + // Increase capacity by CAPACITY_INCREASE_PERCENTAGE while making sure it doesn't go // higher than the starting value. Using Math.ceil to make sure the number is different than // previous while not being a decimal value - newMaxWorkers = Math.min( - startingMaxWorkers, - Math.ceil(previousMaxWorkers * MAX_WORKERS_INCREASE_PERCENTAGE) + newCapacity = Math.min( + startingCapacity, + Math.ceil(previousCapacity * CAPACITY_INCREASE_PERCENTAGE) ); } - if (newMaxWorkers !== previousMaxWorkers) { + + if (newCapacity !== previousCapacity) { logger.debug( - `Max workers configuration changing from ${previousMaxWorkers} to ${newMaxWorkers} after seeing ${errorCount} "too many request" and/or "execute [inline] script" error(s)` + `Capacity configuration changing from ${previousCapacity} to ${newCapacity} after seeing ${errorCount} "too many request" and/or "execute [inline] script" error(s)` ); - if (previousMaxWorkers === startingMaxWorkers) { + if (previousCapacity === startingCapacity) { logger.warn( - `Max workers configuration is temporarily reduced after Elasticsearch returned ${errorCount} "too many request" and/or "execute [inline] script" error(s).` + `Capacity configuration is temporarily reduced after Elasticsearch returned ${errorCount} "too many request" and/or "execute [inline] script" error(s).` ); } } - return newMaxWorkers; - }, startingMaxWorkers); + return newCapacity; + }, startingCapacity); } function createPollIntervalScan(logger: Logger, startingPollInterval: number) { @@ -186,3 +201,36 @@ function resetErrorCount() { count: 0, }; } + +function getMinCapacity(config: TaskManagerConfig) { + switch (config.claim_strategy) { + case CLAIM_STRATEGY_MGET: + return MIN_COST; + + default: + return MIN_WORKERS; + } +} + +export function calculateStartingCapacity( + config: TaskManagerConfig, + logger: Logger, + defaultCapacity: number +): number { + if (config.capacity !== undefined && config.max_workers !== undefined) { + logger.warn( + `Both "xpack.task_manager.capacity" and "xpack.task_manager.max_workers" configs are set, max_workers will be ignored in favor of capacity and the setting should be removed.` + ); + } + + if (config.capacity) { + // Use capacity if explicitly set + return config.capacity!; + } else if (config.max_workers) { + // Otherwise use max_worker value as capacity, capped at MAX_CAPACITY + return Math.min(config.max_workers, MAX_CAPACITY); + } + + // Neither are set, use the given default capacity + return defaultCapacity; +} diff --git a/x-pack/plugins/task_manager/server/lib/fill_pool.test.ts b/x-pack/plugins/task_manager/server/lib/fill_pool.test.ts index 9fdb16fb5f677..d3533ac058314 100644 --- a/x-pack/plugins/task_manager/server/lib/fill_pool.test.ts +++ b/x-pack/plugins/task_manager/server/lib/fill_pool.test.ts @@ -30,7 +30,6 @@ describe('fillPool', () => { tasksUpdated: tasks?.length ?? 0, tasksConflicted: 0, tasksClaimed: 0, - tasksRejected: 0, }, docs: tasks, }) diff --git a/x-pack/plugins/task_manager/server/lib/get_default_capacity.test.ts b/x-pack/plugins/task_manager/server/lib/get_default_capacity.test.ts new file mode 100644 index 0000000000000..fb68a3620e43c --- /dev/null +++ b/x-pack/plugins/task_manager/server/lib/get_default_capacity.test.ts @@ -0,0 +1,185 @@ +/* + * 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 { CLAIM_STRATEGY_DEFAULT, CLAIM_STRATEGY_MGET, DEFAULT_CAPACITY } from '../config'; +import { getDefaultCapacity } from './get_default_capacity'; + +describe('getDefaultCapacity', () => { + it('returns default capacity when not in cloud', () => { + expect( + getDefaultCapacity({ + heapSizeLimit: 851443712, + isCloud: false, + isServerless: false, + isBackgroundTaskNodeOnly: false, + claimStrategy: CLAIM_STRATEGY_MGET, + }) + ).toBe(DEFAULT_CAPACITY); + + expect( + getDefaultCapacity({ + heapSizeLimit: 851443712, + isCloud: false, + isServerless: true, + isBackgroundTaskNodeOnly: false, + claimStrategy: CLAIM_STRATEGY_MGET, + }) + ).toBe(DEFAULT_CAPACITY); + + expect( + getDefaultCapacity({ + heapSizeLimit: 851443712, + isCloud: false, + isServerless: false, + isBackgroundTaskNodeOnly: true, + claimStrategy: CLAIM_STRATEGY_MGET, + }) + ).toBe(DEFAULT_CAPACITY); + + expect( + getDefaultCapacity({ + heapSizeLimit: 851443712, + isCloud: false, + isServerless: true, + isBackgroundTaskNodeOnly: true, + claimStrategy: CLAIM_STRATEGY_MGET, + }) + ).toBe(DEFAULT_CAPACITY); + }); + + it('returns default capacity when default claim strategy', () => { + expect( + getDefaultCapacity({ + heapSizeLimit: 851443712, + isCloud: true, + isServerless: false, + isBackgroundTaskNodeOnly: false, + claimStrategy: CLAIM_STRATEGY_DEFAULT, + }) + ).toBe(DEFAULT_CAPACITY); + + expect( + getDefaultCapacity({ + heapSizeLimit: 851443712, + isCloud: true, + isServerless: false, + isBackgroundTaskNodeOnly: true, + claimStrategy: CLAIM_STRATEGY_DEFAULT, + }) + ).toBe(DEFAULT_CAPACITY); + }); + + it('returns default capacity when serverless', () => { + expect( + getDefaultCapacity({ + heapSizeLimit: 851443712, + isCloud: false, + isServerless: true, + isBackgroundTaskNodeOnly: false, + claimStrategy: CLAIM_STRATEGY_MGET, + }) + ).toBe(DEFAULT_CAPACITY); + + expect( + getDefaultCapacity({ + heapSizeLimit: 851443712, + isCloud: false, + isServerless: true, + isBackgroundTaskNodeOnly: true, + claimStrategy: CLAIM_STRATEGY_MGET, + }) + ).toBe(DEFAULT_CAPACITY); + + expect( + getDefaultCapacity({ + heapSizeLimit: 851443712, + isCloud: true, + isServerless: true, + isBackgroundTaskNodeOnly: false, + claimStrategy: CLAIM_STRATEGY_MGET, + }) + ).toBe(DEFAULT_CAPACITY); + + expect( + getDefaultCapacity({ + heapSizeLimit: 851443712, + isCloud: true, + isServerless: true, + isBackgroundTaskNodeOnly: true, + claimStrategy: CLAIM_STRATEGY_MGET, + }) + ).toBe(DEFAULT_CAPACITY); + }); + + it('returns capacity as expected when in cloud and claim strategy is mget', () => { + // 1GB + expect( + getDefaultCapacity({ + heapSizeLimit: 851443712, + isCloud: true, + isServerless: false, + isBackgroundTaskNodeOnly: false, + claimStrategy: CLAIM_STRATEGY_MGET, + }) + ).toBe(10); + + // 1GB but somehow background task node only is true + expect( + getDefaultCapacity({ + heapSizeLimit: 851443712, + isCloud: true, + isServerless: false, + isBackgroundTaskNodeOnly: true, + claimStrategy: CLAIM_STRATEGY_MGET, + }) + ).toBe(10); + + // 2GB + expect( + getDefaultCapacity({ + heapSizeLimit: 1702887424, + isCloud: true, + isServerless: false, + isBackgroundTaskNodeOnly: false, + claimStrategy: CLAIM_STRATEGY_MGET, + }) + ).toBe(15); + + // 2GB but somehow background task node only is true + expect( + getDefaultCapacity({ + heapSizeLimit: 1702887424, + isCloud: true, + isServerless: false, + isBackgroundTaskNodeOnly: true, + claimStrategy: CLAIM_STRATEGY_MGET, + }) + ).toBe(15); + + // 4GB + expect( + getDefaultCapacity({ + heapSizeLimit: 3405774848, + isCloud: true, + isServerless: false, + isBackgroundTaskNodeOnly: false, + claimStrategy: CLAIM_STRATEGY_MGET, + }) + ).toBe(25); + + // 4GB background task only + expect( + getDefaultCapacity({ + heapSizeLimit: 3405774848, + isCloud: true, + isServerless: false, + isBackgroundTaskNodeOnly: true, + claimStrategy: CLAIM_STRATEGY_MGET, + }) + ).toBe(50); + }); +}); diff --git a/x-pack/plugins/task_manager/server/lib/get_default_capacity.ts b/x-pack/plugins/task_manager/server/lib/get_default_capacity.ts new file mode 100644 index 0000000000000..aeafa0f63c4d7 --- /dev/null +++ b/x-pack/plugins/task_manager/server/lib/get_default_capacity.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 { CLAIM_STRATEGY_MGET, DEFAULT_CAPACITY } from '../config'; + +interface GetDefaultCapacityOpts { + claimStrategy?: string; + heapSizeLimit: number; + isCloud: boolean; + isServerless: boolean; + isBackgroundTaskNodeOnly: boolean; +} + +// Map instance size to desired capacity +const HEAP_TO_CAPACITY_MAP = [ + { minHeap: 0, maxHeap: 1, capacity: 10 }, + { minHeap: 1, maxHeap: 2, capacity: 15 }, + { minHeap: 2, maxHeap: 4, capacity: 25, backgroundTaskNodeOnly: false }, + { minHeap: 2, maxHeap: 4, capacity: 50, backgroundTaskNodeOnly: true }, +]; + +export function getDefaultCapacity({ + claimStrategy, + heapSizeLimit: heapSizeLimitInBytes, + isCloud, + isServerless, + isBackgroundTaskNodeOnly, +}: GetDefaultCapacityOpts) { + // perform heap size based calculations only in cloud + if (isCloud && !isServerless && claimStrategy === CLAIM_STRATEGY_MGET) { + // convert bytes to GB + const heapSizeLimitInGB = heapSizeLimitInBytes / 1e9; + + const config = HEAP_TO_CAPACITY_MAP.find((map) => { + return ( + heapSizeLimitInGB > map.minHeap && + heapSizeLimitInGB <= map.maxHeap && + (map.backgroundTaskNodeOnly === undefined || + isBackgroundTaskNodeOnly === map.backgroundTaskNodeOnly) + ); + }); + + return config?.capacity ?? DEFAULT_CAPACITY; + } + + return DEFAULT_CAPACITY; +} diff --git a/x-pack/plugins/task_manager/server/lib/log_health_metrics.test.ts b/x-pack/plugins/task_manager/server/lib/log_health_metrics.test.ts index ea0793b60266b..a39568df5fdd2 100644 --- a/x-pack/plugins/task_manager/server/lib/log_health_metrics.test.ts +++ b/x-pack/plugins/task_manager/server/lib/log_health_metrics.test.ts @@ -435,7 +435,8 @@ function getMockMonitoredHealth(overrides = {}): MonitoredHealth { timestamp: new Date().toISOString(), status: HealthStatus.OK, value: { - max_workers: 10, + capacity: { config: 10, as_cost: 20, as_workers: 10 }, + claim_strategy: 'default', poll_interval: 3000, request_capacity: 1000, monitored_aggregated_stats_refresh_rate: 5000, @@ -454,16 +455,19 @@ function getMockMonitoredHealth(overrides = {}): MonitoredHealth { status: HealthStatus.OK, value: { count: 4, + cost: 8, task_types: { - actions_telemetry: { count: 2, status: { idle: 2 } }, - alerting_telemetry: { count: 1, status: { idle: 1 } }, - session_cleanup: { count: 1, status: { idle: 1 } }, + actions_telemetry: { count: 2, cost: 4, status: { idle: 2 } }, + alerting_telemetry: { count: 1, cost: 2, status: { idle: 1 } }, + session_cleanup: { count: 1, cost: 2, status: { idle: 1 } }, }, schedule: [], overdue: 0, + overdue_cost: 0, overdue_non_recurring: 0, estimatedScheduleDensity: [], non_recurring: 20, + non_recurring_cost: 40, owner_ids: 2, estimated_schedule_density: [], capacity_requirements: { diff --git a/x-pack/plugins/task_manager/server/metrics/create_aggregator.test.ts b/x-pack/plugins/task_manager/server/metrics/create_aggregator.test.ts index 309617a8e4cc3..b1cf9a90b6cb6 100644 --- a/x-pack/plugins/task_manager/server/metrics/create_aggregator.test.ts +++ b/x-pack/plugins/task_manager/server/metrics/create_aggregator.test.ts @@ -45,7 +45,6 @@ const config: TaskManagerConfig = { warn_threshold: 5000, }, max_attempts: 9, - max_workers: 10, metrics_reset_interval: 30000, monitored_aggregated_stats_refresh_rate: 5000, monitored_stats_health_verbose_log: { diff --git a/x-pack/plugins/task_manager/server/monitoring/background_task_utilization_statistics.ts b/x-pack/plugins/task_manager/server/monitoring/background_task_utilization_statistics.ts index 837f29c83f108..5a9a9e07aadf7 100644 --- a/x-pack/plugins/task_manager/server/monitoring/background_task_utilization_statistics.ts +++ b/x-pack/plugins/task_manager/server/monitoring/background_task_utilization_statistics.ts @@ -21,7 +21,7 @@ import { } from '../task_events'; import { MonitoredStat } from './monitoring_stats_stream'; import { AggregatedStat, AggregatedStatProvider } from '../lib/runtime_statistics_aggregator'; -import { createRunningAveragedStat } from './task_run_calcultors'; +import { createRunningAveragedStat } from './task_run_calculators'; import { DEFAULT_WORKER_UTILIZATION_RUNNING_AVERAGE_WINDOW } from '../config'; export interface PublicBackgroundTaskUtilizationStat extends JsonObject { diff --git a/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.test.ts b/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.test.ts index 263f2e9987b7c..9791ac805e500 100644 --- a/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.test.ts +++ b/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.test.ts @@ -21,7 +21,7 @@ describe('estimateCapacity', () => { estimateCapacity( logger, mockStats( - { max_workers: 10, poll_interval: 3000 }, + { capacity: { config: 10, as_cost: 20, as_workers: 10 }, poll_interval: 3000 }, { owner_ids: 1, overdue_non_recurring: 0, @@ -77,7 +77,7 @@ describe('estimateCapacity', () => { estimateCapacity( logger, mockStats( - { max_workers: 10, poll_interval: 3000 }, + { capacity: { config: 10, as_cost: 20, as_workers: 10 }, poll_interval: 3000 }, { owner_ids: 1, overdue_non_recurring: 0, @@ -135,7 +135,7 @@ describe('estimateCapacity', () => { estimateCapacity( logger, mockStats( - { max_workers: 10, poll_interval: 3000 }, + { capacity: { config: 10, as_cost: 20, as_workers: 10 }, poll_interval: 3000 }, { owner_ids: 1, overdue_non_recurring: 0, @@ -172,7 +172,7 @@ describe('estimateCapacity', () => { estimateCapacity( logger, mockStats( - { max_workers: 10, poll_interval: 3000 }, + { capacity: { config: 10, as_cost: 20, as_workers: 10 }, poll_interval: 3000 }, { owner_ids: 1, overdue_non_recurring: 0, @@ -228,7 +228,7 @@ describe('estimateCapacity', () => { estimateCapacity( logger, mockStats( - { max_workers: 10, poll_interval: 3000 }, + { capacity: { config: 10, as_cost: 20, as_workers: 10 }, poll_interval: 3000 }, { // 0 active tasks at this moment in time, so no owners identifiable owner_ids: 0, @@ -285,7 +285,7 @@ describe('estimateCapacity', () => { estimateCapacity( logger, mockStats( - { max_workers: 10, poll_interval: 3000 }, + { capacity: { config: 10, as_cost: 20, as_workers: 10 }, poll_interval: 3000 }, { owner_ids: 3, overdue_non_recurring: 0, @@ -347,7 +347,7 @@ describe('estimateCapacity', () => { estimateCapacity( logger, mockStats( - { max_workers: 10, poll_interval: 3000 }, + { capacity: { config: 10, as_cost: 20, as_workers: 10 }, poll_interval: 3000 }, { owner_ids: provisionedKibanaInstances, overdue_non_recurring: 0, @@ -428,7 +428,7 @@ describe('estimateCapacity', () => { estimateCapacity( logger, mockStats( - { max_workers: 10, poll_interval: 3000 }, + { capacity: { config: 10, as_cost: 20, as_workers: 10 }, poll_interval: 3000 }, { owner_ids: provisionedKibanaInstances, overdue_non_recurring: 0, @@ -510,7 +510,7 @@ describe('estimateCapacity', () => { estimateCapacity( logger, mockStats( - { max_workers: 10, poll_interval: 3000 }, + { capacity: { config: 10, as_cost: 20, as_workers: 10 }, poll_interval: 3000 }, { owner_ids: 1, overdue_non_recurring: 0, @@ -578,7 +578,7 @@ describe('estimateCapacity', () => { estimateCapacity( logger, mockStats( - { max_workers: 10, poll_interval: 3000 }, + { capacity: { config: 10, as_cost: 20, as_workers: 10 }, poll_interval: 3000 }, { owner_ids: 1, overdue_non_recurring: 0, @@ -643,7 +643,7 @@ describe('estimateCapacity', () => { estimateCapacity( logger, mockStats( - { max_workers: 10, poll_interval: 3000 }, + { capacity: { config: 10, as_cost: 20, as_workers: 10 }, poll_interval: 3000 }, { owner_ids: 1, overdue_non_recurring: 0, @@ -708,7 +708,7 @@ describe('estimateCapacity', () => { estimateCapacity( logger, mockStats( - { max_workers: 10, poll_interval: 3000 }, + { capacity: { config: 10, as_cost: 20, as_workers: 10 }, poll_interval: 3000 }, { owner_ids: 1, overdue_non_recurring: 0, @@ -784,7 +784,7 @@ describe('estimateCapacity', () => { estimateCapacity( logger, mockStats( - { max_workers: 10, poll_interval: 3000 }, + { capacity: { config: 10, as_cost: 20, as_workers: 10 }, poll_interval: 3000 }, { owner_ids: 1, overdue_non_recurring: 0, @@ -862,7 +862,7 @@ describe('estimateCapacity', () => { estimateCapacity( logger, mockStats( - { max_workers: 10, poll_interval: 3000 }, + { capacity: { config: 10, as_cost: 20, as_workers: 10 }, poll_interval: 3000 }, { overdue: undefined, owner_ids: 1, @@ -949,7 +949,8 @@ function mockStats( status: HealthStatus.OK, timestamp: new Date().toISOString(), value: { - max_workers: 0, + capacity: { config: 10, as_cost: 20, as_workers: 10 }, + claim_strategy: 'default', poll_interval: 0, request_capacity: 1000, monitored_aggregated_stats_refresh_rate: 5000, @@ -969,16 +970,19 @@ function mockStats( timestamp: new Date().toISOString(), value: { count: 4, + cost: 8, task_types: { - actions_telemetry: { count: 2, status: { idle: 2 } }, - alerting_telemetry: { count: 1, status: { idle: 1 } }, - session_cleanup: { count: 1, status: { idle: 1 } }, + actions_telemetry: { count: 2, cost: 4, status: { idle: 2 } }, + alerting_telemetry: { count: 1, cost: 2, status: { idle: 1 } }, + session_cleanup: { count: 1, cost: 2, status: { idle: 1 } }, }, schedule: [], overdue: 0, + overdue_cost: 0, overdue_non_recurring: 0, estimated_schedule_density: [], non_recurring: 20, + non_recurring_cost: 40, owner_ids: 2, capacity_requirements: { per_minute: 150, diff --git a/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.ts b/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.ts index b12382f16e27b..d1c2f3591ea22 100644 --- a/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.ts +++ b/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.ts @@ -10,7 +10,7 @@ import stats from 'stats-lite'; import { JsonObject } from '@kbn/utility-types'; import { Logger } from '@kbn/core/server'; import { RawMonitoringStats, RawMonitoredStat, HealthStatus } from './monitoring_stats_stream'; -import { AveragedStat } from './task_run_calcultors'; +import { AveragedStat } from './task_run_calculators'; import { TaskPersistenceTypes } from './task_run_statistics'; import { asErr, asOk, map, Result } from '../lib/result_type'; @@ -61,8 +61,10 @@ export function estimateCapacity( non_recurring: percentageOfExecutionsUsedByNonRecurringTasks, } = capacityStats.runtime.value.execution.persistence; const { overdue, capacity_requirements: capacityRequirements } = workload; - const { poll_interval: pollInterval, max_workers: maxWorkers } = - capacityStats.configuration.value; + const { + poll_interval: pollInterval, + capacity: { config: configuredCapacity }, + } = capacityStats.configuration.value; /** * On average, how many polling cycles does it take to execute a task? @@ -78,10 +80,10 @@ export function estimateCapacity( ); /** - * Given the current configuration how much task capacity do we have? + * Given the current configuration how much capacity do we have to run normal cost tasks? */ const capacityPerMinutePerKibana = Math.round( - ((60 * 1000) / (averagePollIntervalsPerExecution * pollInterval)) * maxWorkers + ((60 * 1000) / (averagePollIntervalsPerExecution * pollInterval)) * configuredCapacity ); /** diff --git a/x-pack/plugins/task_manager/server/monitoring/configuration_statistics.test.ts b/x-pack/plugins/task_manager/server/monitoring/configuration_statistics.test.ts index 822356e2d6534..0b5387b66dece 100644 --- a/x-pack/plugins/task_manager/server/monitoring/configuration_statistics.test.ts +++ b/x-pack/plugins/task_manager/server/monitoring/configuration_statistics.test.ts @@ -13,7 +13,6 @@ import { TaskManagerConfig } from '../config'; describe('Configuration Statistics Aggregator', () => { test('merges the static config with the merged configs', async () => { const configuration: TaskManagerConfig = { - max_workers: 10, max_attempts: 9, poll_interval: 6000000, allow_reading_invalid_state: false, @@ -55,7 +54,8 @@ describe('Configuration Statistics Aggregator', () => { }; const managedConfig = { - maxWorkersConfiguration$: new Subject(), + startingCapacity: 10, + capacityConfiguration$: new Subject(), pollIntervalConfiguration$: new Subject(), }; @@ -65,7 +65,12 @@ describe('Configuration Statistics Aggregator', () => { .pipe(take(3), bufferCount(3)) .subscribe(([initial, updatedWorkers, updatedInterval]) => { expect(initial.value).toEqual({ - max_workers: 10, + capacity: { + config: 10, + as_workers: 10, + as_cost: 20, + }, + claim_strategy: 'default', poll_interval: 6000000, request_capacity: 1000, monitored_aggregated_stats_refresh_rate: 5000, @@ -79,7 +84,12 @@ describe('Configuration Statistics Aggregator', () => { }, }); expect(updatedWorkers.value).toEqual({ - max_workers: 8, + capacity: { + config: 8, + as_workers: 8, + as_cost: 16, + }, + claim_strategy: 'default', poll_interval: 6000000, request_capacity: 1000, monitored_aggregated_stats_refresh_rate: 5000, @@ -93,7 +103,12 @@ describe('Configuration Statistics Aggregator', () => { }, }); expect(updatedInterval.value).toEqual({ - max_workers: 8, + capacity: { + config: 8, + as_workers: 8, + as_cost: 16, + }, + claim_strategy: 'default', poll_interval: 3000, request_capacity: 1000, monitored_aggregated_stats_refresh_rate: 5000, @@ -108,7 +123,7 @@ describe('Configuration Statistics Aggregator', () => { }); resolve(); }, reject); - managedConfig.maxWorkersConfiguration$.next(8); + managedConfig.capacityConfiguration$.next(8); managedConfig.pollIntervalConfiguration$.next(3000); } catch (error) { reject(error); diff --git a/x-pack/plugins/task_manager/server/monitoring/configuration_statistics.ts b/x-pack/plugins/task_manager/server/monitoring/configuration_statistics.ts index dc3221351a33e..c606b63694b0f 100644 --- a/x-pack/plugins/task_manager/server/monitoring/configuration_statistics.ts +++ b/x-pack/plugins/task_manager/server/monitoring/configuration_statistics.ts @@ -8,9 +8,11 @@ import { combineLatest, of } from 'rxjs'; import { pick, merge } from 'lodash'; import { map, startWith } from 'rxjs'; +import { JsonObject } from '@kbn/utility-types'; import { AggregatedStatProvider } from '../lib/runtime_statistics_aggregator'; -import { TaskManagerConfig } from '../config'; +import { CLAIM_STRATEGY_DEFAULT, TaskManagerConfig } from '../config'; import { ManagedConfiguration } from '../lib/create_managed_configuration'; +import { getCapacityInCost, getCapacityInWorkers } from '../task_pool'; const CONFIG_FIELDS_TO_EXPOSE = [ 'request_capacity', @@ -19,10 +21,19 @@ const CONFIG_FIELDS_TO_EXPOSE = [ 'monitored_task_execution_thresholds', ] as const; +interface CapacityConfig extends JsonObject { + capacity: { + config: number; + as_workers: number; + as_cost: number; + }; +} + export type ConfigStat = Pick< TaskManagerConfig, - 'max_workers' | 'poll_interval' | (typeof CONFIG_FIELDS_TO_EXPOSE)[number] ->; + 'poll_interval' | 'claim_strategy' | (typeof CONFIG_FIELDS_TO_EXPOSE)[number] +> & + CapacityConfig; export function createConfigurationAggregator( config: TaskManagerConfig, @@ -30,16 +41,21 @@ export function createConfigurationAggregator( ): AggregatedStatProvider { return combineLatest([ of(pick(config, ...CONFIG_FIELDS_TO_EXPOSE)), + of({ claim_strategy: config.claim_strategy ?? CLAIM_STRATEGY_DEFAULT }), managedConfig.pollIntervalConfiguration$.pipe( startWith(config.poll_interval), map>((pollInterval) => ({ poll_interval: pollInterval, })) ), - managedConfig.maxWorkersConfiguration$.pipe( - startWith(config.max_workers), - map>((maxWorkers) => ({ - max_workers: maxWorkers, + managedConfig.capacityConfiguration$.pipe( + startWith(managedConfig.startingCapacity), + map((capacity) => ({ + capacity: { + config: capacity, + as_workers: getCapacityInWorkers(capacity), + as_cost: getCapacityInCost(capacity), + }, })) ), ]).pipe( diff --git a/x-pack/plugins/task_manager/server/monitoring/ephemeral_task_statistics.test.ts b/x-pack/plugins/task_manager/server/monitoring/ephemeral_task_statistics.test.ts index d7135837e052e..ac16070d7c131 100644 --- a/x-pack/plugins/task_manager/server/monitoring/ephemeral_task_statistics.test.ts +++ b/x-pack/plugins/task_manager/server/monitoring/ephemeral_task_statistics.test.ts @@ -176,11 +176,11 @@ describe('Ephemeral Task Statistics', () => { }); const runningAverageWindowSize = 5; - const maxWorkers = 10; + const capacity = 10; const ephemeralTaskAggregator = createEphemeralTaskAggregator( ephemeralTaskLifecycle, runningAverageWindowSize, - maxWorkers + capacity ); function expectWindowEqualsUpdate( @@ -229,7 +229,7 @@ describe('Ephemeral Task Statistics', () => { }); }); -test('returns the average load added per polling cycle cycle by ephemeral tasks when load exceeds max workers', async () => { +test('returns the average load added per polling cycle cycle by ephemeral tasks when load exceeds capacity', async () => { const tasksExecuted = [0, 5, 10, 20, 15, 10, 5, 0, 0, 0, 0, 0]; const expectedLoad = [0, 50, 100, 200, 150, 100, 50, 0, 0, 0, 0, 0]; @@ -241,11 +241,11 @@ test('returns the average load added per polling cycle cycle by ephemeral tasks }); const runningAverageWindowSize = 5; - const maxWorkers = 10; + const capacity = 10; const ephemeralTaskAggregator = createEphemeralTaskAggregator( ephemeralTaskLifecycle, runningAverageWindowSize, - maxWorkers + capacity ); function expectWindowEqualsUpdate( diff --git a/x-pack/plugins/task_manager/server/monitoring/ephemeral_task_statistics.ts b/x-pack/plugins/task_manager/server/monitoring/ephemeral_task_statistics.ts index b77eae1080fbc..d02080a56a1aa 100644 --- a/x-pack/plugins/task_manager/server/monitoring/ephemeral_task_statistics.ts +++ b/x-pack/plugins/task_manager/server/monitoring/ephemeral_task_statistics.ts @@ -17,7 +17,7 @@ import { AveragedStat, calculateRunningAverage, createRunningAveragedStat, -} from './task_run_calcultors'; +} from './task_run_calculators'; import { HealthStatus } from './monitoring_stats_stream'; export interface EphemeralTaskStat extends JsonObject { @@ -35,7 +35,7 @@ export interface SummarizedEphemeralTaskStat extends JsonObject { export function createEphemeralTaskAggregator( ephemeralTaskLifecycle: EphemeralTaskLifecycle, runningAverageWindowSize: number, - maxWorkers: number + capacity: number ): AggregatedStatProvider { const ephemeralTaskRunEvents$ = ephemeralTaskLifecycle.events.pipe( filter((taskEvent: TaskLifecycleEvent) => isTaskRunEvent(taskEvent)) @@ -70,7 +70,7 @@ export function createEphemeralTaskAggregator( map(([tasksRanSincePreviousQueueSize, ephemeralQueueSize]) => ({ queuedTasks: ephemeralQueuedTasksQueue(ephemeralQueueSize), executionsPerCycle: ephemeralQueueExecutionsPerCycleQueue(tasksRanSincePreviousQueueSize), - load: ephemeralTaskLoadQueue(calculateWorkerLoad(maxWorkers, tasksRanSincePreviousQueueSize)), + load: ephemeralTaskLoadQueue(calculateWorkerLoad(capacity, tasksRanSincePreviousQueueSize)), })), startWith({ queuedTasks: [], diff --git a/x-pack/plugins/task_manager/server/monitoring/index.ts b/x-pack/plugins/task_manager/server/monitoring/index.ts index 9ee32e97d7758..5dc024b53de10 100644 --- a/x-pack/plugins/task_manager/server/monitoring/index.ts +++ b/x-pack/plugins/task_manager/server/monitoring/index.ts @@ -18,6 +18,7 @@ import { TaskPollingLifecycle } from '../polling_lifecycle'; import { ManagedConfiguration } from '../lib/create_managed_configuration'; import { EphemeralTaskLifecycle } from '../ephemeral_task_lifecycle'; import { AdHocTaskCounter } from '../lib/adhoc_task_counter'; +import { TaskTypeDictionary } from '../task_type_dictionary'; export type { MonitoringStats, RawMonitoringStats } from './monitoring_stats_stream'; export { @@ -27,27 +28,20 @@ export { createMonitoringStatsStream, } from './monitoring_stats_stream'; +export interface CreateMonitoringStatsOpts { + taskStore: TaskStore; + elasticsearchAndSOAvailability$: Observable; + config: TaskManagerConfig; + managedConfig: ManagedConfiguration; + logger: Logger; + adHocTaskCounter: AdHocTaskCounter; + taskDefinitions: TaskTypeDictionary; + taskPollingLifecycle?: TaskPollingLifecycle; + ephemeralTaskLifecycle?: EphemeralTaskLifecycle; +} + export function createMonitoringStats( - taskStore: TaskStore, - elasticsearchAndSOAvailability$: Observable, - config: TaskManagerConfig, - managedConfig: ManagedConfiguration, - logger: Logger, - adHocTaskCounter: AdHocTaskCounter, - taskPollingLifecycle?: TaskPollingLifecycle, - ephemeralTaskLifecycle?: EphemeralTaskLifecycle + opts: CreateMonitoringStatsOpts ): Observable { - return createMonitoringStatsStream( - createAggregators( - taskStore, - elasticsearchAndSOAvailability$, - config, - managedConfig, - logger, - adHocTaskCounter, - taskPollingLifecycle, - ephemeralTaskLifecycle - ), - config - ); + return createMonitoringStatsStream(createAggregators(opts)); } diff --git a/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.test.ts b/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.test.ts index f4da53871ffa3..075b663e4ce83 100644 --- a/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.test.ts +++ b/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.test.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { TaskManagerConfig } from '../config'; import { of, Subject } from 'rxjs'; import { take, bufferCount } from 'rxjs'; import { createMonitoringStatsStream } from './monitoring_stats_stream'; @@ -17,51 +16,9 @@ beforeEach(() => { }); describe('createMonitoringStatsStream', () => { - const configuration: TaskManagerConfig = { - max_workers: 10, - max_attempts: 9, - poll_interval: 6000000, - allow_reading_invalid_state: false, - version_conflict_threshold: 80, - monitored_stats_required_freshness: 6000000, - request_capacity: 1000, - monitored_aggregated_stats_refresh_rate: 5000, - monitored_stats_health_verbose_log: { - enabled: false, - level: 'debug' as const, - warn_delayed_task_start_in_seconds: 60, - }, - monitored_stats_running_average_window: 50, - monitored_task_execution_thresholds: { - default: { - error_threshold: 90, - warn_threshold: 80, - }, - custom: {}, - }, - ephemeral_tasks: { - enabled: true, - request_capacity: 10, - }, - unsafe: { - exclude_task_types: [], - authenticate_background_task_utilization: true, - }, - event_loop_delay: { - monitor: true, - warn_threshold: 5000, - }, - worker_utilization_running_average_window: 5, - metrics_reset_interval: 3000, - claim_strategy: 'default', - request_timeouts: { - update_by_query: 1000, - }, - }; - it('returns the initial config used to configure Task Manager', async () => { return new Promise((resolve) => { - createMonitoringStatsStream(of(), configuration) + createMonitoringStatsStream(of()) .pipe(take(1)) .subscribe((firstValue) => { expect(firstValue.stats).toEqual({}); @@ -74,7 +31,7 @@ describe('createMonitoringStatsStream', () => { const aggregatedStats$ = new Subject(); return new Promise((resolve) => { - createMonitoringStatsStream(aggregatedStats$, configuration) + createMonitoringStatsStream(aggregatedStats$) .pipe(take(3), bufferCount(3)) .subscribe(([initialValue, secondValue, thirdValue]) => { expect(initialValue.stats).toMatchObject({ @@ -82,7 +39,7 @@ describe('createMonitoringStatsStream', () => { stats: { configuration: { value: { - max_workers: 10, + capacity: 10, poll_interval: 6000000, request_capacity: 1000, monitored_aggregated_stats_refresh_rate: 5000, @@ -115,7 +72,7 @@ describe('createMonitoringStatsStream', () => { configuration: { timestamp: expect.any(String), value: { - max_workers: 10, + capacity: 10, poll_interval: 6000000, request_capacity: 1000, monitored_aggregated_stats_refresh_rate: 5000, @@ -148,7 +105,7 @@ describe('createMonitoringStatsStream', () => { configuration: { timestamp: expect.any(String), value: { - max_workers: 10, + capacity: 10, poll_interval: 6000000, request_capacity: 1000, monitored_aggregated_stats_refresh_rate: 5000, diff --git a/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.ts b/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.ts index 5ee6465dae0eb..e1bffb55d54fa 100644 --- a/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.ts +++ b/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.ts @@ -10,8 +10,6 @@ import { map, scan } from 'rxjs'; import { set } from '@kbn/safer-lodash-set'; import { Logger } from '@kbn/core/server'; import { JsonObject } from '@kbn/utility-types'; -import { TaskStore } from '../task_store'; -import { TaskPollingLifecycle } from '../polling_lifecycle'; import { createWorkloadAggregator, summarizeWorkloadStat, @@ -37,11 +35,9 @@ import { import { ConfigStat, createConfigurationAggregator } from './configuration_statistics'; import { TaskManagerConfig } from '../config'; -import { ManagedConfiguration } from '../lib/create_managed_configuration'; -import { EphemeralTaskLifecycle } from '../ephemeral_task_lifecycle'; import { CapacityEstimationStat, withCapacityEstimate } from './capacity_estimation'; -import { AdHocTaskCounter } from '../lib/adhoc_task_counter'; import { AggregatedStatProvider } from '../lib/runtime_statistics_aggregator'; +import { CreateMonitoringStatsOpts } from '.'; export interface MonitoringStats { last_update: string; @@ -81,26 +77,28 @@ export interface RawMonitoringStats { }; } -export function createAggregators( - taskStore: TaskStore, - elasticsearchAndSOAvailability$: Observable, - config: TaskManagerConfig, - managedConfig: ManagedConfiguration, - logger: Logger, - adHocTaskCounter: AdHocTaskCounter, - taskPollingLifecycle?: TaskPollingLifecycle, - ephemeralTaskLifecycle?: EphemeralTaskLifecycle -): AggregatedStatProvider { +export function createAggregators({ + taskStore, + elasticsearchAndSOAvailability$, + config, + managedConfig, + logger, + taskDefinitions, + adHocTaskCounter, + taskPollingLifecycle, + ephemeralTaskLifecycle, +}: CreateMonitoringStatsOpts): AggregatedStatProvider { const aggregators: AggregatedStatProvider[] = [ createConfigurationAggregator(config, managedConfig), - createWorkloadAggregator( + createWorkloadAggregator({ taskStore, elasticsearchAndSOAvailability$, - config.monitored_aggregated_stats_refresh_rate, - config.poll_interval, - logger - ), + refreshInterval: config.monitored_aggregated_stats_refresh_rate, + pollInterval: config.poll_interval, + logger, + taskDefinitions, + }), ]; if (taskPollingLifecycle) { aggregators.push( @@ -118,7 +116,7 @@ export function createAggregators( createEphemeralTaskAggregator( ephemeralTaskLifecycle, config.monitored_stats_running_average_window, - config.max_workers + managedConfig.startingCapacity ) ); } @@ -126,8 +124,7 @@ export function createAggregators( } export function createMonitoringStatsStream( - provider$: AggregatedStatProvider, - config: TaskManagerConfig + provider$: AggregatedStatProvider ): Observable { const initialStats = { last_update: new Date().toISOString(), diff --git a/x-pack/plugins/task_manager/server/monitoring/task_run_calcultors.test.ts b/x-pack/plugins/task_manager/server/monitoring/task_run_calculators.test.ts similarity index 98% rename from x-pack/plugins/task_manager/server/monitoring/task_run_calcultors.test.ts rename to x-pack/plugins/task_manager/server/monitoring/task_run_calculators.test.ts index b5f6be8b7524d..46df2b1b21d42 100644 --- a/x-pack/plugins/task_manager/server/monitoring/task_run_calcultors.test.ts +++ b/x-pack/plugins/task_manager/server/monitoring/task_run_calculators.test.ts @@ -12,7 +12,7 @@ import { calculateFrequency, createRunningAveragedStat, createMapOfRunningAveragedStats, -} from './task_run_calcultors'; +} from './task_run_calculators'; describe('calculateRunningAverage', () => { test('calculates the running average and median of a window of values', async () => { diff --git a/x-pack/plugins/task_manager/server/monitoring/task_run_calcultors.ts b/x-pack/plugins/task_manager/server/monitoring/task_run_calculators.ts similarity index 100% rename from x-pack/plugins/task_manager/server/monitoring/task_run_calcultors.ts rename to x-pack/plugins/task_manager/server/monitoring/task_run_calculators.ts diff --git a/x-pack/plugins/task_manager/server/monitoring/task_run_statistics.ts b/x-pack/plugins/task_manager/server/monitoring/task_run_statistics.ts index 6a7f10b7e75b6..517b29a54cd64 100644 --- a/x-pack/plugins/task_manager/server/monitoring/task_run_statistics.ts +++ b/x-pack/plugins/task_manager/server/monitoring/task_run_statistics.ts @@ -35,7 +35,7 @@ import { calculateFrequency, createRunningAveragedStat, createMapOfRunningAveragedStats, -} from './task_run_calcultors'; +} from './task_run_calculators'; import { HealthStatus } from './monitoring_stats_stream'; import { TaskPollingLifecycle } from '../polling_lifecycle'; import { TaskExecutionFailureThreshold, TaskManagerConfig } from '../config'; diff --git a/x-pack/plugins/task_manager/server/monitoring/workload_statistics.test.ts b/x-pack/plugins/task_manager/server/monitoring/workload_statistics.test.ts index 7ef860efa783a..cd37c6661ec00 100644 --- a/x-pack/plugins/task_manager/server/monitoring/workload_statistics.test.ts +++ b/x-pack/plugins/task_manager/server/monitoring/workload_statistics.test.ts @@ -15,13 +15,14 @@ import { padBuckets, estimateRecurringTaskScheduling, } from './workload_statistics'; -import { ConcreteTaskInstance } from '../task'; +import { ConcreteTaskInstance, TaskCost } from '../task'; import { times } from 'lodash'; import { taskStoreMock } from '../task_store.mock'; import { of, Subject } from 'rxjs'; import { sleep } from '../test_utils'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { TaskTypeDictionary } from '../task_type_dictionary'; type ResponseWithAggs = Omit, 'aggregations'> & { aggregations: WorkloadAggregationResponse; @@ -32,52 +33,98 @@ const asApiResponse = (body: ResponseWithAggs) => .createSuccessTransportRequestPromise(body as estypes.SearchResponse) .then((res) => res.body as ResponseWithAggs); +const logger = loggingSystemMock.create().get(); + +const definitions = new TaskTypeDictionary(logger); +definitions.registerTaskDefinitions({ + report: { + title: 'report', + cost: TaskCost.ExtraLarge, + createTaskRunner: jest.fn(), + }, + foo: { + title: 'foo', + createTaskRunner: jest.fn(), + }, + bar: { + title: 'bar', + cost: TaskCost.Tiny, + createTaskRunner: jest.fn(), + }, +}); describe('Workload Statistics Aggregator', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + test('queries the Task Store at a fixed interval for the current workload', async () => { const taskStore = taskStoreMock.create({}); taskStore.aggregate.mockResolvedValue( asApiResponse({ - hits: { - hits: [], - max_score: 0, - total: { value: 0, relation: 'eq' }, - }, + hits: { hits: [], max_score: 0, total: { value: 3, relation: 'eq' } }, took: 1, timed_out: false, - _shards: { - total: 1, - successful: 1, - skipped: 1, - failed: 0, - }, + _shards: { total: 1, successful: 1, skipped: 1, failed: 0 }, aggregations: { taskType: { - buckets: [], + buckets: [ + { + key: 'foo', + doc_count: 1, + status: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [{ key: 'idle', doc_count: 1 }], + }, + }, + { + key: 'bar', + doc_count: 1, + status: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [{ key: 'claiming', doc_count: 1 }], + }, + }, + { + key: 'report', + doc_count: 1, + status: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [{ key: 'idle', doc_count: 1 }], + }, + }, + ], doc_count_error_upper_bound: 0, sum_other_doc_count: 0, }, schedule: { - buckets: [], + buckets: [{ key: '1m', doc_count: 8 }], doc_count_error_upper_bound: 0, sum_other_doc_count: 0, }, nonRecurringTasks: { - doc_count: 13, - }, - ownerIds: { - ownerIds: { - value: 1, + doc_count: 1, + taskType: { + buckets: [{ key: 'report', doc_count: 1 }], + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, }, }, + ownerIds: { ownerIds: { value: 1 } }, // The `FiltersAggregate` doesn't cover the case of a nested `AggregationsAggregationContainer`, in which `FiltersAggregate` // would not have a `buckets` property, but rather a keyed property that's inferred from the request. // @ts-expect-error idleTasks: { doc_count: 0, overdue: { - doc_count: 0, - nonRecurring: { - doc_count: 0, + doc_count: 1, + nonRecurring: { doc_count: 0 }, + taskTypes: { + buckets: [{ key: 'foo', doc_count: 1 }], + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, }, }, scheduleDensity: { @@ -89,9 +136,7 @@ describe('Workload Statistics Aggregator', () => { to: 1.601651976274e12, to_as_string: '2020-10-02T15:19:36.274Z', doc_count: 0, - histogram: { - buckets: [], - }, + histogram: { buckets: [] }, }, ], }, @@ -100,87 +145,51 @@ describe('Workload Statistics Aggregator', () => { }) ); - const workloadAggregator = createWorkloadAggregator( + const workloadAggregator = createWorkloadAggregator({ taskStore, - of(true), - 10, - 3000, - loggingSystemMock.create().get() - ); + elasticsearchAndSOAvailability$: of(true), + refreshInterval: 10, + pollInterval: 3000, + logger, + taskDefinitions: definitions, + }); return new Promise((resolve) => { workloadAggregator.pipe(first()).subscribe(() => { expect(taskStore.aggregate).toHaveBeenCalledWith({ aggs: { taskType: { - terms: { size: 100, field: 'task.taskType' }, - aggs: { - status: { - terms: { field: 'task.status' }, - }, - }, + terms: { size: 3, field: 'task.taskType' }, + aggs: { status: { terms: { field: 'task.status' } } }, }, schedule: { - terms: { - field: 'task.schedule.interval', - size: 100, - }, + terms: { field: 'task.schedule.interval', size: 100 }, }, nonRecurringTasks: { - missing: { field: 'task.schedule' }, + missing: { field: 'task.schedule.interval' }, + aggs: { taskType: { terms: { size: 3, field: 'task.taskType' } } }, }, ownerIds: { - filter: { - range: { - 'task.startedAt': { - gte: 'now-1w/w', - }, - }, - }, - aggs: { - ownerIds: { - cardinality: { - field: 'task.ownerId', - }, - }, - }, + filter: { range: { 'task.startedAt': { gte: 'now-1w/w' } } }, + aggs: { ownerIds: { cardinality: { field: 'task.ownerId' } } }, }, idleTasks: { - filter: { - term: { 'task.status': 'idle' }, - }, + filter: { term: { 'task.status': 'idle' } }, aggs: { scheduleDensity: { - range: { - field: 'task.runAt', - ranges: [{ from: 'now', to: 'now+1m' }], - }, + range: { field: 'task.runAt', ranges: [{ from: 'now', to: 'now+1m' }] }, aggs: { histogram: { - date_histogram: { - field: 'task.runAt', - fixed_interval: '3s', - }, - aggs: { - interval: { - terms: { - field: 'task.schedule.interval', - }, - }, - }, + date_histogram: { field: 'task.runAt', fixed_interval: '3s' }, + aggs: { interval: { terms: { field: 'task.schedule.interval' } } }, }, }, }, overdue: { - filter: { - range: { - 'task.runAt': { lt: 'now' }, - }, - }, + filter: { range: { 'task.runAt': { lt: 'now' } } }, aggs: { - nonRecurring: { - missing: { field: 'task.schedule' }, - }, + nonRecurring: { missing: { field: 'task.schedule.interval' } }, + taskTypes: { terms: { size: 3, field: 'task.taskType' } }, }, }, }, @@ -192,137 +201,189 @@ describe('Workload Statistics Aggregator', () => { }); }); - const mockAggregatedResult = () => - asApiResponse({ - hits: { - hits: [], - max_score: 0, - total: { value: 4, relation: 'eq' }, + const mockResult = (overrides = {}): ResponseWithAggs => ({ + hits: { hits: [], max_score: 0, total: { value: 4, relation: 'eq' } }, + took: 1, + timed_out: false, + _shards: { total: 1, successful: 1, skipped: 1, failed: 0 }, + aggregations: { + schedule: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { key: '3600s', doc_count: 1 }, + { key: '60s', doc_count: 1 }, + { key: '720m', doc_count: 1 }, + ], }, - took: 1, - timed_out: false, - _shards: { - total: 1, - successful: 1, - skipped: 1, - failed: 0, + taskType: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'foo', + doc_count: 2, + status: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [{ key: 'idle', doc_count: 2 }], + }, + }, + { + key: 'bar', + doc_count: 1, + status: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [{ key: 'idle', doc_count: 1 }], + }, + }, + { + key: 'report', + doc_count: 1, + status: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [{ key: 'idle', doc_count: 1 }], + }, + }, + ], }, - aggregations: { - schedule: { + nonRecurringTasks: { + doc_count: 1, + taskType: { + buckets: [{ key: 'report', doc_count: 1 }], doc_count_error_upper_bound: 0, sum_other_doc_count: 0, + }, + }, + ownerIds: { ownerIds: { value: 1 } }, + // The `FiltersAggregate` doesn't cover the case of a nested `AggregationsAggregationContainer`, in which `FiltersAggregate` + // would not have a `buckets` property, but rather a keyed property that's inferred from the request. + // @ts-expect-error + idleTasks: { + doc_count: 3, + overdue: { + doc_count: 2, + nonRecurring: { doc_count: 1 }, + taskTypes: { + buckets: [{ key: 'foo', doc_count: 1 }], + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + }, + }, + scheduleDensity: { buckets: [ - { - key: '3600s', - doc_count: 1, - }, - { - key: '60s', - doc_count: 1, - }, - { - key: '720m', - doc_count: 1, - }, + mockHistogram(0, 7 * 3000 + 500, 60 * 1000, 3000, [2, 2, 5, 0, 0, 0, 0, 0, 0, 1]), ], }, + }, + ...overrides, + }, + }); + + const mockAggregatedResult = () => asApiResponse(mockResult()); + const mockAggregatedResultWithUnknownTaskType = () => + asApiResponse( + mockResult({ taskType: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [ { - key: 'actions_telemetry', + key: 'unknownType', + doc_count: 1, + status: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [{ key: 'idle', doc_count: 1 }], + }, + }, + { + key: 'foo', doc_count: 2, status: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, - buckets: [ - { - key: 'idle', - doc_count: 2, - }, - ], + buckets: [{ key: 'idle', doc_count: 2 }], }, }, { - key: 'alerting_telemetry', + key: 'bar', doc_count: 1, status: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, - buckets: [ - { - key: 'idle', - doc_count: 1, - }, - ], + buckets: [{ key: 'idle', doc_count: 1 }], }, }, { - key: 'session_cleanup', + key: 'report', doc_count: 1, status: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, - buckets: [ - { - key: 'idle', - doc_count: 1, - }, - ], + buckets: [{ key: 'idle', doc_count: 1 }], }, }, ], }, - nonRecurringTasks: { - doc_count: 13, - }, - ownerIds: { - ownerIds: { - value: 1, - }, - }, - // The `FiltersAggregate` doesn't cover the case of a nested `AggregationsAggregationContainer`, in which `FiltersAggregate` - // would not have a `buckets` property, but rather a keyed property that's inferred from the request. - // @ts-expect-error - idleTasks: { - doc_count: 13, - overdue: { - doc_count: 6, - nonRecurring: { - doc_count: 6, - }, - }, - scheduleDensity: { - buckets: [ - mockHistogram(0, 7 * 3000 + 500, 60 * 1000, 3000, [2, 2, 5, 0, 0, 0, 0, 0, 0, 1]), - ], - }, - }, - }, - }); + }) + ); test('returns a summary of the workload by task type', async () => { const taskStore = taskStoreMock.create({}); taskStore.aggregate.mockResolvedValue(mockAggregatedResult()); - const workloadAggregator = createWorkloadAggregator( + const workloadAggregator = createWorkloadAggregator({ taskStore, - of(true), - 10, - 3000, - loggingSystemMock.create().get() - ); + elasticsearchAndSOAvailability$: of(true), + refreshInterval: 10, + pollInterval: 3000, + logger, + taskDefinitions: definitions, + }); + + return new Promise((resolve) => { + workloadAggregator.pipe(first()).subscribe((result) => { + expect(result.key).toEqual('workload'); + expect(result.value).toMatchObject({ + count: 4, + cost: 15, + task_types: { + foo: { count: 2, cost: 4, status: { idle: 2 } }, + bar: { count: 1, cost: 1, status: { idle: 1 } }, + report: { count: 1, cost: 10, status: { idle: 1 } }, + }, + }); + resolve(); + }); + }); + }); + + test('excludes unregistered task types from the summary', async () => { + const taskStore = taskStoreMock.create({}); + taskStore.aggregate.mockResolvedValue(mockAggregatedResultWithUnknownTaskType()); + + const workloadAggregator = createWorkloadAggregator({ + taskStore, + elasticsearchAndSOAvailability$: of(true), + refreshInterval: 10, + pollInterval: 3000, + logger, + taskDefinitions: definitions, + }); return new Promise((resolve) => { workloadAggregator.pipe(first()).subscribe((result) => { expect(result.key).toEqual('workload'); expect(result.value).toMatchObject({ count: 4, + cost: 15, task_types: { - actions_telemetry: { count: 2, status: { idle: 2 } }, - alerting_telemetry: { count: 1, status: { idle: 1 } }, - session_cleanup: { count: 1, status: { idle: 1 } }, + foo: { count: 2, cost: 4, status: { idle: 2 } }, + bar: { count: 1, cost: 1, status: { idle: 1 } }, + report: { count: 1, cost: 10, status: { idle: 1 } }, }, }); resolve(); @@ -336,13 +397,14 @@ describe('Workload Statistics Aggregator', () => { const availability$ = new Subject(); - const workloadAggregator = createWorkloadAggregator( + const workloadAggregator = createWorkloadAggregator({ taskStore, - availability$, - 10, - 3000, - loggingSystemMock.create().get() - ); + elasticsearchAndSOAvailability$: of(true), + refreshInterval: 10, + pollInterval: 3000, + logger, + taskDefinitions: definitions, + }); return new Promise(async (resolve, reject) => { try { @@ -350,25 +412,11 @@ describe('Workload Statistics Aggregator', () => { expect(result.key).toEqual('workload'); expect(result.value).toMatchObject({ count: 4, + cost: 15, task_types: { - actions_telemetry: { - count: 2, - status: { - idle: 2, - }, - }, - alerting_telemetry: { - count: 1, - status: { - idle: 1, - }, - }, - session_cleanup: { - count: 1, - status: { - idle: 1, - }, - }, + foo: { count: 2, cost: 4, status: { idle: 2 } }, + bar: { count: 1, cost: 1, status: { idle: 1 } }, + report: { count: 1, cost: 10, status: { idle: 1 } }, }, }); resolve(); @@ -389,19 +437,22 @@ describe('Workload Statistics Aggregator', () => { const taskStore = taskStoreMock.create({}); taskStore.aggregate.mockResolvedValue(mockAggregatedResult()); - const workloadAggregator = createWorkloadAggregator( + const workloadAggregator = createWorkloadAggregator({ taskStore, - of(true), - 10, - 3000, - loggingSystemMock.create().get() - ); + elasticsearchAndSOAvailability$: of(true), + refreshInterval: 10, + pollInterval: 3000, + logger, + taskDefinitions: definitions, + }); return new Promise((resolve) => { workloadAggregator.pipe(first()).subscribe((result) => { expect(result.key).toEqual('workload'); expect(result.value).toMatchObject({ - overdue: 6, + overdue: 2, + overdue_cost: 2, + overdue_non_recurring: 1, }); resolve(); }); @@ -412,13 +463,14 @@ describe('Workload Statistics Aggregator', () => { const taskStore = taskStoreMock.create({}); taskStore.aggregate.mockResolvedValue(mockAggregatedResult()); - const workloadAggregator = createWorkloadAggregator( + const workloadAggregator = createWorkloadAggregator({ taskStore, - of(true), - 10, - 3000, - loggingSystemMock.create().get() - ); + elasticsearchAndSOAvailability$: of(true), + refreshInterval: 10, + pollInterval: 3000, + logger, + taskDefinitions: definitions, + }); return new Promise((resolve) => { workloadAggregator.pipe(first()).subscribe((result) => { @@ -440,13 +492,14 @@ describe('Workload Statistics Aggregator', () => { const taskStore = taskStoreMock.create({}); taskStore.aggregate.mockResolvedValue(mockAggregatedResult()); - const workloadAggregator = createWorkloadAggregator( + const workloadAggregator = createWorkloadAggregator({ taskStore, - of(true), - 60 * 1000, - 3000, - loggingSystemMock.create().get() - ); + elasticsearchAndSOAvailability$: of(true), + refreshInterval: 60 * 1000, + pollInterval: 3000, + logger, + taskDefinitions: definitions, + }); return new Promise((resolve) => { workloadAggregator.pipe(first()).subscribe(() => { @@ -478,13 +531,14 @@ describe('Workload Statistics Aggregator', () => { const taskStore = taskStoreMock.create({}); taskStore.aggregate.mockResolvedValue(mockAggregatedResult()); - const workloadAggregator = createWorkloadAggregator( + const workloadAggregator = createWorkloadAggregator({ taskStore, - of(true), - 15 * 60 * 1000, - 3000, - loggingSystemMock.create().get() - ); + elasticsearchAndSOAvailability$: of(true), + refreshInterval: 15 * 60 * 1000, + pollInterval: 3000, + logger, + taskDefinitions: definitions, + }); return new Promise((resolve) => { workloadAggregator.pipe(first()).subscribe((result) => { @@ -517,42 +571,41 @@ describe('Workload Statistics Aggregator', () => { const taskStore = taskStoreMock.create({}); taskStore.aggregate .mockResolvedValueOnce( - mockAggregatedResult().then((res) => - setTaskTypeCount(res, 'alerting_telemetry', { - idle: 2, - }) - ) + mockAggregatedResult().then((res) => setTaskTypeCount(res, 'foo', { idle: 2 })) ) .mockRejectedValueOnce(new Error('Elasticsearch has gone poof')) .mockResolvedValueOnce( - mockAggregatedResult().then((res) => - setTaskTypeCount(res, 'alerting_telemetry', { - idle: 1, - failed: 1, - }) - ) + mockAggregatedResult().then((res) => setTaskTypeCount(res, 'foo', { idle: 1, failed: 1 })) ); - const logger = loggingSystemMock.create().get(); - const workloadAggregator = createWorkloadAggregator(taskStore, of(true), 10, 3000, logger); + const workloadAggregator = createWorkloadAggregator({ + taskStore, + elasticsearchAndSOAvailability$: of(true), + refreshInterval: 10, + pollInterval: 3000, + logger, + taskDefinitions: definitions, + }); return new Promise((resolve, reject) => { workloadAggregator.pipe(take(2), bufferCount(2)).subscribe((results) => { expect(results[0].key).toEqual('workload'); expect(results[0].value).toMatchObject({ - count: 5, + count: 4, + cost: 15, task_types: { - actions_telemetry: { count: 2, status: { idle: 2 } }, - alerting_telemetry: { count: 2, status: { idle: 2 } }, - session_cleanup: { count: 1, status: { idle: 1 } }, + bar: { count: 1, cost: 1, status: { idle: 1 } }, + report: { count: 1, cost: 10, status: { idle: 1 } }, + foo: { count: 2, cost: 4, status: { idle: 2 } }, }, }); expect(results[1].key).toEqual('workload'); expect(results[1].value).toMatchObject({ - count: 5, + count: 4, + cost: 15, task_types: { - actions_telemetry: { count: 2, status: { idle: 2 } }, - alerting_telemetry: { count: 2, status: { idle: 1, failed: 1 } }, - session_cleanup: { count: 1, status: { idle: 1 } }, + bar: { count: 1, cost: 1, status: { idle: 1 } }, + report: { count: 1, cost: 10, status: { idle: 1 } }, + foo: { count: 2, cost: 4, status: { idle: 1, failed: 1 } }, }, }); resolve(); @@ -567,49 +620,27 @@ describe('Workload Statistics Aggregator', () => { const taskStore = taskStoreMock.create({}); taskStore.aggregate.mockResolvedValue( asApiResponse({ - hits: { - hits: [], - max_score: 0, - total: { value: 4, relation: 'eq' }, - }, + hits: { hits: [], max_score: 0, total: { value: 4, relation: 'eq' } }, took: 1, timed_out: false, - _shards: { - total: 1, - successful: 1, - skipped: 1, - failed: 0, - }, + _shards: { total: 1, successful: 1, skipped: 1, failed: 0 }, aggregations: { schedule: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [ // repeats each cycle - { - key: `${pollingIntervalInSeconds}s`, - doc_count: 1, - }, - { - key: `10s`, // 6 times per minute - doc_count: 20, - }, - { - key: `60s`, // 1 times per minute - doc_count: 10, - }, - { - key: '15m', // 4 times per hour - doc_count: 90, - }, - { - key: '720m', // 2 times per day - doc_count: 10, - }, - { - key: '3h', // 8 times per day - doc_count: 100, - }, + { key: `${pollingIntervalInSeconds}s`, doc_count: 1 }, + // 6 times per minute + { key: `10s`, doc_count: 20 }, + // 1 times per minute + { key: `60s`, doc_count: 10 }, + // 4 times per hour + { key: '15m', doc_count: 90 }, + // 2 times per day + { key: '720m', doc_count: 10 }, + // 8 times per day + { key: '3h', doc_count: 100 }, ], }, taskType: { @@ -619,12 +650,13 @@ describe('Workload Statistics Aggregator', () => { }, nonRecurringTasks: { doc_count: 13, - }, - ownerIds: { - ownerIds: { - value: 3, + taskType: { + buckets: [{ key: 'report', doc_count: 13 }], + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, }, }, + ownerIds: { ownerIds: { value: 3 } }, // The `FiltersAggregate` doesn't cover the case of a nested `AggregationContainer`, in which `FiltersAggregate` // would not have a `buckets` property, but rather a keyed property that's inferred from the request. // @ts-expect-error @@ -632,8 +664,11 @@ describe('Workload Statistics Aggregator', () => { doc_count: 13, overdue: { doc_count: 6, - nonRecurring: { - doc_count: 0, + nonRecurring: { doc_count: 0 }, + taskTypes: { + buckets: [{ key: 'foo', doc_count: 6 }], + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, }, }, scheduleDensity: { @@ -646,13 +681,14 @@ describe('Workload Statistics Aggregator', () => { }) ); - const workloadAggregator = createWorkloadAggregator( + const workloadAggregator = createWorkloadAggregator({ taskStore, - of(true), - 10, - pollingIntervalInSeconds * 1000, - loggingSystemMock.create().get() - ); + elasticsearchAndSOAvailability$: of(true), + refreshInterval: 10, + pollInterval: pollingIntervalInSeconds * 1000, + logger, + taskDefinitions: definitions, + }); return new Promise((resolve) => { workloadAggregator.pipe(first()).subscribe((result) => { @@ -660,7 +696,7 @@ describe('Workload Statistics Aggregator', () => { expect(result.value).toMatchObject({ capacity_requirements: { - // these are buckets of required capacity, rather than aggregated requirmenets. + // these are buckets of required capacity, rather than aggregated requirements. per_minute: 150, per_hour: 360, per_day: 820, @@ -675,14 +711,14 @@ describe('Workload Statistics Aggregator', () => { const refreshInterval = 1000; const taskStore = taskStoreMock.create({}); - const logger = loggingSystemMock.create().get(); - const workloadAggregator = createWorkloadAggregator( + const workloadAggregator = createWorkloadAggregator({ taskStore, - of(true), + elasticsearchAndSOAvailability$: of(true), refreshInterval, - 3000, - logger - ); + pollInterval: 3000, + logger, + taskDefinitions: definitions, + }); return new Promise((resolve, reject) => { let errorWasThrowAt = 0; @@ -694,9 +730,7 @@ describe('Workload Statistics Aggregator', () => { reject(new Error(`Elasticsearch is still poof`)); } - return setTaskTypeCount(await mockAggregatedResult(), 'alerting_telemetry', { - idle: 2, - }); + return setTaskTypeCount(await mockAggregatedResult(), 'foo', { idle: 2 }); }); workloadAggregator.pipe(take(2), bufferCount(2)).subscribe((results) => { @@ -799,7 +833,7 @@ describe('estimateRecurringTaskScheduling', () => { }); describe('padBuckets', () => { - test('returns zeroed out bucklets when there are no buckets in the histogram', async () => { + test('returns zeroed out buckets when there are no buckets in the histogram', async () => { expect( padBuckets(10, 3000, { key: '2020-10-02T19:47:28.128Z-2020-10-02T19:48:28.128Z', diff --git a/x-pack/plugins/task_manager/server/monitoring/workload_statistics.ts b/x-pack/plugins/task_manager/server/monitoring/workload_statistics.ts index 6c372ce0fc453..b62ca8e8169e9 100644 --- a/x-pack/plugins/task_manager/server/monitoring/workload_statistics.ts +++ b/x-pack/plugins/task_manager/server/monitoring/workload_statistics.ts @@ -16,7 +16,9 @@ import { AggregatedStatProvider } from '../lib/runtime_statistics_aggregator'; import { parseIntervalAsSecond, asInterval, parseIntervalAsMillisecond } from '../lib/intervals'; import { HealthStatus } from './monitoring_stats_stream'; import { TaskStore } from '../task_store'; -import { createRunningAveragedStat } from './task_run_calcultors'; +import { createRunningAveragedStat } from './task_run_calculators'; +import { TaskTypeDictionary } from '../task_type_dictionary'; +import { TaskCost } from '../task'; interface StatusStat extends JsonObject { [status: string]: number; @@ -24,16 +26,20 @@ interface StatusStat extends JsonObject { interface TaskTypeStat extends JsonObject { [taskType: string]: { count: number; + cost: number; status: StatusStat; }; } interface RawWorkloadStat extends JsonObject { count: number; + cost: number; task_types: TaskTypeStat; schedule: Array<[string, number]>; non_recurring: number; + non_recurring_cost: number; overdue: number; + overdue_cost: number; overdue_non_recurring: number; estimated_schedule_density: number[]; capacity_requirements: CapacityRequirements; @@ -109,22 +115,34 @@ type ScheduleDensityResult = AggregationResultOf< type ScheduledIntervals = ScheduleDensityResult['histogram']['buckets'][0]; // Set an upper bound just in case a customer sets a really high refresh rate -const MAX_SHCEDULE_DENSITY_BUCKETS = 50; +const MAX_SCHEDULE_DENSITY_BUCKETS = 50; + +interface CreateWorkloadAggregatorOpts { + taskStore: TaskStore; + elasticsearchAndSOAvailability$: Observable; + refreshInterval: number; + pollInterval: number; + logger: Logger; + taskDefinitions: TaskTypeDictionary; +} -export function createWorkloadAggregator( - taskStore: TaskStore, - elasticsearchAndSOAvailability$: Observable, - refreshInterval: number, - pollInterval: number, - logger: Logger -): AggregatedStatProvider { +export function createWorkloadAggregator({ + taskStore, + elasticsearchAndSOAvailability$, + refreshInterval, + pollInterval, + logger, + taskDefinitions, +}: CreateWorkloadAggregatorOpts): AggregatedStatProvider { // calculate scheduleDensity going two refreshIntervals or 1 minute into into the future // (the longer of the two) const scheduleDensityBuckets = Math.min( Math.max(Math.round(60000 / pollInterval), Math.round((refreshInterval * 2) / pollInterval)), - MAX_SHCEDULE_DENSITY_BUCKETS + MAX_SCHEDULE_DENSITY_BUCKETS ); + const totalNumTaskDefinitions = taskDefinitions.getAllTypes().length; + const taskTypeTermAggSize = Math.min(totalNumTaskDefinitions, 10000); const ownerIdsQueue = createRunningAveragedStat(scheduleDensityBuckets); return combineLatest([timer(0, refreshInterval), elasticsearchAndSOAvailability$]).pipe( @@ -133,39 +151,24 @@ export function createWorkloadAggregator( taskStore.aggregate({ aggs: { taskType: { - terms: { size: 100, field: 'task.taskType' }, - aggs: { - status: { - terms: { field: 'task.status' }, - }, - }, + terms: { size: taskTypeTermAggSize, field: 'task.taskType' }, + aggs: { status: { terms: { field: 'task.status' } } }, }, schedule: { terms: { field: 'task.schedule.interval', size: 100 }, }, nonRecurringTasks: { - missing: { field: 'task.schedule' }, - }, - ownerIds: { - filter: { - range: { - 'task.startedAt': { - gte: 'now-1w/w', - }, - }, - }, + missing: { field: 'task.schedule.interval' }, aggs: { - ownerIds: { - cardinality: { - field: 'task.ownerId', - }, - }, + taskType: { terms: { size: taskTypeTermAggSize, field: 'task.taskType' } }, }, }, + ownerIds: { + filter: { range: { 'task.startedAt': { gte: 'now-1w/w' } } }, + aggs: { ownerIds: { cardinality: { field: 'task.ownerId' } } }, + }, idleTasks: { - filter: { - term: { 'task.status': 'idle' }, - }, + filter: { term: { 'task.status': 'idle' } }, aggs: { scheduleDensity: { // create a window of upcoming tasks @@ -187,7 +190,7 @@ export function createWorkloadAggregator( field: 'task.runAt', fixed_interval: asInterval(pollInterval), }, - // break down each bucket in the historgram by schedule + // break down each bucket in the histogram by schedule aggs: { interval: { terms: { field: 'task.schedule.interval' }, @@ -197,15 +200,10 @@ export function createWorkloadAggregator( }, }, overdue: { - filter: { - range: { - 'task.runAt': { lt: 'now' }, - }, - }, + filter: { range: { 'task.runAt': { lt: 'now' } } }, aggs: { - nonRecurring: { - missing: { field: 'task.schedule' }, - }, + taskTypes: { terms: { size: taskTypeTermAggSize, field: 'task.taskType' } }, + nonRecurring: { missing: { field: 'task.schedule.interval' } }, }, }, }, @@ -226,11 +224,13 @@ export function createWorkloadAggregator( const taskTypes = aggregations.taskType.buckets; const nonRecurring = aggregations.nonRecurringTasks.doc_count; + const nonRecurringTaskTypes = aggregations.nonRecurringTasks.taskType.buckets; const ownerIds = aggregations.ownerIds.ownerIds.value; const { overdue: { doc_count: overdue, + taskTypes: { buckets: taskTypesOverdue = [] } = {}, nonRecurring: { doc_count: overdueNonRecurring }, }, scheduleDensity: { buckets: [scheduleDensity] = [] } = {}, @@ -243,6 +243,7 @@ export function createWorkloadAggregator( asSeconds: parseIntervalAsSecond(schedule.key as string), count: schedule.doc_count, }; + accm.schedules.push(parsedSchedule); if (parsedSchedule.asSeconds <= 60) { accm.cadence.perMinute += @@ -257,11 +258,7 @@ export function createWorkloadAggregator( return accm; }, { - cadence: { - perMinute: 0, - perHour: 0, - perDay: 0, - }, + cadence: { perMinute: 0, perHour: 0, perDay: 0 }, schedules: [] as Array<{ interval: string; asSeconds: number; @@ -270,20 +267,42 @@ export function createWorkloadAggregator( } ); + const totalNonRecurringCost = getTotalCost(nonRecurringTaskTypes, taskDefinitions); + const totalOverdueCost = getTotalCost(taskTypesOverdue, taskDefinitions); + + let totalCost = 0; + const taskTypeSummary = taskTypes.reduce((acc, bucket) => { + const value = bucket as TaskTypeWithStatusBucket; + const taskDef = taskDefinitions.get(value.key as string); + if (taskDef) { + const cost = value.doc_count * taskDef?.cost ?? TaskCost.Normal; + + totalCost += cost; + return Object.assign(acc, { + [value.key as string]: { + count: value.doc_count, + cost, + status: mapValues(keyBy(value.status.buckets, 'key'), 'doc_count'), + }, + }); + } else { + // task type is not registered with dictionary, do not add to summary + return acc; + } + }, {}); + const summary: WorkloadStat = { count, - task_types: mapValues(keyBy(taskTypes, 'key'), ({ doc_count: docCount, status }) => { - return { - count: docCount, - status: mapValues(keyBy(status.buckets, 'key'), 'doc_count'), - }; - }), + cost: totalCost, + task_types: taskTypeSummary, non_recurring: nonRecurring, + non_recurring_cost: totalNonRecurringCost, owner_ids: ownerIdsQueue(ownerIds), schedule: schedules .sort((scheduleLeft, scheduleRight) => scheduleLeft.asSeconds - scheduleRight.asSeconds) .map((schedule) => [schedule.interval, schedule.count]), overdue, + overdue_cost: totalOverdueCost, overdue_non_recurring: overdueNonRecurring, estimated_schedule_density: padBuckets( scheduleDensityBuckets, @@ -457,40 +476,37 @@ export interface WorkloadAggregationResponse { taskType: TaskTypeAggregation; schedule: ScheduleAggregation; idleTasks: IdleTasksAggregation; - nonRecurringTasks: { - doc_count: number; - }; - ownerIds: { - ownerIds: { - value: number; - }; - }; + nonRecurringTasks: { doc_count: number; taskType: TaskTypeAggregation }; + ownerIds: { ownerIds: { value: number } }; [otherAggs: string]: estypes.AggregationsAggregate; } + +export type TaskTypeWithStatusBucket = TaskTypeBucket & { + status: { + buckets: Array<{ + doc_count: number; + key: string | number; + }>; + doc_count_error_upper_bound?: number | undefined; + sum_other_doc_count?: number | undefined; + }; +}; + +export interface TaskTypeBucket { + doc_count: number; + key: string | number; +} + // @ts-expect-error key doesn't accept a string export interface TaskTypeAggregation extends estypes.AggregationsFiltersAggregate { - buckets: Array<{ - doc_count: number; - key: string | number; - status: { - buckets: Array<{ - doc_count: number; - key: string | number; - }>; - doc_count_error_upper_bound?: number | undefined; - sum_other_doc_count?: number | undefined; - }; - }>; + buckets: Array; doc_count_error_upper_bound?: number | undefined; sum_other_doc_count?: number | undefined; } // @ts-expect-error key doesn't accept a string export interface ScheduleAggregation extends estypes.AggregationsFiltersAggregate { - buckets: Array<{ - doc_count: number; - key: string | number; - }>; + buckets: Array<{ doc_count: number; key: string | number }>; doc_count_error_upper_bound?: number | undefined; sum_other_doc_count?: number | undefined; } @@ -518,9 +534,8 @@ export interface IdleTasksAggregation extends estypes.AggregationsFiltersAggrega }; overdue: { doc_count: number; - nonRecurring: { - doc_count: number; - }; + nonRecurring: { doc_count: number }; + taskTypes: TaskTypeAggregation; }; } @@ -537,3 +552,16 @@ interface DateRangeBucket { from_as_string?: string; doc_count: number; } + +function getTotalCost(taskTypeBuckets: TaskTypeBucket[], definitions: TaskTypeDictionary): number { + let cost = 0; + for (const bucket of taskTypeBuckets) { + const taskDef = definitions.get(bucket.key as string); + if (taskDef) { + cost += bucket.doc_count * taskDef?.cost ?? TaskCost.Normal; + } else { + // task type is not registered with dictionary, do not add to cost + } + } + return cost; +} diff --git a/x-pack/plugins/task_manager/server/plugin.test.ts b/x-pack/plugins/task_manager/server/plugin.test.ts index 7b80920a57559..353062dfec4fc 100644 --- a/x-pack/plugins/task_manager/server/plugin.test.ts +++ b/x-pack/plugins/task_manager/server/plugin.test.ts @@ -11,6 +11,7 @@ import { TaskManagerConfig } from './config'; import { Subject } from 'rxjs'; import { bufferCount, take } from 'rxjs'; import { CoreStatus, ServiceStatusLevels } from '@kbn/core/server'; +import { cloudMock } from '@kbn/cloud-plugin/public/mocks'; import { taskPollingLifecycleMock } from './polling_lifecycle.mock'; import { TaskPollingLifecycle } from './polling_lifecycle'; import type { TaskPollingLifecycle as TaskPollingLifecycleClass } from './polling_lifecycle'; @@ -38,7 +39,6 @@ jest.mock('./ephemeral_task_lifecycle', () => { const coreStart = coreMock.createStart(); const pluginInitializerContextParams = { - max_workers: 10, max_attempts: 9, poll_interval: 3000, version_conflict_threshold: 80, @@ -148,7 +148,9 @@ describe('TaskManagerPlugin', () => { pluginInitializerContext.node.roles.backgroundTasks = true; const taskManagerPlugin = new TaskManagerPlugin(pluginInitializerContext); taskManagerPlugin.setup(coreMock.createSetup(), { usageCollection: undefined }); - taskManagerPlugin.start(coreStart); + taskManagerPlugin.start(coreStart, { + cloud: cloudMock.createStart(), + }); expect(TaskPollingLifecycle as jest.Mock).toHaveBeenCalledTimes(1); expect( @@ -163,7 +165,9 @@ describe('TaskManagerPlugin', () => { pluginInitializerContext.node.roles.backgroundTasks = false; const taskManagerPlugin = new TaskManagerPlugin(pluginInitializerContext); taskManagerPlugin.setup(coreMock.createSetup(), { usageCollection: undefined }); - taskManagerPlugin.start(coreStart); + taskManagerPlugin.start(coreStart, { + cloud: cloudMock.createStart(), + }); expect(TaskPollingLifecycle as jest.Mock).not.toHaveBeenCalled(); expect( diff --git a/x-pack/plugins/task_manager/server/plugin.ts b/x-pack/plugins/task_manager/server/plugin.ts index 1926b48b31ea6..23ef12605baa2 100644 --- a/x-pack/plugins/task_manager/server/plugin.ts +++ b/x-pack/plugins/task_manager/server/plugin.ts @@ -18,6 +18,7 @@ import { ServiceStatusLevels, CoreStatus, } from '@kbn/core/server'; +import type { CloudStart } from '@kbn/cloud-plugin/server'; import { registerDeleteInactiveNodesTaskDefinition, scheduleDeleteInactiveNodesTaskDefinition, @@ -43,6 +44,7 @@ import { setupIntervalLogging } from './lib/log_health_metrics'; import { metricsStream, Metrics } from './metrics'; import { TaskManagerMetricsCollector } from './metrics/task_metrics_collector'; import { TaskPartitioner } from './lib/task_partitioner'; +import { getDefaultCapacity } from './lib/get_default_capacity'; export interface TaskManagerSetupContract { /** @@ -76,6 +78,10 @@ export type TaskManagerStartContract = Pick< getRegisteredTypes: () => string[]; }; +export interface TaskManagerPluginStart { + cloud?: CloudStart; +} + const LogHealthForBackgroundTasksOnlyMinutes = 60; export class TaskManagerPlugin @@ -99,6 +105,7 @@ export class TaskManagerPlugin private taskManagerMetricsCollector?: TaskManagerMetricsCollector; private nodeRoles: PluginInitializerContext['node']['roles']; private kibanaDiscoveryService?: KibanaDiscoveryService; + private heapSizeLimit: number = 0; constructor(private readonly initContext: PluginInitializerContext) { this.initContext = initContext; @@ -122,6 +129,13 @@ export class TaskManagerPlugin ): TaskManagerSetupContract { this.elasticsearchAndSOAvailability$ = getElasticsearchAndSOAvailability(core.status.core$); + core.metrics + .getOpsMetrics$() + .pipe(distinctUntilChanged()) + .subscribe((metrics) => { + this.heapSizeLimit = metrics.process.memory.heap.size_limit; + }); + setupSavedObjects(core.savedObjects, this.config); this.taskManagerId = this.initContext.env.instanceUuid; @@ -232,12 +246,10 @@ export class TaskManagerPlugin }; } - public start({ - savedObjects, - elasticsearch, - executionContext, - docLinks, - }: CoreStart): TaskManagerStartContract { + public start( + { savedObjects, elasticsearch, executionContext, docLinks }: CoreStart, + { cloud }: TaskManagerPluginStart + ): TaskManagerStartContract { const savedObjectsRepository = savedObjects.createInternalRepository([ TASK_SO_NAME, BACKGROUND_TASK_NODE_SO_NAME, @@ -267,11 +279,31 @@ export class TaskManagerPlugin requestTimeouts: this.config.request_timeouts, }); + const isServerless = this.initContext.env.packageInfo.buildFlavor === 'serverless'; + + const defaultCapacity = getDefaultCapacity({ + claimStrategy: this.config?.claim_strategy, + heapSizeLimit: this.heapSizeLimit, + isCloud: cloud?.isCloudEnabled ?? false, + isServerless, + isBackgroundTaskNodeOnly: this.isNodeBackgroundTasksOnly(), + }); + + this.logger.info( + `Task manager isCloud=${ + cloud?.isCloudEnabled ?? false + } isServerless=${isServerless} claimStrategy=${ + this.config!.claim_strategy + } isBackgroundTaskNodeOnly=${this.isNodeBackgroundTasksOnly()} heapSizeLimit=${ + this.heapSizeLimit + } defaultCapacity=${defaultCapacity}` + ); + const managedConfiguration = createManagedConfiguration({ - logger: this.logger, + config: this.config!, errors$: taskStore.errors$, - startingMaxWorkers: this.config!.max_workers, - startingPollInterval: this.config!.poll_interval, + defaultCapacity, + logger: this.logger, }); // Only poll for tasks if configured to run tasks @@ -310,16 +342,17 @@ export class TaskManagerPlugin }); } - createMonitoringStats( + createMonitoringStats({ taskStore, - this.elasticsearchAndSOAvailability$!, - this.config!, - managedConfiguration, - this.logger, - this.adHocTaskCounter, - this.taskPollingLifecycle, - this.ephemeralTaskLifecycle - ).subscribe((stat) => this.monitoringStats$.next(stat)); + elasticsearchAndSOAvailability$: this.elasticsearchAndSOAvailability$!, + config: this.config!, + managedConfig: managedConfiguration, + logger: this.logger, + adHocTaskCounter: this.adHocTaskCounter, + taskDefinitions: this.definitions, + taskPollingLifecycle: this.taskPollingLifecycle, + ephemeralTaskLifecycle: this.ephemeralTaskLifecycle, + }).subscribe((stat) => this.monitoringStats$.next(stat)); metricsStream({ config: this.config!, diff --git a/x-pack/plugins/task_manager/server/polling/delay_on_claim_conflicts.test.ts b/x-pack/plugins/task_manager/server/polling/delay_on_claim_conflicts.test.ts index f06c43bc15587..11741aeadcf2d 100644 --- a/x-pack/plugins/task_manager/server/polling/delay_on_claim_conflicts.test.ts +++ b/x-pack/plugins/task_manager/server/polling/delay_on_claim_conflicts.test.ts @@ -22,10 +22,10 @@ describe('delayOnClaimConflicts', () => { 'initializes with a delay of 0', fakeSchedulers(async () => { const pollInterval = 100; - const maxWorkers = 10; + const capacity = 10; const taskLifecycleEvents$ = new Subject(); const delays = delayOnClaimConflicts( - of(maxWorkers), + of(capacity), of(pollInterval), taskLifecycleEvents$, 80, @@ -42,11 +42,11 @@ describe('delayOnClaimConflicts', () => { 'emits a random delay whenever p50 of claim clashes exceed 80% of available max_workers', fakeSchedulers(async () => { const pollInterval = 100; - const maxWorkers = 10; + const capacity = 10; const taskLifecycleEvents$ = new Subject(); const delays$ = firstValueFrom( - delayOnClaimConflicts(of(maxWorkers), of(pollInterval), taskLifecycleEvents$, 80, 2).pipe( + delayOnClaimConflicts(of(capacity), of(pollInterval), taskLifecycleEvents$, 80, 2).pipe( take(2), bufferCount(2) ) @@ -60,7 +60,6 @@ describe('delayOnClaimConflicts', () => { tasksUpdated: 0, tasksConflicted: 8, tasksClaimed: 0, - tasksRejected: 0, }, docs: [], }) @@ -94,7 +93,6 @@ describe('delayOnClaimConflicts', () => { tasksUpdated: 0, tasksConflicted: 8, tasksClaimed: 0, - tasksRejected: 0, }, docs: [], }) @@ -111,7 +109,6 @@ describe('delayOnClaimConflicts', () => { tasksUpdated: 0, tasksConflicted: 10, tasksClaimed: 0, - tasksRejected: 0, }, docs: [], }) @@ -137,18 +134,14 @@ describe('delayOnClaimConflicts', () => { 'doesnt emit a new delay when conflicts have reduced', fakeSchedulers(async () => { const pollInterval = 100; - const maxWorkers = 10; + const capacity = 10; const taskLifecycleEvents$ = new Subject(); const handler = jest.fn(); - delayOnClaimConflicts( - of(maxWorkers), - of(pollInterval), - taskLifecycleEvents$, - 80, - 2 - ).subscribe(handler); + delayOnClaimConflicts(of(capacity), of(pollInterval), taskLifecycleEvents$, 80, 2).subscribe( + handler + ); await sleep(0); expect(handler).toHaveBeenCalledWith(0); @@ -161,7 +154,6 @@ describe('delayOnClaimConflicts', () => { tasksUpdated: 0, tasksConflicted: 8, tasksClaimed: 0, - tasksRejected: 0, }, docs: [], }) @@ -182,7 +174,6 @@ describe('delayOnClaimConflicts', () => { tasksUpdated: 0, tasksConflicted: 7, tasksClaimed: 0, - tasksRejected: 0, }, docs: [], }) @@ -201,7 +192,6 @@ describe('delayOnClaimConflicts', () => { tasksUpdated: 0, tasksConflicted: 9, tasksClaimed: 0, - tasksRejected: 0, }, docs: [], }) diff --git a/x-pack/plugins/task_manager/server/polling/delay_on_claim_conflicts.ts b/x-pack/plugins/task_manager/server/polling/delay_on_claim_conflicts.ts index f491d58fc59ee..21b16b1a8d5c5 100644 --- a/x-pack/plugins/task_manager/server/polling/delay_on_claim_conflicts.ts +++ b/x-pack/plugins/task_manager/server/polling/delay_on_claim_conflicts.ts @@ -19,13 +19,14 @@ import { ManagedConfiguration } from '../lib/create_managed_configuration'; import { TaskLifecycleEvent } from '../polling_lifecycle'; import { isTaskPollingCycleEvent } from '../task_events'; import { ClaimAndFillPoolResult } from '../lib/fill_pool'; -import { createRunningAveragedStat } from '../monitoring/task_run_calcultors'; +import { createRunningAveragedStat } from '../monitoring/task_run_calculators'; +import { getCapacityInWorkers } from '../task_pool'; /** * Emits a delay amount in ms to apply to polling whenever the task store exceeds a threshold of claim claimClashes */ export function delayOnClaimConflicts( - maxWorkersConfiguration$: ManagedConfiguration['maxWorkersConfiguration$'], + capacityConfiguration$: ManagedConfiguration['capacityConfiguration$'], pollIntervalConfiguration$: ManagedConfiguration['pollIntervalConfiguration$'], taskLifecycleEvents$: Observable, claimClashesPercentageThreshold: number, @@ -37,7 +38,7 @@ export function delayOnClaimConflicts( merge( of(0), combineLatest([ - maxWorkersConfiguration$, + capacityConfiguration$, pollIntervalConfiguration$, taskLifecycleEvents$.pipe( map>((taskEvent: TaskLifecycleEvent) => @@ -51,7 +52,10 @@ export function delayOnClaimConflicts( map((claimClashes: Option) => (claimClashes as Some).value) ), ]).pipe( - map(([maxWorkers, pollInterval, latestClaimConflicts]) => { + map(([capacity, pollInterval, latestClaimConflicts]) => { + // convert capacity to maxWorkers + const maxWorkers = getCapacityInWorkers(capacity); + // add latest claimConflict count to queue claimConflictQueue(latestClaimConflicts); diff --git a/x-pack/plugins/task_manager/server/polling_lifecycle.test.ts b/x-pack/plugins/task_manager/server/polling_lifecycle.test.ts index baf45cb65ea1e..e804f1c166cee 100644 --- a/x-pack/plugins/task_manager/server/polling_lifecycle.test.ts +++ b/x-pack/plugins/task_manager/server/polling_lifecycle.test.ts @@ -20,6 +20,8 @@ import { asOk, Err, isErr, isOk, Result } from './lib/result_type'; import { FillPoolResult } from './lib/fill_pool'; import { ElasticsearchResponseError } from './lib/identify_es_error'; import { executionContextServiceMock } from '@kbn/core/server/mocks'; +import { TaskCost } from './task'; +import { CLAIM_STRATEGY_MGET } from './config'; import { TaskPartitioner } from './lib/task_partitioner'; import { KibanaDiscoveryService } from './kibana_discovery_service'; @@ -44,7 +46,6 @@ describe('TaskPollingLifecycle', () => { const taskManagerOpts = { config: { enabled: true, - max_workers: 10, index: 'foo', max_attempts: 9, poll_interval: 6000000, @@ -90,7 +91,8 @@ describe('TaskPollingLifecycle', () => { unusedTypes: [], definitions: new TaskTypeDictionary(taskManagerLogger), middleware: createInitialMiddleware(), - maxWorkersConfiguration$: of(100), + startingCapacity: 20, + capacityConfiguration$: of(20), pollIntervalConfiguration$: of(100), executionContext, taskPartitioner: new TaskPartitioner('test', {} as KibanaDiscoveryService), @@ -105,12 +107,23 @@ describe('TaskPollingLifecycle', () => { afterEach(() => clock.restore()); describe('start', () => { + taskManagerOpts.definitions.registerTaskDefinitions({ + report: { + title: 'report', + maxConcurrency: 1, + cost: TaskCost.ExtraLarge, + createTaskRunner: jest.fn(), + }, + quickReport: { + title: 'quickReport', + maxConcurrency: 5, + createTaskRunner: jest.fn(), + }, + }); + test('begins polling once the ES and SavedObjects services are available', () => { const elasticsearchAndSOAvailability$ = new Subject(); - new TaskPollingLifecycle({ - ...taskManagerOpts, - elasticsearchAndSOAvailability$, - }); + new TaskPollingLifecycle({ ...taskManagerOpts, elasticsearchAndSOAvailability$ }); clock.tick(150); expect(mockTaskClaiming.claimAvailableTasksIfCapacityIsAvailable).not.toHaveBeenCalled(); @@ -121,56 +134,71 @@ describe('TaskPollingLifecycle', () => { expect(mockTaskClaiming.claimAvailableTasksIfCapacityIsAvailable).toHaveBeenCalled(); }); - test('provides TaskClaiming with the capacity available', () => { + test('provides TaskClaiming with the capacity available when strategy = CLAIM_STRATEGY_DEFAULT', () => { const elasticsearchAndSOAvailability$ = new Subject(); - const maxWorkers$ = new Subject(); - taskManagerOpts.definitions.registerTaskDefinitions({ - report: { - title: 'report', - maxConcurrency: 1, - createTaskRunner: jest.fn(), - }, - quickReport: { - title: 'quickReport', - maxConcurrency: 5, - createTaskRunner: jest.fn(), - }, - }); + const capacity$ = new Subject(); new TaskPollingLifecycle({ ...taskManagerOpts, elasticsearchAndSOAvailability$, - maxWorkersConfiguration$: maxWorkers$, + capacityConfiguration$: capacity$, }); const taskClaimingGetCapacity = (TaskClaiming as jest.Mock).mock - .calls[0][0].getCapacity; + .calls[0][0].getAvailableCapacity; - maxWorkers$.next(20); - expect(taskClaimingGetCapacity()).toEqual(20); + capacity$.next(40); + expect(taskClaimingGetCapacity()).toEqual(40); expect(taskClaimingGetCapacity('report')).toEqual(1); expect(taskClaimingGetCapacity('quickReport')).toEqual(5); - maxWorkers$.next(30); - expect(taskClaimingGetCapacity()).toEqual(30); + capacity$.next(60); + expect(taskClaimingGetCapacity()).toEqual(60); expect(taskClaimingGetCapacity('report')).toEqual(1); expect(taskClaimingGetCapacity('quickReport')).toEqual(5); - maxWorkers$.next(2); - expect(taskClaimingGetCapacity()).toEqual(2); + capacity$.next(4); + expect(taskClaimingGetCapacity()).toEqual(4); expect(taskClaimingGetCapacity('report')).toEqual(1); - expect(taskClaimingGetCapacity('quickReport')).toEqual(2); + expect(taskClaimingGetCapacity('quickReport')).toEqual(4); }); - }); - describe('stop', () => { - test('stops polling once the ES and SavedObjects services become unavailable', () => { + test('provides TaskClaiming with the capacity available when strategy = CLAIM_STRATEGY_MGET', () => { const elasticsearchAndSOAvailability$ = new Subject(); + const capacity$ = new Subject(); + new TaskPollingLifecycle({ - elasticsearchAndSOAvailability$, ...taskManagerOpts, + config: { ...taskManagerOpts.config, claim_strategy: CLAIM_STRATEGY_MGET }, + elasticsearchAndSOAvailability$, + capacityConfiguration$: capacity$, }); + const taskClaimingGetCapacity = (TaskClaiming as jest.Mock).mock + .calls[0][0].getAvailableCapacity; + + capacity$.next(40); + expect(taskClaimingGetCapacity()).toEqual(80); + expect(taskClaimingGetCapacity('report')).toEqual(10); + expect(taskClaimingGetCapacity('quickReport')).toEqual(10); + + capacity$.next(60); + expect(taskClaimingGetCapacity()).toEqual(120); + expect(taskClaimingGetCapacity('report')).toEqual(10); + expect(taskClaimingGetCapacity('quickReport')).toEqual(10); + + capacity$.next(4); + expect(taskClaimingGetCapacity()).toEqual(8); + expect(taskClaimingGetCapacity('report')).toEqual(8); + expect(taskClaimingGetCapacity('quickReport')).toEqual(8); + }); + }); + + describe('stop', () => { + test('stops polling once the ES and SavedObjects services become unavailable', () => { + const elasticsearchAndSOAvailability$ = new Subject(); + new TaskPollingLifecycle({ elasticsearchAndSOAvailability$, ...taskManagerOpts }); + elasticsearchAndSOAvailability$.next(true); clock.tick(150); @@ -216,7 +244,7 @@ describe('TaskPollingLifecycle', () => { of( asOk({ docs: [], - stats: { tasksUpdated: 0, tasksConflicted: 0, tasksClaimed: 0, tasksRejected: 0 }, + stats: { tasksUpdated: 0, tasksConflicted: 0, tasksClaimed: 0 }, }) ) ); @@ -298,7 +326,47 @@ describe('TaskPollingLifecycle', () => { of( asOk({ docs: [], - stats: { tasksUpdated: 0, tasksConflicted: 0, tasksClaimed: 0, tasksRejected: 0 }, + stats: { tasksUpdated: 0, tasksConflicted: 0, tasksClaimed: 0 }, + }) + ) + ); + const elasticsearchAndSOAvailability$ = new Subject(); + const taskPollingLifecycle = new TaskPollingLifecycle({ + ...taskManagerOpts, + elasticsearchAndSOAvailability$, + }); + + const emittedEvents: TaskLifecycleEvent[] = []; + + taskPollingLifecycle.events.subscribe((event: TaskLifecycleEvent) => + emittedEvents.push(event) + ); + + elasticsearchAndSOAvailability$.next(true); + expect(mockTaskClaiming.claimAvailableTasksIfCapacityIsAvailable).toHaveBeenCalled(); + await retryUntil('workerUtilizationEvent emitted', () => { + return !!emittedEvents.find( + (event: TaskLifecycleEvent) => event.id === 'workerUtilization' + ); + }); + + const workerUtilizationEvent = emittedEvents.find( + (event: TaskLifecycleEvent) => event.id === 'workerUtilization' + ); + expect(workerUtilizationEvent).toEqual({ + id: 'workerUtilization', + type: 'TASK_MANAGER_STAT', + event: { tag: 'ok', value: 0 }, + }); + }); + + test('should set utilization to max when capacity is not fully reached but there are tasks left unclaimed', async () => { + clock.restore(); + mockTaskClaiming.claimAvailableTasksIfCapacityIsAvailable.mockImplementation(() => + of( + asOk({ + docs: [], + stats: { tasksUpdated: 0, tasksConflicted: 0, tasksClaimed: 0, tasksLeftUnclaimed: 2 }, }) ) ); @@ -321,6 +389,15 @@ describe('TaskPollingLifecycle', () => { (event: TaskLifecycleEvent) => event.id === 'workerUtilization' ); }); + + const workerUtilizationEvent = emittedEvents.find( + (event: TaskLifecycleEvent) => event.id === 'workerUtilization' + ); + expect(workerUtilizationEvent).toEqual({ + id: 'workerUtilization', + type: 'TASK_MANAGER_STAT', + event: { tag: 'ok', value: 100 }, + }); }); test('should emit event when polling error occurs', async () => { diff --git a/x-pack/plugins/task_manager/server/polling_lifecycle.ts b/x-pack/plugins/task_manager/server/polling_lifecycle.ts index 3b9c5621da0b9..f13a7ad20806c 100644 --- a/x-pack/plugins/task_manager/server/polling_lifecycle.ts +++ b/x-pack/plugins/task_manager/server/polling_lifecycle.ts @@ -45,6 +45,8 @@ import { TaskClaiming } from './queries/task_claiming'; import { ClaimOwnershipResult } from './task_claimers'; import { TaskPartitioner } from './lib/task_partitioner'; +const MAX_BUFFER_OPERATIONS = 100; + export interface ITaskEventEmitter { get events(): Observable; } @@ -101,7 +103,7 @@ export class TaskPollingLifecycle implements ITaskEventEmitter this.events$.next(event); this.bufferedStore = new BufferedTaskStore(this.store, { - bufferMaxOperations: config.max_workers, + bufferMaxOperations: MAX_BUFFER_OPERATIONS, logger, }); this.pool = new TaskPool({ logger, - maxWorkers$: maxWorkersConfiguration$, + strategy: config.claim_strategy, + capacity$: capacityConfiguration$, + definitions: this.definitions, }); this.pool.load.subscribe(emitEvent); @@ -142,17 +146,7 @@ export class TaskPollingLifecycle implements ITaskEventEmitter - taskType && this.definitions.get(taskType)?.maxConcurrency - ? Math.max( - Math.min( - this.pool.availableWorkers, - this.definitions.get(taskType)!.maxConcurrency! - - this.pool.getOccupiedWorkersByType(taskType) - ), - 0 - ) - : this.pool.availableWorkers, + getAvailableCapacity: (taskType?: string) => this.pool.availableCapacity(taskType), taskPartitioner, }); // pipe taskClaiming events into the lifecycle event stream @@ -163,7 +157,7 @@ export class TaskPollingLifecycle implements ITaskEventEmitter | undefined; if (claimStrategy === CLAIM_STRATEGY_DEFAULT) { pollIntervalDelay$ = delayOnClaimConflicts( - maxWorkersConfiguration$, + capacityConfiguration$, pollIntervalConfiguration$, this.events$, config.version_conflict_threshold, @@ -177,19 +171,22 @@ export class TaskPollingLifecycle implements ITaskEventEmitter { - const capacity = this.pool.availableWorkers; + const capacity = this.pool.availableCapacity(); if (!capacity) { + const usedCapacityPercentage = this.pool.usedCapacityPercentage; + // if there isn't capacity, emit a load event so that we can expose how often // high load causes the poller to skip work (work isn't called when there is no capacity) - this.emitEvent(asTaskManagerStatEvent('load', asOk(this.pool.workerLoad))); + this.emitEvent(asTaskManagerStatEvent('load', asOk(usedCapacityPercentage))); // Emit event indicating task manager utilization - this.emitEvent(asTaskManagerStatEvent('workerUtilization', asOk(this.pool.workerLoad))); + this.emitEvent(asTaskManagerStatEvent('workerUtilization', asOk(usedCapacityPercentage))); } return capacity; }, work: this.pollForWork, }); + this.subscribeToPoller(poller.events$); elasticsearchAndSOAvailability$.subscribe((areESAndSOAvailable) => { @@ -262,7 +259,7 @@ export class TaskPollingLifecycle implements ITaskEventEmitter { + mapOk((results: TimedFillPoolResult) => { // Emit event indicating task manager utilization % at the end of a polling cycle - // This represents the number of workers busy + number of tasks claimed in this cycle - this.emitEvent(asTaskManagerStatEvent('workerUtilization', asOk(this.pool.workerLoad))); + + // Get the actual utilization as a percentage + let tmUtilization = this.pool.usedCapacityPercentage; + + // Check whether there are any tasks left unclaimed + // If we're not at capacity and there are unclaimed tasks, then + // there must be high cost tasks that need to be claimed + // Artificially inflate the utilization to represent the unclaimed load + if (tmUtilization < 100 && (results.stats?.tasksLeftUnclaimed ?? 0) > 0) { + tmUtilization = 100; + } + + this.emitEvent(asTaskManagerStatEvent('workerUtilization', asOk(tmUtilization))); }) ) ) diff --git a/x-pack/plugins/task_manager/server/queries/task_claiming.test.ts b/x-pack/plugins/task_manager/server/queries/task_claiming.test.ts index bc4adb71dd4a1..de57a73f80533 100644 --- a/x-pack/plugins/task_manager/server/queries/task_claiming.test.ts +++ b/x-pack/plugins/task_manager/server/queries/task_claiming.test.ts @@ -80,7 +80,7 @@ describe('TaskClaiming', () => { unusedTypes: [], taskStore: taskStoreMock.create({ taskManagerId: '' }), maxAttempts: 2, - getCapacity: () => 10, + getAvailableCapacity: () => 10, taskPartitioner, }); @@ -130,7 +130,7 @@ describe('TaskClaiming', () => { unusedTypes: [], taskStore: taskStoreMock.create({ taskManagerId: '' }), maxAttempts: 2, - getCapacity: () => 10, + getAvailableCapacity: () => 10, taskPartitioner, }); diff --git a/x-pack/plugins/task_manager/server/queries/task_claiming.ts b/x-pack/plugins/task_manager/server/queries/task_claiming.ts index 188f47b0d2d2f..f5ef18452509b 100644 --- a/x-pack/plugins/task_manager/server/queries/task_claiming.ts +++ b/x-pack/plugins/task_manager/server/queries/task_claiming.ts @@ -38,7 +38,7 @@ export interface TaskClaimingOpts { taskStore: TaskStore; maxAttempts: number; excludedTaskTypes: string[]; - getCapacity: (taskType?: string) => number; + getAvailableCapacity: (taskType?: string) => number; taskPartitioner: TaskPartitioner; } @@ -87,7 +87,7 @@ export class TaskClaiming { private definitions: TaskTypeDictionary; private events$: Subject; private taskStore: TaskStore; - private getCapacity: (taskType?: string) => number; + private getAvailableCapacity: (taskType?: string) => number; private logger: Logger; private readonly taskClaimingBatchesByType: TaskClaimingBatches; private readonly taskMaxAttempts: Record; @@ -106,7 +106,7 @@ export class TaskClaiming { this.definitions = opts.definitions; this.maxAttempts = opts.maxAttempts; this.taskStore = opts.taskStore; - this.getCapacity = opts.getCapacity; + this.getAvailableCapacity = opts.getAvailableCapacity; this.logger = opts.logger.get('taskClaiming'); this.taskClaimingBatchesByType = this.partitionIntoClaimingBatches(this.definitions); this.taskMaxAttempts = Object.fromEntries(this.normalizeMaxAttempts(this.definitions)); @@ -170,13 +170,13 @@ export class TaskClaiming { public claimAvailableTasksIfCapacityIsAvailable( claimingOptions: Omit ): Observable> { - if (this.getCapacity()) { + if (this.getAvailableCapacity()) { const opts: TaskClaimerOpts = { batches: this.getClaimingBatches(), claimOwnershipUntil: claimingOptions.claimOwnershipUntil, taskStore: this.taskStore, events$: this.events$, - getCapacity: this.getCapacity, + getCapacity: this.getAvailableCapacity, unusedTypes: this.unusedTypes, definitions: this.definitions, taskMaxAttempts: this.taskMaxAttempts, diff --git a/x-pack/plugins/task_manager/server/routes/health.test.ts b/x-pack/plugins/task_manager/server/routes/health.test.ts index a97d99079bc58..9c08c5b5fb4c4 100644 --- a/x-pack/plugins/task_manager/server/routes/health.test.ts +++ b/x-pack/plugins/task_manager/server/routes/health.test.ts @@ -823,7 +823,8 @@ function mockHealthStats(overrides = {}) { configuration: { timestamp: new Date().toISOString(), value: { - max_workers: 10, + capacity: { config: 10, as_cost: 20, as_workers: 10 }, + claim_strategy: 'default', poll_interval: 3000, request_capacity: 1000, monitored_aggregated_stats_refresh_rate: 5000, @@ -841,16 +842,19 @@ function mockHealthStats(overrides = {}) { timestamp: new Date().toISOString(), value: { count: 4, + cost: 8, task_types: { - actions_telemetry: { count: 2, status: { idle: 2 } }, - alerting_telemetry: { count: 1, status: { idle: 1 } }, - session_cleanup: { count: 1, status: { idle: 1 } }, + actions_telemetry: { count: 2, cost: 4, status: { idle: 2 } }, + alerting_telemetry: { count: 1, cost: 2, status: { idle: 1 } }, + session_cleanup: { count: 1, cost: 2, status: { idle: 1 } }, }, schedule: [], overdue: 0, + overdue_cost: 2, overdue_non_recurring: 0, estimatedScheduleDensity: [], non_recurring: 20, + non_recurring_cost: 40, owner_ids: [0, 0, 0, 1, 2, 0, 0, 2, 2, 2, 1, 2, 1, 1], estimated_schedule_density: [], capacity_requirements: { diff --git a/x-pack/plugins/task_manager/server/task.ts b/x-pack/plugins/task_manager/server/task.ts index 8a47424ce4965..772d2615ce84a 100644 --- a/x-pack/plugins/task_manager/server/task.ts +++ b/x-pack/plugins/task_manager/server/task.ts @@ -18,6 +18,12 @@ export enum TaskPriority { Normal = 50, } +export enum TaskCost { + Tiny = 1, + Normal = 2, + ExtraLarge = 10, +} + /* * Type definitions and validations for tasks. */ @@ -129,6 +135,10 @@ export const taskDefinitionSchema = schema.object( * Priority of this task type. Defaults to "NORMAL" if not defined */ priority: schema.maybe(schema.number()), + /** + * Cost to run this task type. Defaults to "Normal". + */ + cost: schema.number({ defaultValue: TaskCost.Normal }), /** * An optional more detailed description of what this task does. */ @@ -174,7 +184,7 @@ export const taskDefinitionSchema = schema.object( paramsSchema: schema.maybe(schema.any()), }, { - validate({ timeout, priority }) { + validate({ timeout, priority, cost }) { if (!isInterval(timeout) || isErr(tryAsResult(() => parseIntervalAsMillisecond(timeout)))) { return `Invalid timeout "${timeout}". Timeout must be of the form "{number}{cadance}" where number is an integer. Example: 5m.`; } @@ -184,6 +194,12 @@ export const taskDefinitionSchema = schema.object( .filter((key) => isNaN(Number(key))) .map((key) => `${key} => ${TaskPriority[key as keyof typeof TaskPriority]}`)}`; } + + if (cost && (!isNumber(cost) || !(cost in TaskCost))) { + return `Invalid cost "${cost}". Cost must be one of ${Object.keys(TaskCost) + .filter((key) => isNaN(Number(key))) + .map((key) => `${key} => ${TaskCost[key as keyof typeof TaskCost]}`)}`; + } }, } ); diff --git a/x-pack/plugins/task_manager/server/task_claimers/index.ts b/x-pack/plugins/task_manager/server/task_claimers/index.ts index 1caa6e2addb0f..134c72041f96f 100644 --- a/x-pack/plugins/task_manager/server/task_claimers/index.ts +++ b/x-pack/plugins/task_manager/server/task_claimers/index.ts @@ -37,6 +37,7 @@ export interface ClaimOwnershipResult { tasksUpdated: number; tasksConflicted: number; tasksClaimed: number; + tasksLeftUnclaimed?: number; }; docs: ConcreteTaskInstance[]; timing?: TaskTiming; @@ -61,13 +62,12 @@ export function getTaskClaimer(logger: Logger, strategy: string): TaskClaimerFn return claimAvailableTasksDefault; } -export function getEmptyClaimOwnershipResult() { +export function getEmptyClaimOwnershipResult(): ClaimOwnershipResult { return { stats: { tasksUpdated: 0, tasksConflicted: 0, tasksClaimed: 0, - tasksRejected: 0, }, docs: [], }; diff --git a/x-pack/plugins/task_manager/server/task_claimers/strategy_default.test.ts b/x-pack/plugins/task_manager/server/task_claimers/strategy_default.test.ts index 8aa206bbe1872..d58fd83486efa 100644 --- a/x-pack/plugins/task_manager/server/task_claimers/strategy_default.test.ts +++ b/x-pack/plugins/task_manager/server/task_claimers/strategy_default.test.ts @@ -133,7 +133,7 @@ describe('TaskClaiming', () => { excludedTaskTypes, unusedTypes: unusedTaskTypes, maxAttempts: taskClaimingOpts.maxAttempts ?? 2, - getCapacity: taskClaimingOpts.getCapacity ?? (() => 10), + getAvailableCapacity: taskClaimingOpts.getAvailableCapacity ?? (() => 10), taskPartitioner, ...taskClaimingOpts, }); @@ -158,7 +158,7 @@ describe('TaskClaiming', () => { excludedTaskTypes?: string[]; unusedTaskTypes?: string[]; }) { - const getCapacity = taskClaimingOpts.getCapacity ?? (() => 10); + const getCapacity = taskClaimingOpts.getAvailableCapacity ?? (() => 10); const { taskClaiming, store } = initialiseTestClaiming({ storeOpts, taskClaimingOpts, @@ -447,7 +447,7 @@ if (doc['task.runAt'].size()!=0) { }, taskClaimingOpts: { maxAttempts, - getCapacity: (type) => { + getAvailableCapacity: (type) => { switch (type) { case 'limitedToOne': case 'anotherLimitedToOne': @@ -577,7 +577,7 @@ if (doc['task.runAt'].size()!=0) { }, taskClaimingOpts: { maxAttempts, - getCapacity: (type) => { + getAvailableCapacity: (type) => { switch (type) { case 'limitedToTwo': return 2; @@ -686,7 +686,7 @@ if (doc['task.runAt'].size()!=0) { }, taskClaimingOpts: { maxAttempts, - getCapacity: (type) => { + getAvailableCapacity: (type) => { switch (type) { case 'limitedToOne': case 'anotherLimitedToOne': @@ -1139,7 +1139,7 @@ if (doc['task.runAt'].size()!=0) { storeOpts: { taskManagerId, }, - taskClaimingOpts: { getCapacity: () => maxDocs }, + taskClaimingOpts: { getAvailableCapacity: () => maxDocs }, claimingOpts: { claimOwnershipUntil, }, @@ -1219,9 +1219,9 @@ if (doc['task.runAt'].size()!=0) { function instantiateStoreWithMockedApiResponses({ taskManagerId = uuidv4(), definitions = taskDefinitions, - getCapacity = () => 10, + getAvailableCapacity = () => 10, tasksClaimed, - }: Partial> & { + }: Partial> & { taskManagerId?: string; tasksClaimed?: ConcreteTaskInstance[][]; } = {}) { @@ -1254,7 +1254,7 @@ if (doc['task.runAt'].size()!=0) { unusedTypes: [], taskStore, maxAttempts: 2, - getCapacity, + getAvailableCapacity, taskPartitioner, }); diff --git a/x-pack/plugins/task_manager/server/task_claimers/strategy_mget.test.ts b/x-pack/plugins/task_manager/server/task_claimers/strategy_mget.test.ts index f06431229c0b2..e9df1bda7b81d 100644 --- a/x-pack/plugins/task_manager/server/task_claimers/strategy_mget.test.ts +++ b/x-pack/plugins/task_manager/server/task_claimers/strategy_mget.test.ts @@ -15,10 +15,11 @@ import { ConcreteTaskInstance, ConcreteTaskInstanceVersion, TaskPriority, + TaskCost, } from '../task'; import { SearchOpts, StoreOpts } from '../task_store'; import { asTaskClaimEvent, TaskEvent } from '../task_events'; -import { asOk, isOk, unwrap } from '../lib/result_type'; +import { asOk, asErr, isOk, unwrap } from '../lib/result_type'; import { TaskTypeDictionary } from '../task_type_dictionary'; import { mockLogger } from '../test_utils'; import { @@ -33,6 +34,7 @@ import apm from 'elastic-apm-node'; import { TASK_MANAGER_TRANSACTION_TYPE } from '../task_running'; import { ClaimOwnershipResult } from '.'; import { FillPoolResult } from '../lib/fill_pool'; +import { SavedObjectsErrorHelpers } from '@kbn/core/server'; import { TaskPartitioner } from '../lib/task_partitioner'; import type { MustNotCondition } from '../queries/query_clauses'; import { @@ -48,6 +50,7 @@ jest.mock('../constants', () => ({ 'anotherLimitedToOne', 'limitedToTwo', 'limitedToFive', + 'yawn', ], })); @@ -70,14 +73,18 @@ const taskDefinitions = new TaskTypeDictionary(taskManagerLogger); taskDefinitions.registerTaskDefinitions({ report: { title: 'report', + cost: TaskCost.Normal, createTaskRunner: jest.fn(), }, dernstraight: { title: 'dernstraight', + cost: TaskCost.ExtraLarge, createTaskRunner: jest.fn(), }, yawn: { title: 'yawn', + cost: TaskCost.Tiny, + maxConcurrency: 1, createTaskRunner: jest.fn(), }, }); @@ -107,6 +114,17 @@ describe('TaskClaiming', () => { }); describe('claimAvailableTasks', () => { + function getVersionMapsFromTasks(tasks: ConcreteTaskInstance[]) { + const versionMap = new Map(); + const docLatestVersions = new Map(); + for (const task of tasks) { + versionMap.set(task.id, { esId: task.id, seqNo: 32, primaryTerm: 32 }); + docLatestVersions.set(`task:${task.id}`, { esId: task.id, seqNo: 32, primaryTerm: 32 }); + } + + return { versionMap, docLatestVersions }; + } + function initialiseTestClaiming({ storeOpts = {}, taskClaimingOpts = {}, @@ -127,20 +145,27 @@ describe('TaskClaiming', () => { store.convertToSavedObjectIds.mockImplementation((ids) => ids.map((id) => `task:${id}`)); if (hits == null) hits = [generateFakeTasks(1)]; + + const docVersion = []; if (versionMaps == null) { - versionMaps = [new Map()]; + versionMaps = []; for (const oneHit of hits) { const map = new Map(); - versionMaps.push(map); + const mapWithTaskPrefix = new Map(); for (const task of oneHit) { map.set(task.id, { esId: task.id, seqNo: 32, primaryTerm: 32 }); + mapWithTaskPrefix.set(`task:${task.id}`, { esId: task.id, seqNo: 32, primaryTerm: 32 }); } + versionMaps.push(map); + docVersion.push(mapWithTaskPrefix); } } for (let i = 0; i < hits.length; i++) { store.fetch.mockResolvedValueOnce({ docs: hits[i], versionMap: versionMaps[i] }); - store.getDocVersions.mockResolvedValueOnce(versionMaps[i]); + store.getDocVersions.mockResolvedValueOnce(docVersion[i]); + const oneBulkGetResult = hits[i].map((hit) => asOk(hit)); + store.bulkGet.mockResolvedValueOnce(oneBulkGetResult); const oneBulkResult = hits[i].map((hit) => asOk(hit)); store.bulkUpdate.mockResolvedValueOnce(oneBulkResult); } @@ -153,7 +178,7 @@ describe('TaskClaiming', () => { excludedTaskTypes, unusedTypes: unusedTaskTypes, maxAttempts: taskClaimingOpts.maxAttempts ?? 2, - getCapacity: taskClaimingOpts.getCapacity ?? (() => 10), + getAvailableCapacity: taskClaimingOpts.getAvailableCapacity ?? (() => 10), taskPartitioner, ...taskClaimingOpts, }); @@ -200,6 +225,14 @@ describe('TaskClaiming', () => { return unwrap(resultOrErr) as ClaimOwnershipResult; }); + expect(apm.startTransaction).toHaveBeenCalledWith( + TASK_MANAGER_MARK_AS_CLAIMED, + TASK_MANAGER_TRANSACTION_TYPE + ); + expect(mockApmTrans.end).toHaveBeenCalledWith('success'); + + expect(store.fetch.mock.calls).toMatchObject({}); + expect(store.getDocVersions.mock.calls).toMatchObject({}); return results.map((result, index) => ({ result, args: { @@ -286,8 +319,1250 @@ describe('TaskClaiming', () => { expect(result).toMatchObject({}); }); + test('should limit claimed tasks based on task cost and available capacity', async () => { + const store = taskStoreMock.create({ taskManagerId: 'test-test' }); + store.convertToSavedObjectIds.mockImplementation((ids) => ids.map((id) => `task:${id}`)); + + const fetchedTasks = [ + mockInstance({ id: `id-1`, taskType: 'report' }), // total cost = 2 + mockInstance({ id: `id-2`, taskType: 'report' }), // total cost = 4 + mockInstance({ id: `id-3`, taskType: 'yawn' }), // total cost = 5 + mockInstance({ id: `id-4`, taskType: 'dernstraight' }), // claiming this will exceed the available capacity + mockInstance({ id: `id-5`, taskType: 'report' }), + mockInstance({ id: `id-6`, taskType: 'report' }), + ]; + + const { versionMap, docLatestVersions } = getVersionMapsFromTasks(fetchedTasks); + store.fetch.mockResolvedValueOnce({ docs: fetchedTasks, versionMap }); + store.getDocVersions.mockResolvedValueOnce(docLatestVersions); + + store.bulkGet.mockResolvedValueOnce( + [fetchedTasks[0], fetchedTasks[1], fetchedTasks[2]].map(asOk) + ); + store.bulkUpdate.mockResolvedValueOnce( + [fetchedTasks[0], fetchedTasks[1], fetchedTasks[2]].map(asOk) + ); + + const taskClaiming = new TaskClaiming({ + logger: taskManagerLogger, + strategy: CLAIM_STRATEGY_MGET, + definitions: taskDefinitions, + taskStore: store, + excludedTaskTypes: [], + unusedTypes: [], + maxAttempts: 2, + getAvailableCapacity: () => 10, + taskPartitioner, + }); + + const [resultOrErr] = await getAllAsPromise( + taskClaiming.claimAvailableTasksIfCapacityIsAvailable({ claimOwnershipUntil: new Date() }) + ); + + if (!isOk(resultOrErr)) { + expect(resultOrErr).toBe(undefined); + } + + const result = unwrap(resultOrErr) as ClaimOwnershipResult; + + expect(apm.startTransaction).toHaveBeenCalledWith( + TASK_MANAGER_MARK_AS_CLAIMED, + TASK_MANAGER_TRANSACTION_TYPE + ); + expect(mockApmTrans.end).toHaveBeenCalledWith('success'); + + expect(taskManagerLogger.debug).toHaveBeenCalledWith( + 'task claimer claimed: 3; stale: 0; conflicts: 0; missing: 0; capacity reached: 3; updateErrors: 0; removed: 0;', + { tags: ['claimAvailableTasksMget'] } + ); + + expect(store.fetch.mock.calls[0][0]).toMatchObject({ size: 40, seq_no_primary_term: true }); + expect(store.getDocVersions).toHaveBeenCalledWith([ + 'task:id-1', + 'task:id-2', + 'task:id-3', + 'task:id-4', + 'task:id-5', + 'task:id-6', + ]); + expect(store.bulkUpdate).toHaveBeenCalledTimes(1); + expect(store.bulkUpdate).toHaveBeenCalledWith( + [ + { + ...fetchedTasks[0], + ownerId: 'test-test', + retryAt: fetchedTasks[0].runAt, + status: 'claiming', + }, + { + ...fetchedTasks[1], + ownerId: 'test-test', + retryAt: fetchedTasks[1].runAt, + status: 'claiming', + }, + { + ...fetchedTasks[2], + ownerId: 'test-test', + retryAt: fetchedTasks[2].runAt, + status: 'claiming', + }, + ], + { validate: false, excludeLargeFields: true } + ); + expect(store.bulkGet).toHaveBeenCalledWith(['id-1', 'id-2', 'id-3']); + + expect(result.stats).toEqual({ + tasksClaimed: 3, + tasksConflicted: 0, + tasksUpdated: 3, + tasksLeftUnclaimed: 3, + }); + expect(result.docs.length).toEqual(3); + }); + + test('should not claim tasks of removed type', async () => { + const store = taskStoreMock.create({ taskManagerId: 'test-test' }); + store.convertToSavedObjectIds.mockImplementation((ids) => ids.map((id) => `task:${id}`)); + + const fetchedTasks = [ + mockInstance({ id: `id-1`, taskType: 'report' }), + mockInstance({ id: `id-2`, taskType: 'report' }), + mockInstance({ id: `id-3`, taskType: 'yawn' }), + ]; + + const { versionMap, docLatestVersions } = getVersionMapsFromTasks(fetchedTasks); + store.fetch.mockResolvedValueOnce({ docs: fetchedTasks, versionMap }); + store.getDocVersions.mockResolvedValueOnce(docLatestVersions); + + store.bulkGet.mockResolvedValueOnce([fetchedTasks[2]].map(asOk)); + store.bulkUpdate.mockResolvedValueOnce([fetchedTasks[2]].map(asOk)); + store.bulkUpdate.mockResolvedValueOnce([fetchedTasks[0], fetchedTasks[1]].map(asOk)); + + const taskClaiming = new TaskClaiming({ + logger: taskManagerLogger, + strategy: CLAIM_STRATEGY_MGET, + definitions: taskDefinitions, + taskStore: store, + excludedTaskTypes: [], + unusedTypes: ['report'], + maxAttempts: 2, + getAvailableCapacity: () => 10, + taskPartitioner, + }); + + const [resultOrErr] = await getAllAsPromise( + taskClaiming.claimAvailableTasksIfCapacityIsAvailable({ claimOwnershipUntil: new Date() }) + ); + + if (!isOk(resultOrErr)) { + expect(resultOrErr).toBe(undefined); + } + + const result = unwrap(resultOrErr) as ClaimOwnershipResult; + + expect(apm.startTransaction).toHaveBeenCalledWith( + TASK_MANAGER_MARK_AS_CLAIMED, + TASK_MANAGER_TRANSACTION_TYPE + ); + expect(mockApmTrans.end).toHaveBeenCalledWith('success'); + + expect(taskManagerLogger.debug).toHaveBeenCalledWith( + 'task claimer claimed: 1; stale: 0; conflicts: 0; missing: 0; capacity reached: 0; updateErrors: 0; removed: 2;', + { tags: ['claimAvailableTasksMget'] } + ); + + expect(store.fetch.mock.calls[0][0]).toMatchObject({ size: 40, seq_no_primary_term: true }); + expect(store.getDocVersions).toHaveBeenCalledWith(['task:id-1', 'task:id-2', 'task:id-3']); + expect(store.bulkUpdate).toHaveBeenCalledTimes(2); + expect(store.bulkUpdate).toHaveBeenNthCalledWith( + 1, + [ + { + ...fetchedTasks[2], + ownerId: 'test-test', + retryAt: fetchedTasks[2].runAt, + status: 'claiming', + }, + ], + { validate: false, excludeLargeFields: true } + ); + expect(store.bulkUpdate).toHaveBeenNthCalledWith( + 2, + [ + { + ...fetchedTasks[0], + status: 'unrecognized', + }, + { + ...fetchedTasks[1], + status: 'unrecognized', + }, + ], + { validate: false, excludeLargeFields: true } + ); + expect(store.bulkGet).toHaveBeenCalledWith(['id-3']); + + expect(result.stats).toEqual({ + tasksClaimed: 1, + tasksConflicted: 0, + tasksUpdated: 1, + tasksLeftUnclaimed: 0, + }); + expect(result.docs.length).toEqual(1); + }); + + test('should log warning if error updating single removed task as unrecognized', async () => { + const store = taskStoreMock.create({ taskManagerId: 'test-test' }); + store.convertToSavedObjectIds.mockImplementation((ids) => ids.map((id) => `task:${id}`)); + + const fetchedTasks = [ + mockInstance({ id: `id-1`, taskType: 'report' }), + mockInstance({ id: `id-2`, taskType: 'report' }), + mockInstance({ id: `id-3`, taskType: 'yawn' }), + ]; + + const { versionMap, docLatestVersions } = getVersionMapsFromTasks(fetchedTasks); + store.fetch.mockResolvedValueOnce({ docs: fetchedTasks, versionMap }); + store.getDocVersions.mockResolvedValueOnce(docLatestVersions); + + store.bulkGet.mockResolvedValueOnce([fetchedTasks[2]].map(asOk)); + store.bulkUpdate.mockResolvedValueOnce([fetchedTasks[2]].map(asOk)); + store.bulkUpdate.mockResolvedValueOnce([ + asOk(fetchedTasks[0]), + // @ts-expect-error + asErr({ + type: 'task', + id: fetchedTasks[1].id, + error: SavedObjectsErrorHelpers.createBadRequestError(), + }), + ]); + + const taskClaiming = new TaskClaiming({ + logger: taskManagerLogger, + strategy: CLAIM_STRATEGY_MGET, + definitions: taskDefinitions, + taskStore: store, + excludedTaskTypes: [], + unusedTypes: ['report'], + maxAttempts: 2, + getAvailableCapacity: () => 10, + taskPartitioner, + }); + + const [resultOrErr] = await getAllAsPromise( + taskClaiming.claimAvailableTasksIfCapacityIsAvailable({ claimOwnershipUntil: new Date() }) + ); + + if (!isOk(resultOrErr)) { + expect(resultOrErr).toBe(undefined); + } + + const result = unwrap(resultOrErr) as ClaimOwnershipResult; + + expect(apm.startTransaction).toHaveBeenCalledWith( + TASK_MANAGER_MARK_AS_CLAIMED, + TASK_MANAGER_TRANSACTION_TYPE + ); + expect(mockApmTrans.end).toHaveBeenCalledWith('success'); + + expect(taskManagerLogger.warn).toHaveBeenCalledWith( + 'Error updating task id-2:task to mark as unrecognized during claim: Bad Request', + { tags: ['claimAvailableTasksMget'] } + ); + expect(taskManagerLogger.debug).toHaveBeenCalledWith( + 'task claimer claimed: 1; stale: 0; conflicts: 0; missing: 0; capacity reached: 0; updateErrors: 0; removed: 1;', + { tags: ['claimAvailableTasksMget'] } + ); + + expect(store.fetch.mock.calls[0][0]).toMatchObject({ size: 40, seq_no_primary_term: true }); + expect(store.getDocVersions).toHaveBeenCalledWith(['task:id-1', 'task:id-2', 'task:id-3']); + expect(store.bulkUpdate).toHaveBeenCalledTimes(2); + expect(store.bulkUpdate).toHaveBeenNthCalledWith( + 1, + [ + { + ...fetchedTasks[2], + ownerId: 'test-test', + retryAt: fetchedTasks[2].runAt, + status: 'claiming', + }, + ], + { validate: false, excludeLargeFields: true } + ); + expect(store.bulkUpdate).toHaveBeenNthCalledWith( + 2, + [ + { + ...fetchedTasks[0], + status: 'unrecognized', + }, + { + ...fetchedTasks[1], + status: 'unrecognized', + }, + ], + { validate: false, excludeLargeFields: true } + ); + expect(store.bulkGet).toHaveBeenCalledWith(['id-3']); + + expect(result.stats).toEqual({ + tasksClaimed: 1, + tasksConflicted: 0, + tasksUpdated: 1, + tasksLeftUnclaimed: 0, + }); + expect(result.docs.length).toEqual(1); + }); + + test('should log warning if error updating all removed tasks as unrecognized', async () => { + const store = taskStoreMock.create({ taskManagerId: 'test-test' }); + store.convertToSavedObjectIds.mockImplementation((ids) => ids.map((id) => `task:${id}`)); + + const fetchedTasks = [ + mockInstance({ id: `id-1`, taskType: 'report' }), + mockInstance({ id: `id-2`, taskType: 'report' }), + mockInstance({ id: `id-3`, taskType: 'yawn' }), + ]; + + const { versionMap, docLatestVersions } = getVersionMapsFromTasks(fetchedTasks); + store.fetch.mockResolvedValueOnce({ docs: fetchedTasks, versionMap }); + store.getDocVersions.mockResolvedValueOnce(docLatestVersions); + + store.bulkGet.mockResolvedValueOnce([fetchedTasks[2]].map(asOk)); + store.bulkUpdate.mockResolvedValueOnce([fetchedTasks[2]].map(asOk)); + store.bulkUpdate.mockRejectedValueOnce(new Error('Oh no')); + + const taskClaiming = new TaskClaiming({ + logger: taskManagerLogger, + strategy: CLAIM_STRATEGY_MGET, + definitions: taskDefinitions, + taskStore: store, + excludedTaskTypes: [], + unusedTypes: ['report'], + maxAttempts: 2, + getAvailableCapacity: () => 10, + taskPartitioner, + }); + + const [resultOrErr] = await getAllAsPromise( + taskClaiming.claimAvailableTasksIfCapacityIsAvailable({ claimOwnershipUntil: new Date() }) + ); + + if (!isOk(resultOrErr)) { + expect(resultOrErr).toBe(undefined); + } + + const result = unwrap(resultOrErr) as ClaimOwnershipResult; + + expect(apm.startTransaction).toHaveBeenCalledWith( + TASK_MANAGER_MARK_AS_CLAIMED, + TASK_MANAGER_TRANSACTION_TYPE + ); + expect(mockApmTrans.end).toHaveBeenCalledWith('success'); + + expect(taskManagerLogger.warn).toHaveBeenCalledWith( + 'Error updating tasks to mark as unrecognized during claim: Error: Oh no', + { tags: ['claimAvailableTasksMget'] } + ); + expect(taskManagerLogger.debug).toHaveBeenCalledWith( + 'task claimer claimed: 1; stale: 0; conflicts: 0; missing: 0; capacity reached: 0; updateErrors: 0; removed: 0;', + { tags: ['claimAvailableTasksMget'] } + ); + + expect(store.fetch.mock.calls[0][0]).toMatchObject({ size: 40, seq_no_primary_term: true }); + expect(store.getDocVersions).toHaveBeenCalledWith(['task:id-1', 'task:id-2', 'task:id-3']); + expect(store.bulkGet).toHaveBeenCalledWith(['id-3']); + expect(store.bulkUpdate).toHaveBeenCalledTimes(2); + expect(store.bulkUpdate).toHaveBeenNthCalledWith( + 1, + [ + { + ...fetchedTasks[2], + ownerId: 'test-test', + retryAt: fetchedTasks[2].runAt, + status: 'claiming', + }, + ], + { validate: false, excludeLargeFields: true } + ); + expect(store.bulkUpdate).toHaveBeenNthCalledWith( + 2, + [ + { + ...fetchedTasks[0], + status: 'unrecognized', + }, + { + ...fetchedTasks[1], + status: 'unrecognized', + }, + ], + { validate: false, excludeLargeFields: true } + ); + + expect(result.stats).toEqual({ + tasksClaimed: 1, + tasksConflicted: 0, + tasksUpdated: 1, + tasksLeftUnclaimed: 0, + }); + expect(result.docs.length).toEqual(1); + }); + + test('should handle no tasks to claim', async () => { + const store = taskStoreMock.create({ taskManagerId: 'test-test' }); + store.convertToSavedObjectIds.mockImplementation((ids) => ids.map((id) => `task:${id}`)); + + const fetchedTasks: ConcreteTaskInstance[] = []; + + const { versionMap } = getVersionMapsFromTasks(fetchedTasks); + store.fetch.mockResolvedValueOnce({ docs: fetchedTasks, versionMap }); + + const taskClaiming = new TaskClaiming({ + logger: taskManagerLogger, + strategy: CLAIM_STRATEGY_MGET, + definitions: taskDefinitions, + taskStore: store, + excludedTaskTypes: [], + unusedTypes: [], + maxAttempts: 2, + getAvailableCapacity: () => 10, + taskPartitioner, + }); + + const [resultOrErr] = await getAllAsPromise( + taskClaiming.claimAvailableTasksIfCapacityIsAvailable({ claimOwnershipUntil: new Date() }) + ); + + if (!isOk(resultOrErr)) { + expect(resultOrErr).toBe(undefined); + } + + const result = unwrap(resultOrErr) as ClaimOwnershipResult; + + expect(apm.startTransaction).toHaveBeenCalledWith( + TASK_MANAGER_MARK_AS_CLAIMED, + TASK_MANAGER_TRANSACTION_TYPE + ); + expect(mockApmTrans.end).toHaveBeenCalledWith('success'); + + expect(taskManagerLogger.debug).not.toHaveBeenCalled(); + + expect(store.fetch.mock.calls[0][0]).toMatchObject({ size: 40, seq_no_primary_term: true }); + expect(store.getDocVersions).not.toHaveBeenCalled(); + expect(store.bulkGet).not.toHaveBeenCalled(); + expect(store.bulkUpdate).not.toHaveBeenCalled(); + + expect(result.stats).toEqual({ + tasksClaimed: 0, + tasksConflicted: 0, + tasksUpdated: 0, + }); + expect(result.docs.length).toEqual(0); + }); + + test('should handle tasks with no search version', async () => { + const store = taskStoreMock.create({ taskManagerId: 'test-test' }); + store.convertToSavedObjectIds.mockImplementation((ids) => ids.map((id) => `task:${id}`)); + + const fetchedTasks = [ + mockInstance({ id: `id-1`, taskType: 'report' }), + mockInstance({ id: `id-2`, taskType: 'report' }), + mockInstance({ id: `id-3`, taskType: 'yawn' }), + ]; + + const { versionMap, docLatestVersions } = getVersionMapsFromTasks(fetchedTasks); + versionMap.delete('id-1'); + store.fetch.mockResolvedValueOnce({ docs: fetchedTasks, versionMap }); + store.getDocVersions.mockResolvedValueOnce(docLatestVersions); + + store.bulkGet.mockResolvedValueOnce([fetchedTasks[1], fetchedTasks[2]].map(asOk)); + store.bulkUpdate.mockResolvedValueOnce([fetchedTasks[1], fetchedTasks[2]].map(asOk)); + + const taskClaiming = new TaskClaiming({ + logger: taskManagerLogger, + strategy: CLAIM_STRATEGY_MGET, + definitions: taskDefinitions, + taskStore: store, + excludedTaskTypes: [], + unusedTypes: [], + maxAttempts: 2, + getAvailableCapacity: () => 10, + taskPartitioner, + }); + + const [resultOrErr] = await getAllAsPromise( + taskClaiming.claimAvailableTasksIfCapacityIsAvailable({ claimOwnershipUntil: new Date() }) + ); + + if (!isOk(resultOrErr)) { + expect(resultOrErr).toBe(undefined); + } + + const result = unwrap(resultOrErr) as ClaimOwnershipResult; + + expect(apm.startTransaction).toHaveBeenCalledWith( + TASK_MANAGER_MARK_AS_CLAIMED, + TASK_MANAGER_TRANSACTION_TYPE + ); + expect(mockApmTrans.end).toHaveBeenCalledWith('success'); + + expect(taskManagerLogger.debug).toHaveBeenCalledWith( + 'task claimer claimed: 2; stale: 0; conflicts: 0; missing: 1; capacity reached: 0; updateErrors: 0; removed: 0;', + { tags: ['claimAvailableTasksMget'] } + ); + + expect(store.fetch.mock.calls[0][0]).toMatchObject({ size: 40, seq_no_primary_term: true }); + expect(store.getDocVersions).toHaveBeenCalledWith(['task:id-1', 'task:id-2', 'task:id-3']); + expect(store.bulkUpdate).toHaveBeenCalledTimes(1); + expect(store.bulkUpdate).toHaveBeenCalledWith( + [ + { + ...fetchedTasks[1], + ownerId: 'test-test', + retryAt: fetchedTasks[1].runAt, + status: 'claiming', + }, + { + ...fetchedTasks[2], + ownerId: 'test-test', + retryAt: fetchedTasks[2].runAt, + status: 'claiming', + }, + ], + { validate: false, excludeLargeFields: true } + ); + expect(store.bulkGet).toHaveBeenCalledWith(['id-2', 'id-3']); + + expect(result.stats).toEqual({ + tasksClaimed: 2, + tasksConflicted: 0, + tasksUpdated: 2, + tasksLeftUnclaimed: 0, + }); + expect(result.docs.length).toEqual(2); + }); + + test('should handle tasks with no latest version', async () => { + const store = taskStoreMock.create({ taskManagerId: 'test-test' }); + store.convertToSavedObjectIds.mockImplementation((ids) => ids.map((id) => `task:${id}`)); + + const fetchedTasks = [ + mockInstance({ id: `id-1`, taskType: 'report' }), + mockInstance({ id: `id-2`, taskType: 'report' }), + mockInstance({ id: `id-3`, taskType: 'yawn' }), + ]; + + const { versionMap, docLatestVersions } = getVersionMapsFromTasks(fetchedTasks); + docLatestVersions.delete('task:id-1'); + store.fetch.mockResolvedValueOnce({ docs: fetchedTasks, versionMap }); + store.getDocVersions.mockResolvedValueOnce(docLatestVersions); + + store.bulkGet.mockResolvedValueOnce([fetchedTasks[1], fetchedTasks[2]].map(asOk)); + store.bulkUpdate.mockResolvedValueOnce([fetchedTasks[1], fetchedTasks[2]].map(asOk)); + + const taskClaiming = new TaskClaiming({ + logger: taskManagerLogger, + strategy: CLAIM_STRATEGY_MGET, + definitions: taskDefinitions, + taskStore: store, + excludedTaskTypes: [], + unusedTypes: [], + maxAttempts: 2, + getAvailableCapacity: () => 10, + taskPartitioner, + }); + + const [resultOrErr] = await getAllAsPromise( + taskClaiming.claimAvailableTasksIfCapacityIsAvailable({ claimOwnershipUntil: new Date() }) + ); + + if (!isOk(resultOrErr)) { + expect(resultOrErr).toBe(undefined); + } + + const result = unwrap(resultOrErr) as ClaimOwnershipResult; + + expect(apm.startTransaction).toHaveBeenCalledWith( + TASK_MANAGER_MARK_AS_CLAIMED, + TASK_MANAGER_TRANSACTION_TYPE + ); + expect(mockApmTrans.end).toHaveBeenCalledWith('success'); + + expect(taskManagerLogger.debug).toHaveBeenCalledWith( + 'task claimer claimed: 2; stale: 0; conflicts: 0; missing: 1; capacity reached: 0; updateErrors: 0; removed: 0;', + { tags: ['claimAvailableTasksMget'] } + ); + + expect(store.fetch.mock.calls[0][0]).toMatchObject({ size: 40, seq_no_primary_term: true }); + expect(store.getDocVersions).toHaveBeenCalledWith(['task:id-1', 'task:id-2', 'task:id-3']); + expect(store.bulkUpdate).toHaveBeenCalledTimes(1); + expect(store.bulkUpdate).toHaveBeenCalledWith( + [ + { + ...fetchedTasks[1], + ownerId: 'test-test', + retryAt: fetchedTasks[1].runAt, + status: 'claiming', + }, + { + ...fetchedTasks[2], + ownerId: 'test-test', + retryAt: fetchedTasks[2].runAt, + status: 'claiming', + }, + ], + { validate: false, excludeLargeFields: true } + ); + expect(store.bulkGet).toHaveBeenCalledWith(['id-2', 'id-3']); + + expect(result.stats).toEqual({ + tasksClaimed: 2, + tasksConflicted: 0, + tasksUpdated: 2, + tasksLeftUnclaimed: 0, + }); + expect(result.docs.length).toEqual(2); + }); + + test('should handle stale tasks', async () => { + const store = taskStoreMock.create({ taskManagerId: 'test-test' }); + store.convertToSavedObjectIds.mockImplementation((ids) => ids.map((id) => `task:${id}`)); + + const fetchedTasks = [ + mockInstance({ id: `id-1`, taskType: 'report' }), + mockInstance({ id: `id-2`, taskType: 'report' }), + mockInstance({ id: `id-3`, taskType: 'yawn' }), + ]; + + const { versionMap, docLatestVersions } = getVersionMapsFromTasks(fetchedTasks); + docLatestVersions.set('task:id-1', { esId: 'task:id-1', seqNo: 33, primaryTerm: 33 }); + store.fetch.mockResolvedValueOnce({ docs: fetchedTasks, versionMap }); + store.getDocVersions.mockResolvedValueOnce(docLatestVersions); + + store.bulkGet.mockResolvedValueOnce([fetchedTasks[1], fetchedTasks[2]].map(asOk)); + store.bulkUpdate.mockResolvedValueOnce([fetchedTasks[1], fetchedTasks[2]].map(asOk)); + + const taskClaiming = new TaskClaiming({ + logger: taskManagerLogger, + strategy: CLAIM_STRATEGY_MGET, + definitions: taskDefinitions, + taskStore: store, + excludedTaskTypes: [], + unusedTypes: [], + maxAttempts: 2, + getAvailableCapacity: () => 10, + taskPartitioner, + }); + + const [resultOrErr] = await getAllAsPromise( + taskClaiming.claimAvailableTasksIfCapacityIsAvailable({ claimOwnershipUntil: new Date() }) + ); + + if (!isOk(resultOrErr)) { + expect(resultOrErr).toBe(undefined); + } + + const result = unwrap(resultOrErr) as ClaimOwnershipResult; + + expect(apm.startTransaction).toHaveBeenCalledWith( + TASK_MANAGER_MARK_AS_CLAIMED, + TASK_MANAGER_TRANSACTION_TYPE + ); + expect(mockApmTrans.end).toHaveBeenCalledWith('success'); + + expect(taskManagerLogger.debug).toHaveBeenCalledWith( + 'task claimer claimed: 2; stale: 1; conflicts: 1; missing: 0; capacity reached: 0; updateErrors: 0; removed: 0;', + { tags: ['claimAvailableTasksMget'] } + ); + + expect(store.fetch.mock.calls[0][0]).toMatchObject({ size: 40, seq_no_primary_term: true }); + expect(store.getDocVersions).toHaveBeenCalledWith(['task:id-1', 'task:id-2', 'task:id-3']); + expect(store.bulkUpdate).toHaveBeenCalledTimes(1); + expect(store.bulkUpdate).toHaveBeenCalledWith( + [ + { + ...fetchedTasks[1], + ownerId: 'test-test', + retryAt: fetchedTasks[1].runAt, + status: 'claiming', + }, + { + ...fetchedTasks[2], + ownerId: 'test-test', + retryAt: fetchedTasks[2].runAt, + status: 'claiming', + }, + ], + { validate: false, excludeLargeFields: true } + ); + expect(store.bulkGet).toHaveBeenCalledWith(['id-2', 'id-3']); + + expect(result.stats).toEqual({ + tasksClaimed: 2, + tasksConflicted: 1, + tasksUpdated: 2, + tasksLeftUnclaimed: 0, + }); + expect(result.docs.length).toEqual(2); + }); + + test('should correctly handle limited concurrency tasks', async () => { + const store = taskStoreMock.create({ taskManagerId: 'test-test' }); + store.convertToSavedObjectIds.mockImplementation((ids) => ids.map((id) => `task:${id}`)); + + const fetchedTasks = [ + mockInstance({ id: `id-1`, taskType: 'report' }), + mockInstance({ id: `id-2`, taskType: 'report' }), + mockInstance({ id: `id-3`, taskType: 'yawn' }), + mockInstance({ id: `id-4`, taskType: 'yawn' }), + mockInstance({ id: `id-5`, taskType: 'report' }), + mockInstance({ id: `id-6`, taskType: 'yawn' }), + ]; + + const { versionMap, docLatestVersions } = getVersionMapsFromTasks(fetchedTasks); + store.fetch.mockResolvedValueOnce({ docs: fetchedTasks, versionMap }); + store.getDocVersions.mockResolvedValueOnce(docLatestVersions); + + store.bulkGet.mockResolvedValueOnce( + [fetchedTasks[0], fetchedTasks[1], fetchedTasks[2], fetchedTasks[4]].map(asOk) + ); + store.bulkUpdate.mockResolvedValueOnce( + [fetchedTasks[0], fetchedTasks[1], fetchedTasks[2], fetchedTasks[4]].map(asOk) + ); + + const taskClaiming = new TaskClaiming({ + logger: taskManagerLogger, + strategy: CLAIM_STRATEGY_MGET, + definitions: taskDefinitions, + taskStore: store, + excludedTaskTypes: [], + unusedTypes: [], + maxAttempts: 2, + getAvailableCapacity: () => 10, + taskPartitioner, + }); + + const [resultOrErr] = await getAllAsPromise( + taskClaiming.claimAvailableTasksIfCapacityIsAvailable({ claimOwnershipUntil: new Date() }) + ); + + if (!isOk(resultOrErr)) { + expect(resultOrErr).toBe(undefined); + } + + const result = unwrap(resultOrErr) as ClaimOwnershipResult; + + expect(apm.startTransaction).toHaveBeenCalledWith( + TASK_MANAGER_MARK_AS_CLAIMED, + TASK_MANAGER_TRANSACTION_TYPE + ); + expect(mockApmTrans.end).toHaveBeenCalledWith('success'); + + expect(taskManagerLogger.debug).toHaveBeenCalledWith( + 'task claimer claimed: 4; stale: 0; conflicts: 0; missing: 0; capacity reached: 0; updateErrors: 0; removed: 0;', + { tags: ['claimAvailableTasksMget'] } + ); + + expect(store.fetch.mock.calls[0][0]).toMatchObject({ size: 40, seq_no_primary_term: true }); + expect(store.getDocVersions).toHaveBeenCalledWith([ + 'task:id-1', + 'task:id-2', + 'task:id-3', + 'task:id-4', + 'task:id-5', + 'task:id-6', + ]); + expect(store.bulkUpdate).toHaveBeenCalledTimes(1); + expect(store.bulkUpdate).toHaveBeenCalledWith( + [ + { + ...fetchedTasks[0], + ownerId: 'test-test', + retryAt: fetchedTasks[1].runAt, + status: 'claiming', + }, + { + ...fetchedTasks[1], + ownerId: 'test-test', + retryAt: fetchedTasks[1].runAt, + status: 'claiming', + }, + { + ...fetchedTasks[2], + ownerId: 'test-test', + retryAt: fetchedTasks[2].runAt, + status: 'claiming', + }, + { + ...fetchedTasks[4], + ownerId: 'test-test', + retryAt: fetchedTasks[1].runAt, + status: 'claiming', + }, + ], + { validate: false, excludeLargeFields: true } + ); + expect(store.bulkGet).toHaveBeenCalledWith(['id-1', 'id-2', 'id-3', 'id-5']); + + expect(result.stats).toEqual({ + tasksClaimed: 4, + tasksConflicted: 0, + tasksUpdated: 4, + tasksLeftUnclaimed: 0, + }); + expect(result.docs.length).toEqual(4); + }); + + test('should handle individual errors when bulk getting the full task doc', async () => { + const store = taskStoreMock.create({ taskManagerId: 'test-test' }); + store.convertToSavedObjectIds.mockImplementation((ids) => ids.map((id) => `task:${id}`)); + + const fetchedTasks = [ + mockInstance({ id: `id-1`, taskType: 'report' }), + mockInstance({ id: `id-2`, taskType: 'report' }), + mockInstance({ id: `id-3`, taskType: 'yawn' }), + mockInstance({ id: `id-4`, taskType: 'report' }), + ]; + + const { versionMap, docLatestVersions } = getVersionMapsFromTasks(fetchedTasks); + store.fetch.mockResolvedValueOnce({ docs: fetchedTasks, versionMap }); + store.getDocVersions.mockResolvedValueOnce(docLatestVersions); + store.bulkUpdate.mockResolvedValueOnce( + [fetchedTasks[0], fetchedTasks[1], fetchedTasks[2], fetchedTasks[3]].map(asOk) + ); + store.bulkGet.mockResolvedValueOnce([ + asOk(fetchedTasks[0]), + // @ts-expect-error + asErr({ + type: 'task', + id: fetchedTasks[1].id, + error: new Error('Oh no'), + }), + asOk(fetchedTasks[2]), + asOk(fetchedTasks[3]), + ]); + + const taskClaiming = new TaskClaiming({ + logger: taskManagerLogger, + strategy: CLAIM_STRATEGY_MGET, + definitions: taskDefinitions, + taskStore: store, + excludedTaskTypes: [], + unusedTypes: [], + maxAttempts: 2, + getAvailableCapacity: () => 10, + taskPartitioner, + }); + + const [resultOrErr] = await getAllAsPromise( + taskClaiming.claimAvailableTasksIfCapacityIsAvailable({ claimOwnershipUntil: new Date() }) + ); + + if (!isOk(resultOrErr)) { + expect(resultOrErr).toBe(undefined); + } + + const result = unwrap(resultOrErr) as ClaimOwnershipResult; + + expect(apm.startTransaction).toHaveBeenCalledWith( + TASK_MANAGER_MARK_AS_CLAIMED, + TASK_MANAGER_TRANSACTION_TYPE + ); + expect(mockApmTrans.end).toHaveBeenCalledWith('success'); + + expect(taskManagerLogger.debug).toHaveBeenCalledWith( + 'task claimer claimed: 3; stale: 0; conflicts: 0; missing: 0; capacity reached: 0; updateErrors: 0; removed: 0;', + { tags: ['claimAvailableTasksMget'] } + ); + expect(taskManagerLogger.warn).toHaveBeenCalledWith( + 'Error getting full task id-2:task during claim: Oh no', + { tags: ['claimAvailableTasksMget'] } + ); + + expect(store.fetch.mock.calls[0][0]).toMatchObject({ size: 40, seq_no_primary_term: true }); + expect(store.getDocVersions).toHaveBeenCalledWith([ + 'task:id-1', + 'task:id-2', + 'task:id-3', + 'task:id-4', + ]); + expect(store.bulkUpdate).toHaveBeenCalledTimes(1); + expect(store.bulkUpdate).toHaveBeenCalledWith( + [ + { + ...fetchedTasks[0], + ownerId: 'test-test', + retryAt: fetchedTasks[0].runAt, + status: 'claiming', + }, + { + ...fetchedTasks[1], + ownerId: 'test-test', + retryAt: fetchedTasks[2].runAt, + status: 'claiming', + }, + { + ...fetchedTasks[2], + ownerId: 'test-test', + retryAt: fetchedTasks[2].runAt, + status: 'claiming', + }, + { + ...fetchedTasks[3], + ownerId: 'test-test', + retryAt: fetchedTasks[3].runAt, + status: 'claiming', + }, + ], + { validate: false, excludeLargeFields: true } + ); + expect(store.bulkGet).toHaveBeenCalledWith(['id-1', 'id-2', 'id-3', 'id-4']); + + expect(result.stats).toEqual({ + tasksClaimed: 3, + tasksConflicted: 0, + tasksUpdated: 3, + tasksLeftUnclaimed: 0, + }); + expect(result.docs.length).toEqual(3); + }); + + test('should handle error when bulk getting all full task docs', async () => { + const store = taskStoreMock.create({ taskManagerId: 'test-test' }); + store.convertToSavedObjectIds.mockImplementation((ids) => ids.map((id) => `task:${id}`)); + + const fetchedTasks = [ + mockInstance({ id: `id-1`, taskType: 'report' }), + mockInstance({ id: `id-2`, taskType: 'report' }), + mockInstance({ id: `id-3`, taskType: 'yawn' }), + mockInstance({ id: `id-4`, taskType: 'report' }), + ]; + + const { versionMap, docLatestVersions } = getVersionMapsFromTasks(fetchedTasks); + store.fetch.mockResolvedValueOnce({ docs: fetchedTasks, versionMap }); + store.getDocVersions.mockResolvedValueOnce(docLatestVersions); + store.bulkUpdate.mockResolvedValueOnce( + [fetchedTasks[0], fetchedTasks[1], fetchedTasks[2], fetchedTasks[3]].map(asOk) + ); + store.bulkGet.mockRejectedValueOnce(new Error('oh no')); + + const taskClaiming = new TaskClaiming({ + logger: taskManagerLogger, + strategy: CLAIM_STRATEGY_MGET, + definitions: taskDefinitions, + taskStore: store, + excludedTaskTypes: [], + unusedTypes: [], + maxAttempts: 2, + getAvailableCapacity: () => 10, + taskPartitioner, + }); + + const [resultOrErr] = await getAllAsPromise( + taskClaiming.claimAvailableTasksIfCapacityIsAvailable({ claimOwnershipUntil: new Date() }) + ); + + if (!isOk(resultOrErr)) { + expect(resultOrErr).toBe(undefined); + } + + const result = unwrap(resultOrErr) as ClaimOwnershipResult; + + expect(apm.startTransaction).toHaveBeenCalledWith( + TASK_MANAGER_MARK_AS_CLAIMED, + TASK_MANAGER_TRANSACTION_TYPE + ); + expect(mockApmTrans.end).toHaveBeenCalledWith('success'); + + expect(taskManagerLogger.debug).toHaveBeenCalledWith( + 'task claimer claimed: 0; stale: 0; conflicts: 0; missing: 0; capacity reached: 0; updateErrors: 0; removed: 0;', + { tags: ['claimAvailableTasksMget'] } + ); + expect(taskManagerLogger.warn).toHaveBeenCalledWith( + 'Error getting full task documents during claim: Error: oh no', + { tags: ['claimAvailableTasksMget'] } + ); + + expect(store.fetch.mock.calls[0][0]).toMatchObject({ size: 40, seq_no_primary_term: true }); + expect(store.getDocVersions).toHaveBeenCalledWith([ + 'task:id-1', + 'task:id-2', + 'task:id-3', + 'task:id-4', + ]); + expect(store.bulkUpdate).toHaveBeenCalledTimes(1); + expect(store.bulkUpdate).toHaveBeenCalledWith( + [ + { + ...fetchedTasks[0], + ownerId: 'test-test', + retryAt: fetchedTasks[0].runAt, + status: 'claiming', + }, + { + ...fetchedTasks[1], + ownerId: 'test-test', + retryAt: fetchedTasks[2].runAt, + status: 'claiming', + }, + { + ...fetchedTasks[2], + ownerId: 'test-test', + retryAt: fetchedTasks[2].runAt, + status: 'claiming', + }, + { + ...fetchedTasks[3], + ownerId: 'test-test', + retryAt: fetchedTasks[3].runAt, + status: 'claiming', + }, + ], + { validate: false, excludeLargeFields: true } + ); + expect(store.bulkGet).toHaveBeenCalledWith(['id-1', 'id-2', 'id-3', 'id-4']); + + expect(result.stats).toEqual({ + tasksClaimed: 0, + tasksConflicted: 0, + tasksUpdated: 0, + tasksLeftUnclaimed: 0, + }); + expect(result.docs.length).toEqual(0); + }); + + test('should handle individual errors when bulk updating the task doc', async () => { + const store = taskStoreMock.create({ taskManagerId: 'test-test' }); + store.convertToSavedObjectIds.mockImplementation((ids) => ids.map((id) => `task:${id}`)); + + const fetchedTasks = [ + mockInstance({ id: `id-1`, taskType: 'report' }), + mockInstance({ id: `id-2`, taskType: 'report' }), + mockInstance({ id: `id-3`, taskType: 'yawn' }), + mockInstance({ id: `id-4`, taskType: 'report' }), + ]; + + const { versionMap, docLatestVersions } = getVersionMapsFromTasks(fetchedTasks); + store.fetch.mockResolvedValueOnce({ docs: fetchedTasks, versionMap }); + store.getDocVersions.mockResolvedValueOnce(docLatestVersions); + store.bulkUpdate.mockResolvedValueOnce([ + asOk(fetchedTasks[0]), + // @ts-expect-error + asErr({ + type: 'task', + id: fetchedTasks[1].id, + error: new Error('Oh no'), + }), + asOk(fetchedTasks[2]), + asOk(fetchedTasks[3]), + ]); + store.bulkGet.mockResolvedValueOnce([ + asOk(fetchedTasks[0]), + asOk(fetchedTasks[2]), + asOk(fetchedTasks[3]), + ]); + + const taskClaiming = new TaskClaiming({ + logger: taskManagerLogger, + strategy: CLAIM_STRATEGY_MGET, + definitions: taskDefinitions, + taskStore: store, + excludedTaskTypes: [], + unusedTypes: [], + maxAttempts: 2, + getAvailableCapacity: () => 10, + taskPartitioner, + }); + + const [resultOrErr] = await getAllAsPromise( + taskClaiming.claimAvailableTasksIfCapacityIsAvailable({ claimOwnershipUntil: new Date() }) + ); + + if (!isOk(resultOrErr)) { + expect(resultOrErr).toBe(undefined); + } + + const result = unwrap(resultOrErr) as ClaimOwnershipResult; + + expect(apm.startTransaction).toHaveBeenCalledWith( + TASK_MANAGER_MARK_AS_CLAIMED, + TASK_MANAGER_TRANSACTION_TYPE + ); + expect(mockApmTrans.end).toHaveBeenCalledWith('success'); + + expect(taskManagerLogger.debug).toHaveBeenCalledWith( + 'task claimer claimed: 3; stale: 0; conflicts: 0; missing: 0; capacity reached: 0; updateErrors: 1; removed: 0;', + { tags: ['claimAvailableTasksMget'] } + ); + expect(taskManagerLogger.warn).toHaveBeenCalledWith( + 'Error updating task id-2:task during claim: Oh no', + { tags: ['claimAvailableTasksMget'] } + ); + + expect(store.fetch.mock.calls[0][0]).toMatchObject({ size: 40, seq_no_primary_term: true }); + expect(store.getDocVersions).toHaveBeenCalledWith([ + 'task:id-1', + 'task:id-2', + 'task:id-3', + 'task:id-4', + ]); + expect(store.bulkUpdate).toHaveBeenCalledTimes(1); + expect(store.bulkUpdate).toHaveBeenCalledWith( + [ + { + ...fetchedTasks[0], + ownerId: 'test-test', + retryAt: fetchedTasks[0].runAt, + status: 'claiming', + }, + { + ...fetchedTasks[1], + ownerId: 'test-test', + retryAt: fetchedTasks[1].runAt, + status: 'claiming', + }, + { + ...fetchedTasks[2], + ownerId: 'test-test', + retryAt: fetchedTasks[2].runAt, + status: 'claiming', + }, + { + ...fetchedTasks[3], + ownerId: 'test-test', + retryAt: fetchedTasks[3].runAt, + status: 'claiming', + }, + ], + { validate: false, excludeLargeFields: true } + ); + expect(store.bulkGet).toHaveBeenCalledWith(['id-1', 'id-3', 'id-4']); + + expect(result.stats).toEqual({ + tasksClaimed: 3, + tasksConflicted: 0, + tasksUpdated: 3, + tasksLeftUnclaimed: 0, + }); + expect(result.docs.length).toEqual(3); + }); + + test('should handle error when bulk updating all task docs', async () => { + const store = taskStoreMock.create({ taskManagerId: 'test-test' }); + store.convertToSavedObjectIds.mockImplementation((ids) => ids.map((id) => `task:${id}`)); + + const fetchedTasks = [ + mockInstance({ id: `id-1`, taskType: 'report' }), + mockInstance({ id: `id-2`, taskType: 'report' }), + mockInstance({ id: `id-3`, taskType: 'yawn' }), + mockInstance({ id: `id-4`, taskType: 'report' }), + ]; + + const { versionMap, docLatestVersions } = getVersionMapsFromTasks(fetchedTasks); + store.fetch.mockResolvedValueOnce({ docs: fetchedTasks, versionMap }); + store.getDocVersions.mockResolvedValueOnce(docLatestVersions); + store.bulkUpdate.mockRejectedValueOnce(new Error('oh no')); + store.bulkGet.mockResolvedValueOnce([]); + + const taskClaiming = new TaskClaiming({ + logger: taskManagerLogger, + strategy: CLAIM_STRATEGY_MGET, + definitions: taskDefinitions, + taskStore: store, + excludedTaskTypes: [], + unusedTypes: [], + maxAttempts: 2, + getAvailableCapacity: () => 10, + taskPartitioner, + }); + + const [resultOrErr] = await getAllAsPromise( + taskClaiming.claimAvailableTasksIfCapacityIsAvailable({ claimOwnershipUntil: new Date() }) + ); + + if (!isOk(resultOrErr)) { + expect(resultOrErr).toBe(undefined); + } + + const result = unwrap(resultOrErr) as ClaimOwnershipResult; + + expect(apm.startTransaction).toHaveBeenCalledWith( + TASK_MANAGER_MARK_AS_CLAIMED, + TASK_MANAGER_TRANSACTION_TYPE + ); + expect(mockApmTrans.end).toHaveBeenCalledWith('success'); + + expect(taskManagerLogger.debug).toHaveBeenCalledWith( + 'task claimer claimed: 0; stale: 0; conflicts: 0; missing: 0; capacity reached: 0; updateErrors: 0; removed: 0;', + { tags: ['claimAvailableTasksMget'] } + ); + expect(taskManagerLogger.warn).toHaveBeenCalledWith( + 'Error updating tasks during claim: Error: oh no', + { tags: ['claimAvailableTasksMget'] } + ); + + expect(store.fetch.mock.calls[0][0]).toMatchObject({ size: 40, seq_no_primary_term: true }); + expect(store.getDocVersions).toHaveBeenCalledWith([ + 'task:id-1', + 'task:id-2', + 'task:id-3', + 'task:id-4', + ]); + expect(store.bulkUpdate).toHaveBeenCalledTimes(1); + expect(store.bulkUpdate).toHaveBeenCalledWith( + [ + { + ...fetchedTasks[0], + ownerId: 'test-test', + retryAt: fetchedTasks[0].runAt, + status: 'claiming', + }, + { + ...fetchedTasks[1], + ownerId: 'test-test', + retryAt: fetchedTasks[1].runAt, + status: 'claiming', + }, + { + ...fetchedTasks[2], + ownerId: 'test-test', + retryAt: fetchedTasks[2].runAt, + status: 'claiming', + }, + { + ...fetchedTasks[3], + ownerId: 'test-test', + retryAt: fetchedTasks[3].runAt, + status: 'claiming', + }, + ], + { validate: false, excludeLargeFields: true } + ); + expect(store.bulkGet).toHaveBeenCalledWith([]); + + expect(result.stats).toEqual({ + tasksClaimed: 0, + tasksConflicted: 0, + tasksUpdated: 0, + tasksLeftUnclaimed: 0, + }); + expect(result.docs.length).toEqual(0); + }); + test('it should filter for specific partitions and tasks without partitions', async () => { const taskManagerId = uuidv4(); + const definitions = new TaskTypeDictionary(mockLogger()); + definitions.registerTaskDefinitions({ + foo: { + title: 'foo', + createTaskRunner: jest.fn(), + }, + bar: { + title: 'bar', + createTaskRunner: jest.fn(), + }, + }); const [ { args: { @@ -297,6 +1572,7 @@ describe('TaskClaiming', () => { ] = await testClaimAvailableTasks({ storeOpts: { taskManagerId, + definitions, }, taskClaimingOpts: {}, claimingOpts: { @@ -352,9 +1628,8 @@ describe('TaskClaiming', () => { Object { "terms": Object { "task.taskType": Array [ - "report", - "dernstraight", - "yawn", + "foo", + "bar", ], }, }, @@ -495,9 +1770,9 @@ describe('TaskClaiming', () => { function instantiateStoreWithMockedApiResponses({ taskManagerId = uuidv4(), definitions = taskDefinitions, - getCapacity = () => 10, + getAvailableCapacity = () => 10, tasksClaimed, - }: Partial> & { + }: Partial> & { taskManagerId?: string; tasksClaimed?: ConcreteTaskInstance[][]; } = {}) { @@ -530,7 +1805,7 @@ describe('TaskClaiming', () => { unusedTypes: [], taskStore, maxAttempts: 2, - getCapacity, + getAvailableCapacity, taskPartitioner, }); diff --git a/x-pack/plugins/task_manager/server/task_claimers/strategy_mget.ts b/x-pack/plugins/task_manager/server/task_claimers/strategy_mget.ts index 362c38166339f..7962fdd2b6f8a 100644 --- a/x-pack/plugins/task_manager/server/task_claimers/strategy_mget.ts +++ b/x-pack/plugins/task_manager/server/task_claimers/strategy_mget.ts @@ -7,9 +7,11 @@ // Basic operation of this task claimer: // - search for candidate tasks to run, more than we actually can run +// - initial search returns a slimmer task document for I/O efficiency (no params or state) // - for each task found, do an mget to get the current seq_no and primary_term // - if the mget result doesn't match the search result, the task is stale -// - from the non-stale search results, return as many as we can run +// - from the non-stale search results, return as many as we can run based on available +// capacity and the cost of each task type to run import { SavedObjectsErrorHelpers } from '@kbn/core/server'; @@ -18,7 +20,7 @@ import { Subject, Observable } from 'rxjs'; import { TaskTypeDictionary } from '../task_type_dictionary'; import { TaskClaimerOpts, ClaimOwnershipResult, getEmptyClaimOwnershipResult } from '.'; -import { ConcreteTaskInstance, TaskStatus, ConcreteTaskInstanceVersion } from '../task'; +import { ConcreteTaskInstance, TaskStatus, ConcreteTaskInstanceVersion, TaskCost } from '../task'; import { TASK_MANAGER_TRANSACTION_TYPE } from '../task_running'; import { isLimited, @@ -112,7 +114,10 @@ async function claimAvailableTasks(opts: TaskClaimerOpts): Promise { - if (task.retryAt != null && new Date(task.retryAt).getTime() < Date.now()) { - task.scheduledAt = task.retryAt; - } else { - task.scheduledAt = task.runAt; - } - task.retryAt = claimOwnershipUntil; - task.ownerId = taskStore.taskManagerId; - task.status = TaskStatus.Claiming; + // apply capacity constraint to candidate tasks + const tasksToRun: ConcreteTaskInstance[] = []; + const leftOverTasks: ConcreteTaskInstance[] = []; + + let capacityAccumulator = 0; + for (const task of candidateTasks) { + const taskCost = definitions.get(task.taskType)?.cost ?? TaskCost.Normal; + if (capacityAccumulator + taskCost <= initialCapacity) { + tasksToRun.push(task); + capacityAccumulator += taskCost; + } else { + leftOverTasks.push(task); + capacityAccumulator = initialCapacity; + } + } - return task; + // build the updated task objects we'll claim + const taskUpdates: ConcreteTaskInstance[] = []; + for (const task of tasksToRun) { + taskUpdates.push({ + ...task, + scheduledAt: + task.retryAt != null && new Date(task.retryAt).getTime() < Date.now() + ? task.retryAt + : task.runAt, + status: TaskStatus.Claiming, + retryAt: claimOwnershipUntil, + ownerId: taskStore.taskManagerId, }); + } // perform the task object updates, deal with errors - const finalResults: ConcreteTaskInstance[] = []; + const updatedTasks: ConcreteTaskInstance[] = []; let conflicts = staleTasks.length; let bulkErrors = 0; try { - const updateResults = await taskStore.bulkUpdate(taskUpdates, { validate: false }); + const updateResults = await taskStore.bulkUpdate(taskUpdates, { + validate: false, + excludeLargeFields: true, + }); for (const updateResult of updateResults) { if (isOk(updateResult)) { - finalResults.push(updateResult.value); + updatedTasks.push(updateResult.value); } else { const { id, type, error } = updateResult.error; @@ -209,6 +233,27 @@ async function claimAvailableTasks(opts: TaskClaimerOpts): Promise task.id))).reduce< + ConcreteTaskInstance[] + >((acc, task) => { + if (isOk(task)) { + acc.push(task.value); + } else { + const { id, type, error } = task.error; + logger.warn( + `Error getting full task ${id}:${type} during claim: ${error.message}`, + logMeta + ); + } + return acc; + }, []); + } catch (err) { + logger.warn(`Error getting full task documents during claim: ${err}`, logMeta); + } + // separate update for removed tasks; shouldn't happen often, so unlikely // a performance concern, and keeps the rest of the logic simpler let removedCount = 0; @@ -220,7 +265,10 @@ async function claimAvailableTasks(opts: TaskClaimerOpts): Promise { - beforeEach(() => { - jest.useFakeTimers(); - jest.setSystemTime(new Date(2021, 12, 30)); - }); - - afterEach(() => { - jest.useRealTimers(); - }); - - test('occupiedWorkers are a sum of running tasks', async () => { - const pool = new TaskPool({ - maxWorkers$: of(200), - logger: loggingSystemMock.create().get(), - }); - - const result = await pool.run([{ ...mockTask() }, { ...mockTask() }, { ...mockTask() }]); - - expect(result).toEqual(TaskPoolRunResult.RunningAllClaimedTasks); - expect(pool.occupiedWorkers).toEqual(3); - }); - - test('availableWorkers are a function of total_capacity - occupiedWorkers', async () => { - const pool = new TaskPool({ - maxWorkers$: of(10), - logger: loggingSystemMock.create().get(), - }); - - const result = await pool.run([{ ...mockTask() }, { ...mockTask() }, { ...mockTask() }]); - - expect(result).toEqual(TaskPoolRunResult.RunningAllClaimedTasks); - expect(pool.availableWorkers).toEqual(7); - }); - - test('availableWorkers is 0 until maxWorkers$ pushes a value', async () => { - const maxWorkers$ = new Subject(); - const pool = new TaskPool({ - maxWorkers$, - logger: loggingSystemMock.create().get(), - }); - - expect(pool.availableWorkers).toEqual(0); - maxWorkers$.next(10); - expect(pool.availableWorkers).toEqual(10); - }); - - test('does not run tasks that are beyond its available capacity', async () => { - const pool = new TaskPool({ - maxWorkers$: of(2), - logger: loggingSystemMock.create().get(), - }); - - const shouldRun = mockRun(); - const shouldNotRun = mockRun(); - - const result = await pool.run([ - { ...mockTask(), run: shouldRun }, - { ...mockTask(), run: shouldRun }, - { ...mockTask(), run: shouldNotRun }, - ]); - - expect(result).toEqual(TaskPoolRunResult.RanOutOfCapacity); - expect(pool.availableWorkers).toEqual(0); - expect(shouldRun).toHaveBeenCalledTimes(2); - expect(shouldNotRun).not.toHaveBeenCalled(); - }); - - test('should log when marking a Task as running fails', async () => { - const logger = loggingSystemMock.create().get(); - const pool = new TaskPool({ - maxWorkers$: of(2), - logger, - }); - - const taskFailedToMarkAsRunning = mockTask(); - taskFailedToMarkAsRunning.markTaskAsRunning.mockImplementation(async () => { - throw new Error(`Mark Task as running has failed miserably`); - }); - - const result = await pool.run([mockTask(), taskFailedToMarkAsRunning, mockTask()]); - - expect((logger as jest.Mocked).error.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "Failed to mark Task TaskType \\"shooooo\\" as running: Mark Task as running has failed miserably", - ] - `); - - expect(result).toEqual(TaskPoolRunResult.RunningAtCapacity); - }); - - test('should log when running a Task fails', async () => { - const logger = loggingSystemMock.create().get(); - const pool = new TaskPool({ - maxWorkers$: of(3), - logger, - }); - - const taskFailedToRun = mockTask(); - taskFailedToRun.run.mockImplementation(async () => { - throw new Error(`Run Task has failed miserably`); - }); - - const result = await pool.run([mockTask(), taskFailedToRun, mockTask()]); - - expect((logger as jest.Mocked).warn.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "Task TaskType \\"shooooo\\" failed in attempt to run: Run Task has failed miserably", - ] - `); - - expect(result).toEqual(TaskPoolRunResult.RunningAllClaimedTasks); - }); - - test('should not log when running a Task fails due to the Task SO having been deleted while in flight', async () => { - const logger = loggingSystemMock.create().get(); - const pool = new TaskPool({ - maxWorkers$: of(3), - logger, - }); - - const taskFailedToRun = mockTask(); - taskFailedToRun.run.mockImplementation(async () => { - throw SavedObjectsErrorHelpers.createGenericNotFoundError('task', taskFailedToRun.id); - }); - - const result = await pool.run([mockTask(), taskFailedToRun, mockTask()]); - - expect(logger.debug).toHaveBeenCalledWith( - `Task TaskType "shooooo" failed in attempt to run: Saved object [task/${taskFailedToRun.id}] not found` - ); - expect(logger.warn).not.toHaveBeenCalled(); - - expect(result).toEqual(TaskPoolRunResult.RunningAllClaimedTasks); - }); - - test('Running a task which fails still takes up capacity', async () => { - const logger = loggingSystemMock.create().get(); - const pool = new TaskPool({ - maxWorkers$: of(1), - logger, - }); - - const taskFailedToRun = mockTask(); - taskFailedToRun.run.mockImplementation(async () => { - await sleep(0); - throw new Error(`Run Task has failed miserably`); - }); - - const result = await pool.run([taskFailedToRun, mockTask()]); - - expect(result).toEqual(TaskPoolRunResult.RanOutOfCapacity); - }); - - test('clears up capacity when a task completes', async () => { - const pool = new TaskPool({ - maxWorkers$: of(1), - logger: loggingSystemMock.create().get(), - }); - - const firstWork = resolvable(); - const firstRun = sinon.spy(async () => { - await sleep(0); - firstWork.resolve(); - return asOk({ state: {} }); - }); - const secondWork = resolvable(); - const secondRun = sinon.spy(async () => { - await sleep(0); - secondWork.resolve(); - return asOk({ state: {} }); - }); - - const result = await pool.run([ - { ...mockTask(), run: firstRun }, - { ...mockTask(), run: secondRun }, - ]); - - expect(result).toEqual(TaskPoolRunResult.RanOutOfCapacity); - expect(pool.occupiedWorkers).toEqual(1); - expect(pool.availableWorkers).toEqual(0); - - await firstWork; - sinon.assert.calledOnce(firstRun); - sinon.assert.notCalled(secondRun); - - expect(pool.occupiedWorkers).toEqual(0); - await pool.run([{ ...mockTask(), run: secondRun }]); - expect(pool.occupiedWorkers).toEqual(1); - - expect(pool.availableWorkers).toEqual(0); - - await secondWork; - - expect(pool.occupiedWorkers).toEqual(0); - expect(pool.availableWorkers).toEqual(1); - sinon.assert.calledOnce(secondRun); - }); - - test('run cancels expired tasks prior to running new tasks', async () => { - const logger = loggingSystemMock.create().get(); - const pool = new TaskPool({ - maxWorkers$: of(2), - logger, - }); - - const haltUntilWeAfterFirstRun = resolvable(); - const taskHasExpired = resolvable(); - const haltTaskSoThatItCanBeCanceled = resolvable(); - - const shouldRun = sinon.spy(() => Promise.resolve()); - const shouldNotRun = sinon.spy(() => Promise.resolve()); - const now = new Date(); - const result = await pool.run([ - { - ...mockTask({ id: '1' }), - async run() { - await haltUntilWeAfterFirstRun; - this.isExpired = true; - taskHasExpired.resolve(); - await haltTaskSoThatItCanBeCanceled; - return asOk({ state: {} }); - }, - get expiration() { - return now; - }, - get startedAt() { - // 5 and a half minutes - return moment(now).subtract(5, 'm').subtract(30, 's').toDate(); - }, - cancel: shouldRun, - }, - { - ...mockTask({ id: '2' }), - async run() { - // halt here so that we can verify that this task is counted in `occupiedWorkers` - await haltUntilWeAfterFirstRun; - return asOk({ state: {} }); - }, - cancel: shouldNotRun, - }, - ]); - - expect(result).toEqual(TaskPoolRunResult.RunningAtCapacity); - expect(pool.occupiedWorkers).toEqual(2); - expect(pool.availableWorkers).toEqual(0); - - // release first stage in task so that it has time to expire, but not complete - haltUntilWeAfterFirstRun.resolve(); - await taskHasExpired; - - expect(await pool.run([{ ...mockTask({ id: '3' }) }])).toBeTruthy(); - - sinon.assert.calledOnce(shouldRun); - sinon.assert.notCalled(shouldNotRun); - - expect(pool.occupiedWorkers).toEqual(1); - expect(pool.availableWorkers).toEqual(1); - - haltTaskSoThatItCanBeCanceled.resolve(); - - expect(logger.warn).toHaveBeenCalledWith( - `Cancelling task TaskType "shooooo" as it expired at ${now.toISOString()} after running for 05m 30s (with timeout set at 5m).` - ); - }); - - test('calls to availableWorkers ensures we cancel expired tasks', async () => { - const pool = new TaskPool({ - maxWorkers$: of(1), - logger: loggingSystemMock.create().get(), - }); - - const taskIsRunning = resolvable(); - const taskHasExpired = resolvable(); - const cancel = sinon.spy(() => Promise.resolve()); - const now = new Date(); - expect( - await pool.run([ - { - ...mockTask(), - async run() { - await sleep(10); - this.isExpired = true; - taskIsRunning.resolve(); - await taskHasExpired; - return asOk({ state: {} }); - }, - get expiration() { - return new Date(now.getTime() + 10); - }, - get startedAt() { - return now; - }, - cancel, - }, - ]) - ).toEqual(TaskPoolRunResult.RunningAtCapacity); - - await taskIsRunning; - - sinon.assert.notCalled(cancel); - expect(pool.occupiedWorkers).toEqual(1); - // The call to `availableWorkers` will clear the expired task so it's 1 instead of 0 - expect(pool.availableWorkers).toEqual(1); - sinon.assert.calledOnce(cancel); - - expect(pool.occupiedWorkers).toEqual(0); - expect(pool.availableWorkers).toEqual(1); - // ensure cancel isn't called twice - sinon.assert.calledOnce(cancel); - taskHasExpired.resolve(); - }); - - test('logs if cancellation errors', async () => { - const logger = loggingSystemMock.create().get(); - const pool = new TaskPool({ - logger, - maxWorkers$: of(20), - }); - - const cancelled = resolvable(); - const result = await pool.run([ - { - ...mockTask(), - async run() { - this.isExpired = true; - await sleep(10); - return asOk({ state: {} }); - }, - async cancel() { - cancelled.resolve(); - throw new Error('Dern!'); - }, - toString: () => '"shooooo!"', - }, - ]); - - expect(result).toEqual(TaskPoolRunResult.RunningAllClaimedTasks); - await pool.run([]); - - expect(pool.occupiedWorkers).toEqual(0); - - // Allow the task to cancel... - await cancelled; - - expect((logger as jest.Mocked).error.mock.calls[0][0]).toMatchInlineSnapshot( - `"Failed to cancel task \\"shooooo!\\": Error: Dern!"` - ); - }); - - test('only allows one task with the same id in the task pool', async () => { - const logger = loggingSystemMock.create().get(); - const pool = new TaskPool({ - maxWorkers$: of(2), - logger, - }); - - const shouldRun = mockRun(); - const shouldNotRun = mockRun(); - - const taskId = uuidv4(); - const task1 = mockTask({ id: taskId, run: shouldRun }); - const task2 = mockTask({ - id: taskId, - run: shouldNotRun, - isSameTask() { - return true; - }, - }); - - await pool.run([task1]); - await pool.run([task2]); - - expect(shouldRun).toHaveBeenCalledTimes(1); - expect(shouldNotRun).not.toHaveBeenCalled(); - }); - - // This test is from https://github.com/elastic/kibana/issues/172116 - // It's not clear how to reproduce the actual error, but it is easy to - // reproduce with the wacky test below. It does log the exact error - // from that issue, without the corresponding fix in task_pool.ts - test('works when available workers is 0 but there are tasks to run', async () => { - const logger = loggingSystemMock.create().get(); - const pool = new TaskPool({ - maxWorkers$: of(2), - logger, - }); - - const shouldRun = mockRun(); - - const taskId = uuidv4(); - const task1 = mockTask({ id: taskId, run: shouldRun }); - - // we need to alternate the values of `availableWorkers`. First it - // should be 0, then 1, then 0, then 1, etc. This will cause task_pool.run - // to partition tasks (0 to run, everything as leftover), then at the - // end of run(), to check if it should recurse, it should be > 0. - let awValue = 1; - Object.defineProperty(pool, 'availableWorkers', { - get() { - return ++awValue % 2; - }, - }); - - const result = await pool.run([task1]); - expect(result).toBe(TaskPoolRunResult.RanOutOfCapacity); - - expect((logger as jest.Mocked).warn.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "task pool run attempts exceeded 3; assuming ran out of capacity; availableWorkers: 0, tasksToRun: 0, leftOverTasks: 1, maxWorkers: 2, occupiedWorkers: 0, workerLoad: 0", - ] - `); - }); - - function mockRun() { - return jest.fn(async () => { - await sleep(0); - return asOk({ state: {} }); - }); - } - - function mockTask(overrides = {}) { - return { - isExpired: false, - taskExecutionId: uuidv4(), - id: uuidv4(), - cancel: async () => undefined, - markTaskAsRunning: jest.fn(async () => true), - run: mockRun(), - stage: TaskRunningStage.PENDING, - toString: () => `TaskType "shooooo"`, - isAdHocTaskAndOutOfAttempts: false, - removeTask: jest.fn(), - get expiration() { - return new Date(); - }, - get startedAt() { - return new Date(); - }, - get definition() { - return { - type: '', - title: '', - timeout: '5m', - createTaskRunner: jest.fn(), - }; - }, - isSameTask() { - return false; - }, - ...overrides, - }; - } -}); diff --git a/x-pack/plugins/task_manager/server/task_pool/capacity.mock.ts b/x-pack/plugins/task_manager/server/task_pool/capacity.mock.ts new file mode 100644 index 0000000000000..ed3fd3b07f07c --- /dev/null +++ b/x-pack/plugins/task_manager/server/task_pool/capacity.mock.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. + */ +const createCapacityMock = () => { + return jest.fn().mockImplementation(() => { + return { + determineTasksToRunBasedOnCapacity: jest.fn(), + getUsedCapacityByType: jest.fn(), + usedCapacityPercentage: jest.fn(), + usedCapacity: jest.fn(), + capacity: jest.fn(), + }; + }); +}; + +export const capacityMock = { + create: createCapacityMock(), +}; diff --git a/x-pack/plugins/task_manager/server/task_pool/cost_capacity.test.ts b/x-pack/plugins/task_manager/server/task_pool/cost_capacity.test.ts new file mode 100644 index 0000000000000..b40c6eb2af37d --- /dev/null +++ b/x-pack/plugins/task_manager/server/task_pool/cost_capacity.test.ts @@ -0,0 +1,171 @@ +/* + * 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 { loggingSystemMock } from '@kbn/core/server/mocks'; +import { of, Subject } from 'rxjs'; +import { TaskCost } from '../task'; +import { CostCapacity } from './cost_capacity'; +import { mockTask } from './test_utils'; + +const logger = loggingSystemMock.create().get(); + +describe('CostCapacity', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + test('capacity responds to changes from capacity$ observable', () => { + const capacity$ = new Subject(); + const pool = new CostCapacity({ capacity$, logger }); + + expect(pool.capacity).toBe(0); + + capacity$.next(20); + expect(pool.capacity).toBe(40); + + capacity$.next(16); + expect(pool.capacity).toBe(32); + + expect(logger.debug).toHaveBeenCalledTimes(2); + expect(logger.debug).toHaveBeenNthCalledWith( + 1, + `Task pool now using 40 as the max allowed cost which is based on a capacity of 20` + ); + expect(logger.debug).toHaveBeenNthCalledWith( + 2, + `Task pool now using 32 as the max allowed cost which is based on a capacity of 16` + ); + }); + + test('usedCapacity returns the sum of costs of tasks in the pool', () => { + const pool = new CostCapacity({ capacity$: of(10), logger }); + + const tasksInPool = new Map([ + ['1', { ...mockTask() }], + ['2', { ...mockTask({}, { cost: TaskCost.Tiny }) }], + ['3', { ...mockTask() }], + ]); + + expect(pool.usedCapacity(tasksInPool)).toBe(5); + }); + + test('usedCapacityPercentage returns the percentage of capacity used based on cost of tasks in the pool', () => { + const pool = new CostCapacity({ capacity$: of(10), logger }); + + const tasksInPool = new Map([ + ['1', { ...mockTask() }], + ['2', { ...mockTask({}, { cost: TaskCost.Tiny }) }], + ['3', { ...mockTask() }], + ]); + + expect(pool.usedCapacityPercentage(tasksInPool)).toBe(25); + }); + + test('usedCapacityByType returns the sum of of costs of tasks of specified type in the pool', () => { + const pool = new CostCapacity({ capacity$: of(10), logger }); + + const tasksInPool = [ + { ...mockTask({}, { type: 'type1' }) }, + { ...mockTask({}, { type: 'type1', cost: TaskCost.Tiny }) }, + { ...mockTask({}, { type: 'type2' }) }, + ]; + + expect(pool.getUsedCapacityByType(tasksInPool, 'type1')).toBe(3); + expect(pool.getUsedCapacityByType(tasksInPool, 'type2')).toBe(2); + expect(pool.getUsedCapacityByType(tasksInPool, 'type3')).toBe(0); + }); + + test('availableCapacity returns the full available capacity when no task type is defined', () => { + const pool = new CostCapacity({ capacity$: of(10), logger }); + + const tasksInPool = new Map([ + ['1', { ...mockTask() }], + ['2', { ...mockTask({}, { cost: TaskCost.Tiny }) }], + ['3', { ...mockTask() }], + ]); + + expect(pool.availableCapacity(tasksInPool)).toBe(15); + }); + + test('availableCapacity returns the full available capacity when task type with no maxConcurrency is provided', () => { + const pool = new CostCapacity({ capacity$: of(10), logger }); + + const tasksInPool = new Map([ + ['1', { ...mockTask() }], + ['2', { ...mockTask({}, { cost: TaskCost.Tiny }) }], + ['3', { ...mockTask() }], + ]); + + expect( + pool.availableCapacity(tasksInPool, { + type: 'type1', + cost: TaskCost.Normal, + createTaskRunner: jest.fn(), + timeout: '5m', + }) + ).toBe(15); + }); + + test('availableCapacity returns the available capacity for the task type when task type with maxConcurrency is provided', () => { + const pool = new CostCapacity({ capacity$: of(10), logger }); + + const tasksInPool = new Map([ + ['1', { ...mockTask({}, { type: 'type1' }) }], + ['2', { ...mockTask({}, { cost: TaskCost.Tiny }) }], + ['3', { ...mockTask() }], + ]); + + expect( + pool.availableCapacity(tasksInPool, { + type: 'type1', + maxConcurrency: 3, + cost: TaskCost.Normal, + createTaskRunner: jest.fn(), + timeout: '5m', + }) + ).toBe(4); + }); + + describe('determineTasksToRunBasedOnCapacity', () => { + test('runs all tasks if there is capacity', () => { + const pool = new CostCapacity({ capacity$: of(10), logger }); + const tasks = [{ ...mockTask() }, { ...mockTask() }, { ...mockTask() }]; + const [tasksToRun, leftoverTasks] = pool.determineTasksToRunBasedOnCapacity(tasks, 20); + + expect(tasksToRun).toEqual(tasks); + expect(leftoverTasks).toEqual([]); + }); + + test('runs task in order until capacity is reached', () => { + const pool = new CostCapacity({ capacity$: of(10), logger }); + const tasks = [ + { ...mockTask() }, + { ...mockTask() }, + { ...mockTask() }, + { ...mockTask({}, { cost: TaskCost.ExtraLarge }) }, + { ...mockTask({}, { cost: TaskCost.ExtraLarge }) }, + // technically have capacity for these tasks if we skip the previous task, but we're running + // in order to avoid possibly starving large cost tasks + { ...mockTask() }, + { ...mockTask() }, + ]; + const [tasksToRun, leftoverTasks] = pool.determineTasksToRunBasedOnCapacity(tasks, 20); + + expect(tasksToRun).toEqual([tasks[0], tasks[1], tasks[2], tasks[3]]); + expect(leftoverTasks).toEqual([tasks[4], tasks[5], tasks[6]]); + }); + + test('does not run tasks if there is no capacity', () => { + const pool = new CostCapacity({ capacity$: of(10), logger }); + const tasks = [{ ...mockTask() }, { ...mockTask() }, { ...mockTask() }]; + const [tasksToRun, leftoverTasks] = pool.determineTasksToRunBasedOnCapacity(tasks, 1); + + expect(tasksToRun).toEqual([]); + expect(leftoverTasks).toEqual(tasks); + }); + }); +}); diff --git a/x-pack/plugins/task_manager/server/task_pool/cost_capacity.ts b/x-pack/plugins/task_manager/server/task_pool/cost_capacity.ts new file mode 100644 index 0000000000000..ead7cf1839714 --- /dev/null +++ b/x-pack/plugins/task_manager/server/task_pool/cost_capacity.ts @@ -0,0 +1,111 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Logger } from '@kbn/core/server'; +import { TaskDefinition } from '../task'; +import { TaskRunner } from '../task_running'; +import { CapacityOpts, ICapacity } from './types'; +import { getCapacityInCost } from './utils'; + +export class CostCapacity implements ICapacity { + private maxAllowedCost: number = 0; + private logger: Logger; + + constructor(opts: CapacityOpts) { + this.logger = opts.logger; + opts.capacity$.subscribe((capacity) => { + // Capacity config describes the number of normal-cost tasks that can be + // run simulatenously. Multiple by the cost of a normal cost to determine + // the maximum allowed cost + this.maxAllowedCost = getCapacityInCost(capacity); + this.logger.debug( + `Task pool now using ${this.maxAllowedCost} as the max allowed cost which is based on a capacity of ${capacity}` + ); + }); + } + + public get capacity(): number { + return this.maxAllowedCost; + } + + /** + * Gets how much capacity is currently in use. + */ + public usedCapacity(tasksInPool: Map) { + let result = 0; + tasksInPool.forEach((task) => { + if (task.definition?.cost) { + result += task.definition.cost; + } + }); + return result; + } + + /** + * Gets % of capacity in use + */ + public usedCapacityPercentage(tasksInPool: Map) { + return this.capacity ? Math.round((this.usedCapacity(tasksInPool) * 100) / this.capacity) : 100; + } + + /** + * Gets how much capacity is currently in use by each type. + */ + public getUsedCapacityByType(tasksInPool: TaskRunner[], type: string) { + return tasksInPool.reduce( + (count, runningTask) => + runningTask.definition?.type === type ? count + runningTask.definition.cost : count, + 0 + ); + } + + public availableCapacity( + tasksInPool: Map, + taskDefinition?: TaskDefinition | null + ): number { + const allAvailableCapacity = this.capacity - this.usedCapacity(tasksInPool); + if (taskDefinition && taskDefinition.maxConcurrency) { + // calculate the max capacity that can be used for this task type based on cost + const maxCapacityForType = taskDefinition.maxConcurrency * taskDefinition.cost; + return Math.max( + Math.min( + allAvailableCapacity, + maxCapacityForType - + this.getUsedCapacityByType([...tasksInPool.values()], taskDefinition.type) + ), + 0 + ); + } + + return allAvailableCapacity; + } + + public determineTasksToRunBasedOnCapacity( + tasks: TaskRunner[], + availableCapacity: number + ): [TaskRunner[], TaskRunner[]] { + const tasksToRun: TaskRunner[] = []; + const leftOverTasks: TaskRunner[] = []; + + let capacityAccumulator = 0; + for (const task of tasks) { + const taskCost = task.definition?.cost ?? 0; + if (capacityAccumulator + taskCost <= availableCapacity) { + tasksToRun.push(task); + capacityAccumulator += taskCost; + } else { + leftOverTasks.push(task); + // Don't claim further tasks even if lower cost tasks are next. + // It may be an extra large task and we need to make room for it + // for the next claiming cycle + capacityAccumulator = availableCapacity; + } + } + + return [tasksToRun, leftOverTasks]; + } +} diff --git a/x-pack/plugins/task_manager/server/task_pool/index.ts b/x-pack/plugins/task_manager/server/task_pool/index.ts new file mode 100644 index 0000000000000..979a4536639a6 --- /dev/null +++ b/x-pack/plugins/task_manager/server/task_pool/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { TaskPool, TaskPoolRunResult } from './task_pool'; +export { getCapacityInCost, getCapacityInWorkers } from './utils'; diff --git a/x-pack/plugins/task_manager/server/task_pool.mock.ts b/x-pack/plugins/task_manager/server/task_pool/task_pool.mock.ts similarity index 58% rename from x-pack/plugins/task_manager/server/task_pool.mock.ts rename to x-pack/plugins/task_manager/server/task_pool/task_pool.mock.ts index 77568c8c6cdfa..00c3cfae16317 100644 --- a/x-pack/plugins/task_manager/server/task_pool.mock.ts +++ b/x-pack/plugins/task_manager/server/task_pool/task_pool.mock.ts @@ -8,16 +8,14 @@ import { TaskPool } from './task_pool'; const defaultGetCapacityOverride: () => Partial<{ load: number; - occupiedWorkers: number; - workerLoad: number; - max: number; - availableWorkers: number; + usedCapacity: number; + usedCapacityPercentage: number; + availableCapacity: number; }> = () => ({ load: 0, - occupiedWorkers: 0, - workerLoad: 0, - max: 10, - availableWorkers: 10, + usedCapacity: 0, + usedCapacityPercentage: 0, + availableCapacity: 20, }); const createTaskPoolMock = (getCapacityOverride = defaultGetCapacityOverride) => { @@ -25,19 +23,16 @@ const createTaskPoolMock = (getCapacityOverride = defaultGetCapacityOverride) => get load() { return getCapacityOverride().load ?? 0; }, - get occupiedWorkers() { - return getCapacityOverride().occupiedWorkers ?? 0; + get usedCapacity() { + return getCapacityOverride().usedCapacity ?? 0; }, - get workerLoad() { - return getCapacityOverride().workerLoad ?? 0; + get usedCapacityPercentage() { + return getCapacityOverride().usedCapacityPercentage ?? 0; }, - get max() { - return getCapacityOverride().max ?? 10; + availableCapacity() { + return getCapacityOverride().availableCapacity ?? 20; }, - get availableWorkers() { - return getCapacityOverride().availableWorkers ?? 10; - }, - getOccupiedWorkersByType: jest.fn(), + getUsedCapacityByType: jest.fn(), run: jest.fn(), cancelRunningTasks: jest.fn(), } as unknown as jest.Mocked; diff --git a/x-pack/plugins/task_manager/server/task_pool/task_pool.test.ts b/x-pack/plugins/task_manager/server/task_pool/task_pool.test.ts new file mode 100644 index 0000000000000..e2936b7ccec0a --- /dev/null +++ b/x-pack/plugins/task_manager/server/task_pool/task_pool.test.ts @@ -0,0 +1,867 @@ +/* + * 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 sinon from 'sinon'; +import { of, Subject } from 'rxjs'; +import { TaskPool, TaskPoolRunResult } from './task_pool'; +import { resolvable, sleep } from '../test_utils'; +import { loggingSystemMock } from '@kbn/core/server/mocks'; +import { Logger } from '@kbn/core/server'; +import { asOk } from '../lib/result_type'; +import { SavedObjectsErrorHelpers } from '@kbn/core/server'; +import moment from 'moment'; +import { v4 as uuidv4 } from 'uuid'; +import { TaskCost } from '../task'; +import * as CostCapacityModule from './cost_capacity'; +import * as WorkerCapacityModule from './worker_capacity'; +import { capacityMock } from './capacity.mock'; +import { CLAIM_STRATEGY_DEFAULT, CLAIM_STRATEGY_MGET } from '../config'; +import { mockRun, mockTask } from './test_utils'; +import { TaskTypeDictionary } from '../task_type_dictionary'; + +jest.mock('../constants', () => ({ + CONCURRENCY_ALLOW_LIST_BY_TASK_TYPE: ['report', 'quickReport'], +})); + +describe('TaskPool', () => { + const costCapacityMock = capacityMock.create(); + const workerCapacityMock = capacityMock.create(); + const logger = loggingSystemMock.create().get(); + + const definitions = new TaskTypeDictionary(logger); + definitions.registerTaskDefinitions({ + report: { + title: 'report', + maxConcurrency: 1, + cost: TaskCost.ExtraLarge, + createTaskRunner: jest.fn(), + }, + quickReport: { + title: 'quickReport', + maxConcurrency: 5, + createTaskRunner: jest.fn(), + }, + }); + + beforeEach(() => { + jest.resetAllMocks(); + jest.useFakeTimers(); + jest.setSystemTime(new Date(2021, 12, 30)); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + describe('uses the correct capacity calculator based on the strategy', () => { + let costCapacitySpy: jest.SpyInstance; + let workerCapacitySpy: jest.SpyInstance; + beforeEach(() => { + costCapacitySpy = jest + .spyOn(CostCapacityModule, 'CostCapacity') + .mockImplementation(() => costCapacityMock); + + workerCapacitySpy = jest + .spyOn(WorkerCapacityModule, 'WorkerCapacity') + .mockImplementation(() => workerCapacityMock); + }); + + afterEach(() => { + costCapacitySpy.mockRestore(); + workerCapacitySpy.mockRestore(); + }); + + test('uses CostCapacity to calculate capacity when strategy is mget', () => { + new TaskPool({ capacity$: of(20), definitions, logger, strategy: CLAIM_STRATEGY_MGET }); + + expect(CostCapacityModule.CostCapacity).toHaveBeenCalledTimes(1); + expect(WorkerCapacityModule.WorkerCapacity).not.toHaveBeenCalled(); + }); + + test('uses WorkerCapacity to calculate capacity when strategy is default', () => { + new TaskPool({ capacity$: of(20), definitions, logger, strategy: CLAIM_STRATEGY_DEFAULT }); + + expect(CostCapacityModule.CostCapacity).not.toHaveBeenCalled(); + expect(WorkerCapacityModule.WorkerCapacity).toHaveBeenCalledTimes(1); + }); + + test('uses WorkerCapacity to calculate capacity when strategy is unrecognized', () => { + new TaskPool({ capacity$: of(20), definitions, logger, strategy: 'any old strategy' }); + + expect(CostCapacityModule.CostCapacity).not.toHaveBeenCalled(); + expect(WorkerCapacityModule.WorkerCapacity).toHaveBeenCalledTimes(1); + }); + }); + + describe('with CLAIM_STRATEGY_DEFAULT', () => { + test('usedCapacity is the number running tasks', async () => { + const pool = new TaskPool({ + capacity$: of(10), + definitions, + logger, + strategy: CLAIM_STRATEGY_DEFAULT, + }); + + const result = await pool.run([{ ...mockTask() }, { ...mockTask() }, { ...mockTask() }]); + + expect(result).toEqual(TaskPoolRunResult.RunningAllClaimedTasks); + expect(pool.usedCapacity).toEqual(3); + }); + + test('availableCapacity are a function of total_capacity - usedCapacity', async () => { + const pool = new TaskPool({ + capacity$: of(10), + definitions, + logger, + strategy: CLAIM_STRATEGY_DEFAULT, + }); + + const result = await pool.run([{ ...mockTask() }, { ...mockTask() }, { ...mockTask() }]); + + expect(result).toEqual(TaskPoolRunResult.RunningAllClaimedTasks); + expect(pool.availableCapacity()).toEqual(7); + }); + + test('availableCapacity is 0 until capacity$ pushes a value', async () => { + const capacity$ = new Subject(); + const pool = new TaskPool({ + capacity$, + definitions, + logger, + strategy: CLAIM_STRATEGY_DEFAULT, + }); + + expect(pool.availableCapacity()).toEqual(0); + capacity$.next(10); + expect(pool.availableCapacity()).toEqual(10); + }); + + test('does not run tasks that are beyond its available capacity', async () => { + const pool = new TaskPool({ + capacity$: of(2), + definitions, + logger, + strategy: CLAIM_STRATEGY_DEFAULT, + }); + + const shouldRun = mockRun(); + const shouldNotRun = mockRun(); + + const result = await pool.run([ + { ...mockTask(), run: shouldRun }, + { ...mockTask(), run: shouldRun }, + { ...mockTask(), run: shouldNotRun }, + ]); + + expect(result).toEqual(TaskPoolRunResult.RanOutOfCapacity); + expect(pool.availableCapacity()).toEqual(0); + expect(shouldRun).toHaveBeenCalledTimes(2); + expect(shouldNotRun).not.toHaveBeenCalled(); + }); + + test('should log when marking a Task as running fails', async () => { + const pool = new TaskPool({ + capacity$: of(3), + definitions, + logger, + strategy: CLAIM_STRATEGY_DEFAULT, + }); + + const taskFailedToMarkAsRunning = mockTask(); + taskFailedToMarkAsRunning.markTaskAsRunning.mockImplementation(async () => { + throw new Error(`Mark Task as running has failed miserably`); + }); + + const result = await pool.run([mockTask(), taskFailedToMarkAsRunning, mockTask()]); + + expect((logger as jest.Mocked).error.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "Failed to mark Task TaskType \\"shooooo\\" as running: Mark Task as running has failed miserably", + ] + `); + + expect(result).toEqual(TaskPoolRunResult.RunningAllClaimedTasks); + }); + + test('should log when running a Task fails', async () => { + const pool = new TaskPool({ + capacity$: of(3), + definitions, + logger, + strategy: CLAIM_STRATEGY_DEFAULT, + }); + + const taskFailedToRun = mockTask(); + taskFailedToRun.run.mockImplementation(async () => { + throw new Error(`Run Task has failed miserably`); + }); + + const result = await pool.run([mockTask(), taskFailedToRun, mockTask()]); + + expect((logger as jest.Mocked).warn.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "Task TaskType \\"shooooo\\" failed in attempt to run: Run Task has failed miserably", + ] + `); + + expect(result).toEqual(TaskPoolRunResult.RunningAllClaimedTasks); + }); + + test('should not log when running a Task fails due to the Task SO having been deleted while in flight', async () => { + const pool = new TaskPool({ + capacity$: of(3), + definitions, + logger, + strategy: CLAIM_STRATEGY_DEFAULT, + }); + + const taskFailedToRun = mockTask(); + taskFailedToRun.run.mockImplementation(async () => { + throw SavedObjectsErrorHelpers.createGenericNotFoundError('task', taskFailedToRun.id); + }); + + const result = await pool.run([mockTask(), taskFailedToRun, mockTask()]); + + expect(logger.debug).toHaveBeenCalledWith( + `Task TaskType "shooooo" failed in attempt to run: Saved object [task/${taskFailedToRun.id}] not found` + ); + expect(logger.warn).not.toHaveBeenCalled(); + + expect(result).toEqual(TaskPoolRunResult.RunningAllClaimedTasks); + }); + + test('Running a task which fails still takes up capacity', async () => { + const pool = new TaskPool({ + capacity$: of(1), + definitions, + logger, + strategy: CLAIM_STRATEGY_DEFAULT, + }); + + const taskFailedToRun = mockTask(); + taskFailedToRun.run.mockImplementation(async () => { + await sleep(0); + throw new Error(`Run Task has failed miserably`); + }); + + const result = await pool.run([taskFailedToRun, mockTask()]); + + expect(result).toEqual(TaskPoolRunResult.RanOutOfCapacity); + }); + + test('clears up capacity when a task completes', async () => { + const pool = new TaskPool({ + capacity$: of(1), + definitions, + logger, + strategy: CLAIM_STRATEGY_DEFAULT, + }); + + const firstWork = resolvable(); + const firstRun = sinon.spy(async () => { + await sleep(0); + firstWork.resolve(); + return asOk({ state: {} }); + }); + const secondWork = resolvable(); + const secondRun = sinon.spy(async () => { + await sleep(0); + secondWork.resolve(); + return asOk({ state: {} }); + }); + + const result = await pool.run([ + { ...mockTask(), run: firstRun }, + { ...mockTask(), run: secondRun }, + ]); + + expect(result).toEqual(TaskPoolRunResult.RanOutOfCapacity); + expect(pool.usedCapacity).toEqual(1); + expect(pool.availableCapacity()).toEqual(0); + + await firstWork; + sinon.assert.calledOnce(firstRun); + sinon.assert.notCalled(secondRun); + + expect(pool.usedCapacity).toEqual(0); + await pool.run([{ ...mockTask(), run: secondRun }]); + expect(pool.usedCapacity).toEqual(1); + + expect(pool.availableCapacity()).toEqual(0); + + await secondWork; + + expect(pool.usedCapacity).toEqual(0); + expect(pool.availableCapacity()).toEqual(1); + sinon.assert.calledOnce(secondRun); + }); + + test('run cancels expired tasks prior to running new tasks', async () => { + const pool = new TaskPool({ + capacity$: of(2), + definitions, + logger, + strategy: CLAIM_STRATEGY_DEFAULT, + }); + + const haltUntilWeAfterFirstRun = resolvable(); + const taskHasExpired = resolvable(); + const haltTaskSoThatItCanBeCanceled = resolvable(); + + const shouldRun = sinon.spy(() => Promise.resolve()); + const shouldNotRun = sinon.spy(() => Promise.resolve()); + const now = new Date(); + const result = await pool.run([ + { + ...mockTask({ id: '1' }), + async run() { + await haltUntilWeAfterFirstRun; + this.isExpired = true; + taskHasExpired.resolve(); + await haltTaskSoThatItCanBeCanceled; + return asOk({ state: {} }); + }, + get expiration() { + return now; + }, + get startedAt() { + // 5 and a half minutes + return moment(now).subtract(5, 'm').subtract(30, 's').toDate(); + }, + cancel: shouldRun, + }, + { + ...mockTask({ id: '2' }), + async run() { + // halt here so that we can verify that this task is counted in `occupiedWorkers` + await haltUntilWeAfterFirstRun; + return asOk({ state: {} }); + }, + cancel: shouldNotRun, + }, + ]); + + expect(result).toEqual(TaskPoolRunResult.RunningAtCapacity); + expect(pool.usedCapacity).toEqual(2); + expect(pool.availableCapacity()).toEqual(0); + + // release first stage in task so that it has time to expire, but not complete + haltUntilWeAfterFirstRun.resolve(); + await taskHasExpired; + + expect(await pool.run([{ ...mockTask({ id: '3' }) }])).toBeTruthy(); + + sinon.assert.calledOnce(shouldRun); + sinon.assert.notCalled(shouldNotRun); + + expect(pool.usedCapacity).toEqual(1); + expect(pool.availableCapacity()).toEqual(1); + + haltTaskSoThatItCanBeCanceled.resolve(); + + expect(logger.warn).toHaveBeenCalledWith( + `Cancelling task TaskType "shooooo" as it expired at ${now.toISOString()} after running for 05m 30s (with timeout set at 5m).` + ); + }); + + test('calls to availableWorkers ensures we cancel expired tasks', async () => { + const pool = new TaskPool({ + capacity$: of(1), + definitions, + logger, + strategy: CLAIM_STRATEGY_DEFAULT, + }); + + const taskIsRunning = resolvable(); + const taskHasExpired = resolvable(); + const cancel = sinon.spy(() => Promise.resolve()); + const now = new Date(); + expect( + await pool.run([ + { + ...mockTask(), + async run() { + await sleep(10); + this.isExpired = true; + taskIsRunning.resolve(); + await taskHasExpired; + return asOk({ state: {} }); + }, + get expiration() { + return new Date(now.getTime() + 10); + }, + get startedAt() { + return now; + }, + cancel, + }, + ]) + ).toEqual(TaskPoolRunResult.RunningAtCapacity); + + await taskIsRunning; + + sinon.assert.notCalled(cancel); + expect(pool.usedCapacity).toEqual(1); + // The call to `availableCapacity` will clear the expired task so it's 1 instead of 0 + expect(pool.availableCapacity()).toEqual(1); + sinon.assert.calledOnce(cancel); + + expect(pool.usedCapacity).toEqual(0); + expect(pool.availableCapacity()).toEqual(1); + // ensure cancel isn't called twice + sinon.assert.calledOnce(cancel); + taskHasExpired.resolve(); + }); + + test('logs if cancellation errors', async () => { + const pool = new TaskPool({ + capacity$: of(10), + definitions, + logger, + strategy: CLAIM_STRATEGY_DEFAULT, + }); + + const cancelled = resolvable(); + const result = await pool.run([ + { + ...mockTask(), + async run() { + this.isExpired = true; + await sleep(10); + return asOk({ state: {} }); + }, + async cancel() { + cancelled.resolve(); + throw new Error('Dern!'); + }, + toString: () => '"shooooo!"', + }, + ]); + + expect(result).toEqual(TaskPoolRunResult.RunningAllClaimedTasks); + await pool.run([]); + + expect(pool.usedCapacity).toEqual(0); + + // Allow the task to cancel... + await cancelled; + + expect((logger as jest.Mocked).error.mock.calls[0][0]).toMatchInlineSnapshot( + `"Failed to cancel task \\"shooooo!\\": Error: Dern!"` + ); + }); + + test('only allows one task with the same id in the task pool', async () => { + const pool = new TaskPool({ + capacity$: of(2), + definitions, + logger, + strategy: CLAIM_STRATEGY_DEFAULT, + }); + + const shouldRun = mockRun(); + const shouldNotRun = mockRun(); + + const taskId = uuidv4(); + const task1 = mockTask({ id: taskId, run: shouldRun }); + const task2 = mockTask({ + id: taskId, + run: shouldNotRun, + isSameTask() { + return true; + }, + }); + + await pool.run([task1]); + await pool.run([task2]); + + expect(shouldRun).toHaveBeenCalledTimes(1); + expect(shouldNotRun).not.toHaveBeenCalled(); + }); + }); + + describe('with CLAIM_STRATEGY_MGET', () => { + test('usedCapacity is the sum of the cost of running tasks', async () => { + const pool = new TaskPool({ + capacity$: of(10), + definitions, + logger, + strategy: CLAIM_STRATEGY_MGET, + }); + + const result = await pool.run([{ ...mockTask() }, { ...mockTask() }, { ...mockTask() }]); + + expect(result).toEqual(TaskPoolRunResult.RunningAllClaimedTasks); + expect(pool.usedCapacity).toEqual(3 * TaskCost.Normal); + }); + + test('availableCapacity are a function of total_capacity - usedCapacity', async () => { + const pool = new TaskPool({ + capacity$: of(10), + definitions, + logger, + strategy: CLAIM_STRATEGY_MGET, + }); + + const result = await pool.run([{ ...mockTask() }, { ...mockTask() }, { ...mockTask() }]); + + expect(result).toEqual(TaskPoolRunResult.RunningAllClaimedTasks); + expect(pool.availableCapacity()).toEqual(14); + }); + + test('availableCapacity is 0 until capacity$ pushes a value', async () => { + const capacity$ = new Subject(); + const pool = new TaskPool({ capacity$, definitions, logger, strategy: CLAIM_STRATEGY_MGET }); + + expect(pool.availableCapacity()).toEqual(0); + capacity$.next(20); + expect(pool.availableCapacity()).toEqual(40); + }); + + test('does not run tasks that are beyond its available capacity', async () => { + const pool = new TaskPool({ + capacity$: of(2), + definitions, + logger, + strategy: CLAIM_STRATEGY_MGET, + }); + + const shouldRun = mockRun(); + const shouldNotRun = mockRun(); + + const result = await pool.run([ + { ...mockTask(), run: shouldRun }, + { ...mockTask(), run: shouldRun }, + { ...mockTask(), run: shouldNotRun }, + ]); + + expect(result).toEqual(TaskPoolRunResult.RanOutOfCapacity); + expect(pool.availableCapacity()).toEqual(0); + expect(shouldRun).toHaveBeenCalledTimes(2); + expect(shouldNotRun).not.toHaveBeenCalled(); + }); + + test('should log when marking a Task as running fails', async () => { + const pool = new TaskPool({ + capacity$: of(6), + definitions, + logger, + strategy: CLAIM_STRATEGY_MGET, + }); + + const taskFailedToMarkAsRunning = mockTask(); + taskFailedToMarkAsRunning.markTaskAsRunning.mockImplementation(async () => { + throw new Error(`Mark Task as running has failed miserably`); + }); + + const result = await pool.run([mockTask(), taskFailedToMarkAsRunning, mockTask()]); + + expect((logger as jest.Mocked).error.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "Failed to mark Task TaskType \\"shooooo\\" as running: Mark Task as running has failed miserably", + ] + `); + + expect(result).toEqual(TaskPoolRunResult.RunningAllClaimedTasks); + }); + + test('should log when running a Task fails', async () => { + const pool = new TaskPool({ + capacity$: of(3), + definitions, + logger, + strategy: CLAIM_STRATEGY_MGET, + }); + + const taskFailedToRun = mockTask(); + taskFailedToRun.run.mockImplementation(async () => { + throw new Error(`Run Task has failed miserably`); + }); + + const result = await pool.run([mockTask(), taskFailedToRun, mockTask()]); + + expect((logger as jest.Mocked).warn.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "Task TaskType \\"shooooo\\" failed in attempt to run: Run Task has failed miserably", + ] + `); + + expect(result).toEqual(TaskPoolRunResult.RunningAllClaimedTasks); + }); + + test('should not log when running a Task fails due to the Task SO having been deleted while in flight', async () => { + const pool = new TaskPool({ + capacity$: of(3), + definitions, + logger, + strategy: CLAIM_STRATEGY_MGET, + }); + + const taskFailedToRun = mockTask(); + taskFailedToRun.run.mockImplementation(async () => { + throw SavedObjectsErrorHelpers.createGenericNotFoundError('task', taskFailedToRun.id); + }); + + const result = await pool.run([mockTask(), taskFailedToRun, mockTask()]); + + expect(logger.debug).toHaveBeenCalledWith( + `Task TaskType "shooooo" failed in attempt to run: Saved object [task/${taskFailedToRun.id}] not found` + ); + expect(logger.warn).not.toHaveBeenCalled(); + + expect(result).toEqual(TaskPoolRunResult.RunningAllClaimedTasks); + }); + + test('Running a task which fails still takes up capacity', async () => { + const pool = new TaskPool({ + capacity$: of(1), + definitions, + logger, + strategy: CLAIM_STRATEGY_MGET, + }); + + const taskFailedToRun = mockTask(); + taskFailedToRun.run.mockImplementation(async () => { + await sleep(0); + throw new Error(`Run Task has failed miserably`); + }); + + const result = await pool.run([taskFailedToRun, mockTask()]); + + expect(result).toEqual(TaskPoolRunResult.RanOutOfCapacity); + }); + + test('clears up capacity when a task completes', async () => { + const pool = new TaskPool({ + capacity$: of(1), + definitions, + logger, + strategy: CLAIM_STRATEGY_MGET, + }); + + const firstWork = resolvable(); + const firstRun = sinon.spy(async () => { + await sleep(0); + firstWork.resolve(); + return asOk({ state: {} }); + }); + const secondWork = resolvable(); + const secondRun = sinon.spy(async () => { + await sleep(0); + secondWork.resolve(); + return asOk({ state: {} }); + }); + + const result = await pool.run([ + { ...mockTask(), run: firstRun }, + { ...mockTask(), run: secondRun }, + ]); + + expect(result).toEqual(TaskPoolRunResult.RanOutOfCapacity); + expect(pool.usedCapacity).toEqual(2); + expect(pool.availableCapacity()).toEqual(0); + + await firstWork; + sinon.assert.calledOnce(firstRun); + sinon.assert.notCalled(secondRun); + + expect(pool.usedCapacity).toEqual(0); + await pool.run([{ ...mockTask(), run: secondRun }]); + expect(pool.usedCapacity).toEqual(2); + + expect(pool.availableCapacity()).toEqual(0); + + await secondWork; + + expect(pool.usedCapacity).toEqual(0); + expect(pool.availableCapacity()).toEqual(2); + sinon.assert.calledOnce(secondRun); + }); + + test('run cancels expired tasks prior to running new tasks', async () => { + const pool = new TaskPool({ + capacity$: of(2), + definitions, + logger, + strategy: CLAIM_STRATEGY_MGET, + }); + + const haltUntilWeAfterFirstRun = resolvable(); + const taskHasExpired = resolvable(); + const haltTaskSoThatItCanBeCanceled = resolvable(); + + const shouldRun = sinon.spy(() => Promise.resolve()); + const shouldNotRun = sinon.spy(() => Promise.resolve()); + const now = new Date(); + const result = await pool.run([ + { + ...mockTask({ id: '1' }), + async run() { + await haltUntilWeAfterFirstRun; + this.isExpired = true; + taskHasExpired.resolve(); + await haltTaskSoThatItCanBeCanceled; + return asOk({ state: {} }); + }, + get expiration() { + return now; + }, + get startedAt() { + // 5 and a half minutes + return moment(now).subtract(5, 'm').subtract(30, 's').toDate(); + }, + cancel: shouldRun, + }, + { + ...mockTask({ id: '2' }), + async run() { + // halt here so that we can verify that this task is counted in `occupiedWorkers` + await haltUntilWeAfterFirstRun; + return asOk({ state: {} }); + }, + cancel: shouldNotRun, + }, + ]); + + expect(result).toEqual(TaskPoolRunResult.RunningAtCapacity); + expect(pool.usedCapacity).toEqual(4); + expect(pool.availableCapacity()).toEqual(0); + + // release first stage in task so that it has time to expire, but not complete + haltUntilWeAfterFirstRun.resolve(); + await taskHasExpired; + + expect(await pool.run([{ ...mockTask({ id: '3' }) }])).toBeTruthy(); + + sinon.assert.calledOnce(shouldRun); + sinon.assert.notCalled(shouldNotRun); + + expect(pool.usedCapacity).toEqual(2); + expect(pool.availableCapacity()).toEqual(2); + + haltTaskSoThatItCanBeCanceled.resolve(); + + expect(logger.warn).toHaveBeenCalledWith( + `Cancelling task TaskType "shooooo" as it expired at ${now.toISOString()} after running for 05m 30s (with timeout set at 5m).` + ); + }); + + test('calls to availableWorkers ensures we cancel expired tasks', async () => { + const pool = new TaskPool({ + capacity$: of(1), + definitions, + logger, + strategy: CLAIM_STRATEGY_MGET, + }); + + const taskIsRunning = resolvable(); + const taskHasExpired = resolvable(); + const cancel = sinon.spy(() => Promise.resolve()); + const now = new Date(); + expect( + await pool.run([ + { + ...mockTask(), + async run() { + await sleep(10); + this.isExpired = true; + taskIsRunning.resolve(); + await taskHasExpired; + return asOk({ state: {} }); + }, + get expiration() { + return new Date(now.getTime() + 10); + }, + get startedAt() { + return now; + }, + cancel, + }, + ]) + ).toEqual(TaskPoolRunResult.RunningAtCapacity); + + await taskIsRunning; + + sinon.assert.notCalled(cancel); + expect(pool.usedCapacity).toEqual(2); + // The call to `availableCapacity` will clear the expired task so it's 2 instead of 0 + expect(pool.availableCapacity()).toEqual(2); + sinon.assert.calledOnce(cancel); + + expect(pool.usedCapacity).toEqual(0); + expect(pool.availableCapacity()).toEqual(2); + // ensure cancel isn't called twice + sinon.assert.calledOnce(cancel); + taskHasExpired.resolve(); + }); + + test('logs if cancellation errors', async () => { + const pool = new TaskPool({ + capacity$: of(10), + definitions, + logger, + strategy: CLAIM_STRATEGY_MGET, + }); + + const cancelled = resolvable(); + const result = await pool.run([ + { + ...mockTask(), + async run() { + this.isExpired = true; + await sleep(10); + return asOk({ state: {} }); + }, + async cancel() { + cancelled.resolve(); + throw new Error('Dern!'); + }, + toString: () => '"shooooo!"', + }, + ]); + + expect(result).toEqual(TaskPoolRunResult.RunningAllClaimedTasks); + await pool.run([]); + + expect(pool.usedCapacity).toEqual(0); + + // Allow the task to cancel... + await cancelled; + + expect((logger as jest.Mocked).error.mock.calls[0][0]).toMatchInlineSnapshot( + `"Failed to cancel task \\"shooooo!\\": Error: Dern!"` + ); + }); + + test('only allows one task with the same id in the task pool', async () => { + const pool = new TaskPool({ + capacity$: of(2), + definitions, + logger, + strategy: CLAIM_STRATEGY_MGET, + }); + + const shouldRun = mockRun(); + const shouldNotRun = mockRun(); + + const taskId = uuidv4(); + const task1 = mockTask({ id: taskId, run: shouldRun }); + const task2 = mockTask({ + id: taskId, + run: shouldNotRun, + isSameTask() { + return true; + }, + }); + + await pool.run([task1]); + await pool.run([task2]); + + expect(shouldRun).toHaveBeenCalledTimes(1); + expect(shouldNotRun).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/x-pack/plugins/task_manager/server/task_pool.ts b/x-pack/plugins/task_manager/server/task_pool/task_pool.ts similarity index 73% rename from x-pack/plugins/task_manager/server/task_pool.ts rename to x-pack/plugins/task_manager/server/task_pool/task_pool.ts index d4ae8e46fbc74..abd220796a0c8 100644 --- a/x-pack/plugins/task_manager/server/task_pool.ts +++ b/x-pack/plugins/task_manager/server/task_pool/task_pool.ts @@ -13,13 +13,20 @@ import { Observable, Subject } from 'rxjs'; import moment, { Duration } from 'moment'; import { padStart } from 'lodash'; import { Logger } from '@kbn/core/server'; -import { TaskRunner } from './task_running'; -import { isTaskSavedObjectNotFoundError } from './lib/is_task_not_found_error'; -import { TaskManagerStat } from './task_events'; +import { TaskRunner } from '../task_running'; +import { isTaskSavedObjectNotFoundError } from '../lib/is_task_not_found_error'; +import { TaskManagerStat } from '../task_events'; +import { ICapacity } from './types'; +import { CLAIM_STRATEGY_MGET } from '../config'; +import { WorkerCapacity } from './worker_capacity'; +import { CostCapacity } from './cost_capacity'; +import { TaskTypeDictionary } from '../task_type_dictionary'; -interface Opts { - maxWorkers$: Observable; +interface TaskPoolOpts { + capacity$: Observable; + definitions: TaskTypeDictionary; logger: Logger; + strategy: string; } export enum TaskPoolRunResult { @@ -34,31 +41,43 @@ export enum TaskPoolRunResult { } const VERSION_CONFLICT_MESSAGE = 'Task has been claimed by another Kibana service'; -const MAX_RUN_ATTEMPTS = 3; /** * Runs tasks in batches, taking costs into account. */ export class TaskPool { - private maxWorkers: number = 0; private tasksInPool = new Map(); private logger: Logger; private load$ = new Subject(); + private definitions: TaskTypeDictionary; + private capacityCalculator: ICapacity; /** * Creates an instance of TaskPool. * * @param {Opts} opts - * @prop {number} maxWorkers - The total number of workers / work slots available - * (e.g. maxWorkers is 4, then 2 tasks of cost 2 can run at a time, or 4 tasks of cost 1) + * @prop {number} capacity - The total capacity available + * (e.g. capacity is 4, then 2 tasks of cost 2 can run at a time, or 4 tasks of cost 1) * @prop {Logger} logger - The task manager logger. */ - constructor(opts: Opts) { + constructor(opts: TaskPoolOpts) { this.logger = opts.logger; - opts.maxWorkers$.subscribe((maxWorkers) => { - this.logger.debug(`Task pool now using ${maxWorkers} as the max worker value`); - this.maxWorkers = maxWorkers; - }); + this.definitions = opts.definitions; + + switch (opts.strategy) { + case CLAIM_STRATEGY_MGET: + this.capacityCalculator = new CostCapacity({ + capacity$: opts.capacity$, + logger: this.logger, + }); + break; + + default: + this.capacityCalculator = new WorkerCapacity({ + capacity$: opts.capacity$, + logger: this.logger, + }); + } } public get load(): Observable { @@ -66,38 +85,39 @@ export class TaskPool { } /** - * Gets how many workers are currently in use. + * Gets how much capacity is currently in use. */ - public get occupiedWorkers() { - return this.tasksInPool.size; + public get usedCapacity() { + return this.capacityCalculator.usedCapacity(this.tasksInPool); } /** - * Gets % of workers in use + * Gets how much capacity is currently in use as a percentage */ - public get workerLoad() { - return this.maxWorkers ? Math.round((this.occupiedWorkers * 100) / this.maxWorkers) : 100; + public get usedCapacityPercentage() { + return this.capacityCalculator.usedCapacityPercentage(this.tasksInPool); } /** - * Gets how many workers are currently available. + * Gets how much capacity is currently available. */ - public get availableWorkers() { + public availableCapacity(taskType?: string) { // cancel expired task whenever a call is made to check for capacity // this ensures that we don't end up with a queue of hung tasks causing both // the poller and the pool from hanging due to lack of capacity this.cancelExpiredTasks(); - return this.maxWorkers - this.occupiedWorkers; + + return this.capacityCalculator.availableCapacity( + this.tasksInPool, + taskType ? this.definitions.get(taskType) : null + ); } /** - * Gets how many workers are currently in use by type. + * Gets how much capacity is currently in use by each type. */ - public getOccupiedWorkersByType(type: string) { - return [...this.tasksInPool.values()].reduce( - (count, runningTask) => (runningTask.definition?.type === type ? ++count : count), - 0 - ); + public getUsedCapacityByType(type: string) { + return this.capacityCalculator.getUsedCapacityByType([...this.tasksInPool.values()], type); } /** @@ -108,26 +128,14 @@ export class TaskPool { * @param {TaskRunner[]} tasks * @returns {Promise} */ - public async run(tasks: TaskRunner[], attempt = 1): Promise { - // Note `this.availableWorkers` is a getter with side effects, so we just want + public async run(tasks: TaskRunner[]): Promise { + // Note `this.availableCapacity` has side effects, so we just want // to call it once for this bit of the code. - const availableWorkers = this.availableWorkers; - const [tasksToRun, leftOverTasks] = partitionListByCount(tasks, availableWorkers); - - if (attempt > MAX_RUN_ATTEMPTS) { - const stats = [ - `availableWorkers: ${availableWorkers}`, - `tasksToRun: ${tasksToRun.length}`, - `leftOverTasks: ${leftOverTasks.length}`, - `maxWorkers: ${this.maxWorkers}`, - `occupiedWorkers: ${this.occupiedWorkers}`, - `workerLoad: ${this.workerLoad}`, - ].join(', '); - this.logger.warn( - `task pool run attempts exceeded ${MAX_RUN_ATTEMPTS}; assuming ran out of capacity; ${stats}` - ); - return TaskPoolRunResult.RanOutOfCapacity; - } + const availableCapacity = this.availableCapacity(); + const [tasksToRun, leftOverTasks] = this.capacityCalculator.determineTasksToRunBasedOnCapacity( + tasks, + availableCapacity + ); if (tasksToRun.length) { await Promise.all( @@ -163,11 +171,10 @@ export class TaskPool { } if (leftOverTasks.length) { - if (this.availableWorkers) { - return this.run(leftOverTasks, attempt + 1); - } + // leave any leftover tasks + // they will be available for claiming in 30 seconds return TaskPoolRunResult.RanOutOfCapacity; - } else if (!this.availableWorkers) { + } else if (!this.availableCapacity()) { return TaskPoolRunResult.RunningAtCapacity; } return TaskPoolRunResult.RunningAllClaimedTasks; @@ -242,11 +249,6 @@ export class TaskPool { } } -function partitionListByCount(list: T[], count: number): [T[], T[]] { - const listInCount = list.splice(0, count); - return [listInCount, list]; -} - function durationAsString(duration: Duration): string { const [m, s] = [duration.minutes(), duration.seconds()].map((value) => padStart(`${value}`, 2, '0') diff --git a/x-pack/plugins/task_manager/server/task_pool/test_utils.ts b/x-pack/plugins/task_manager/server/task_pool/test_utils.ts new file mode 100644 index 0000000000000..b518ed7b8f8f5 --- /dev/null +++ b/x-pack/plugins/task_manager/server/task_pool/test_utils.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 { v4 as uuidv4 } from 'uuid'; +import { asOk } from '../lib/result_type'; +import { sleep } from '../test_utils'; +import { TaskRunningStage } from '../task_running'; +import { TaskCost } from '../task'; + +export function mockRun() { + return jest.fn(async () => { + await sleep(0); + return asOk({ state: {} }); + }); +} + +export function mockTask(overrides = {}, definitionOverrides = {}) { + return { + isExpired: false, + taskExecutionId: uuidv4(), + id: uuidv4(), + cancel: async () => undefined, + markTaskAsRunning: jest.fn(async () => true), + run: mockRun(), + stage: TaskRunningStage.PENDING, + toString: () => `TaskType "shooooo"`, + isAdHocTaskAndOutOfAttempts: false, + removeTask: jest.fn(), + get expiration() { + return new Date(); + }, + get startedAt() { + return new Date(); + }, + get definition() { + return { + type: '', + title: '', + timeout: '5m', + cost: TaskCost.Normal, + createTaskRunner: jest.fn(), + ...definitionOverrides, + }; + }, + isSameTask() { + return false; + }, + ...overrides, + }; +} diff --git a/x-pack/plugins/task_manager/server/task_pool/types.ts b/x-pack/plugins/task_manager/server/task_pool/types.ts new file mode 100644 index 0000000000000..759af4f6d6e70 --- /dev/null +++ b/x-pack/plugins/task_manager/server/task_pool/types.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 { Observable } from 'rxjs'; +import { Logger } from '@kbn/core/server'; +import { TaskRunner } from '../task_running'; +import { TaskDefinition } from '../task'; + +export interface ICapacity { + get capacity(): number; + availableCapacity( + tasksInPool: Map, + taskDefinition?: TaskDefinition | null + ): number; + usedCapacity(tasksInPool: Map): number; + usedCapacityPercentage(tasksInPool: Map): number; + getUsedCapacityByType(tasksInPool: TaskRunner[], type: string): number; + determineTasksToRunBasedOnCapacity( + tasks: TaskRunner[], + availableCapacity: number + ): [TaskRunner[], TaskRunner[]]; +} + +export interface CapacityOpts { + capacity$: Observable; + logger: Logger; +} diff --git a/x-pack/plugins/task_manager/server/task_pool/utils.ts b/x-pack/plugins/task_manager/server/task_pool/utils.ts new file mode 100644 index 0000000000000..d4c89be46e02d --- /dev/null +++ b/x-pack/plugins/task_manager/server/task_pool/utils.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 { TaskCost } from '../task'; + +// When configured capacity is the number of normal cost tasks that this Kibana +// can run, the total available workers equals the capacity +export const getCapacityInWorkers = (capacity: number) => capacity; + +// When configured capacity is the number of normal cost tasks that this Kibana +// can run, the total available cost equals the capacity multiplied by the cost of a normal task +export const getCapacityInCost = (capacity: number) => capacity * TaskCost.Normal; diff --git a/x-pack/plugins/task_manager/server/task_pool/worker_capacity.test.ts b/x-pack/plugins/task_manager/server/task_pool/worker_capacity.test.ts new file mode 100644 index 0000000000000..7ed7485ccdd52 --- /dev/null +++ b/x-pack/plugins/task_manager/server/task_pool/worker_capacity.test.ts @@ -0,0 +1,176 @@ +/* + * 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 { loggingSystemMock } from '@kbn/core/server/mocks'; +import { of, Subject } from 'rxjs'; +import { TaskCost } from '../task'; +import { mockTask } from './test_utils'; +import { WorkerCapacity } from './worker_capacity'; + +const logger = loggingSystemMock.create().get(); + +describe('WorkerCapacity', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + test('workers set based on capacity responds to changes from capacity$ observable', () => { + const capacity$ = new Subject(); + const pool = new WorkerCapacity({ capacity$, logger }); + + expect(pool.capacity).toBe(0); + + capacity$.next(20); + expect(pool.capacity).toBe(20); + + capacity$.next(16); + expect(pool.capacity).toBe(16); + + capacity$.next(25); + expect(pool.capacity).toBe(25); + + expect(logger.debug).toHaveBeenCalledTimes(3); + expect(logger.debug).toHaveBeenNthCalledWith( + 1, + 'Task pool now using 20 as the max worker value which is based on a capacity of 20' + ); + expect(logger.debug).toHaveBeenNthCalledWith( + 2, + 'Task pool now using 16 as the max worker value which is based on a capacity of 16' + ); + expect(logger.debug).toHaveBeenNthCalledWith( + 3, + 'Task pool now using 25 as the max worker value which is based on a capacity of 25' + ); + }); + + test('usedCapacity returns the number of tasks in the pool', () => { + const pool = new WorkerCapacity({ capacity$: of(10), logger }); + + const tasksInPool = new Map([ + ['1', { ...mockTask() }], + ['2', { ...mockTask({}, { cost: TaskCost.Tiny }) }], + ['3', { ...mockTask() }], + ]); + + expect(pool.usedCapacity(tasksInPool)).toBe(3); + }); + + test('usedCapacityPercentage returns the percentage of workers in use by tasks in the pool', () => { + const pool = new WorkerCapacity({ capacity$: of(10), logger }); + + const tasksInPool = new Map([ + ['1', { ...mockTask() }], + ['2', { ...mockTask({}, { cost: TaskCost.Tiny }) }], + ['3', { ...mockTask() }], + ]); + + expect(pool.usedCapacityPercentage(tasksInPool)).toBe(30); + }); + + test('usedCapacityByType returns the number of tasks of specified type in the pool', () => { + const pool = new WorkerCapacity({ capacity$: of(10), logger }); + + const tasksInPool = [ + { ...mockTask({}, { type: 'type1' }) }, + { ...mockTask({}, { type: 'type1', cost: TaskCost.Tiny }) }, + { ...mockTask({}, { type: 'type2' }) }, + ]; + + expect(pool.getUsedCapacityByType(tasksInPool, 'type1')).toBe(2); + expect(pool.getUsedCapacityByType(tasksInPool, 'type2')).toBe(1); + expect(pool.getUsedCapacityByType(tasksInPool, 'type3')).toBe(0); + }); + + test('availableCapacity returns the overall number of available workers when no task type is defined', () => { + const pool = new WorkerCapacity({ capacity$: of(10), logger }); + + const tasksInPool = new Map([ + ['1', { ...mockTask() }], + ['2', { ...mockTask({}, { cost: TaskCost.Tiny }) }], + ['3', { ...mockTask() }], + ]); + + expect(pool.availableCapacity(tasksInPool)).toBe(7); + }); + + test('availableCapacity returns the overall number of available workers when task type with no maxConcurrency is provided', () => { + const pool = new WorkerCapacity({ capacity$: of(10), logger }); + + const tasksInPool = new Map([ + ['1', { ...mockTask() }], + ['2', { ...mockTask({}, { cost: TaskCost.Tiny }) }], + ['3', { ...mockTask() }], + ]); + + expect( + pool.availableCapacity(tasksInPool, { + type: 'type1', + cost: TaskCost.Normal, + createTaskRunner: jest.fn(), + timeout: '5m', + }) + ).toBe(7); + }); + + test('availableCapacity returns the number of available workers for the task type when task type with maxConcurrency is provided', () => { + const pool = new WorkerCapacity({ capacity$: of(10), logger }); + + const tasksInPool = new Map([ + ['1', { ...mockTask({}, { type: 'type1' }) }], + ['2', { ...mockTask({}, { cost: TaskCost.Tiny }) }], + ['3', { ...mockTask() }], + ]); + + expect( + pool.availableCapacity(tasksInPool, { + type: 'type1', + maxConcurrency: 3, + cost: TaskCost.Normal, + createTaskRunner: jest.fn(), + timeout: '5m', + }) + ).toBe(2); + }); + + describe('determineTasksToRunBasedOnCapacity', () => { + test('runs all tasks if there are workers available', () => { + const pool = new WorkerCapacity({ capacity$: of(10), logger }); + const tasks = [{ ...mockTask() }, { ...mockTask() }, { ...mockTask() }]; + const [tasksToRun, leftoverTasks] = pool.determineTasksToRunBasedOnCapacity(tasks, 10); + + expect(tasksToRun).toEqual(tasks); + expect(leftoverTasks).toEqual([]); + }); + + test('splits tasks if there are more tasks than available workers', () => { + const pool = new WorkerCapacity({ capacity$: of(10), logger }); + const tasks = [ + { ...mockTask() }, + { ...mockTask() }, + { ...mockTask() }, + { ...mockTask({}, { cost: TaskCost.ExtraLarge }) }, + { ...mockTask({}, { cost: TaskCost.ExtraLarge }) }, + { ...mockTask() }, + { ...mockTask() }, + ]; + const [tasksToRun, leftoverTasks] = pool.determineTasksToRunBasedOnCapacity(tasks, 5); + + expect(tasksToRun).toEqual([tasks[0], tasks[1], tasks[2], tasks[3], tasks[4]]); + expect(leftoverTasks).toEqual([tasks[5], tasks[6]]); + }); + + test('does not run tasks if there is no capacity', () => { + const pool = new WorkerCapacity({ capacity$: of(10), logger }); + const tasks = [{ ...mockTask() }, { ...mockTask() }, { ...mockTask() }]; + const [tasksToRun, leftoverTasks] = pool.determineTasksToRunBasedOnCapacity(tasks, 0); + + expect(tasksToRun).toEqual([]); + expect(leftoverTasks).toEqual(tasks); + }); + }); +}); diff --git a/x-pack/plugins/task_manager/server/task_pool/worker_capacity.ts b/x-pack/plugins/task_manager/server/task_pool/worker_capacity.ts new file mode 100644 index 0000000000000..8363c53f58ec1 --- /dev/null +++ b/x-pack/plugins/task_manager/server/task_pool/worker_capacity.ts @@ -0,0 +1,95 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Logger } from '@kbn/core/server'; +import { TaskRunner } from '../task_running'; +import { CapacityOpts, ICapacity } from './types'; +import { TaskDefinition } from '../task'; +import { getCapacityInWorkers } from './utils'; + +export class WorkerCapacity implements ICapacity { + private workers: number = 0; + private logger: Logger; + + constructor(opts: CapacityOpts) { + this.logger = opts.logger; + opts.capacity$.subscribe((capacity) => { + // Capacity config describes the number of normal-cost tasks that can be + // run simulatenously. This directly corresponds to the number of workers to use. + this.workers = getCapacityInWorkers(capacity); + this.logger.debug( + `Task pool now using ${this.workers} as the max worker value which is based on a capacity of ${capacity}` + ); + }); + } + + public get capacity(): number { + return this.workers; + } + + /** + * Gets how many workers are currently in use. + */ + public usedCapacity(tasksInPool: Map) { + return tasksInPool.size; + } + + /** + * Gets % of workers in use + */ + public usedCapacityPercentage(tasksInPool: Map) { + return this.capacity ? Math.round((this.usedCapacity(tasksInPool) * 100) / this.capacity) : 100; + } + + /** + * Gets how many workers are currently in use by each type. + */ + public getUsedCapacityByType(tasksInPool: TaskRunner[], type: string) { + return tasksInPool.reduce( + (count, runningTask) => (runningTask.definition?.type === type ? ++count : count), + 0 + ); + } + + public availableCapacity( + tasksInPool: Map, + taskDefinition?: TaskDefinition | null + ): number { + const allAvailableCapacity = this.capacity - this.usedCapacity(tasksInPool); + if (taskDefinition && taskDefinition.maxConcurrency) { + // calculate the max workers that can be used for this task type + return Math.max( + Math.min( + allAvailableCapacity, + taskDefinition.maxConcurrency - + this.getUsedCapacityByType([...tasksInPool.values()], taskDefinition.type) + ), + 0 + ); + } + + return allAvailableCapacity; + } + + public determineTasksToRunBasedOnCapacity( + tasks: TaskRunner[], + availableCapacity: number + ): [TaskRunner[], TaskRunner[]] { + const tasksToRun: TaskRunner[] = []; + const leftOverTasks: TaskRunner[] = []; + + for (let i = 0; i < tasks.length; i++) { + if (i >= availableCapacity) { + leftOverTasks.push(tasks[i]); + } else { + tasksToRun.push(tasks[i]); + } + } + + return [tasksToRun, leftOverTasks]; + } +} diff --git a/x-pack/plugins/task_manager/server/task_store.test.ts b/x-pack/plugins/task_manager/server/task_store.test.ts index afde0ae91ea55..9bc1a64140647 100644 --- a/x-pack/plugins/task_manager/server/task_store.test.ts +++ b/x-pack/plugins/task_manager/server/task_store.test.ts @@ -8,7 +8,7 @@ import { schema } from '@kbn/config-schema'; import { Client } from '@elastic/elasticsearch'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import _ from 'lodash'; +import _, { omit } from 'lodash'; import { first } from 'rxjs'; import { @@ -18,7 +18,7 @@ import { SerializedConcreteTaskInstance, } from './task'; import { elasticsearchServiceMock, savedObjectsServiceMock } from '@kbn/core/server/mocks'; -import { TaskStore, SearchOpts, AggregationOpts } from './task_store'; +import { TaskStore, SearchOpts, AggregationOpts, taskInstanceToAttributes } from './task_store'; import { savedObjectsRepositoryMock } from '@kbn/core/server/mocks'; import { SavedObjectAttributes, SavedObjectsErrorHelpers } from '@kbn/core/server'; import { TaskTypeDictionary } from './task_type_dictionary'; @@ -292,12 +292,16 @@ describe('TaskStore', () => { }); }); - async function testFetch(opts?: SearchOpts, hits: Array> = []) { + async function testFetch( + opts?: SearchOpts, + hits: Array> = [], + limitResponse: boolean = false + ) { childEsClient.search.mockResponse({ hits: { hits, total: hits.length }, } as estypes.SearchResponse); - const result = await store.fetch(opts); + const result = await store.fetch(opts, limitResponse); expect(childEsClient.search).toHaveBeenCalledTimes(1); @@ -342,6 +346,18 @@ describe('TaskStore', () => { await expect(store.fetch()).rejects.toThrowErrorMatchingInlineSnapshot(`"Failure"`); expect(await firstErrorPromise).toMatchInlineSnapshot(`[Error: Failure]`); }); + + test('excludes state and params from source when excludeState is true', async () => { + const { args } = await testFetch({}, [], true); + expect(args).toMatchObject({ + index: 'tasky', + body: { + sort: [{ 'task.runAt': 'asc' }], + query: { term: { type: 'task' } }, + }, + _source_excludes: ['task.state', 'task.params'], + }); + }); }); describe('aggregate', () => { @@ -615,10 +631,11 @@ describe('TaskStore', () => { describe('bulkUpdate', () => { let store: TaskStore; + const logger = mockLogger(); beforeAll(() => { store = new TaskStore({ - logger: mockLogger(), + logger, index: 'tasky', taskManagerId: '', serializer, @@ -671,6 +688,125 @@ describe('TaskStore', () => { expect(mockGetValidatedTaskInstanceForUpdating).toHaveBeenCalledWith(task, { validate: false, }); + + expect(savedObjectsClient.bulkUpdate).toHaveBeenCalledWith( + [ + { + id: task.id, + type: 'task', + version: task.version, + attributes: taskInstanceToAttributes(task, task.id), + }, + ], + { refresh: false } + ); + }); + + test(`validates whenever validate:true is passed-in`, async () => { + const task = { + runAt: mockedDate, + scheduledAt: mockedDate, + startedAt: null, + retryAt: null, + id: 'task:324242', + params: { hello: 'world' }, + state: { foo: 'bar' }, + taskType: 'report', + attempts: 3, + status: 'idle' as TaskStatus, + version: '123', + ownerId: null, + traceparent: '', + }; + + savedObjectsClient.bulkUpdate.mockResolvedValue({ + saved_objects: [ + { + id: '324242', + type: 'task', + attributes: { + ...task, + state: '{"foo":"bar"}', + params: '{"hello":"world"}', + }, + references: [], + version: '123', + }, + ], + }); + + await store.bulkUpdate([task], { validate: true }); + + expect(mockGetValidatedTaskInstanceForUpdating).toHaveBeenCalledWith(task, { + validate: true, + }); + + expect(savedObjectsClient.bulkUpdate).toHaveBeenCalledWith( + [ + { + id: task.id, + type: 'task', + version: task.version, + attributes: taskInstanceToAttributes(task, task.id), + }, + ], + { refresh: false } + ); + }); + + test(`logs warning and doesn't validate whenever excludeLargeFields option is passed-in`, async () => { + const task = { + runAt: mockedDate, + scheduledAt: mockedDate, + startedAt: null, + retryAt: null, + id: 'task:324242', + params: { hello: 'world' }, + state: { foo: 'bar' }, + taskType: 'report', + attempts: 3, + status: 'idle' as TaskStatus, + version: '123', + ownerId: null, + traceparent: '', + }; + + savedObjectsClient.bulkUpdate.mockResolvedValue({ + saved_objects: [ + { + id: '324242', + type: 'task', + attributes: { + ...task, + state: '{"foo":"bar"}', + params: '{"hello":"world"}', + }, + references: [], + version: '123', + }, + ], + }); + + await store.bulkUpdate([task], { validate: true, excludeLargeFields: true }); + + expect(logger.warn).toHaveBeenCalledWith( + `Skipping validation for bulk update because excludeLargeFields=true.` + ); + expect(mockGetValidatedTaskInstanceForUpdating).toHaveBeenCalledWith(task, { + validate: false, + }); + + expect(savedObjectsClient.bulkUpdate).toHaveBeenCalledWith( + [ + { + id: task.id, + type: 'task', + version: task.version, + attributes: omit(taskInstanceToAttributes(task, task.id), ['state', 'params']), + }, + ], + { refresh: false } + ); }); test('pushes error from saved objects client to errors$', async () => { diff --git a/x-pack/plugins/task_manager/server/task_store.ts b/x-pack/plugins/task_manager/server/task_store.ts index e0ad3dfae149a..9b58d7bc3c18b 100644 --- a/x-pack/plugins/task_manager/server/task_store.ts +++ b/x-pack/plugins/task_manager/server/task_store.ts @@ -84,6 +84,11 @@ export interface FetchResult { versionMap: Map; } +export interface BulkUpdateOpts { + validate: boolean; + excludeLargeFields?: boolean; +} + export type BulkUpdateResult = Result< ConcreteTaskInstance, { type: string; id: string; error: SavedObjectError } @@ -108,6 +113,7 @@ export class TaskStore { public readonly taskManagerId: string; public readonly errors$ = new Subject(); public readonly taskValidator: TaskValidator; + private readonly logger: Logger; private esClient: ElasticsearchClient; private esClientWithoutRetries: ElasticsearchClient; @@ -134,6 +140,7 @@ export class TaskStore { this.serializer = opts.serializer; this.savedObjectsRepository = opts.savedObjectsRepository; this.adHocTaskCounter = opts.adHocTaskCounter; + this.logger = opts.logger; this.taskValidator = new TaskValidator({ logger: opts.logger, definitions: opts.definitions, @@ -232,15 +239,13 @@ export class TaskStore { * Fetches a list of scheduled tasks with default sorting. * * @param opts - The query options used to filter tasks + * @param limitResponse - Whether to exclude the task state and params from the source for a smaller respose payload */ - public async fetch({ - sort = [{ 'task.runAt': 'asc' }], - ...opts - }: SearchOpts = {}): Promise { - return this.search({ - ...opts, - sort, - }); + public async fetch( + { sort = [{ 'task.runAt': 'asc' }], ...opts }: SearchOpts = {}, + limitResponse: boolean = false + ): Promise { + return this.search({ ...opts, sort }, limitResponse); } /** @@ -296,13 +301,23 @@ export class TaskStore { */ public async bulkUpdate( docs: ConcreteTaskInstance[], - options: { validate: boolean } + { validate, excludeLargeFields = false }: BulkUpdateOpts ): Promise { + // if we're excluding large fields (state and params), we cannot apply validation so log a warning + if (validate && excludeLargeFields) { + validate = false; + this.logger.warn(`Skipping validation for bulk update because excludeLargeFields=true.`); + } + const attributesByDocId = docs.reduce((attrsById, doc) => { const taskInstance = this.taskValidator.getValidatedTaskInstanceForUpdating(doc, { - validate: options.validate, + validate, }); - attrsById.set(doc.id, taskInstanceToAttributes(taskInstance, doc.id)); + const taskAttributes = taskInstanceToAttributes(taskInstance, doc.id); + attrsById.set( + doc.id, + excludeLargeFields ? omit(taskAttributes, 'state', 'params') : taskAttributes + ); return attrsById; }, new Map()); @@ -342,7 +357,7 @@ export class TaskStore { ), }); const result = this.taskValidator.getValidatedTaskInstanceFromReading(taskInstance, { - validate: options.validate, + validate, }); return asOk(result); }); @@ -489,18 +504,20 @@ export class TaskStore { } } - private async search(opts: SearchOpts = {}): Promise { + private async search( + opts: SearchOpts = {}, + limitResponse: boolean = false + ): Promise { const { query } = ensureQueryOnlyReturnsTaskObjects(opts); try { const result = await this.esClientWithoutRetries.search({ index: this.index, ignore_unavailable: true, - body: { - ...opts, - query, - }, + body: { ...opts, query }, + ...(limitResponse ? { _source_excludes: ['task.state', 'task.params'] } : {}), }); + const { hits: { hits: tasks }, } = result; @@ -627,7 +644,10 @@ export function correctVersionConflictsForContinuation( return maxDocs && versionConflicts + updated > maxDocs ? maxDocs - updated : versionConflicts; } -function taskInstanceToAttributes(doc: TaskInstance, id: string): SerializedConcreteTaskInstance { +export function taskInstanceToAttributes( + doc: TaskInstance, + id: string +): SerializedConcreteTaskInstance { return { ...omit(doc, 'id', 'version'), params: JSON.stringify(doc.params || {}), diff --git a/x-pack/plugins/task_manager/server/task_type_dictionary.test.ts b/x-pack/plugins/task_manager/server/task_type_dictionary.test.ts index f2faeec4b9877..7be758be03567 100644 --- a/x-pack/plugins/task_manager/server/task_type_dictionary.test.ts +++ b/x-pack/plugins/task_manager/server/task_type_dictionary.test.ts @@ -6,7 +6,7 @@ */ import { get } from 'lodash'; -import { RunContext, TaskDefinition, TaskPriority } from './task'; +import { RunContext, TaskCost, TaskDefinition, TaskPriority } from './task'; import { mockLogger } from './test_utils'; import { sanitizeTaskDefinitions, @@ -53,6 +53,7 @@ describe('taskTypeDictionary', () => { const logger = mockLogger(); beforeEach(() => { + jest.resetAllMocks(); definitions = new TaskTypeDictionary(logger); }); @@ -64,6 +65,7 @@ describe('taskTypeDictionary', () => { expect(result).toMatchInlineSnapshot(` Array [ Object { + "cost": 2, "createTaskRunner": [Function], "description": "one super cool task", "timeout": "5m", @@ -71,6 +73,7 @@ describe('taskTypeDictionary', () => { "type": "test_task_type_0", }, Object { + "cost": 2, "createTaskRunner": [Function], "description": "one super cool task", "timeout": "5m", @@ -78,6 +81,7 @@ describe('taskTypeDictionary', () => { "type": "test_task_type_1", }, Object { + "cost": 2, "createTaskRunner": [Function], "description": "one super cool task", "timeout": "5m", @@ -225,6 +229,7 @@ describe('taskTypeDictionary', () => { createTaskRunner: expect.any(Function), maxConcurrency: 2, priority: 1, + cost: 2, timeout: '5m', title: 'foo', type: 'foo', @@ -247,6 +252,41 @@ describe('taskTypeDictionary', () => { expect(definitions.get('foo')).toEqual(undefined); }); + it('uses task cost if specified', () => { + definitions.registerTaskDefinitions({ + foo: { + title: 'foo', + maxConcurrency: 2, + cost: TaskCost.ExtraLarge, + createTaskRunner: jest.fn(), + }, + }); + expect(definitions.get('foo')).toEqual({ + createTaskRunner: expect.any(Function), + maxConcurrency: 2, + cost: 10, + timeout: '5m', + title: 'foo', + type: 'foo', + }); + }); + + it('does not register task with invalid cost schema', () => { + definitions.registerTaskDefinitions({ + foo: { + title: 'foo', + maxConcurrency: 2, + // @ts-expect-error upgrade typescript v5.1.6 + cost: 23, + createTaskRunner: jest.fn(), + }, + }); + expect(logger.error).toHaveBeenCalledWith( + `Could not sanitize task definitions: Invalid cost \"23\". Cost must be one of Tiny => 1,Normal => 2,ExtraLarge => 10` + ); + expect(definitions.get('foo')).toEqual(undefined); + }); + it('throws error when registering duplicate task type', () => { definitions.registerTaskDefinitions({ foo: { diff --git a/x-pack/plugins/task_manager/server/task_type_dictionary.ts b/x-pack/plugins/task_manager/server/task_type_dictionary.ts index 85c6f3ad28eb6..e0b28eccea3cb 100644 --- a/x-pack/plugins/task_manager/server/task_type_dictionary.ts +++ b/x-pack/plugins/task_manager/server/task_type_dictionary.ts @@ -7,7 +7,13 @@ import { ObjectType } from '@kbn/config-schema'; import { Logger } from '@kbn/core/server'; -import { TaskDefinition, taskDefinitionSchema, TaskRunCreatorFunction, TaskPriority } from './task'; +import { + TaskDefinition, + taskDefinitionSchema, + TaskRunCreatorFunction, + TaskPriority, + TaskCost, +} from './task'; import { CONCURRENCY_ALLOW_LIST_BY_TASK_TYPE } from './constants'; /** @@ -50,6 +56,10 @@ export interface TaskRegisterDefinition { * claimed before low priority */ priority?: TaskPriority; + /** + * An optional definition of the cost associated with running the task. + */ + cost?: TaskCost; /** * An optional more detailed description of what this task does. */ diff --git a/x-pack/plugins/task_manager/server/usage/task_manager_usage_collector.test.ts b/x-pack/plugins/task_manager/server/usage/task_manager_usage_collector.test.ts index 019d8bd47c57a..067a32c8a486d 100644 --- a/x-pack/plugins/task_manager/server/usage/task_manager_usage_collector.test.ts +++ b/x-pack/plugins/task_manager/server/usage/task_manager_usage_collector.test.ts @@ -174,7 +174,8 @@ function getMockMonitoredHealth(overrides = {}): MonitoredHealth { timestamp: new Date().toISOString(), status: HealthStatus.OK, value: { - max_workers: 10, + capacity: { config: 10, as_cost: 20, as_workers: 10 }, + claim_strategy: 'default', poll_interval: 3000, request_capacity: 1000, monitored_aggregated_stats_refresh_rate: 5000, @@ -193,16 +194,19 @@ function getMockMonitoredHealth(overrides = {}): MonitoredHealth { status: HealthStatus.OK, value: { count: 4, + cost: 8, task_types: { - actions_telemetry: { count: 2, status: { idle: 2 } }, - alerting_telemetry: { count: 1, status: { idle: 1 } }, - session_cleanup: { count: 1, status: { idle: 1 } }, + actions_telemetry: { count: 2, cost: 4, status: { idle: 2 } }, + alerting_telemetry: { count: 1, cost: 2, status: { idle: 1 } }, + session_cleanup: { count: 1, cost: 2, status: { idle: 1 } }, }, schedule: [], overdue: 0, + overdue_cost: 0, overdue_non_recurring: 0, estimatedScheduleDensity: [], non_recurring: 20, + non_recurring_cost: 40, owner_ids: 2, estimated_schedule_density: [], capacity_requirements: { diff --git a/x-pack/plugins/task_manager/tsconfig.json b/x-pack/plugins/task_manager/tsconfig.json index 5ae81e9097114..232ab3a36f2c8 100644 --- a/x-pack/plugins/task_manager/tsconfig.json +++ b/x-pack/plugins/task_manager/tsconfig.json @@ -25,7 +25,8 @@ "@kbn/alerting-state-types", "@kbn/core-saved-objects-api-server", "@kbn/logging", - "@kbn/core-lifecycle-server" + "@kbn/core-lifecycle-server", + "@kbn/cloud-plugin" ], "exclude": ["target/**/*"] } diff --git a/x-pack/test/plugin_api_integration/test_suites/task_manager/health_route.ts b/x-pack/test/plugin_api_integration/test_suites/task_manager/health_route.ts index 066a004df3814..44d2257f8a957 100644 --- a/x-pack/test/plugin_api_integration/test_suites/task_manager/health_route.ts +++ b/x-pack/test/plugin_api_integration/test_suites/task_manager/health_route.ts @@ -140,7 +140,12 @@ export default function ({ getService }: FtrProviderContext) { }, }, request_capacity: 1000, - max_workers: 10, + capacity: { + config: 10, + as_workers: 10, + as_cost: 20, + }, + claim_strategy: 'default', }); }); diff --git a/x-pack/test/task_manager_claimer_mget/test_suites/task_manager/health_route.ts b/x-pack/test/task_manager_claimer_mget/test_suites/task_manager/health_route.ts index 066a004df3814..aa4f68e1fedd8 100644 --- a/x-pack/test/task_manager_claimer_mget/test_suites/task_manager/health_route.ts +++ b/x-pack/test/task_manager_claimer_mget/test_suites/task_manager/health_route.ts @@ -140,7 +140,12 @@ export default function ({ getService }: FtrProviderContext) { }, }, request_capacity: 1000, - max_workers: 10, + capacity: { + config: 10, + as_workers: 10, + as_cost: 20, + }, + claim_strategy: 'unsafe_mget', }); }); From 34f1f0f75b01df87720a2f81de46381dc3876378 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Wed, 7 Aug 2024 13:28:14 -0600 Subject: [PATCH 30/44] [Security solution] Create new class instance for each use of LangChain chat model (#190004) --- .../execute_custom_llm_chain/index.ts | 54 +++++++++++-------- .../graphs/default_assistant_graph/graph.ts | 10 ++-- .../graphs/default_assistant_graph/index.ts | 27 ++++++---- .../nodes/generate_chat_title.ts | 2 + .../default_assistant_graph/nodes/respond.ts | 4 +- .../graphs/default_assistant_graph/types.ts | 2 - 6 files changed, 55 insertions(+), 44 deletions(-) diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/execute_custom_llm_chain/index.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/execute_custom_llm_chain/index.ts index 38759d4d68ea3..0af5f0453ec8b 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/execute_custom_llm_chain/index.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/execute_custom_llm_chain/index.ts @@ -57,23 +57,32 @@ export const callAgentExecutor: AgentExecutor = async ({ const isOpenAI = llmType === 'openai'; const llmClass = getLlmClass(llmType, bedrockChatEnabled); - const llm = new llmClass({ - actionsClient, - connectorId, - llmType, - logger, - // possible client model override, - // let this be undefined otherwise so the connector handles the model - model: request.body.model, - // ensure this is defined because we default to it in the language_models - // This is where the LangSmith logs (Metadata > Invocation Params) are set - temperature: getDefaultArguments(llmType).temperature, - signal: abortSignal, - streaming: isStream, - // prevents the agent from retrying on failure - // failure could be due to bad connector, we should deliver that result to the client asap - maxRetries: 0, - }); + /** + * Creates a new instance of llmClass. + * + * This function ensures that a new llmClass instance is created every time it is called. + * This is necessary to avoid any potential side effects from shared state. By always + * creating a new instance, we prevent other uses of llm from binding and changing + * the state unintentionally. For this reason, never assign this value to a variable (ex const llm = createLlmInstance()) + */ + const createLlmInstance = () => + new llmClass({ + actionsClient, + connectorId, + llmType, + logger, + // possible client model override, + // let this be undefined otherwise so the connector handles the model + model: request.body.model, + // ensure this is defined because we default to it in the language_models + // This is where the LangSmith logs (Metadata > Invocation Params) are set + temperature: getDefaultArguments(llmType).temperature, + signal: abortSignal, + streaming: isStream, + // prevents the agent from retrying on failure + // failure could be due to bad connector, we should deliver that result to the client asap + maxRetries: 0, + }); const anonymizationFieldsRes = await dataClients?.anonymizationFieldsDataClient?.findDocuments({ @@ -99,7 +108,7 @@ export const callAgentExecutor: AgentExecutor = async ({ const modelExists = await esStore.isModelInstalled(); // Create a chain that uses the ELSER backed ElasticsearchStore, override k=10 for esql query generation for now - const chain = RetrievalQAChain.fromLLM(llm, esStore.asRetriever(10)); + const chain = RetrievalQAChain.fromLLM(createLlmInstance(), esStore.asRetriever(10)); // Fetch any applicable tools that the source plugin may have registered const assistantToolParams: AssistantToolParams = { @@ -108,7 +117,6 @@ export const callAgentExecutor: AgentExecutor = async ({ chain, esClient, isEnabledKnowledgeBase: true, - llm, logger, modelExists, onNewReplacements, @@ -118,7 +126,7 @@ export const callAgentExecutor: AgentExecutor = async ({ }; const tools: ToolInterface[] = assistantTools.flatMap( - (tool) => tool.getTool(assistantToolParams) ?? [] + (tool) => tool.getTool({ ...assistantToolParams, llm: createLlmInstance() }) ?? [] ); logger.debug( @@ -132,14 +140,14 @@ export const callAgentExecutor: AgentExecutor = async ({ }; // isOpenAI check is not on agentType alone because typescript doesn't like const executor = isOpenAI - ? await initializeAgentExecutorWithOptions(tools, llm, { + ? await initializeAgentExecutorWithOptions(tools, createLlmInstance(), { agentType: 'openai-functions', ...executorArgs, }) : llmType === 'bedrock' && bedrockChatEnabled ? new lcAgentExecutor({ agent: await createToolCallingAgent({ - llm, + llm: createLlmInstance(), tools, prompt: ChatPromptTemplate.fromMessages([ ['system', 'You are a helpful assistant'], @@ -151,7 +159,7 @@ export const callAgentExecutor: AgentExecutor = async ({ }), tools, }) - : await initializeAgentExecutorWithOptions(tools, llm, { + : await initializeAgentExecutorWithOptions(tools, createLlmInstance(), { agentType: 'structured-chat-zero-shot-react-description', ...executorArgs, returnIntermediateSteps: false, diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/graph.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/graph.ts index 7ee0e6912b563..6eac3f1c98303 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/graph.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/graph.ts @@ -41,8 +41,7 @@ interface GetDefaultAssistantGraphParams { agentRunnable: AgentRunnableSequence; dataClients?: AssistantDataClients; conversationId?: string; - getLlmInstance: () => BaseChatModel; - llm: BaseChatModel; + createLlmInstance: () => BaseChatModel; logger: Logger; tools: StructuredTool[]; responseLanguage: string; @@ -61,8 +60,7 @@ export const getDefaultAssistantGraph = ({ agentRunnable, conversationId, dataClients, - getLlmInstance, - llm, + createLlmInstance, logger, responseLanguage, tools, @@ -106,7 +104,6 @@ export const getDefaultAssistantGraph = ({ // Default node parameters const nodeParams: NodeParamsBase = { - model: llm, logger, }; @@ -131,6 +128,7 @@ export const getDefaultAssistantGraph = ({ const generateChatTitleNode = (state: AgentState) => generateChatTitle({ ...nodeParams, + model: createLlmInstance(), state, responseLanguage, }); @@ -154,7 +152,7 @@ export const getDefaultAssistantGraph = ({ const respondNode = (state: AgentState) => respond({ ...nodeParams, - llm: getLlmInstance(), + model: createLlmInstance(), state, }); const shouldContinueEdge = (state: AgentState) => shouldContinue({ ...nodeParams, state }); diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/index.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/index.ts index 40336561149e6..758a3a757eb76 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/index.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/index.ts @@ -57,7 +57,16 @@ export const callAssistantGraph: AgentExecutor = async ({ const logger = parentLogger.get('defaultAssistantGraph'); const isOpenAI = llmType === 'openai'; const llmClass = getLlmClass(llmType, bedrockChatEnabled); - const getLlmInstance = () => + + /** + * Creates a new instance of llmClass. + * + * This function ensures that a new llmClass instance is created every time it is called. + * This is necessary to avoid any potential side effects from shared state. By always + * creating a new instance, we prevent other uses of llm from binding and changing + * the state unintentionally. For this reason, never assign this value to a variable (ex const llm = createLlmInstance()) + */ + const createLlmInstance = () => new llmClass({ actionsClient, connectorId, @@ -76,8 +85,6 @@ export const callAssistantGraph: AgentExecutor = async ({ maxRetries: 0, }); - const llm = getLlmInstance(); - const anonymizationFieldsRes = await dataClients?.anonymizationFieldsDataClient?.findDocuments({ perPage: 1000, @@ -93,7 +100,7 @@ export const callAssistantGraph: AgentExecutor = async ({ const modelExists = await esStore.isModelInstalled(); // Create a chain that uses the ELSER backed ElasticsearchStore, override k=10 for esql query generation for now - const chain = RetrievalQAChain.fromLLM(getLlmInstance(), esStore.asRetriever(10)); + const chain = RetrievalQAChain.fromLLM(createLlmInstance(), esStore.asRetriever(10)); // Check if KB is available const isEnabledKnowledgeBase = (await dataClients?.kbDataClient?.isModelDeployed()) ?? false; @@ -106,7 +113,6 @@ export const callAssistantGraph: AgentExecutor = async ({ esClient, isEnabledKnowledgeBase, kbDataClient: dataClients?.kbDataClient, - llm, logger, modelExists, onNewReplacements, @@ -116,26 +122,26 @@ export const callAssistantGraph: AgentExecutor = async ({ }; const tools: StructuredTool[] = assistantTools.flatMap( - (tool) => tool.getTool(assistantToolParams) ?? [] + (tool) => tool.getTool({ ...assistantToolParams, llm: createLlmInstance() }) ?? [] ); const agentRunnable = isOpenAI ? await createOpenAIFunctionsAgent({ - llm, + llm: createLlmInstance(), tools, prompt: openAIFunctionAgentPrompt, streamRunnable: isStream, }) : llmType && ['bedrock', 'gemini'].includes(llmType) && bedrockChatEnabled ? await createToolCallingAgent({ - llm, + llm: createLlmInstance(), tools, prompt: llmType === 'bedrock' ? bedrockToolCallingAgentPrompt : geminiToolCallingAgentPrompt, streamRunnable: isStream, }) : await createStructuredChatAgent({ - llm, + llm: createLlmInstance(), tools, prompt: structuredChatAgentPrompt, streamRunnable: isStream, @@ -147,9 +153,8 @@ export const callAssistantGraph: AgentExecutor = async ({ agentRunnable, conversationId, dataClients, - llm, // we need to pass it like this or streaming does not work for bedrock - getLlmInstance, + createLlmInstance, logger, tools, responseLanguage, diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/generate_chat_title.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/generate_chat_title.ts index 74cf0fca3929a..9cda33fdbabbc 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/generate_chat_title.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/generate_chat_title.ts @@ -7,6 +7,7 @@ import { StringOutputParser } from '@langchain/core/output_parsers'; import { ChatPromptTemplate } from '@langchain/core/prompts'; +import { BaseChatModel } from '@langchain/core/language_models/chat_models'; import { AgentState, NodeParamsBase } from '../types'; export const GENERATE_CHAT_TITLE_PROMPT = (responseLanguage: string) => @@ -25,6 +26,7 @@ export const GENERATE_CHAT_TITLE_PROMPT = (responseLanguage: string) => export interface GenerateChatTitleParams extends NodeParamsBase { responseLanguage: string; state: AgentState; + model: BaseChatModel; } export const GENERATE_CHAT_TITLE_NODE = 'generateChatTitle'; diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/respond.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/respond.ts index bb3b3a518e06d..7c11b96bbca0d 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/respond.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/respond.ts @@ -11,7 +11,7 @@ import { AGENT_NODE_TAG } from './run_agent'; import { AgentState } from '../types'; export const RESPOND_NODE = 'respond'; -export const respond = async ({ llm, state }: { llm: BaseChatModel; state: AgentState }) => { +export const respond = async ({ model, state }: { model: BaseChatModel; state: AgentState }) => { if (state?.agentOutcome && 'returnValues' in state.agentOutcome) { const userMessage = [ 'user', @@ -21,7 +21,7 @@ export const respond = async ({ llm, state }: { llm: BaseChatModel; state: Agent Do not verify, confirm or anything else. Just reply with the same content as provided above.`, ] as [StringWithAutocomplete<'user'>, string]; - const responseMessage = await llm + const responseMessage = await model // use AGENT_NODE_TAG to identify as agent node for stream parsing .withConfig({ runName: 'Summarizer', tags: [AGENT_NODE_TAG] }) .invoke([userMessage]); diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/types.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/types.ts index 4ee4f1ba1b148..5d86a0f6b97ed 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/types.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/types.ts @@ -7,7 +7,6 @@ import { BaseMessage } from '@langchain/core/messages'; import { AgentAction, AgentFinish, AgentStep } from '@langchain/core/agents'; -import { BaseChatModel } from '@langchain/core/language_models/chat_models'; import type { Logger } from '@kbn/logging'; import { ConversationResponse } from '@kbn/elastic-assistant-common'; @@ -25,5 +24,4 @@ export interface AgentState extends AgentStateBase { export interface NodeParamsBase { logger: Logger; - model: BaseChatModel; } From 412473f3c1658824ca8fe963ca44a91db9462a5a Mon Sep 17 00:00:00 2001 From: Sandra G Date: Wed, 7 Aug 2024 15:51:01 -0400 Subject: [PATCH 31/44] [Obs AI Assistant] Function tests for AI Assistant and Settings (#189920) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolves https://github.com/elastic/kibana/issues/182924 - Functional tests for Observability AI Assistant settings - Functional tests accessing the AI Assistant - Makes settings inputs disabled if user does not have advanced settings permissions - Modified useEditableSetting to catch and surface errors when attempting to update. Currently errors do not surface when saving advanced settings fails and page refreshes with no notifications. The page will not be refreshed in this case. - Other plugins that use useEditableSetting, like APM, will also see the toast error now, depending on implementation.
      Screenshot of saving APM advanced setting when request fails: Screenshot 2024-08-05 at 3 50 11 PM
      --- .../ai_assistant_selection_page.tsx | 1 + .../chat/welcome_message_connectors.tsx | 2 +- .../conversations/conversation_view.tsx | 7 +- .../routes/components/settings_page.tsx | 4 +- .../settings_tab/settings_tab.test.tsx | 2 +- .../components/settings_tab/ui_settings.tsx | 14 +- .../public/hooks/use_editable_settings.tsx | 12 + x-pack/test/functional/config.base.js | 9 + .../common/connectors.ts | 37 +++ .../common/ui/index.ts | 14 ++ .../tests/contextual_insights/index.spec.ts | 36 +-- .../assistant_security.spec.ts | 151 ++++++++++++ .../tests/feature_controls/helpers.ts | 58 +++++ .../settings_security.spec.ts | 232 ++++++++++++++++++ 14 files changed, 538 insertions(+), 41 deletions(-) create mode 100644 x-pack/test/observability_ai_assistant_functional/common/connectors.ts create mode 100644 x-pack/test/observability_ai_assistant_functional/tests/feature_controls/assistant_security.spec.ts create mode 100644 x-pack/test/observability_ai_assistant_functional/tests/feature_controls/helpers.ts create mode 100644 x-pack/test/observability_ai_assistant_functional/tests/feature_controls/settings_security.spec.ts diff --git a/src/plugins/ai_assistant_management/selection/public/routes/components/ai_assistant_selection_page.tsx b/src/plugins/ai_assistant_management/selection/public/routes/components/ai_assistant_selection_page.tsx index feaecf237e587..ad99c58af1f83 100644 --- a/src/plugins/ai_assistant_management/selection/public/routes/components/ai_assistant_selection_page.tsx +++ b/src/plugins/ai_assistant_management/selection/public/routes/components/ai_assistant_selection_page.tsx @@ -72,6 +72,7 @@ export function AiAssistantSelectionPage() { {!observabilityAIAssistantEnabled ? ( diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/welcome_message_connectors.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/welcome_message_connectors.tsx index 77e8289deb6c5..a264a7cd30489 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/welcome_message_connectors.tsx +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/welcome_message_connectors.tsx @@ -47,7 +47,7 @@ export function WelcomeMessageConnectors({ (connectors.error.body as { statusCode: number }).statusCode === 403; return ( -
      +
      diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/routes/conversations/conversation_view.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/routes/conversations/conversation_view.tsx index 3ac7df9f0fcd8..da34c98b86fbc 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/routes/conversations/conversation_view.tsx +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/routes/conversations/conversation_view.tsx @@ -135,7 +135,12 @@ export function ConversationView() { `; return ( - + +

      {i18n.translate( @@ -141,6 +141,6 @@ export function SettingsPage() { {selectedTabContent} - +

      ); } diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_management/public/routes/components/settings_tab/settings_tab.test.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_management/public/routes/components/settings_tab/settings_tab.test.tsx index 39045fdf998ab..9b7022efc324c 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_management/public/routes/components/settings_tab/settings_tab.test.tsx +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_management/public/routes/components/settings_tab/settings_tab.test.tsx @@ -83,7 +83,7 @@ describe('SettingsTab', () => { } ); - fireEvent.click(getByTestId('apmBottomBarActionsButton')); + fireEvent.click(getByTestId('observabilityAiAssistantManagementBottomBarActionsButton')); await waitFor(() => expect(windowLocationReloadMock).toHaveBeenCalledTimes(1)); }); diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_management/public/routes/components/settings_tab/ui_settings.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_management/public/routes/components/settings_tab/ui_settings.tsx index 4e7b60c780f81..5b1f1c36fd3dd 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_management/public/routes/components/settings_tab/ui_settings.tsx +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_management/public/routes/components/settings_tab/ui_settings.tsx @@ -27,11 +27,17 @@ const settingsKeys = [ ]; export function UISettings() { - const { docLinks, settings, notifications } = useKibana().services; + const { + docLinks, + settings, + notifications, + application: { capabilities }, + } = useKibana().services; const { fields, handleFieldChange, unsavedChanges, saveAll, isSaving, cleanUnsavedChanges } = useEditableSettings(settingsKeys); + const canEditAdvancedSettings = capabilities.advancedSettings?.save; async function handleSave() { try { await saveAll(); @@ -71,7 +77,7 @@ export function UISettings() { > @@ -84,11 +90,11 @@ export function UISettings() { onDiscardChanges={cleanUnsavedChanges} onSave={handleSave} saveLabel={i18n.translate( - 'xpack.observabilityAiAssistantManagement.apmSettings.saveButton', + 'xpack.observabilityAiAssistantManagement.settings.saveButton', { defaultMessage: 'Save changes' } )} unsavedChangesCount={Object.keys(unsavedChanges).length} - appTestSubj="apm" + appTestSubj="observabilityAiAssistantManagement" areChangesInvalid={hasInvalidChanges} /> )} diff --git a/x-pack/plugins/observability_solution/observability_shared/public/hooks/use_editable_settings.tsx b/x-pack/plugins/observability_solution/observability_shared/public/hooks/use_editable_settings.tsx index 235e8e2ab50f1..7f05722019d7e 100644 --- a/x-pack/plugins/observability_solution/observability_shared/public/hooks/use_editable_settings.tsx +++ b/x-pack/plugins/observability_solution/observability_shared/public/hooks/use_editable_settings.tsx @@ -80,6 +80,10 @@ export function useEditableSettings(settingsKeys: string[]) { async function saveAll() { if (settings && !isEmpty(unsavedChanges)) { + let updateErrorOccurred = false; + const subscription = settings.client.getUpdateErrors$().subscribe((error) => { + updateErrorOccurred = true; + }); try { setIsSaving(true); const arr = Object.entries(unsavedChanges).map(([key, value]) => @@ -88,8 +92,16 @@ export function useEditableSettings(settingsKeys: string[]) { await Promise.all(arr); setForceReloadSettings((state) => ++state); cleanUnsavedChanges(); + if (updateErrorOccurred) { + throw new Error('One or more settings updates failed'); + } + } catch (e) { + throw e; } finally { setIsSaving(false); + if (subscription) { + subscription.unsubscribe(); + } } } } diff --git a/x-pack/test/functional/config.base.js b/x-pack/test/functional/config.base.js index 56ecbef55759f..4fdb988fef098 100644 --- a/x-pack/test/functional/config.base.js +++ b/x-pack/test/functional/config.base.js @@ -185,6 +185,15 @@ export default async function ({ readConfigFile }) { maintenanceWindows: { pathname: '/app/management/insightsAndAlerting/maintenanceWindows', }, + obsAIAssistant: { + pathname: '/app/observabilityAIAssistant', + }, + aiAssistantManagementSelection: { + pathname: '/app/management/kibana/aiAssistantManagementSelection', + }, + obsAIAssistantManagement: { + pathname: '/app/management/kibana/observabilityAiAssistantManagement', + }, }, suiteTags: { diff --git a/x-pack/test/observability_ai_assistant_functional/common/connectors.ts b/x-pack/test/observability_ai_assistant_functional/common/connectors.ts new file mode 100644 index 0000000000000..0930c1e4ff7c4 --- /dev/null +++ b/x-pack/test/observability_ai_assistant_functional/common/connectors.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Agent as SuperTestAgent } from 'supertest'; +import { LlmProxy } from '../../observability_ai_assistant_api_integration/common/create_llm_proxy'; +export async function createConnector(proxy: LlmProxy, supertest: SuperTestAgent) { + await supertest + .post('/api/actions/connector') + .set('kbn-xsrf', 'foo') + .send({ + name: 'foo', + config: { + apiProvider: 'OpenAI', + apiUrl: `http://localhost:${proxy.getPort()}`, + defaultModel: 'gpt-4', + }, + secrets: { apiKey: 'myApiKey' }, + connector_type_id: '.gen-ai', + }) + .expect(200); +} + +export async function deleteConnectors(supertest: SuperTestAgent) { + const connectors = await supertest.get('/api/actions/connectors').expect(200); + const promises = connectors.body.map((connector: { id: string }) => { + return supertest + .delete(`/api/actions/connector/${connector.id}`) + .set('kbn-xsrf', 'foo') + .expect(204); + }); + + return Promise.all(promises); +} diff --git a/x-pack/test/observability_ai_assistant_functional/common/ui/index.ts b/x-pack/test/observability_ai_assistant_functional/common/ui/index.ts index d0a45f61e17be..a5d2802dfbcc5 100644 --- a/x-pack/test/observability_ai_assistant_functional/common/ui/index.ts +++ b/x-pack/test/observability_ai_assistant_functional/common/ui/index.ts @@ -33,6 +33,8 @@ const pages = { retryButton: 'observabilityAiAssistantWelcomeMessageSetUpKnowledgeBaseButton', conversationLink: 'observabilityAiAssistantConversationsLink', positiveFeedbackButton: 'observabilityAiAssistantFeedbackButtonsPositiveButton', + connectorsErrorMsg: 'observabilityAiAssistantConnectorsError', + conversationsPage: 'observabilityAiAssistantConversationsPage', }, createConnectorFlyout: { flyout: 'create-connector-flyout', @@ -48,6 +50,18 @@ const pages = { button: 'obsAiAssistantInsightButton', text: 'obsAiAssistantInsightResponse', }, + links: { + solutionMenuLink: 'observability-nav-observabilityAIAssistant-ai_assistant', + globalHeaderButton: 'observabilityAiAssistantAppNavControlButton', + }, + settings: { + settingsPage: 'aiAssistantSettingsPage', + managementLink: 'aiAssistantManagementSelection', + logsIndexPatternInput: + 'management-settings-editField-observability:aiAssistantLogsIndexPattern', + saveButton: 'observabilityAiAssistantManagementBottomBarActionsButton', + aiAssistantCard: 'aiAssistantSelectionPageObservabilityCard', + }, }; export async function ObservabilityAIAssistantUIProvider({ diff --git a/x-pack/test/observability_ai_assistant_functional/tests/contextual_insights/index.spec.ts b/x-pack/test/observability_ai_assistant_functional/tests/contextual_insights/index.spec.ts index 41ad6f793ee93..7355b508ce5a0 100644 --- a/x-pack/test/observability_ai_assistant_functional/tests/contextual_insights/index.spec.ts +++ b/x-pack/test/observability_ai_assistant_functional/tests/contextual_insights/index.spec.ts @@ -14,6 +14,7 @@ import { LlmProxy, } from '../../../observability_ai_assistant_api_integration/common/create_llm_proxy'; import { FtrProviderContext } from '../../ftr_provider_context'; +import { deleteConnectors, createConnector } from '../../common/connectors'; export default function ApiTest({ getService, getPageObjects }: FtrProviderContext) { const ui = getService('observabilityAIAssistantUI'); @@ -60,35 +61,6 @@ export default function ApiTest({ getService, getPageObjects }: FtrProviderConte await apmSynthtraceEsClient.index(documents); } - async function createConnector(proxy: LlmProxy) { - await supertest - .post('/api/actions/connector') - .set('kbn-xsrf', 'foo') - .send({ - name: 'foo', - config: { - apiProvider: 'OpenAI', - apiUrl: `http://localhost:${proxy.getPort()}`, - defaultModel: 'gpt-4', - }, - secrets: { apiKey: 'myApiKey' }, - connector_type_id: '.gen-ai', - }) - .expect(200); - } - - async function deleteConnectors() { - const connectors = await supertest.get('/api/actions/connectors').expect(200); - const promises = connectors.body.map((connector: { id: string }) => { - return supertest - .delete(`/api/actions/connector/${connector.id}`) - .set('kbn-xsrf', 'foo') - .expect(204); - }); - - return Promise.all(promises); - } - async function navigateToError() { await common.navigateToUrl('apm', 'services/opbeans-go/errors/some-expection-key', { shouldUseHashForSubUrl: false, @@ -111,7 +83,7 @@ export default function ApiTest({ getService, getPageObjects }: FtrProviderConte describe('Contextual insights for APM errors', () => { before(async () => { await Promise.all([ - deleteConnectors(), // cleanup previous connectors + deleteConnectors(supertest), // cleanup previous connectors apmSynthtraceEsClient.clean(), // cleanup previous synthtrace data ]); @@ -123,7 +95,7 @@ export default function ApiTest({ getService, getPageObjects }: FtrProviderConte after(async () => { await Promise.all([ - deleteConnectors(), // cleanup previous connectors + deleteConnectors(supertest), // cleanup previous connectors apmSynthtraceEsClient.clean(), // cleanup synthtrace data ui.auth.logout(), // logout ]); @@ -141,7 +113,7 @@ export default function ApiTest({ getService, getPageObjects }: FtrProviderConte before(async () => { proxy = await createLlmProxy(log); - await createConnector(proxy); + await createConnector(proxy, supertest); }); after(async () => { diff --git a/x-pack/test/observability_ai_assistant_functional/tests/feature_controls/assistant_security.spec.ts b/x-pack/test/observability_ai_assistant_functional/tests/feature_controls/assistant_security.spec.ts new file mode 100644 index 0000000000000..1108c7251b89d --- /dev/null +++ b/x-pack/test/observability_ai_assistant_functional/tests/feature_controls/assistant_security.spec.ts @@ -0,0 +1,151 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; +import { + createLlmProxy, + LlmProxy, +} from '../../../observability_ai_assistant_api_integration/common/create_llm_proxy'; +import { createConnector, deleteConnectors } from '../../common/connectors'; +import { createAndLoginUserWithCustomRole, deleteAndLogoutUser } from './helpers'; + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const log = getService('log'); + const supertest = getService('supertest'); + const PageObjects = getPageObjects(['common', 'error', 'navigationalSearch', 'security']); + const ui = getService('observabilityAIAssistantUI'); + const testSubjects = getService('testSubjects'); + + describe('ai assistant privileges', () => { + describe('all privileges', () => { + before(async () => { + await createAndLoginUserWithCustomRole(getPageObjects, getService, { + // need some obs app or obs menu wont show where we can click on AI Assistant + infrastructure: ['all'], + observabilityAIAssistant: ['all'], + // requires connectors to chat + actions: ['read'], + }); + }); + + after(async () => { + await deleteAndLogoutUser(getService, getPageObjects); + }); + + it('shows AI Assistant link in solution nav', async () => { + // navigate to an observability app so the left side o11y menu shows up + await PageObjects.common.navigateToUrl('infraOps', '', { + ensureCurrentUrl: true, + shouldLoginIfPrompted: false, + }); + await testSubjects.existOrFail(ui.pages.links.solutionMenuLink); + }); + it('shows AI Assistant button in global nav', async () => { + await testSubjects.existOrFail(ui.pages.links.globalHeaderButton); + }); + it('shows AI Assistant conversations link in search', async () => { + await PageObjects.navigationalSearch.searchFor('observability ai assistant'); + const results = await PageObjects.navigationalSearch.getDisplayedResults(); + expect(results[0].label).to.eql('Observability AI Assistant / Conversations'); + }); + describe('with no connector setup', () => { + before(async () => { + await deleteConnectors(supertest); + }); + it('loads conversations UI with setup connector message', async () => { + await PageObjects.common.navigateToUrl('obsAIAssistant', '', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, + }); + await testSubjects.existOrFail(ui.pages.conversations.setupGenAiConnectorsButtonSelector); + }); + }); + describe('with connector setup', () => { + let proxy: LlmProxy; + + before(async () => { + await deleteConnectors(supertest); + proxy = await createLlmProxy(log); + await createConnector(proxy, supertest); + }); + + after(async () => { + proxy.close(); + await deleteConnectors(supertest); + }); + it('loads conversations UI with ability to chat', async () => { + await PageObjects.common.navigateToUrl('obsAIAssistant', '', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, + }); + const chatInputElement = await testSubjects.find(ui.pages.conversations.chatInput); + await testSubjects.existOrFail(ui.pages.conversations.chatInput); + const isDisabled = await chatInputElement.getAttribute('disabled'); + expect(isDisabled).to.be(null); + }); + }); + }); + describe('no actions privileges', () => { + before(async () => { + await createAndLoginUserWithCustomRole(getPageObjects, getService, { + // need some obs app or obs menu wont show where we can click on AI Assistant + infrastructure: ['all'], + observabilityAIAssistant: ['all'], + }); + }); + it('loads conversations UI with connector error message', async () => { + await PageObjects.common.navigateToUrl('obsAIAssistant', '', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, + }); + await testSubjects.existOrFail(ui.pages.conversations.connectorsErrorMsg); + }); + after(async () => { + await deleteAndLogoutUser(getService, getPageObjects); + }); + }); + describe('no privileges', () => { + before(async () => { + await createAndLoginUserWithCustomRole(getPageObjects, getService, { + // need some obs app or obs menu wont show where we can click on AI Assistant + infrastructure: ['all'], + }); + }); + it('shows no AI Assistant link in solution nav', async () => { + // navigate to an observability app so the left side o11y menu shows up + await PageObjects.common.navigateToUrl('infraOps', '', { + ensureCurrentUrl: true, + shouldLoginIfPrompted: false, + }); + await testSubjects.missingOrFail(ui.pages.links.solutionMenuLink); + }); + it('shows no AI Assistant button in global nav', async () => { + await testSubjects.missingOrFail(ui.pages.links.globalHeaderButton); + }); + it('shows no AI Assistant conversations link in global search', async () => { + await PageObjects.navigationalSearch.searchFor('observability ai assistant'); + const results = await PageObjects.navigationalSearch.getDisplayedResults(); + expect(results.length).to.eql(0); + }); + it('cannot navigate to AI Assistant page', async () => { + await PageObjects.common.navigateToUrl('obsAIAssistant', '', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, + }); + await testSubjects.missingOrFail(ui.pages.conversations.conversationsPage); + }); + after(async () => { + await deleteAndLogoutUser(getService, getPageObjects); + }); + }); + }); +} diff --git a/x-pack/test/observability_ai_assistant_functional/tests/feature_controls/helpers.ts b/x-pack/test/observability_ai_assistant_functional/tests/feature_controls/helpers.ts new file mode 100644 index 0000000000000..206b5d0df78f7 --- /dev/null +++ b/x-pack/test/observability_ai_assistant_functional/tests/feature_controls/helpers.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { InheritedFtrProviderContext } from '../../ftr_provider_context'; + +const AI_ASSISTANT_ROLE_NAME = 'ai_assistant_role'; +const AI_ASSISTANT_USER_NAME = 'ai_assistant_user'; +const AI_ASSISTANT_USER_PASSWORD = `${AI_ASSISTANT_USER_NAME}-password`; + +export const createAndLoginUserWithCustomRole = async ( + getPageObjects: InheritedFtrProviderContext['getPageObjects'], + getService: InheritedFtrProviderContext['getService'], + featurePrivileges: { [key: string]: string[] } +) => { + const security = getService('security'); + const PageObjects = getPageObjects(['security']); + + const kibanaPrivileges = [ + { + feature: featurePrivileges, + spaces: ['*'], + }, + ]; + + await security.role.create(AI_ASSISTANT_ROLE_NAME, { + kibana: kibanaPrivileges, + }); + + await security.user.create(AI_ASSISTANT_USER_NAME, { + password: AI_ASSISTANT_USER_PASSWORD, + roles: [AI_ASSISTANT_ROLE_NAME], + full_name: 'test user', + }); + + await PageObjects.security.forceLogout(); + + await PageObjects.security.login(AI_ASSISTANT_USER_NAME, AI_ASSISTANT_USER_PASSWORD, { + expectSpaceSelector: false, + }); +}; + +export const deleteAndLogoutUser = async ( + getService: InheritedFtrProviderContext['getService'], + getPageObjects: InheritedFtrProviderContext['getPageObjects'] +) => { + const security = getService('security'); + const PageObjects = getPageObjects(['security']); + + await PageObjects.security.forceLogout(); + await Promise.all([ + security.role.delete(AI_ASSISTANT_ROLE_NAME), + security.user.delete(AI_ASSISTANT_USER_NAME), + ]); +}; diff --git a/x-pack/test/observability_ai_assistant_functional/tests/feature_controls/settings_security.spec.ts b/x-pack/test/observability_ai_assistant_functional/tests/feature_controls/settings_security.spec.ts new file mode 100644 index 0000000000000..cea40d3ad10ce --- /dev/null +++ b/x-pack/test/observability_ai_assistant_functional/tests/feature_controls/settings_security.spec.ts @@ -0,0 +1,232 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; +import { interceptRequest } from '../../common/intercept_request'; +import { createAndLoginUserWithCustomRole, deleteAndLogoutUser } from './helpers'; + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const browser = getService('browser'); + const PageObjects = getPageObjects(['common', 'error', 'navigationalSearch', 'security']); + const ui = getService('observabilityAIAssistantUI'); + const testSubjects = getService('testSubjects'); + const driver = getService('__webdriver__'); + const retry = getService('retry'); + const toasts = getService('toasts'); + + describe('ai assistant management privileges', () => { + describe('all privileges', () => { + before(async () => { + await createAndLoginUserWithCustomRole(getPageObjects, getService, { + // we need all these privileges to view and modify Obs AI Assistant settings view + observabilityAIAssistant: ['all'], + // aiAssistantManagementSelection determines link visibility in stack management and navigating to the page + // but not whether you can read/write the settings + aiAssistantManagementSelection: ['all'], + // advancedSettings determines whether user can read/write the settings + advancedSettings: ['all'], + }); + }); + + after(async () => { + await deleteAndLogoutUser(getService, getPageObjects); + }); + + it('shows AI Assistant settings link in solution nav', async () => { + await PageObjects.common.navigateToUrl('management', '', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, + }); + await testSubjects.existOrFail(ui.pages.settings.managementLink); + }); + + it('allows access to ai assistant settings landing page', async () => { + await PageObjects.common.navigateToUrl('aiAssistantManagementSelection', '', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, + }); + }); + it('allows access to obs ai assistant settings view', async () => { + await PageObjects.common.navigateToUrl('obsAIAssistantManagement', '', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, + }); + await testSubjects.existOrFail(ui.pages.settings.settingsPage); + }); + it('allows updating of an advanced setting', async () => { + const testLogsIndexPattern = 'my-logs-index-pattern'; + const logsIndexPatternInput = await testSubjects.find( + ui.pages.settings.logsIndexPatternInput + ); + await logsIndexPatternInput.clearValue(); + await logsIndexPatternInput.type(testLogsIndexPattern); + const saveButton = await testSubjects.find(ui.pages.settings.saveButton); + await saveButton.click(); + await browser.refresh(); + const logsIndexPatternInputValue = await logsIndexPatternInput.getAttribute('value'); + expect(logsIndexPatternInputValue).to.be(testLogsIndexPattern); + // reset the value + await logsIndexPatternInput.clearValue(); + await logsIndexPatternInput.type('logs-*'); + await saveButton.click(); + }); + it('displays failure toast on failed request', async () => { + const logsIndexPatternInput = await testSubjects.find( + ui.pages.settings.logsIndexPatternInput + ); + // Wait until the input has the default value 'logs-*' to prevent flakiness + await retry.waitFor('input field to have default value', async () => { + const value = await logsIndexPatternInput.getAttribute('value'); + return value === 'logs-*'; + }); + await logsIndexPatternInput.clearValue(); + await logsIndexPatternInput.type('test'); + + await interceptRequest( + driver.driver, + '*kibana\\/settings*', + (responseFactory) => { + return responseFactory.fail(); + }, + async () => { + await testSubjects.click(ui.pages.settings.saveButton); + } + ); + + await retry.waitFor('Error saving settings toast', async () => { + const count = await toasts.getCount(); + return count > 0; + }); + }); + }); + describe('with advancedSettings read privilege', () => { + before(async () => { + await createAndLoginUserWithCustomRole(getPageObjects, getService, { + observabilityAIAssistant: ['all'], + aiAssistantManagementSelection: ['all'], + advancedSettings: ['read'], + }); + }); + + after(async () => { + await deleteAndLogoutUser(getService, getPageObjects); + }); + + it('shows AI Assistant settings link in solution nav', async () => { + await PageObjects.common.navigateToUrl('management', '', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, + }); + await testSubjects.existOrFail(ui.pages.settings.managementLink); + }); + + it('allows access to ai assistant settings landing page', async () => { + await PageObjects.common.navigateToUrl('aiAssistantManagementSelection', '', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, + }); + await testSubjects.existOrFail(ui.pages.settings.aiAssistantCard); + }); + it('allows access to obs ai assistant settings page', async () => { + await PageObjects.common.navigateToUrl('obsAIAssistantManagement', '', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, + }); + await testSubjects.existOrFail(ui.pages.settings.settingsPage); + }); + it('has disabled inputs', async () => { + const logsIndexPatternInput = await testSubjects.find( + ui.pages.settings.logsIndexPatternInput + ); + expect(await logsIndexPatternInput.getAttribute('disabled')).to.be('true'); + }); + }); + describe('observabilityAIAssistant privilege with no aiAssistantManagementSelection privilege', () => { + before(async () => { + await createAndLoginUserWithCustomRole(getPageObjects, getService, { + // we need at least one feature available to login + observabilityAIAssistant: ['all'], + }); + }); + + after(async () => { + await deleteAndLogoutUser(getService, getPageObjects); + }); + + it('does not show AI Assistant settings link in solution nav', async () => { + await PageObjects.common.navigateToUrl('management', '', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, + }); + await testSubjects.missingOrFail(ui.pages.settings.managementLink); + }); + + it('does not allow access to ai assistant settings landing page', async () => { + await PageObjects.common.navigateToUrl('aiAssistantManagementSelection', '', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, + }); + await testSubjects.missingOrFail(ui.pages.settings.aiAssistantCard); + }); + it('allows access to obs ai assistant settings page', async () => { + await PageObjects.common.navigateToUrl('obsAIAssistantManagement', '', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, + }); + await testSubjects.missingOrFail(ui.pages.settings.settingsPage); + }); + }); + describe('aiAssistantManagementSelection privilege with no observabilityAIAssistant privilege', () => { + before(async () => { + await createAndLoginUserWithCustomRole(getPageObjects, getService, { + aiAssistantManagementSelection: ['all'], + advancedSettings: ['all'], + }); + }); + + after(async () => { + await deleteAndLogoutUser(getService, getPageObjects); + }); + + it('shows AI Assistant settings link in solution nav', async () => { + await PageObjects.common.navigateToUrl('management', '', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, + }); + await testSubjects.existOrFail(ui.pages.settings.managementLink); + }); + + it('allows access to ai assistant settings landing page', async () => { + await PageObjects.common.navigateToUrl('aiAssistantManagementSelection', '', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, + }); + await testSubjects.existOrFail(ui.pages.settings.aiAssistantCard); + }); + it('does not allow access to obs ai assistant settings page', async () => { + await PageObjects.common.navigateToUrl('obsAIAssistantManagement', '', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, + }); + await testSubjects.missingOrFail(ui.pages.settings.settingsPage); + }); + }); + }); +} From 5f49cc50178d4abc67be420534f8cfeafb60c954 Mon Sep 17 00:00:00 2001 From: Saarika Bhasi <55930906+saarikabhasi@users.noreply.github.com> Date: Wed, 7 Aug 2024 16:03:53 -0400 Subject: [PATCH 32/44] [Search] [Playground]Improve follow up question flow (#189848) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Adds the query used for searching. This improves visibility to user on the query using for follow up questions. Screenshot 2024-08-03 at 3 24 10 PM ### 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] [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: Elastic Machine --- .../message_list/assistant_message.test.tsx | 2 +- .../message_list/assistant_message.tsx | 104 +++++++++++------- .../public/hooks/use_ai_assist_chat.ts | 2 +- .../plugins/search_playground/public/types.ts | 4 +- .../public/utils/transform_to_messages.ts | 3 + .../server/lib/conversational_chain.ts | 11 ++ 6 files changed, 84 insertions(+), 42 deletions(-) diff --git a/x-pack/plugins/search_playground/public/components/message_list/assistant_message.test.tsx b/x-pack/plugins/search_playground/public/components/message_list/assistant_message.test.tsx index f3a903331cf3b..4608546616a51 100644 --- a/x-pack/plugins/search_playground/public/components/message_list/assistant_message.test.tsx +++ b/x-pack/plugins/search_playground/public/components/message_list/assistant_message.test.tsx @@ -38,7 +38,7 @@ describe('AssistantMessage component', () => { createdAt: new Date(), citations: [], retrievalDocs: [{ content: '', metadata: { _id: '1', _index: 'index', _score: 1 } }], - inputTokens: { context: 20, total: 10 }, + inputTokens: { context: 20, total: 10, searchQuery: 'Test question' }, }; it('renders message content correctly', () => { diff --git a/x-pack/plugins/search_playground/public/components/message_list/assistant_message.tsx b/x-pack/plugins/search_playground/public/components/message_list/assistant_message.tsx index 7e94059bdc272..e0b14c2a31934 100644 --- a/x-pack/plugins/search_playground/public/components/message_list/assistant_message.tsx +++ b/x-pack/plugins/search_playground/public/components/message_list/assistant_message.tsx @@ -53,53 +53,79 @@ export const AssistantMessage: React.FC = ({ message }) = return ( <> {!!retrievalDocs?.length && ( - + <> +

      - {` `}

      + } + /> + + +

      + + {` `} +

      +
      - setIsDocsFlyoutOpen(true)} - > - - + setIsDocsFlyoutOpen(true)} + > + + - {isDocsFlyoutOpen && ( - setIsDocsFlyoutOpen(false)} - retrievalDocs={retrievalDocs} - /> - )} - - } - /> + {isDocsFlyoutOpen && ( + setIsDocsFlyoutOpen(false)} + retrievalDocs={retrievalDocs} + /> + )} + + } + /> + )} {retrievalDocs?.length === 0 && ( annotation.type === 'context_clipped' )?.count, + searchQuery: annotations?.find( + (annotation): annotation is AnnotationTokens => annotation.type === 'search_query' + )?.question, }, } as AIMessage; } diff --git a/x-pack/plugins/search_playground/server/lib/conversational_chain.ts b/x-pack/plugins/search_playground/server/lib/conversational_chain.ts index c63481e93c98f..922f672bda5c6 100644 --- a/x-pack/plugins/search_playground/server/lib/conversational_chain.ts +++ b/x-pack/plugins/search_playground/server/lib/conversational_chain.ts @@ -198,6 +198,13 @@ class ConversationalChainFn { context: RunnableSequence.from([(input) => input.question, retrievalChain]), question: (input) => input.question, }, + RunnableLambda.from((inputs) => { + data.appendMessageAnnotation({ + type: 'search_query', + question: inputs.question, + }); + return inputs; + }), RunnableLambda.from(clipContext(this.options?.rag?.inputTokensLimit, prompt, data)), RunnableLambda.from(registerContextTokenCounts(data)), prompt, @@ -236,6 +243,10 @@ class ConversationalChainFn { type: 'prompt_token_count', count: getTokenEstimateFromMessages(msg), }); + data.appendMessageAnnotation({ + type: 'search_query', + question, + }); } }, // callback for prompt based models (Bedrock uses ActionsClientLlm) From 35872af38076d06a292c524d1467d296876bf706 Mon Sep 17 00:00:00 2001 From: Tim Sullivan Date: Wed, 7 Aug 2024 15:10:40 -0700 Subject: [PATCH 33/44] [Reporting] Change response behavior for internal Download API (#189588) ## Summary Closes https://github.com/elastic/kibana/issues/183846 ### Checklist Delete any items that are not applicable to this PR. - [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] Test the UI in Stack Management > Reporting for jobs that are pending and failed. * To cause a report job to fail, restart the Kibana server during report execution ### Release Note Changed internal APIs for generating reports to return 200 response for pending report jobs and failed report jobs. If this impacts your workflow, please switch to using public APIs for generating reports: https://www.elastic.co/guide/en/kibana/current/automating-report-generation.html#use-a-script --- .../server/routes/common/jobs/constants.ts | 18 ++ .../common/jobs/get_document_payload.test.ts | 153 +++++++++++----- .../common/jobs/get_document_payload.ts | 31 ++-- .../routes/common/jobs/get_job_routes.ts | 171 ++++++++++-------- .../jobs/job_management_pre_routing.test.ts | 6 + .../common/jobs/job_management_pre_routing.ts | 3 +- .../routes/common/jobs/jobs_query.test.ts | 2 +- .../server/routes/common/jobs/jobs_query.ts | 7 +- .../management/integration_tests/jobs.test.ts | 19 +- .../server/routes/internal/management/jobs.ts | 25 ++- .../reporting/server/routes/public/jobs.ts | 2 +- 11 files changed, 282 insertions(+), 155 deletions(-) create mode 100644 x-pack/plugins/reporting/server/routes/common/jobs/constants.ts diff --git a/x-pack/plugins/reporting/server/routes/common/jobs/constants.ts b/x-pack/plugins/reporting/server/routes/common/jobs/constants.ts new file mode 100644 index 0000000000000..13c2c7ee50bac --- /dev/null +++ b/x-pack/plugins/reporting/server/routes/common/jobs/constants.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. + */ + +export const STATUS_CODES = { + COMPLETED: 200, + PENDING: { + INTERNAL: 202, + PUBLIC: 503, + }, + FAILED: { + INTERNAL: 202, + PUBLIC: 500, + }, +}; diff --git a/x-pack/plugins/reporting/server/routes/common/jobs/get_document_payload.test.ts b/x-pack/plugins/reporting/server/routes/common/jobs/get_document_payload.test.ts index cbd5f93425847..12de35cd9f32d 100644 --- a/x-pack/plugins/reporting/server/routes/common/jobs/get_document_payload.test.ts +++ b/x-pack/plugins/reporting/server/routes/common/jobs/get_document_payload.test.ts @@ -13,8 +13,10 @@ import { CSV_JOB_TYPE } from '@kbn/reporting-export-types-csv-common'; import { PDF_JOB_TYPE, PDF_JOB_TYPE_V2 } from '@kbn/reporting-export-types-pdf-common'; import { createMockConfigSchema } from '@kbn/reporting-mocks-server'; +import { ReportingCore } from '../../..'; import { ContentStream, getContentStream } from '../../../lib'; import { createMockReportingCore } from '../../../test_helpers'; +import { STATUS_CODES } from './constants'; import { getDocumentPayloadFactory } from './get_document_payload'; import { jobsQueryFactory } from './jobs_query'; @@ -22,13 +24,14 @@ jest.mock('../../../lib/content_stream'); jest.mock('./jobs_query'); describe('getDocumentPayload', () => { + let core: ReportingCore; let getDocumentPayload: ReturnType; beforeEach(async () => { const schema = createMockConfigSchema(); - const core = await createMockReportingCore(schema); + core = await createMockReportingCore(schema); - getDocumentPayload = getDocumentPayloadFactory(core); + getDocumentPayload = getDocumentPayloadFactory(core, { isInternal: false }); (getContentStream as jest.MockedFunction).mockResolvedValue( new Readable({ @@ -66,7 +69,7 @@ describe('getDocumentPayload', () => { headers: expect.objectContaining({ 'Content-Length': '1024', }), - statusCode: 200, + statusCode: STATUS_CODES.COMPLETED, }) ); }); @@ -96,57 +99,115 @@ describe('getDocumentPayload', () => { 'kbn-csv-contains-formulas': true, 'kbn-max-size-reached': true, }), - statusCode: 200, + statusCode: STATUS_CODES.COMPLETED, }) ); }); }); - describe('when the report is failed', () => { - it('should return payload for the failed report', async () => { - await expect( - getDocumentPayload({ - id: 'id1', - index: '.reporting-12345', - status: JOB_STATUS.FAILED, - jobtype: PDF_JOB_TYPE_V2, - output: {}, - payload: {}, - } as ReportApiJSON) - ).resolves.toEqual( - expect.objectContaining({ - contentType: 'application/json', - content: { - message: expect.stringContaining('Some error'), - }, - headers: {}, - statusCode: 500, - }) - ); + describe('public API behavior', () => { + beforeEach(() => { + getDocumentPayload = getDocumentPayloadFactory(core, { isInternal: false }); + }); + + describe('when the report is failed', () => { + it('should return payload for the failed report', async () => { + await expect( + getDocumentPayload({ + id: 'id1', + index: '.reporting-12345', + status: JOB_STATUS.FAILED, + jobtype: PDF_JOB_TYPE_V2, + output: {}, + payload: {}, + } as ReportApiJSON) + ).resolves.toEqual( + expect.objectContaining({ + contentType: 'application/json', + content: { + message: expect.stringContaining('Some error'), + }, + headers: {}, + statusCode: STATUS_CODES.FAILED.PUBLIC, + }) + ); + }); + }); + + describe('when the report is incomplete', () => { + it('should return payload for the pending report', async () => { + await expect( + getDocumentPayload({ + id: 'id1', + index: '.reporting-12345', + status: JOB_STATUS.PENDING, + jobtype: PDF_JOB_TYPE_V2, + output: {}, + payload: {}, + } as ReportApiJSON) + ).resolves.toEqual( + expect.objectContaining({ + contentType: 'text/plain', + content: 'pending', + headers: { + 'retry-after': '30', + }, + statusCode: STATUS_CODES.PENDING.PUBLIC, + }) + ); + }); }); }); - describe('when the report is incomplete', () => { - it('should return payload for the pending report', async () => { - await expect( - getDocumentPayload({ - id: 'id1', - index: '.reporting-12345', - status: JOB_STATUS.PENDING, - jobtype: PDF_JOB_TYPE_V2, - output: {}, - payload: {}, - } as ReportApiJSON) - ).resolves.toEqual( - expect.objectContaining({ - contentType: 'text/plain', - content: 'pending', - headers: { - 'retry-after': '30', - }, - statusCode: 503, - }) - ); + describe('internal API behavior', () => { + beforeEach(() => { + getDocumentPayload = getDocumentPayloadFactory(core, { isInternal: true }); + }); + + describe('when the report is failed', () => { + it('should return payload for the failed report', async () => { + await expect( + getDocumentPayload({ + id: 'id1', + index: '.reporting-12345', + status: JOB_STATUS.FAILED, + jobtype: PDF_JOB_TYPE_V2, + output: {}, + payload: {}, + } as ReportApiJSON) + ).resolves.toEqual( + expect.objectContaining({ + contentType: 'application/json', + content: { + message: expect.stringContaining('Some error'), + }, + headers: {}, + statusCode: STATUS_CODES.FAILED.INTERNAL, + }) + ); + }); + }); + + describe('when the report is incomplete', () => { + it('should return payload for the pending report', async () => { + await expect( + getDocumentPayload({ + id: 'id1', + index: '.reporting-12345', + status: JOB_STATUS.PENDING, + jobtype: PDF_JOB_TYPE_V2, + output: {}, + payload: {}, + } as ReportApiJSON) + ).resolves.toEqual( + expect.objectContaining({ + contentType: 'text/plain', + content: 'pending', + headers: { 'retry-after': '30' }, + statusCode: STATUS_CODES.PENDING.INTERNAL, + }) + ); + }); }); }); }); diff --git a/x-pack/plugins/reporting/server/routes/common/jobs/get_document_payload.ts b/x-pack/plugins/reporting/server/routes/common/jobs/get_document_payload.ts index c77e04ec66c22..2a20fa0b475c4 100644 --- a/x-pack/plugins/reporting/server/routes/common/jobs/get_document_payload.ts +++ b/x-pack/plugins/reporting/server/routes/common/jobs/get_document_payload.ts @@ -8,13 +8,14 @@ import { Stream } from 'stream'; import { ResponseHeaders } from '@kbn/core-http-server'; +import { JOB_STATUS } from '@kbn/reporting-common'; import { ReportApiJSON } from '@kbn/reporting-common/types'; import { CSV_JOB_TYPE, CSV_JOB_TYPE_DEPRECATED } from '@kbn/reporting-export-types-csv-common'; -import { JOB_STATUS } from '@kbn/reporting-common'; import { ExportType } from '@kbn/reporting-server'; import { ReportingCore } from '../../..'; import { getContentStream } from '../../../lib'; +import { STATUS_CODES } from './constants'; import { jobsQueryFactory } from './jobs_query'; export interface ErrorFromPayload { @@ -53,7 +54,10 @@ const getReportingHeaders = (output: TaskRunResult, exportType: ExportType) => { return metaDataHeaders; }; -export function getDocumentPayloadFactory(reporting: ReportingCore) { +export function getDocumentPayloadFactory( + reporting: ReportingCore, + { isInternal }: { isInternal: boolean } +) { const { logger: _logger } = reporting.getPluginSetupDeps(); const logger = _logger.get('download-report'); const exportTypesRegistry = reporting.getExportTypesRegistry(); @@ -75,7 +79,7 @@ export function getDocumentPayloadFactory(reporting: ReportingCore) { return { filename, content, - statusCode: 200, + statusCode: STATUS_CODES.COMPLETED, contentType, headers: { ...headers, @@ -84,29 +88,29 @@ export function getDocumentPayloadFactory(reporting: ReportingCore) { }; } - // @TODO: These should be semantic HTTP codes as 500/503's indicate - // error then these are really operating properly. async function getFailure({ id }: ReportApiJSON): Promise { - const jobsQuery = jobsQueryFactory(reporting); + const jobsQuery = jobsQueryFactory(reporting, { isInternal }); const error = await jobsQuery.getError(id); - logger.debug(`Report job ${id} has failed. Sending statusCode: 500`); + // For download requested over public API, status code for failed job must be 500 to integrate with Watcher + const statusCode = isInternal ? STATUS_CODES.FAILED.INTERNAL : STATUS_CODES.FAILED.PUBLIC; + logger.debug(`Report job ${id} has failed. Sending statusCode: ${statusCode}`); return { - statusCode: 500, - content: { - message: `Reporting generation failed: ${error}`, - }, + statusCode, + content: { message: `Reporting generation failed: ${error}` }, contentType: 'application/json', headers: {}, }; } function getIncomplete({ id, status }: ReportApiJSON): Payload { - logger.debug(`Report job ${id} is processing. Sending statusCode: 503`); + // For download requested over public API, status code for processing/pending job must be 503 to integrate with Watcher + const statusCode = isInternal ? STATUS_CODES.PENDING.INTERNAL : STATUS_CODES.PENDING.PUBLIC; + logger.debug(`Report job ${id} is processing. Sending statusCode: ${statusCode}`); return { - statusCode: 503, + statusCode, content: status, contentType: 'text/plain', headers: { 'retry-after': '30' }, @@ -124,7 +128,6 @@ export function getDocumentPayloadFactory(reporting: ReportingCore) { } } - // send a 503 indicating that the report isn't completed yet return getIncomplete(report); }; } diff --git a/x-pack/plugins/reporting/server/routes/common/jobs/get_job_routes.ts b/x-pack/plugins/reporting/server/routes/common/jobs/get_job_routes.ts index 64a3b0224ad71..dc01a29db780a 100644 --- a/x-pack/plugins/reporting/server/routes/common/jobs/get_job_routes.ts +++ b/x-pack/plugins/reporting/server/routes/common/jobs/get_job_routes.ts @@ -30,8 +30,11 @@ interface HandlerOpts { res: KibanaResponseFactory; } -export const commonJobsRouteHandlerFactory = (reporting: ReportingCore) => { - const jobsQuery = jobsQueryFactory(reporting); +export const commonJobsRouteHandlerFactory = ( + reporting: ReportingCore, + { isInternal }: { isInternal: boolean } +) => { + const jobsQuery = jobsQueryFactory(reporting, { isInternal }); const handleDownloadReport = ({ path, user, context, req, res }: HandlerOpts) => { const counters = getCounters(req.route.method, path, reporting.getUsageCounter()); @@ -43,36 +46,48 @@ export const commonJobsRouteHandlerFactory = (reporting: ReportingCore) => { const { docId } = req.params; - return jobManagementPreRouting(reporting, res, docId, user, counters, async (doc) => { - const payload = await jobsQuery.getDocumentPayload(doc); - const { contentType, content, filename, statusCode } = payload; - - if (!contentType || !ALLOWED_JOB_CONTENT_TYPES.includes(contentType)) { - return res.badRequest({ - body: `Unsupported content-type of ${contentType} specified by job output`, - }); - } - - const body = typeof content === 'string' ? Buffer.from(content) : content; - - const headers = { - ...payload.headers, - 'content-type': contentType, - }; - - if (filename) { - // event tracking of the downloaded file, if - // the report job was completed successfully - // and a file is available - const eventTracker = reporting.getEventTracker(docId, doc.jobtype, doc.payload.objectType); - const timeSinceCreation = Date.now() - new Date(doc.created_at).valueOf(); - eventTracker?.downloadReport({ timeSinceCreation }); - - return res.file({ body, headers, filename }); + return jobManagementPreRouting( + reporting, + res, + docId, + user, + counters, + { isInternal }, + async (doc) => { + const payload = await jobsQuery.getDocumentPayload(doc); + const { contentType, content, filename, statusCode } = payload; + + if (!contentType || !ALLOWED_JOB_CONTENT_TYPES.includes(contentType)) { + return res.badRequest({ + body: `Unsupported content-type of ${contentType} specified by job output`, + }); + } + + const body = typeof content === 'string' ? Buffer.from(content) : content; + + const headers = { + ...payload.headers, + 'content-type': contentType, + }; + + if (filename) { + // event tracking of the downloaded file, if + // the report job was completed successfully + // and a file is available + const eventTracker = reporting.getEventTracker( + docId, + doc.jobtype, + doc.payload.objectType + ); + const timeSinceCreation = Date.now() - new Date(doc.created_at).valueOf(); + eventTracker?.downloadReport({ timeSinceCreation }); + + return res.file({ body, headers, filename }); + } + + return res.custom({ body, headers, statusCode }); } - - return res.custom({ body, headers, statusCode }); - }); + ); }; const handleDeleteReport = ({ path, user, context, req, res }: HandlerOpts) => { @@ -85,52 +100,64 @@ export const commonJobsRouteHandlerFactory = (reporting: ReportingCore) => { const { docId } = req.params; - return jobManagementPreRouting(reporting, res, docId, user, counters, async (doc) => { - const docIndex = doc.index; - const stream = await getContentStream(reporting, { id: docId, index: docIndex }); - const reportingSetup = reporting.getPluginSetupDeps(); - const logger = reportingSetup.logger.get('delete-report'); - - // An "error" event is emitted if an error is - // passed to the `stream.end` callback from - // the _final method of the ContentStream. - // This event must be handled. - stream.on('error', (err) => { - logger.error(err); - }); - - try { - // Overwriting existing content with an - // empty buffer to remove all the chunks. - await new Promise((resolve, reject) => { - stream.end('', 'utf8', (error?: Error) => { - if (error) { - // handle error that could be thrown - // from the _write method of the ContentStream - reject(error); - } else { - resolve(); - } - }); + return jobManagementPreRouting( + reporting, + res, + docId, + user, + counters, + { isInternal }, + async (doc) => { + const docIndex = doc.index; + const stream = await getContentStream(reporting, { id: docId, index: docIndex }); + const reportingSetup = reporting.getPluginSetupDeps(); + const logger = reportingSetup.logger.get('delete-report'); + + // An "error" event is emitted if an error is + // passed to the `stream.end` callback from + // the _final method of the ContentStream. + // This event must be handled. + stream.on('error', (err) => { + logger.error(err); }); - await jobsQuery.delete(docIndex, docId); + try { + // Overwriting existing content with an + // empty buffer to remove all the chunks. + await new Promise((resolve, reject) => { + stream.end('', 'utf8', (error?: Error) => { + if (error) { + // handle error that could be thrown + // from the _write method of the ContentStream + reject(error); + } else { + resolve(); + } + }); + }); - // event tracking of the deleted report - const eventTracker = reporting.getEventTracker(docId, doc.jobtype, doc.payload.objectType); - const timeSinceCreation = Date.now() - new Date(doc.created_at).valueOf(); - eventTracker?.deleteReport({ timeSinceCreation }); + await jobsQuery.delete(docIndex, docId); - return res.ok({ - body: { deleted: true }, - }); - } catch (error) { - logger.error(error); - return res.customError({ - statusCode: 500, - }); + // event tracking of the deleted report + const eventTracker = reporting.getEventTracker( + docId, + doc.jobtype, + doc.payload.objectType + ); + const timeSinceCreation = Date.now() - new Date(doc.created_at).valueOf(); + eventTracker?.deleteReport({ timeSinceCreation }); + + return res.ok({ + body: { deleted: true }, + }); + } catch (error) { + logger.error(error); + return res.customError({ + statusCode: 500, + }); + } } - }); + ); }; return { diff --git a/x-pack/plugins/reporting/server/routes/common/jobs/job_management_pre_routing.test.ts b/x-pack/plugins/reporting/server/routes/common/jobs/job_management_pre_routing.test.ts index cc06ef2e0826c..2f796fd83fbe0 100644 --- a/x-pack/plugins/reporting/server/routes/common/jobs/job_management_pre_routing.test.ts +++ b/x-pack/plugins/reporting/server/routes/common/jobs/job_management_pre_routing.test.ts @@ -31,6 +31,7 @@ const mockCounters = { errorCounter: jest.fn(), }; const mockUser = { username: 'joeuser' }; +const options = { isInternal: false }; beforeEach(async () => { mockSetupDeps = createMockPluginSetup({ @@ -70,6 +71,7 @@ it(`should return 404 if the docId isn't resolve`, async function () { 'doc123', mockUser, mockCounters, + options, handler ); @@ -97,6 +99,7 @@ it(`should return forbidden if job type is unrecognized`, async function () { 'doc123', mockUser, mockCounters, + options, handler ); @@ -124,6 +127,7 @@ it(`should call callback when document is available`, async function () { 'doc123', mockUser, mockCounters, + options, handler ); @@ -154,6 +158,7 @@ describe('usage counters', () => { 'doc123', mockUser, mockCounters, + options, handler ); @@ -177,6 +182,7 @@ describe('usage counters', () => { 'doc123', mockUser, mockCounters, + options, handler ); diff --git a/x-pack/plugins/reporting/server/routes/common/jobs/job_management_pre_routing.ts b/x-pack/plugins/reporting/server/routes/common/jobs/job_management_pre_routing.ts index e9e89c61289b7..e42874468cccf 100644 --- a/x-pack/plugins/reporting/server/routes/common/jobs/job_management_pre_routing.ts +++ b/x-pack/plugins/reporting/server/routes/common/jobs/job_management_pre_routing.ts @@ -29,6 +29,7 @@ export const jobManagementPreRouting = async ( jobId: JobId, user: ReportingUser, counters: Counters, + { isInternal }: { isInternal: boolean }, cb: JobManagementResponseHandler ) => { const licenseInfo = await reporting.getLicenseInfo(); @@ -36,7 +37,7 @@ export const jobManagementPreRouting = async ( management: { jobTypes = [] }, } = licenseInfo; - const jobsQuery = jobsQueryFactory(reporting); + const jobsQuery = jobsQueryFactory(reporting, { isInternal }); const doc = await jobsQuery.get(user, jobId); if (!doc) { diff --git a/x-pack/plugins/reporting/server/routes/common/jobs/jobs_query.test.ts b/x-pack/plugins/reporting/server/routes/common/jobs/jobs_query.test.ts index 616079c0aa27e..8875c7eb874c7 100644 --- a/x-pack/plugins/reporting/server/routes/common/jobs/jobs_query.test.ts +++ b/x-pack/plugins/reporting/server/routes/common/jobs/jobs_query.test.ts @@ -23,7 +23,7 @@ describe('jobsQuery', () => { const core = await createMockReportingCore(schema); client = (await core.getEsClient()).asInternalUser as typeof client; - jobsQuery = jobsQueryFactory(core); + jobsQuery = jobsQueryFactory(core, { isInternal: false }); }); describe('list', () => { diff --git a/x-pack/plugins/reporting/server/routes/common/jobs/jobs_query.ts b/x-pack/plugins/reporting/server/routes/common/jobs/jobs_query.ts index 56b0ac4677449..3d602bf81f44f 100644 --- a/x-pack/plugins/reporting/server/routes/common/jobs/jobs_query.ts +++ b/x-pack/plugins/reporting/server/routes/common/jobs/jobs_query.ts @@ -53,7 +53,10 @@ export interface JobsQueryFactory { delete(deleteIndex: string, id: string): Promise>; } -export function jobsQueryFactory(reportingCore: ReportingCore): JobsQueryFactory { +export function jobsQueryFactory( + reportingCore: ReportingCore, + { isInternal }: { isInternal: boolean } +): JobsQueryFactory { async function execQuery< T extends (client: ElasticsearchClient) => Promise> | undefined> >(callback: T): Promise> | undefined> { @@ -202,7 +205,7 @@ export function jobsQueryFactory(reportingCore: ReportingCore): JobsQueryFactory }, async getDocumentPayload(doc: ReportApiJSON) { - const getDocumentPayload = getDocumentPayloadFactory(reportingCore); + const getDocumentPayload = getDocumentPayloadFactory(reportingCore, { isInternal }); return await getDocumentPayload(doc); }, diff --git a/x-pack/plugins/reporting/server/routes/internal/management/integration_tests/jobs.test.ts b/x-pack/plugins/reporting/server/routes/internal/management/integration_tests/jobs.test.ts index e145598eb7d76..143922d2cfedd 100644 --- a/x-pack/plugins/reporting/server/routes/internal/management/integration_tests/jobs.test.ts +++ b/x-pack/plugins/reporting/server/routes/internal/management/integration_tests/jobs.test.ts @@ -34,6 +34,7 @@ import { } from '../../../../test_helpers'; import { ReportingRequestHandlerContext } from '../../../../types'; import { EventTracker } from '../../../../usage'; +import { STATUS_CODES } from '../../../common/jobs/constants'; import { registerJobInfoRoutesInternal as registerJobInfoRoutes } from '../jobs'; type SetupServerReturn = Awaited>; @@ -254,7 +255,7 @@ describe(`Reporting Job Management Routes: Internal`, () => { .expect(403); }); - it('when a job is incomplete', async () => { + it('when a job is incomplete, "internal" API endpoint should return appropriate response', async () => { mockEsClient.search.mockResponseOnce( getHits({ jobtype: mockJobTypeUnencoded, @@ -267,13 +268,13 @@ describe(`Reporting Job Management Routes: Internal`, () => { await server.start(); await supertest(httpSetup.server.listener) .get(`${INTERNAL_ROUTES.JOBS.DOWNLOAD_PREFIX}/dank`) - .expect(503) + .expect(STATUS_CODES.PENDING.INTERNAL) .expect('Content-Type', 'text/plain; charset=utf-8') .expect('Retry-After', '30') .then(({ text }) => expect(text).toEqual('pending')); }); - it('when a job fails', async () => { + it('when a job fails, "internal" API endpoint should return appropriate response', async () => { mockEsClient.search.mockResponse( getHits({ jobtype: mockJobTypeUnencoded, @@ -287,7 +288,7 @@ describe(`Reporting Job Management Routes: Internal`, () => { await server.start(); await supertest(httpSetup.server.listener) .get(`${INTERNAL_ROUTES.JOBS.DOWNLOAD_PREFIX}/dank`) - .expect(500) + .expect(STATUS_CODES.FAILED.INTERNAL) .expect('Content-Type', 'application/json; charset=utf-8') .then(({ body }) => expect(body.message).toEqual('Reporting generation failed: job failure message') @@ -301,7 +302,7 @@ describe(`Reporting Job Management Routes: Internal`, () => { await server.start(); await supertest(httpSetup.server.listener) .get(`${INTERNAL_ROUTES.JOBS.DOWNLOAD_PREFIX}/dank`) - .expect(200) + .expect(STATUS_CODES.COMPLETED) .expect('Content-Type', 'text/csv; charset=utf-8') .expect('content-disposition', 'attachment; filename=report.csv'); }); @@ -318,7 +319,7 @@ describe(`Reporting Job Management Routes: Internal`, () => { await supertest(httpSetup.server.listener) .get(`${INTERNAL_ROUTES.JOBS.DOWNLOAD_PREFIX}/dope`) - .expect(200) + .expect(STATUS_CODES.COMPLETED) .expect('Content-Type', 'text/csv; charset=utf-8') .expect('content-disposition', 'attachment; filename=report.csv'); }); @@ -334,7 +335,7 @@ describe(`Reporting Job Management Routes: Internal`, () => { await server.start(); await supertest(httpSetup.server.listener) .get(`${INTERNAL_ROUTES.JOBS.DOWNLOAD_PREFIX}/dank`) - .expect(200) + .expect(STATUS_CODES.COMPLETED) .expect('Content-Type', 'text/csv; charset=utf-8') .then(({ text }) => expect(text).toEqual('test')); }); @@ -373,7 +374,7 @@ describe(`Reporting Job Management Routes: Internal`, () => { await server.start(); await supertest(httpSetup.server.listener) .get(`${INTERNAL_ROUTES.JOBS.DOWNLOAD_PREFIX}/japanese-dashboard`) - .expect(200) + .expect(STATUS_CODES.COMPLETED) .expect('Content-Type', 'application/pdf') .expect( 'content-disposition', @@ -446,7 +447,7 @@ describe(`Reporting Job Management Routes: Internal`, () => { await server.start(); await supertest(httpSetup.server.listener) .get(`${INTERNAL_ROUTES.JOBS.DOWNLOAD_PREFIX}/dank`) - .expect(200) + .expect(STATUS_CODES.COMPLETED) .expect('Content-Type', 'text/csv; charset=utf-8') .expect('content-disposition', 'attachment; filename=report.csv'); diff --git a/x-pack/plugins/reporting/server/routes/internal/management/jobs.ts b/x-pack/plugins/reporting/server/routes/internal/management/jobs.ts index 44a085a76dd91..21e7b92cc4b8f 100644 --- a/x-pack/plugins/reporting/server/routes/internal/management/jobs.ts +++ b/x-pack/plugins/reporting/server/routes/internal/management/jobs.ts @@ -22,7 +22,7 @@ const { JOBS } = INTERNAL_ROUTES; export function registerJobInfoRoutesInternal(reporting: ReportingCore) { const setupDeps = reporting.getPluginSetupDeps(); const { router } = setupDeps; - const jobsQuery = jobsQueryFactory(reporting); + const jobsQuery = jobsQueryFactory(reporting, { isInternal: true }); const registerInternalGetList = () => { // list jobs in the queue, paginated @@ -105,7 +105,7 @@ export function registerJobInfoRoutesInternal(reporting: ReportingCore) { }; // use common route handlers that are shared for public and internal routes - const jobHandlers = commonJobsRouteHandlerFactory(reporting); + const jobHandlers = commonJobsRouteHandlerFactory(reporting, { isInternal: true }); const registerInternalGetInfo = () => { // return some info about the job @@ -126,13 +126,20 @@ export function registerJobInfoRoutesInternal(reporting: ReportingCore) { } const { docId } = req.params; - return jobManagementPreRouting(reporting, res, docId, user, counters, async (doc) => - res.ok({ - body: doc, - headers: { - 'content-type': 'application/json', - }, - }) + return jobManagementPreRouting( + reporting, + res, + docId, + user, + counters, + { isInternal: true }, + async (doc) => + res.ok({ + body: doc, + headers: { + 'content-type': 'application/json', + }, + }) ); }) ); diff --git a/x-pack/plugins/reporting/server/routes/public/jobs.ts b/x-pack/plugins/reporting/server/routes/public/jobs.ts index 54ebf3d4e0c1c..04d417c4eb89f 100644 --- a/x-pack/plugins/reporting/server/routes/public/jobs.ts +++ b/x-pack/plugins/reporting/server/routes/public/jobs.ts @@ -16,7 +16,7 @@ export function registerJobInfoRoutesPublic(reporting: ReportingCore) { const { router } = setupDeps; // use common route handlers that are shared for public and internal routes - const jobHandlers = commonJobsRouteHandlerFactory(reporting); + const jobHandlers = commonJobsRouteHandlerFactory(reporting, { isInternal: false }); const registerPublicDownloadReport = () => { // trigger a download of the output from a job From 1ee31fc64f1a74172a5ef97c12e753ab6d5b60ee Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Thu, 8 Aug 2024 00:29:28 +0100 Subject: [PATCH 34/44] skip flaky suites (#190090) --- .../apps/discover_ml_uptime/discover/search_source_alert.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/functional_with_es_ssl/apps/discover_ml_uptime/discover/search_source_alert.ts b/x-pack/test/functional_with_es_ssl/apps/discover_ml_uptime/discover/search_source_alert.ts index 300aa0fb0a8a1..192ae3e1641d9 100644 --- a/x-pack/test/functional_with_es_ssl/apps/discover_ml_uptime/discover/search_source_alert.ts +++ b/x-pack/test/functional_with_es_ssl/apps/discover_ml_uptime/discover/search_source_alert.ts @@ -324,7 +324,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(await titleElem.getAttribute('value')).to.equal(dataView); }; - describe('Search source Alert', () => { + // FLAKY: https://github.com/elastic/kibana/issues/190090 + describ.skip('Search source Alert', () => { before(async () => { await security.testUser.setRoles(['discover_alert']); From a054b692d598cf1ee48a0d3fc5a540ced77b43a3 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Thu, 8 Aug 2024 00:30:20 +0100 Subject: [PATCH 35/44] skip flaky suites (#189943) --- .../functional/test_suites/common/discover/group1/_discover.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test_serverless/functional/test_suites/common/discover/group1/_discover.ts b/x-pack/test_serverless/functional/test_suites/common/discover/group1/_discover.ts index a5274115cfbaf..cec2a78754b1b 100644 --- a/x-pack/test_serverless/functional/test_suites/common/discover/group1/_discover.ts +++ b/x-pack/test_serverless/functional/test_suites/common/discover/group1/_discover.ts @@ -216,7 +216,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); - describe('time zone switch', () => { + // FLAKY: https://github.com/elastic/kibana/issues/189943 + describe.skip('time zone switch', () => { it('should show bars in the correct time zone after switching', async function () { await kibanaServer.uiSettings.update({ 'dateFormat:tz': 'America/Phoenix' }); await PageObjects.common.navigateToApp('discover'); From 3bc36b1d2240c51547c9f81ae44c0f43de2d4921 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Thu, 8 Aug 2024 01:01:58 +0100 Subject: [PATCH 36/44] skip flaky suites (#190058) --- .../functional/test_suites/common/discover/group1/_discover.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/test_serverless/functional/test_suites/common/discover/group1/_discover.ts b/x-pack/test_serverless/functional/test_suites/common/discover/group1/_discover.ts index cec2a78754b1b..8567b292b2407 100644 --- a/x-pack/test_serverless/functional/test_suites/common/discover/group1/_discover.ts +++ b/x-pack/test_serverless/functional/test_suites/common/discover/group1/_discover.ts @@ -217,6 +217,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); // FLAKY: https://github.com/elastic/kibana/issues/189943 + // FLAKY: https://github.com/elastic/kibana/issues/190058 describe.skip('time zone switch', () => { it('should show bars in the correct time zone after switching', async function () { await kibanaServer.uiSettings.update({ 'dateFormat:tz': 'America/Phoenix' }); From 36a4a3e5796c0b81b94d2d6e5222e73cc98adca4 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Thu, 8 Aug 2024 01:49:02 +0100 Subject: [PATCH 37/44] skip flaky suites (#190090) --- .../apps/discover_ml_uptime/discover/search_source_alert.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/test/functional_with_es_ssl/apps/discover_ml_uptime/discover/search_source_alert.ts b/x-pack/test/functional_with_es_ssl/apps/discover_ml_uptime/discover/search_source_alert.ts index 192ae3e1641d9..314bc202a6268 100644 --- a/x-pack/test/functional_with_es_ssl/apps/discover_ml_uptime/discover/search_source_alert.ts +++ b/x-pack/test/functional_with_es_ssl/apps/discover_ml_uptime/discover/search_source_alert.ts @@ -325,7 +325,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }; // FLAKY: https://github.com/elastic/kibana/issues/190090 - describ.skip('Search source Alert', () => { + describe.skip('Search source Alert', () => { before(async () => { await security.testUser.setRoles(['discover_alert']); From a18fcf3c9a65637fe717ad39d9ba7fe8992d25de Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 8 Aug 2024 15:00:19 +1000 Subject: [PATCH 38/44] [api-docs] 2024-08-08 Daily api_docs build (#190104) Generated by https://buildkite.com/elastic/kibana-api-docs-daily/builds/793 --- api_docs/actions.mdx | 2 +- api_docs/advanced_settings.mdx | 2 +- .../ai_assistant_management_selection.mdx | 2 +- api_docs/aiops.mdx | 2 +- api_docs/alerting.mdx | 2 +- api_docs/apm.devdocs.json | 52 +- api_docs/apm.mdx | 4 +- api_docs/apm_data_access.devdocs.json | 8 +- api_docs/apm_data_access.mdx | 4 +- api_docs/assets_data_access.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_quality.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/dataset_quality.mdx | 2 +- api_docs/deprecations_by_api.mdx | 3 +- api_docs/deprecations_by_plugin.mdx | 4 +- 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/discover_shared.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/entity_manager.mdx | 2 +- api_docs/es_ui_shared.mdx | 2 +- api_docs/esql.mdx | 2 +- api_docs/esql_data_grid.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.devdocs.json | 4 +- 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/fields_metadata.mdx | 2 +- api_docs/file_upload.mdx | 2 +- api_docs/files.mdx | 2 +- api_docs/files_management.mdx | 2 +- api_docs/fleet.devdocs.json | 12 +- 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/inference.mdx | 2 +- api_docs/infra.mdx | 2 +- api_docs/ingest_pipelines.mdx | 2 +- api_docs/inspector.mdx | 2 +- api_docs/integration_assistant.mdx | 2 +- api_docs/interactive_setup.mdx | 2 +- api_docs/investigate.devdocs.json | 415 +-------- api_docs/investigate.mdx | 4 +- api_docs/investigate_app.mdx | 2 +- api_docs/kbn_ace.mdx | 2 +- api_docs/kbn_actions_types.mdx | 2 +- api_docs/kbn_aiops_components.mdx | 2 +- api_docs/kbn_aiops_log_pattern_analysis.mdx | 2 +- api_docs/kbn_aiops_log_rate_analysis.mdx | 2 +- .../kbn_alerting_api_integration_helpers.mdx | 2 +- api_docs/kbn_alerting_comparators.mdx | 2 +- api_docs/kbn_alerting_state_types.mdx | 2 +- api_docs/kbn_alerting_types.mdx | 2 +- api_docs/kbn_alerts_as_data_utils.mdx | 2 +- api_docs/kbn_alerts_grouping.mdx | 2 +- api_docs/kbn_alerts_ui_shared.mdx | 2 +- api_docs/kbn_analytics.mdx | 2 +- api_docs/kbn_analytics_collection_utils.mdx | 2 +- api_docs/kbn_apm_config_loader.mdx | 2 +- api_docs/kbn_apm_data_view.mdx | 2 +- api_docs/kbn_apm_synthtrace.mdx | 2 +- api_docs/kbn_apm_synthtrace_client.mdx | 2 +- api_docs/kbn_apm_types.mdx | 2 +- api_docs/kbn_apm_utils.mdx | 2 +- api_docs/kbn_avc_banner.mdx | 2 +- api_docs/kbn_axe_config.mdx | 2 +- api_docs/kbn_bfetch_error.mdx | 2 +- api_docs/kbn_calculate_auto.mdx | 2 +- .../kbn_calculate_width_from_char_count.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_mock.mdx | 2 +- api_docs/kbn_code_owners.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 +- ...tent_management_table_list_view_common.mdx | 2 +- ...ntent_management_table_list_view_table.mdx | 2 +- .../kbn_content_management_user_profiles.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 | 4 + 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 +- .../kbn_core_plugins_contracts_browser.mdx | 2 +- .../kbn_core_plugins_contracts_server.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_security_browser.mdx | 2 +- .../kbn_core_security_browser_internal.mdx | 2 +- api_docs/kbn_core_security_browser_mocks.mdx | 2 +- api_docs/kbn_core_security_common.mdx | 2 +- api_docs/kbn_core_security_server.mdx | 2 +- .../kbn_core_security_server_internal.mdx | 2 +- api_docs/kbn_core_security_server_mocks.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_profile_browser.mdx | 2 +- ...kbn_core_user_profile_browser_internal.mdx | 2 +- .../kbn_core_user_profile_browser_mocks.mdx | 2 +- api_docs/kbn_core_user_profile_common.mdx | 2 +- api_docs/kbn_core_user_profile_server.mdx | 2 +- .../kbn_core_user_profile_server_internal.mdx | 2 +- .../kbn_core_user_profile_server_mocks.mdx | 2 +- api_docs/kbn_core_user_settings_server.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_icons.mdx | 2 +- api_docs/kbn_custom_integrations.mdx | 2 +- api_docs/kbn_cypress_config.mdx | 2 +- api_docs/kbn_data_forge.mdx | 2 +- api_docs/kbn_data_service.mdx | 2 +- api_docs/kbn_data_stream_adapter.mdx | 2 +- api_docs/kbn_data_view_utils.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_fleet.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_deeplinks_security.mdx | 2 +- api_docs/kbn_deeplinks_shared.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.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_data_quality_dashboard.mdx | 2 +- api_docs/kbn_elastic_agent_utils.mdx | 2 +- api_docs/kbn_elastic_assistant.mdx | 2 +- api_docs/kbn_elastic_assistant_common.mdx | 2 +- api_docs/kbn_entities_schema.mdx | 2 +- api_docs/kbn_es.devdocs.json | 12 + api_docs/kbn_es.mdx | 4 +- 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_esql_ast.mdx | 2 +- api_docs/kbn_esql_utils.mdx | 2 +- api_docs/kbn_esql_validation_autocomplete.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 +- api_docs/kbn_formatters.mdx | 2 +- ...tr_common_functional_services.devdocs.json | 830 +++++++++++++++++- .../kbn_ftr_common_functional_services.mdx | 7 +- .../kbn_ftr_common_functional_ui_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_grouping.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_index_management.mdx | 2 +- api_docs/kbn_inference_integration_flyout.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_ipynb.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_json_schemas.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_lens_formula_docs.mdx | 2 +- api_docs/kbn_logging.mdx | 2 +- api_docs/kbn_logging_mocks.mdx | 2 +- api_docs/kbn_managed_content_badge.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 +- api_docs/kbn_management_settings_ids.mdx | 2 +- ...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_cancellable_search.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_time_buckets.mdx | 2 +- .../kbn_ml_trained_models_utils.devdocs.json | 17 +- api_docs/kbn_ml_trained_models_utils.mdx | 4 +- api_docs/kbn_ml_ui_actions.mdx | 2 +- api_docs/kbn_ml_url_state.mdx | 2 +- api_docs/kbn_mock_idp_utils.mdx | 2 +- api_docs/kbn_monaco.mdx | 2 +- api_docs/kbn_object_versioning.mdx | 2 +- api_docs/kbn_observability_alert_details.mdx | 2 +- .../kbn_observability_alerting_rule_utils.mdx | 2 +- .../kbn_observability_alerting_test_data.mdx | 2 +- ...ility_get_padded_alert_time_range_util.mdx | 2 +- api_docs/kbn_openapi_bundler.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 +- api_docs/kbn_panel_loader.mdx | 2 +- ..._performance_testing_dataset_extractor.mdx | 2 +- api_docs/kbn_plugin_check.mdx | 2 +- api_docs/kbn_plugin_generator.mdx | 2 +- api_docs/kbn_plugin_helpers.mdx | 2 +- api_docs/kbn_presentation_containers.mdx | 2 +- api_docs/kbn_presentation_publishing.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_hooks.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_recently_accessed.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_reporting_csv_share_panel.mdx | 2 +- api_docs/kbn_reporting_export_types_csv.mdx | 2 +- .../kbn_reporting_export_types_csv_common.mdx | 2 +- api_docs/kbn_reporting_export_types_pdf.mdx | 2 +- .../kbn_reporting_export_types_pdf_common.mdx | 2 +- api_docs/kbn_reporting_export_types_png.mdx | 2 +- .../kbn_reporting_export_types_png_common.mdx | 2 +- api_docs/kbn_reporting_mocks_server.mdx | 2 +- api_docs/kbn_reporting_public.mdx | 2 +- api_docs/kbn_reporting_server.mdx | 2 +- api_docs/kbn_resizable_layout.mdx | 2 +- .../kbn_response_ops_feature_flag_service.mdx | 2 +- api_docs/kbn_rison.mdx | 2 +- api_docs/kbn_rollup.mdx | 2 +- api_docs/kbn_router_to_openapispec.mdx | 2 +- api_docs/kbn_router_utils.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.mdx | 2 +- api_docs/kbn_search_errors.mdx | 2 +- api_docs/kbn_search_index_documents.mdx | 2 +- api_docs/kbn_search_response_warnings.mdx | 2 +- api_docs/kbn_search_types.mdx | 2 +- api_docs/kbn_security_api_key_management.mdx | 2 +- api_docs/kbn_security_form_components.mdx | 2 +- api_docs/kbn_security_hardening.mdx | 2 +- api_docs/kbn_security_plugin_types_common.mdx | 2 +- api_docs/kbn_security_plugin_types_public.mdx | 2 +- api_docs/kbn_security_plugin_types_server.mdx | 2 +- ...kbn_security_solution_distribution_bar.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 +- ...ion_exception_list_components.devdocs.json | 10 +- ...ritysolution_exception_list_components.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 +- .../kbn_securitysolution_utils.devdocs.json | 47 + api_docs/kbn_securitysolution_utils.mdx | 4 +- api_docs/kbn_server_http_tools.mdx | 2 +- api_docs/kbn_server_route_repository.mdx | 2 +- .../kbn_server_route_repository_utils.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 +- .../kbn_shared_ux_button_exit_full_screen.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_error_boundary.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 +- api_docs/kbn_shared_ux_page_no_data.mdx | 2 +- .../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_tabbed_modal.mdx | 2 +- api_docs/kbn_shared_ux_utility.mdx | 2 +- api_docs/kbn_slo_schema.mdx | 2 +- api_docs/kbn_some_dev_log.mdx | 2 +- api_docs/kbn_sort_predicates.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_synthetics_e2e.mdx | 2 +- api_docs/kbn_telemetry_tools.mdx | 2 +- api_docs/kbn_test.devdocs.json | 31 +- api_docs/kbn_test.mdx | 4 +- api_docs/kbn_test_eui_helpers.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_timerange.mdx | 2 +- api_docs/kbn_tooling_log.mdx | 2 +- api_docs/kbn_triggers_actions_ui_types.mdx | 2 +- api_docs/kbn_try_in_console.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.devdocs.json | 483 +++++++--- api_docs/kbn_unified_data_table.mdx | 4 +- api_docs/kbn_unified_doc_viewer.mdx | 2 +- api_docs/kbn_unified_field_list.devdocs.json | 2 +- api_docs/kbn_unified_field_list.mdx | 2 +- api_docs/kbn_unsaved_changes_badge.mdx | 2 +- api_docs/kbn_unsaved_changes_prompt.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_visualization_utils.mdx | 2 +- api_docs/kbn_xstate_utils.mdx | 2 +- api_docs/kbn_yarn_lock_validator.mdx | 2 +- api_docs/kbn_zod.mdx | 2 +- api_docs/kbn_zod_helpers.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/logs_data_access.devdocs.json | 56 +- api_docs/logs_data_access.mdx | 12 +- api_docs/logs_explorer.mdx | 2 +- api_docs/logs_shared.devdocs.json | 145 ++- api_docs/logs_shared.mdx | 4 +- api_docs/management.mdx | 2 +- api_docs/maps.mdx | 2 +- api_docs/maps_ems.mdx | 2 +- api_docs/metrics_data_access.devdocs.json | 28 +- api_docs/metrics_data_access.mdx | 4 +- api_docs/ml.mdx | 2 +- api_docs/mock_idp_plugin.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.mdx | 2 +- api_docs/observability_a_i_assistant.mdx | 2 +- api_docs/observability_a_i_assistant_app.mdx | 2 +- .../observability_ai_assistant_management.mdx | 2 +- api_docs/observability_logs_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 | 34 +- api_docs/presentation_panel.mdx | 2 +- api_docs/presentation_util.mdx | 2 +- 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/search_connectors.mdx | 2 +- api_docs/search_homepage.mdx | 2 +- api_docs/search_inference_endpoints.mdx | 2 +- api_docs/search_notebooks.mdx | 2 +- api_docs/search_playground.mdx | 2 +- api_docs/security.mdx | 2 +- api_docs/security_solution.devdocs.json | 46 +- api_docs/security_solution.mdx | 4 +- 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/slo.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.devdocs.json | 38 +- api_docs/task_manager.mdx | 4 +- 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/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 | 8 +- api_docs/visualizations.mdx | 2 +- 731 files changed, 2338 insertions(+), 1408 deletions(-) diff --git a/api_docs/actions.mdx b/api_docs/actions.mdx index 672f194623726..7449636928e52 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: 2024-08-07 +date: 2024-08-08 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 94a2480f721ad..09c2a29dd9a19 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'advancedSettings'] --- import advancedSettingsObj from './advanced_settings.devdocs.json'; diff --git a/api_docs/ai_assistant_management_selection.mdx b/api_docs/ai_assistant_management_selection.mdx index fdb361cedf895..6386ac8b99b4c 100644 --- a/api_docs/ai_assistant_management_selection.mdx +++ b/api_docs/ai_assistant_management_selection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/aiAssistantManagementSelection title: "aiAssistantManagementSelection" image: https://source.unsplash.com/400x175/?github description: API docs for the aiAssistantManagementSelection plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'aiAssistantManagementSelection'] --- import aiAssistantManagementSelectionObj from './ai_assistant_management_selection.devdocs.json'; diff --git a/api_docs/aiops.mdx b/api_docs/aiops.mdx index adee779df8bc3..e181c7e51e620 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: 2024-08-07 +date: 2024-08-08 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 543e36019ac83..1d652096cc61a 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'alerting'] --- import alertingObj from './alerting.devdocs.json'; diff --git a/api_docs/apm.devdocs.json b/api_docs/apm.devdocs.json index a3858978c2b2b..6707c3d01cb69 100644 --- a/api_docs/apm.devdocs.json +++ b/api_docs/apm.devdocs.json @@ -418,7 +418,7 @@ "label": "APIEndpoint", "description": [], "signature": [ - "\"POST /internal/apm/data_view/static\" | \"GET /internal/apm/data_view/index_pattern\" | \"GET /internal/apm/environments\" | \"GET /internal/apm/services/{serviceName}/errors/groups/main_statistics\" | \"GET /internal/apm/services/{serviceName}/errors/groups/main_statistics_by_transaction_name\" | \"POST /internal/apm/services/{serviceName}/errors/groups/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/errors/{groupId}/samples\" | \"GET /internal/apm/services/{serviceName}/errors/{groupId}/error/{errorId}\" | \"GET /internal/apm/services/{serviceName}/errors/distribution\" | \"GET /internal/apm/services/{serviceName}/errors/{groupId}/top_erroneous_transactions\" | \"POST /internal/apm/latency/overall_distribution/transactions\" | \"GET /internal/apm/services/{serviceName}/metrics/charts\" | \"GET /internal/apm/services/{serviceName}/metrics/nodes\" | \"GET /internal/apm/services/{serviceName}/metrics/serverless/charts\" | \"GET /internal/apm/services/{serviceName}/metrics/serverless/summary\" | \"GET /internal/apm/services/{serviceName}/metrics/serverless/functions_overview\" | \"GET /internal/apm/services/{serviceName}/metrics/serverless/active_instances\" | \"GET /internal/apm/observability_overview\" | \"GET /internal/apm/observability_overview/has_data\" | \"GET /internal/apm/service-map\" | \"GET /internal/apm/service-map/service/{serviceName}\" | \"GET /internal/apm/service-map/dependency\" | \"GET /internal/apm/services\" | \"POST /internal/apm/services/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/metadata/details\" | \"GET /internal/apm/services/{serviceName}/metadata/icons\" | \"GET /internal/apm/services/{serviceName}/agent\" | \"GET /internal/apm/services/{serviceName}/transaction_types\" | \"GET /internal/apm/services/{serviceName}/node/{serviceNodeName}/metadata\" | \"GET /api/apm/services/{serviceName}/annotation/search 2023-10-31\" | \"POST /api/apm/services/{serviceName}/annotation 2023-10-31\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/details/{serviceNodeName}\" | \"GET /internal/apm/services/{serviceName}/throughput\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/main_statistics\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/dependencies\" | \"GET /internal/apm/services/{serviceName}/dependencies/breakdown\" | \"GET /internal/apm/services/{serviceName}/anomaly_charts\" | \"GET /internal/apm/services/{serviceName}/alerts_count\" | \"GET /internal/apm/entities/services\" | \"GET /internal/apm/entities/services/{serviceName}/logs_rate_timeseries\" | \"GET /internal/apm/entities/services/{serviceName}/logs_error_rate_timeseries\" | \"POST /internal/apm/entities/services/detailed_statistics\" | \"GET /internal/apm/service-groups\" | \"GET /internal/apm/service-group\" | \"POST /internal/apm/service-group\" | \"DELETE /internal/apm/service-group\" | \"GET /internal/apm/service-group/services\" | \"GET /internal/apm/service-group/counts\" | \"GET /internal/apm/suggestions\" | \"GET /internal/apm/traces/{traceId}\" | \"GET /internal/apm/traces\" | \"GET /internal/apm/traces/{traceId}/root_transaction\" | \"GET /internal/apm/transactions/{transactionId}\" | \"GET /internal/apm/traces/find\" | \"POST /internal/apm/traces/aggregated_critical_path\" | \"GET /internal/apm/traces/{traceId}/transactions/{transactionId}\" | \"GET /internal/apm/traces/{traceId}/spans/{spanId}\" | \"GET /internal/apm/transactions\" | \"GET /internal/apm/services/{serviceName}/transactions/groups/main_statistics\" | \"GET /internal/apm/services/{serviceName}/transactions/groups/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/latency\" | \"GET /internal/apm/services/{serviceName}/transactions/traces/samples\" | \"GET /internal/apm/services/{serviceName}/transaction/charts/breakdown\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/error_rate\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/coldstart_rate\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/coldstart_rate_by_transaction_name\" | \"GET /internal/apm/rule_types/transaction_error_rate/chart_preview\" | \"GET /internal/apm/rule_types/error_count/chart_preview\" | \"GET /internal/apm/rule_types/transaction_duration/chart_preview\" | \"GET /api/apm/settings/agent-configuration 2023-10-31\" | \"GET /api/apm/settings/agent-configuration/view 2023-10-31\" | \"DELETE /api/apm/settings/agent-configuration 2023-10-31\" | \"PUT /api/apm/settings/agent-configuration 2023-10-31\" | \"POST /api/apm/settings/agent-configuration/search 2023-10-31\" | \"GET /api/apm/settings/agent-configuration/environments 2023-10-31\" | \"GET /api/apm/settings/agent-configuration/agent_name 2023-10-31\" | \"GET /internal/apm/settings/anomaly-detection/jobs\" | \"POST /internal/apm/settings/anomaly-detection/jobs\" | \"GET /internal/apm/settings/anomaly-detection/environments\" | \"POST /internal/apm/settings/anomaly-detection/update_to_v3\" | \"GET /internal/apm/settings/apm-index-settings\" | \"GET /internal/apm/settings/apm-indices\" | \"POST /internal/apm/settings/apm-indices/save\" | \"GET /internal/apm/settings/custom_links/transaction\" | \"GET /internal/apm/settings/custom_links\" | \"POST /internal/apm/settings/custom_links\" | \"PUT /internal/apm/settings/custom_links/{id}\" | \"DELETE /internal/apm/settings/custom_links/{id}\" | \"GET /api/apm/sourcemaps 2023-10-31\" | \"POST /api/apm/sourcemaps 2023-10-31\" | \"DELETE /api/apm/sourcemaps/{id} 2023-10-31\" | \"POST /internal/apm/sourcemaps/migrate_fleet_artifacts\" | \"GET /internal/apm/fleet/has_apm_policies\" | \"GET /internal/apm/fleet/agents\" | \"POST /api/apm/fleet/apm_server_schema 2023-10-31\" | \"GET /internal/apm/fleet/apm_server_schema/unsupported\" | \"GET /internal/apm/fleet/migration_check\" | \"POST /internal/apm/fleet/cloud_apm_package_policy\" | \"GET /internal/apm/fleet/java_agent_versions\" | \"GET /internal/apm/dependencies/top_dependencies\" | \"GET /internal/apm/dependencies/upstream_services\" | \"GET /internal/apm/dependencies/metadata\" | \"GET /internal/apm/dependencies/charts/latency\" | \"GET /internal/apm/dependencies/charts/throughput\" | \"GET /internal/apm/dependencies/charts/error_rate\" | \"GET /internal/apm/dependencies/operations\" | \"GET /internal/apm/dependencies/charts/distribution\" | \"GET /internal/apm/dependencies/operations/spans\" | \"GET /internal/apm/correlations/field_candidates/transactions\" | \"GET /internal/apm/correlations/field_value_stats/transactions\" | \"POST /internal/apm/correlations/field_value_pairs/transactions\" | \"POST /internal/apm/correlations/significant_correlations/transactions\" | \"POST /internal/apm/correlations/p_values/transactions\" | \"GET /internal/apm/fallback_to_transactions\" | \"GET /internal/apm/has_data\" | \"GET /internal/apm/has_entities\" | \"GET /internal/apm/event_metadata/{processorEvent}/{id}\" | \"GET /internal/apm/agent_keys\" | \"GET /internal/apm/agent_keys/privileges\" | \"POST /internal/apm/api_key/invalidate\" | \"POST /api/apm/agent_keys 2023-10-31\" | \"GET /internal/apm/storage_explorer\" | \"GET /internal/apm/services/{serviceName}/storage_details\" | \"GET /internal/apm/storage_chart\" | \"GET /internal/apm/storage_explorer/privileges\" | \"GET /internal/apm/storage_explorer_summary_stats\" | \"GET /internal/apm/storage_explorer/is_cross_cluster_search\" | \"GET /internal/apm/storage_explorer/get_services\" | \"GET /internal/apm/traces/{traceId}/span_links/{spanId}/parents\" | \"GET /internal/apm/traces/{traceId}/span_links/{spanId}/children\" | \"GET /internal/apm/services/{serviceName}/infrastructure_attributes\" | \"GET /internal/apm/debug-telemetry\" | \"GET /internal/apm/time_range_metadata\" | \"GET /internal/apm/settings/labs\" | \"GET /internal/apm/get_agents_per_service\" | \"GET /internal/apm/get_latest_agent_versions\" | \"GET /internal/apm/services/{serviceName}/agent_instances\" | \"GET /internal/apm/mobile-services/{serviceName}/error/http_error_rate\" | \"GET /internal/apm/mobile-services/{serviceName}/errors/groups/main_statistics\" | \"POST /internal/apm/mobile-services/{serviceName}/errors/groups/detailed_statistics\" | \"GET /internal/apm/mobile-services/{serviceName}/error_terms\" | \"POST /internal/apm/mobile-services/{serviceName}/crashes/groups/detailed_statistics\" | \"GET /internal/apm/mobile-services/{serviceName}/crashes/groups/main_statistics\" | \"GET /internal/apm/mobile-services/{serviceName}/crashes/distribution\" | \"GET /internal/apm/services/{serviceName}/mobile/filters\" | \"GET /internal/apm/mobile-services/{serviceName}/most_used_charts\" | \"GET /internal/apm/mobile-services/{serviceName}/transactions/charts/sessions\" | \"GET /internal/apm/mobile-services/{serviceName}/transactions/charts/http_requests\" | \"GET /internal/apm/mobile-services/{serviceName}/stats\" | \"GET /internal/apm/mobile-services/{serviceName}/location/stats\" | \"GET /internal/apm/mobile-services/{serviceName}/terms\" | \"GET /internal/apm/mobile-services/{serviceName}/main_statistics\" | \"GET /internal/apm/mobile-services/{serviceName}/detailed_statistics\" | \"GET /internal/apm/diagnostics\" | \"POST /internal/apm/assistant/get_apm_timeseries\" | \"GET /internal/apm/assistant/get_downstream_dependencies\" | \"GET /internal/apm/services/{serviceName}/profiling/flamegraph\" | \"GET /internal/apm/profiling/status\" | \"GET /internal/apm/services/{serviceName}/profiling/functions\" | \"GET /internal/apm/services/{serviceName}/profiling/hosts/flamegraph\" | \"GET /internal/apm/services/{serviceName}/profiling/hosts/functions\" | \"POST /internal/apm/custom-dashboard\" | \"DELETE /internal/apm/custom-dashboard\" | \"GET /internal/apm/services/{serviceName}/dashboards\"" + "\"POST /internal/apm/data_view/static\" | \"GET /internal/apm/data_view/index_pattern\" | \"GET /internal/apm/environments\" | \"GET /internal/apm/services/{serviceName}/errors/groups/main_statistics\" | \"GET /internal/apm/services/{serviceName}/errors/groups/main_statistics_by_transaction_name\" | \"POST /internal/apm/services/{serviceName}/errors/groups/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/errors/{groupId}/samples\" | \"GET /internal/apm/services/{serviceName}/errors/{groupId}/error/{errorId}\" | \"GET /internal/apm/services/{serviceName}/errors/distribution\" | \"GET /internal/apm/services/{serviceName}/errors/{groupId}/top_erroneous_transactions\" | \"POST /internal/apm/latency/overall_distribution/transactions\" | \"GET /internal/apm/services/{serviceName}/metrics/charts\" | \"GET /internal/apm/services/{serviceName}/metrics/nodes\" | \"GET /internal/apm/services/{serviceName}/metrics/serverless/charts\" | \"GET /internal/apm/services/{serviceName}/metrics/serverless/summary\" | \"GET /internal/apm/services/{serviceName}/metrics/serverless/functions_overview\" | \"GET /internal/apm/services/{serviceName}/metrics/serverless/active_instances\" | \"GET /internal/apm/observability_overview\" | \"GET /internal/apm/observability_overview/has_data\" | \"GET /internal/apm/service-map\" | \"GET /internal/apm/service-map/service/{serviceName}\" | \"GET /internal/apm/service-map/dependency\" | \"GET /internal/apm/services\" | \"POST /internal/apm/services/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/metadata/details\" | \"GET /internal/apm/services/{serviceName}/metadata/icons\" | \"GET /internal/apm/services/{serviceName}/agent\" | \"GET /internal/apm/services/{serviceName}/transaction_types\" | \"GET /internal/apm/services/{serviceName}/node/{serviceNodeName}/metadata\" | \"GET /api/apm/services/{serviceName}/annotation/search 2023-10-31\" | \"POST /api/apm/services/{serviceName}/annotation 2023-10-31\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/details/{serviceNodeName}\" | \"GET /internal/apm/services/{serviceName}/throughput\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/main_statistics\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/dependencies\" | \"GET /internal/apm/services/{serviceName}/dependencies/breakdown\" | \"GET /internal/apm/services/{serviceName}/anomaly_charts\" | \"GET /internal/apm/services/{serviceName}/alerts_count\" | \"GET /internal/apm/entities/services\" | \"GET /internal/apm/entities/services/{serviceName}/logs_rate_timeseries\" | \"GET /internal/apm/entities/services/{serviceName}/logs_error_rate_timeseries\" | \"POST /internal/apm/entities/services/detailed_statistics\" | \"GET /internal/apm/entities/services/{serviceName}/summary\" | \"GET /internal/apm/service-groups\" | \"GET /internal/apm/service-group\" | \"POST /internal/apm/service-group\" | \"DELETE /internal/apm/service-group\" | \"GET /internal/apm/service-group/services\" | \"GET /internal/apm/service-group/counts\" | \"GET /internal/apm/suggestions\" | \"GET /internal/apm/traces/{traceId}\" | \"GET /internal/apm/traces\" | \"GET /internal/apm/traces/{traceId}/root_transaction\" | \"GET /internal/apm/transactions/{transactionId}\" | \"GET /internal/apm/traces/find\" | \"POST /internal/apm/traces/aggregated_critical_path\" | \"GET /internal/apm/traces/{traceId}/transactions/{transactionId}\" | \"GET /internal/apm/traces/{traceId}/spans/{spanId}\" | \"GET /internal/apm/transactions\" | \"GET /internal/apm/services/{serviceName}/transactions/groups/main_statistics\" | \"GET /internal/apm/services/{serviceName}/transactions/groups/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/latency\" | \"GET /internal/apm/services/{serviceName}/transactions/traces/samples\" | \"GET /internal/apm/services/{serviceName}/transaction/charts/breakdown\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/error_rate\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/coldstart_rate\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/coldstart_rate_by_transaction_name\" | \"GET /internal/apm/rule_types/transaction_error_rate/chart_preview\" | \"GET /internal/apm/rule_types/error_count/chart_preview\" | \"GET /internal/apm/rule_types/transaction_duration/chart_preview\" | \"GET /api/apm/settings/agent-configuration 2023-10-31\" | \"GET /api/apm/settings/agent-configuration/view 2023-10-31\" | \"DELETE /api/apm/settings/agent-configuration 2023-10-31\" | \"PUT /api/apm/settings/agent-configuration 2023-10-31\" | \"POST /api/apm/settings/agent-configuration/search 2023-10-31\" | \"GET /api/apm/settings/agent-configuration/environments 2023-10-31\" | \"GET /api/apm/settings/agent-configuration/agent_name 2023-10-31\" | \"GET /internal/apm/settings/anomaly-detection/jobs\" | \"POST /internal/apm/settings/anomaly-detection/jobs\" | \"GET /internal/apm/settings/anomaly-detection/environments\" | \"POST /internal/apm/settings/anomaly-detection/update_to_v3\" | \"GET /internal/apm/settings/apm-index-settings\" | \"GET /internal/apm/settings/apm-indices\" | \"POST /internal/apm/settings/apm-indices/save\" | \"GET /internal/apm/settings/custom_links/transaction\" | \"GET /internal/apm/settings/custom_links\" | \"POST /internal/apm/settings/custom_links\" | \"PUT /internal/apm/settings/custom_links/{id}\" | \"DELETE /internal/apm/settings/custom_links/{id}\" | \"GET /api/apm/sourcemaps 2023-10-31\" | \"POST /api/apm/sourcemaps 2023-10-31\" | \"DELETE /api/apm/sourcemaps/{id} 2023-10-31\" | \"POST /internal/apm/sourcemaps/migrate_fleet_artifacts\" | \"GET /internal/apm/fleet/has_apm_policies\" | \"GET /internal/apm/fleet/agents\" | \"POST /api/apm/fleet/apm_server_schema 2023-10-31\" | \"GET /internal/apm/fleet/apm_server_schema/unsupported\" | \"GET /internal/apm/fleet/migration_check\" | \"POST /internal/apm/fleet/cloud_apm_package_policy\" | \"GET /internal/apm/fleet/java_agent_versions\" | \"GET /internal/apm/dependencies/top_dependencies\" | \"GET /internal/apm/dependencies/upstream_services\" | \"GET /internal/apm/dependencies/metadata\" | \"GET /internal/apm/dependencies/charts/latency\" | \"GET /internal/apm/dependencies/charts/throughput\" | \"GET /internal/apm/dependencies/charts/error_rate\" | \"GET /internal/apm/dependencies/operations\" | \"GET /internal/apm/dependencies/charts/distribution\" | \"GET /internal/apm/dependencies/operations/spans\" | \"GET /internal/apm/correlations/field_candidates/transactions\" | \"GET /internal/apm/correlations/field_value_stats/transactions\" | \"POST /internal/apm/correlations/field_value_pairs/transactions\" | \"POST /internal/apm/correlations/significant_correlations/transactions\" | \"POST /internal/apm/correlations/p_values/transactions\" | \"GET /internal/apm/fallback_to_transactions\" | \"GET /internal/apm/has_data\" | \"GET /internal/apm/has_entities\" | \"GET /internal/apm/event_metadata/{processorEvent}/{id}\" | \"GET /internal/apm/agent_keys\" | \"GET /internal/apm/agent_keys/privileges\" | \"POST /internal/apm/api_key/invalidate\" | \"POST /api/apm/agent_keys 2023-10-31\" | \"GET /internal/apm/storage_explorer\" | \"GET /internal/apm/services/{serviceName}/storage_details\" | \"GET /internal/apm/storage_chart\" | \"GET /internal/apm/storage_explorer/privileges\" | \"GET /internal/apm/storage_explorer_summary_stats\" | \"GET /internal/apm/storage_explorer/is_cross_cluster_search\" | \"GET /internal/apm/storage_explorer/get_services\" | \"GET /internal/apm/traces/{traceId}/span_links/{spanId}/parents\" | \"GET /internal/apm/traces/{traceId}/span_links/{spanId}/children\" | \"GET /internal/apm/services/{serviceName}/infrastructure_attributes\" | \"GET /internal/apm/debug-telemetry\" | \"GET /internal/apm/time_range_metadata\" | \"GET /internal/apm/settings/labs\" | \"GET /internal/apm/get_agents_per_service\" | \"GET /internal/apm/get_latest_agent_versions\" | \"GET /internal/apm/services/{serviceName}/agent_instances\" | \"GET /internal/apm/mobile-services/{serviceName}/error/http_error_rate\" | \"GET /internal/apm/mobile-services/{serviceName}/errors/groups/main_statistics\" | \"POST /internal/apm/mobile-services/{serviceName}/errors/groups/detailed_statistics\" | \"GET /internal/apm/mobile-services/{serviceName}/error_terms\" | \"POST /internal/apm/mobile-services/{serviceName}/crashes/groups/detailed_statistics\" | \"GET /internal/apm/mobile-services/{serviceName}/crashes/groups/main_statistics\" | \"GET /internal/apm/mobile-services/{serviceName}/crashes/distribution\" | \"GET /internal/apm/services/{serviceName}/mobile/filters\" | \"GET /internal/apm/mobile-services/{serviceName}/most_used_charts\" | \"GET /internal/apm/mobile-services/{serviceName}/transactions/charts/sessions\" | \"GET /internal/apm/mobile-services/{serviceName}/transactions/charts/http_requests\" | \"GET /internal/apm/mobile-services/{serviceName}/stats\" | \"GET /internal/apm/mobile-services/{serviceName}/location/stats\" | \"GET /internal/apm/mobile-services/{serviceName}/terms\" | \"GET /internal/apm/mobile-services/{serviceName}/main_statistics\" | \"GET /internal/apm/mobile-services/{serviceName}/detailed_statistics\" | \"GET /internal/apm/diagnostics\" | \"POST /internal/apm/assistant/get_apm_timeseries\" | \"GET /internal/apm/assistant/get_downstream_dependencies\" | \"GET /internal/apm/services/{serviceName}/profiling/flamegraph\" | \"GET /internal/apm/profiling/status\" | \"GET /internal/apm/services/{serviceName}/profiling/functions\" | \"GET /internal/apm/services/{serviceName}/profiling/hosts/flamegraph\" | \"GET /internal/apm/services/{serviceName}/profiling/hosts/functions\" | \"POST /internal/apm/custom-dashboard\" | \"DELETE /internal/apm/custom-dashboard\" | \"GET /internal/apm/services/{serviceName}/dashboards\"" ], "path": "x-pack/plugins/observability_solution/apm/server/routes/apm_routes/get_global_apm_server_route_repository.ts", "deprecated": false, @@ -6101,6 +6101,56 @@ "SavedServiceGroup", "[]; }>; } & ", "APMRouteCreateOptions", + "; \"GET /internal/apm/entities/services/{serviceName}/summary\": { endpoint: \"GET /internal/apm/entities/services/{serviceName}/summary\"; params?: ", + "TypeC", + "<{ path: ", + "TypeC", + "<{ serviceName: ", + "StringC", + "; }>; query: ", + "IntersectionC", + "<[", + "TypeC", + "<{ environment: ", + "UnionC", + "<[", + "LiteralC", + "<\"ENVIRONMENT_NOT_DEFINED\">, ", + "LiteralC", + "<\"ENVIRONMENT_ALL\">, ", + "BrandC", + "<", + "StringC", + ", ", + { + "pluginId": "@kbn/io-ts-utils", + "scope": "common", + "docId": "kibKbnIoTsUtilsPluginApi", + "section": "def-common.NonEmptyStringBrand", + "text": "NonEmptyStringBrand" + }, + ">]>; }>, ", + "TypeC", + "<{ start: ", + "Type", + "; end: ", + "Type", + "; }>]>; }> | undefined; handler: ({}: ", + "APMRouteHandlerResources", + " & { params: { path: { serviceName: string; }; query: { environment: \"ENVIRONMENT_NOT_DEFINED\" | \"ENVIRONMENT_ALL\" | ", + "Branded", + "; } & { start: number; end: number; }; }; }) => Promise<", + "ServiceEntities", + ">; } & ", + "APMRouteCreateOptions", "; \"POST /internal/apm/entities/services/detailed_statistics\": { endpoint: \"POST /internal/apm/entities/services/detailed_statistics\"; params?: ", "TypeC", "<{ query: ", diff --git a/api_docs/apm.mdx b/api_docs/apm.mdx index d03f322973fb0..3e55be0bd3e79 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apm'] --- import apmObj from './apm.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/te | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 29 | 0 | 29 | 118 | +| 29 | 0 | 29 | 119 | ## Client diff --git a/api_docs/apm_data_access.devdocs.json b/api_docs/apm_data_access.devdocs.json index ee59228e5d335..7334b593ed85f 100644 --- a/api_docs/apm_data_access.devdocs.json +++ b/api_docs/apm_data_access.devdocs.json @@ -1922,7 +1922,9 @@ "section": "def-common.ApmDataSource", "text": "ApmDataSource" }, - " & { hasDocs: boolean; hasDurationSummaryField: boolean; })[]>; }" + " & { hasDocs: boolean; hasDurationSummaryField: boolean; })[]>; getHostNames: ({ start, end, size, query, documentSources }: ", + "HostNamesRequest", + ") => Promise; }" ], "path": "x-pack/plugins/observability_solution/apm_data_access/server/types.ts", "deprecated": false, @@ -2099,7 +2101,9 @@ "section": "def-common.ApmDataSource", "text": "ApmDataSource" }, - " & { hasDocs: boolean; hasDurationSummaryField: boolean; })[]>; }" + " & { hasDocs: boolean; hasDurationSummaryField: boolean; })[]>; getHostNames: ({ start, end, size, query, documentSources }: ", + "HostNamesRequest", + ") => Promise; }" ], "path": "x-pack/plugins/observability_solution/apm_data_access/server/types.ts", "deprecated": false, diff --git a/api_docs/apm_data_access.mdx b/api_docs/apm_data_access.mdx index 1988dbbb5a11d..6294ba93848f6 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apmDataAccess'] --- import apmDataAccessObj from './apm_data_access.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs- | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 74 | 0 | 74 | 0 | +| 74 | 0 | 74 | 1 | ## Server diff --git a/api_docs/assets_data_access.mdx b/api_docs/assets_data_access.mdx index 579f1e520a3d2..a189576951681 100644 --- a/api_docs/assets_data_access.mdx +++ b/api_docs/assets_data_access.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/assetsDataAccess title: "assetsDataAccess" image: https://source.unsplash.com/400x175/?github description: API docs for the assetsDataAccess plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'assetsDataAccess'] --- import assetsDataAccessObj from './assets_data_access.devdocs.json'; diff --git a/api_docs/banners.mdx b/api_docs/banners.mdx index af942f5ae0f8b..84d6b366c7add 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: 2024-08-07 +date: 2024-08-08 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 b57e622daa75d..bae85adad9c14 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: 2024-08-07 +date: 2024-08-08 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 6ffc2639f406f..754fce85f051d 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: 2024-08-07 +date: 2024-08-08 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 11f233951704a..f0a8ab3641636 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: 2024-08-07 +date: 2024-08-08 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 c43018cedf416..be0f650f460b4 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: 2024-08-07 +date: 2024-08-08 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 cb3b2ba93fbb1..156f3995e0e4a 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: 2024-08-07 +date: 2024-08-08 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 9888caada05e3..3f272f9846742 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: 2024-08-07 +date: 2024-08-08 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 c9e1188a88330..0ca6934f9e88b 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: 2024-08-07 +date: 2024-08-08 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 02684a95e7500..3d9336662ca7c 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: 2024-08-07 +date: 2024-08-08 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 c9e007247ac38..522a024bc7f0d 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: 2024-08-07 +date: 2024-08-08 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 78035c25276e2..c42c1685de826 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: 2024-08-07 +date: 2024-08-08 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 622d796fe1cc0..950a3056de8b7 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: 2024-08-07 +date: 2024-08-08 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 529ba4ce8bf37..e6e3f510a98fe 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: 2024-08-07 +date: 2024-08-08 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 1fcf95bd955e7..fa7d1b01841ab 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: 2024-08-07 +date: 2024-08-08 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 df43524999607..980cd85a0da79 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: 2024-08-07 +date: 2024-08-08 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 764daea305671..49129c208c426 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: 2024-08-07 +date: 2024-08-08 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 9da50818e6725..aa86036114e90 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data'] --- import dataObj from './data.devdocs.json'; diff --git a/api_docs/data_quality.mdx b/api_docs/data_quality.mdx index 6765367289e86..0d8a6810f92ad 100644 --- a/api_docs/data_quality.mdx +++ b/api_docs/data_quality.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataQuality title: "dataQuality" image: https://source.unsplash.com/400x175/?github description: API docs for the dataQuality plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataQuality'] --- import dataQualityObj from './data_quality.devdocs.json'; diff --git a/api_docs/data_query.mdx b/api_docs/data_query.mdx index b21c3fdc253a4..217b5ce6db793 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: 2024-08-07 +date: 2024-08-08 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 ea4229bd303b9..a9df47c7f1a12 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: 2024-08-07 +date: 2024-08-08 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 c1f87937ec888..9b376d766fbde 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: 2024-08-07 +date: 2024-08-08 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 1f7feedae6d75..3ecaf07a64b0d 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: 2024-08-07 +date: 2024-08-08 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 15b5c45a8bccf..33c093e969ca1 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: 2024-08-07 +date: 2024-08-08 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 a3d9af241cb37..f2d5f799c7b2b 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: 2024-08-07 +date: 2024-08-08 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 2e1abe2618192..8ee48d6b5ab84 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataVisualizer'] --- import dataVisualizerObj from './data_visualizer.devdocs.json'; diff --git a/api_docs/dataset_quality.mdx b/api_docs/dataset_quality.mdx index c3dd6889b8078..e43153f58c2a1 100644 --- a/api_docs/dataset_quality.mdx +++ b/api_docs/dataset_quality.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/datasetQuality title: "datasetQuality" image: https://source.unsplash.com/400x175/?github description: API docs for the datasetQuality plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'datasetQuality'] --- import datasetQualityObj from './dataset_quality.devdocs.json'; diff --git a/api_docs/deprecations_by_api.mdx b/api_docs/deprecations_by_api.mdx index d8ae8d09f5040..c620d677c637e 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -66,6 +66,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | securitySolution | - | | | securitySolution | - | | | @kbn/monaco, securitySolution | - | +| | cloudSecurityPosture, securitySolution | - | | | fleet, exploratoryView, osquery, synthetics | - | | | alerting, observabilityAIAssistant, fleet, cloudSecurityPosture, enterpriseSearch, serverlessSearch, transform, upgradeAssistant, apm, entityManager, synthetics, security | - | | | cloudChat | - | diff --git a/api_docs/deprecations_by_plugin.mdx b/api_docs/deprecations_by_plugin.mdx index 7710317b0fafe..1cdf64b98f75b 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -599,6 +599,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [setup_routes.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cloud_security_posture/server/routes/setup_routes.ts#:~:text=authc), [setup_routes.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cloud_security_posture/server/routes/setup_routes.ts#:~:text=authc) | - | | | [csp_benchmark_rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cloud_security_posture/server/saved_objects/csp_benchmark_rule.ts#:~:text=migrations) | - | | | [csp_benchmark_rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cloud_security_posture/server/saved_objects/csp_benchmark_rule.ts#:~:text=schemas), [csp_settings.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cloud_security_posture/server/saved_objects/csp_settings.ts#:~:text=schemas) | - | +| | [cloud_security_data_table.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cloud_security_posture/public/components/cloud_security_data_table/cloud_security_data_table.tsx#:~:text=externalControlColumns) | - | @@ -1391,6 +1392,7 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | | [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/blocklist/constants.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_NAME), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/blocklist/constants.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_NAME), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/blocklists/index.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_NAME), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/blocklists/index.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_NAME) | - | | | [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/blocklist/constants.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_DESCRIPTION), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/blocklist/constants.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_DESCRIPTION), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/blocklists/index.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_DESCRIPTION), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/blocklists/index.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_DESCRIPTION) | - | | | [use_colors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/resolver/view/use_colors.ts#:~:text=darkMode), [use_colors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/resolver/view/use_colors.ts#:~:text=darkMode), [use_colors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/resolver/view/use_colors.ts#:~:text=darkMode), [use_colors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/resolver/view/use_colors.ts#:~:text=darkMode), [use_colors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/resolver/view/use_colors.ts#:~:text=darkMode) | - | +| | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/index.tsx#:~:text=externalControlColumns) | - | diff --git a/api_docs/deprecations_by_team.mdx b/api_docs/deprecations_by_team.mdx index 2c0aa8408e946..214172dd17893 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/dev_tools.mdx b/api_docs/dev_tools.mdx index 9eca842fbdc37..4188c4ba2acb2 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: 2024-08-07 +date: 2024-08-08 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 4cf9fde819c2f..672e4f49463dc 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: 2024-08-07 +date: 2024-08-08 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 b8b32e636f848..5ee65b1dadaf2 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discoverEnhanced'] --- import discoverEnhancedObj from './discover_enhanced.devdocs.json'; diff --git a/api_docs/discover_shared.mdx b/api_docs/discover_shared.mdx index d4f82221ba35a..bf048563a789e 100644 --- a/api_docs/discover_shared.mdx +++ b/api_docs/discover_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discoverShared title: "discoverShared" image: https://source.unsplash.com/400x175/?github description: API docs for the discoverShared plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discoverShared'] --- import discoverSharedObj from './discover_shared.devdocs.json'; diff --git a/api_docs/ecs_data_quality_dashboard.mdx b/api_docs/ecs_data_quality_dashboard.mdx index a3930aca59330..280c1e5a357e0 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: 2024-08-07 +date: 2024-08-08 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 05cfb286e08a0..f5ae164d99b85 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: 2024-08-07 +date: 2024-08-08 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 ddfa12c73a261..8300959954d01 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: 2024-08-07 +date: 2024-08-08 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 2bcb74c009390..89748a9650e5d 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: 2024-08-07 +date: 2024-08-08 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 dd8aca1b6479a..070da8f90d84b 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: 2024-08-07 +date: 2024-08-08 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 f5b915a76c0b0..b1fcb1fabf984 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'enterpriseSearch'] --- import enterpriseSearchObj from './enterprise_search.devdocs.json'; diff --git a/api_docs/entity_manager.mdx b/api_docs/entity_manager.mdx index 07a5bbf43158c..6ef69d72a9216 100644 --- a/api_docs/entity_manager.mdx +++ b/api_docs/entity_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/entityManager title: "entityManager" image: https://source.unsplash.com/400x175/?github description: API docs for the entityManager plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'entityManager'] --- import entityManagerObj from './entity_manager.devdocs.json'; diff --git a/api_docs/es_ui_shared.mdx b/api_docs/es_ui_shared.mdx index fd4d3b28a1313..423b38a595e27 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'esUiShared'] --- import esUiSharedObj from './es_ui_shared.devdocs.json'; diff --git a/api_docs/esql.mdx b/api_docs/esql.mdx index a0f8ea59b756c..46e6f91e8ed4a 100644 --- a/api_docs/esql.mdx +++ b/api_docs/esql.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/esql title: "esql" image: https://source.unsplash.com/400x175/?github description: API docs for the esql plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'esql'] --- import esqlObj from './esql.devdocs.json'; diff --git a/api_docs/esql_data_grid.mdx b/api_docs/esql_data_grid.mdx index f72f2013f16fb..0b9da1f1b28fa 100644 --- a/api_docs/esql_data_grid.mdx +++ b/api_docs/esql_data_grid.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/esqlDataGrid title: "esqlDataGrid" image: https://source.unsplash.com/400x175/?github description: API docs for the esqlDataGrid plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'esqlDataGrid'] --- import esqlDataGridObj from './esql_data_grid.devdocs.json'; diff --git a/api_docs/event_annotation.mdx b/api_docs/event_annotation.mdx index 2f85bcccd724c..e769509fecb52 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: 2024-08-07 +date: 2024-08-08 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 147f993f32990..4472515fd16d5 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: 2024-08-07 +date: 2024-08-08 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 16eb6d7d75453..61d7c8b147f79 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: 2024-08-07 +date: 2024-08-08 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 9fa18e1cdb7e7..7bb11d2ecf996 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: 2024-08-07 +date: 2024-08-08 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 08f7f093a6f00..532dcdae8cc35 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionError'] --- import expressionErrorObj from './expression_error.devdocs.json'; diff --git a/api_docs/expression_gauge.devdocs.json b/api_docs/expression_gauge.devdocs.json index e973d03f2d740..16f8d3bf1e5a1 100644 --- a/api_docs/expression_gauge.devdocs.json +++ b/api_docs/expression_gauge.devdocs.json @@ -708,7 +708,7 @@ "label": "labelMajorMode", "description": [], "signature": [ - "\"none\" | \"auto\" | \"custom\"" + "\"none\" | \"custom\" | \"auto\"" ], "path": "src/plugins/chart_expressions/expression_gauge/common/types/expression_functions.ts", "deprecated": false, @@ -1143,7 +1143,7 @@ "label": "GaugeLabelMajorMode", "description": [], "signature": [ - "\"none\" | \"auto\" | \"custom\"" + "\"none\" | \"custom\" | \"auto\"" ], "path": "src/plugins/chart_expressions/expression_gauge/common/types/expression_functions.ts", "deprecated": false, diff --git a/api_docs/expression_gauge.mdx b/api_docs/expression_gauge.mdx index 0f77055291a68..317aec53da813 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: 2024-08-07 +date: 2024-08-08 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 0a0de23648249..9d7cf427a2689 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: 2024-08-07 +date: 2024-08-08 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 8fbfa20727507..bd382177187cb 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: 2024-08-07 +date: 2024-08-08 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 1a67231d0cc82..0359f92a2cecb 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: 2024-08-07 +date: 2024-08-08 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 f85e0e8b9ed87..40a4597f7eb42 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: 2024-08-07 +date: 2024-08-08 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 ce7144ad34584..c8181e3ef7e45 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: 2024-08-07 +date: 2024-08-08 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 756d4fb71db18..bca3f02aee2cd 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: 2024-08-07 +date: 2024-08-08 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 861658118a243..61962142e91a8 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: 2024-08-07 +date: 2024-08-08 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 3f21799201d43..9d3e9708df9df 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: 2024-08-07 +date: 2024-08-08 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 cac4d4a571e6b..fdb64005bba0f 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: 2024-08-07 +date: 2024-08-08 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 947656d33e3f5..add1124a27f16 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: 2024-08-07 +date: 2024-08-08 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 861c48025bffe..5565fed950a80 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: 2024-08-07 +date: 2024-08-08 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 7643e50766a1f..f36ddf6d641c1 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: 2024-08-07 +date: 2024-08-08 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 3da128d911d2a..5cc03a9ae31c9 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: 2024-08-07 +date: 2024-08-08 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 26543b408d478..f04f1eee3f345 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fieldFormats'] --- import fieldFormatsObj from './field_formats.devdocs.json'; diff --git a/api_docs/fields_metadata.mdx b/api_docs/fields_metadata.mdx index 8353c47f9c6fd..c5edf3e1200bf 100644 --- a/api_docs/fields_metadata.mdx +++ b/api_docs/fields_metadata.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fieldsMetadata title: "fieldsMetadata" image: https://source.unsplash.com/400x175/?github description: API docs for the fieldsMetadata plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fieldsMetadata'] --- import fieldsMetadataObj from './fields_metadata.devdocs.json'; diff --git a/api_docs/file_upload.mdx b/api_docs/file_upload.mdx index ee11e0995b9fa..4c2bbfd6d9f0e 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: 2024-08-07 +date: 2024-08-08 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 8b5ffcc0d49ac..2c4d25fe33985 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: 2024-08-07 +date: 2024-08-08 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 37107f596f7b8..cac7220d1d4b3 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: 2024-08-07 +date: 2024-08-08 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 8743a2c493c80..6895c384f966a 100644 --- a/api_docs/fleet.devdocs.json +++ b/api_docs/fleet.devdocs.json @@ -8089,13 +8089,7 @@ "description": [], "signature": [ "(options: { pkgName: string; pkgVersion?: string | undefined; spaceId?: string | undefined; force?: boolean | undefined; }) => Promise<", - { - "pluginId": "fleet", - "scope": "common", - "docId": "kibFleetPluginApi", - "section": "def-common.Installation", - "text": "Installation" - }, + "EnsurePackageResult", ">" ], "path": "x-pack/plugins/fleet/server/services/epm/package_service.ts", @@ -27646,7 +27640,7 @@ "label": "PackageSpecCategory", "description": [], "signature": [ - "\"monitoring\" | \"security\" | \"connector\" | \"observability\" | \"infrastructure\" | \"cloud\" | \"custom\" | \"enterprise_search\" | \"advanced_analytics_ueba\" | \"analytics_engine\" | \"application_observability\" | \"app_search\" | \"auditd\" | \"authentication\" | \"aws\" | \"azure\" | \"big_data\" | \"cdn_security\" | \"config_management\" | \"connector_client\" | \"containers\" | \"crawler\" | \"credential_management\" | \"crm\" | \"custom_logs\" | \"database_security\" | \"datastore\" | \"dns_security\" | \"edr_xdr\" | \"cloudsecurity_cdr\" | \"elasticsearch_sdk\" | \"elastic_stack\" | \"email_security\" | \"firewall_security\" | \"google_cloud\" | \"iam\" | \"ids_ips\" | \"java_observability\" | \"kubernetes\" | \"language_client\" | \"languages\" | \"load_balancer\" | \"message_queue\" | \"native_search\" | \"network\" | \"network_security\" | \"notification\" | \"os_system\" | \"process_manager\" | \"productivity\" | \"productivity_security\" | \"proxy_security\" | \"sdk_search\" | \"stream_processing\" | \"support\" | \"threat_intel\" | \"ticketing\" | \"version_control\" | \"virtualization\" | \"vpn_security\" | \"vulnerability_management\" | \"web\" | \"web_application_firewall\" | \"websphere\" | \"workplace_search_content_source\" | \"workplace_search\"" + "\"monitoring\" | \"security\" | \"connector\" | \"observability\" | \"custom\" | \"infrastructure\" | \"cloud\" | \"enterprise_search\" | \"advanced_analytics_ueba\" | \"analytics_engine\" | \"application_observability\" | \"app_search\" | \"auditd\" | \"authentication\" | \"aws\" | \"azure\" | \"big_data\" | \"cdn_security\" | \"config_management\" | \"connector_client\" | \"containers\" | \"crawler\" | \"credential_management\" | \"crm\" | \"custom_logs\" | \"database_security\" | \"datastore\" | \"dns_security\" | \"edr_xdr\" | \"cloudsecurity_cdr\" | \"elasticsearch_sdk\" | \"elastic_stack\" | \"email_security\" | \"firewall_security\" | \"google_cloud\" | \"iam\" | \"ids_ips\" | \"java_observability\" | \"kubernetes\" | \"language_client\" | \"languages\" | \"load_balancer\" | \"message_queue\" | \"native_search\" | \"network\" | \"network_security\" | \"notification\" | \"os_system\" | \"process_manager\" | \"productivity\" | \"productivity_security\" | \"proxy_security\" | \"sdk_search\" | \"stream_processing\" | \"support\" | \"threat_intel\" | \"ticketing\" | \"version_control\" | \"virtualization\" | \"vpn_security\" | \"vulnerability_management\" | \"web\" | \"web_application_firewall\" | \"websphere\" | \"workplace_search_content_source\" | \"workplace_search\"" ], "path": "x-pack/plugins/fleet/common/types/models/package_spec.ts", "deprecated": false, @@ -27768,7 +27762,7 @@ "label": "RegistrySearchResult", "description": [], "signature": [ - "{ type?: \"input\" | \"integration\" | undefined; version: string; name: string; title: string; description: string; path: string; download: string; internal?: boolean | undefined; icons?: (", + "{ type?: \"input\" | \"integration\" | undefined; version: string; name: string; title: string; description: string; path: string; internal?: boolean | undefined; download: string; icons?: (", { "pluginId": "fleet", "scope": "common", diff --git a/api_docs/fleet.mdx b/api_docs/fleet.mdx index 7a8fdf6f4b361..b33a738e24bbe 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: 2024-08-07 +date: 2024-08-08 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 | |-------------------|-----------|------------------------|-----------------| -| 1351 | 5 | 1229 | 73 | +| 1351 | 5 | 1229 | 74 | ## Client diff --git a/api_docs/global_search.mdx b/api_docs/global_search.mdx index d89ffc6a6a012..270e5a206cd3b 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: 2024-08-07 +date: 2024-08-08 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 f9d6407b95bb9..dc2bec84e837e 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: 2024-08-07 +date: 2024-08-08 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 88aa39914ca36..9ab87fbd39102 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: 2024-08-07 +date: 2024-08-08 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 342e391af28f9..7df20ef137d69 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: 2024-08-07 +date: 2024-08-08 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 ebe9e81954f00..295fc27fc0d7a 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: 2024-08-07 +date: 2024-08-08 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 0ee3f75ef0b4b..7fb7fd6369280 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexManagement'] --- import indexManagementObj from './index_management.devdocs.json'; diff --git a/api_docs/inference.mdx b/api_docs/inference.mdx index 4ea61e42b5400..3b7a0ff19fe9b 100644 --- a/api_docs/inference.mdx +++ b/api_docs/inference.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/inference title: "inference" image: https://source.unsplash.com/400x175/?github description: API docs for the inference plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'inference'] --- import inferenceObj from './inference.devdocs.json'; diff --git a/api_docs/infra.mdx b/api_docs/infra.mdx index 77fb8307f5587..95ff57c5ca1c9 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'infra'] --- import infraObj from './infra.devdocs.json'; diff --git a/api_docs/ingest_pipelines.mdx b/api_docs/ingest_pipelines.mdx index c7894aff2c06f..ab6a8a908c210 100644 --- a/api_docs/ingest_pipelines.mdx +++ b/api_docs/ingest_pipelines.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ingestPipelines title: "ingestPipelines" image: https://source.unsplash.com/400x175/?github description: API docs for the ingestPipelines plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ingestPipelines'] --- import ingestPipelinesObj from './ingest_pipelines.devdocs.json'; diff --git a/api_docs/inspector.mdx b/api_docs/inspector.mdx index 6020d9ff3fccf..79d1f3dbcec0e 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'inspector'] --- import inspectorObj from './inspector.devdocs.json'; diff --git a/api_docs/integration_assistant.mdx b/api_docs/integration_assistant.mdx index 31c9bdc8c0011..292b321958009 100644 --- a/api_docs/integration_assistant.mdx +++ b/api_docs/integration_assistant.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/integrationAssistant title: "integrationAssistant" image: https://source.unsplash.com/400x175/?github description: API docs for the integrationAssistant plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'integrationAssistant'] --- import integrationAssistantObj from './integration_assistant.devdocs.json'; diff --git a/api_docs/interactive_setup.mdx b/api_docs/interactive_setup.mdx index dc035d9d210d1..74a771382dc74 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'interactiveSetup'] --- import interactiveSetupObj from './interactive_setup.devdocs.json'; diff --git a/api_docs/investigate.devdocs.json b/api_docs/investigate.devdocs.json index 7048808862f2d..84d3ffbb38d41 100644 --- a/api_docs/investigate.devdocs.json +++ b/api_docs/investigate.devdocs.json @@ -145,7 +145,7 @@ "label": "getEsFilterFromGlobalParameters", "description": [], "signature": [ - "({\n filters,\n timeRange,\n}: Partial<", + "({ timeRange }: Partial<", "GlobalWidgetParameters", ">) => { bool: ", { @@ -166,7 +166,7 @@ "id": "def-public.getEsFilterFromGlobalParameters.$1", "type": "Object", "tags": [], - "label": "{\n filters,\n timeRange,\n}", + "label": "{ timeRange }", "description": [], "signature": [ "Partial<", @@ -487,27 +487,6 @@ "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", "deprecated": false, "trackAdoption": false - }, - { - "parentPluginId": "investigate", - "id": "def-public.GlobalWidgetParameters.filters", - "type": "Array", - "tags": [], - "label": "filters", - "description": [], - "signature": [ - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.Filter", - "text": "Filter" - }, - "[]" - ], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false } ], "initialIsOpen": false @@ -681,17 +660,6 @@ "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", "deprecated": false, "trackAdoption": false - }, - { - "parentPluginId": "investigate", - "id": "def-public.InvestigateWidget.locked", - "type": "boolean", - "tags": [], - "label": "locked", - "description": [], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false } ], "initialIsOpen": false @@ -749,27 +717,6 @@ "deprecated": false, "trackAdoption": false }, - { - "parentPluginId": "investigate", - "id": "def-public.Investigation.revisions", - "type": "Array", - "tags": [], - "label": "revisions", - "description": [], - "signature": [ - { - "pluginId": "investigate", - "scope": "common", - "docId": "kibInvestigatePluginApi", - "section": "def-common.InvestigationRevision", - "text": "InvestigationRevision" - }, - "[]" - ], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false - }, { "parentPluginId": "investigate", "id": "def-public.Investigation.title", @@ -783,43 +730,7 @@ }, { "parentPluginId": "investigate", - "id": "def-public.Investigation.revision", - "type": "string", - "tags": [], - "label": "revision", - "description": [], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "investigate", - "id": "def-public.InvestigationRevision", - "type": "Interface", - "tags": [], - "label": "InvestigationRevision", - "description": [], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "investigate", - "id": "def-public.InvestigationRevision.id", - "type": "string", - "tags": [], - "label": "id", - "description": [], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "investigate", - "id": "def-public.InvestigationRevision.items", + "id": "def-public.Investigation.items", "type": "Array", "tags": [], "label": "items", @@ -840,7 +751,7 @@ }, { "parentPluginId": "investigate", - "id": "def-public.InvestigationRevision.parameters", + "id": "def-public.Investigation.parameters", "type": "Object", "tags": [], "label": "parameters", @@ -921,7 +832,7 @@ "section": "def-common.InvestigateWidget", "text": "InvestigateWidget" }, - "<{}, {}>, \"type\" | \"title\" | \"columns\" | \"description\" | \"rows\" | \"locked\"> & { parameters: ", + "<{}, {}>, \"type\" | \"title\" | \"columns\" | \"description\" | \"rows\"> & { parameters: ", "_DeepPartialObject", "<", "GlobalWidgetParameters", @@ -932,136 +843,6 @@ "trackAdoption": false } ] - }, - { - "parentPluginId": "investigate", - "id": "def-public.WidgetRenderAPI.blocks", - "type": "Object", - "tags": [], - "label": "blocks", - "description": [], - "signature": [ - "{ publish: (blocks: ", - { - "pluginId": "investigate", - "scope": "common", - "docId": "kibInvestigatePluginApi", - "section": "def-common.WorkflowBlock", - "text": "WorkflowBlock" - }, - "[]) => UnregisterFunction; }" - ], - "path": "x-pack/plugins/observability_solution/investigate/public/types.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "investigate", - "id": "def-public.WorkflowBlock", - "type": "Interface", - "tags": [], - "label": "WorkflowBlock", - "description": [], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "investigate", - "id": "def-public.WorkflowBlock.id", - "type": "string", - "tags": [], - "label": "id", - "description": [], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "investigate", - "id": "def-public.WorkflowBlock.content", - "type": "string", - "tags": [], - "label": "content", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "investigate", - "id": "def-public.WorkflowBlock.description", - "type": "string", - "tags": [], - "label": "description", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "investigate", - "id": "def-public.WorkflowBlock.loading", - "type": "boolean", - "tags": [], - "label": "loading", - "description": [], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "investigate", - "id": "def-public.WorkflowBlock.onClick", - "type": "Function", - "tags": [], - "label": "onClick", - "description": [], - "signature": [ - "(() => void) | undefined" - ], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "investigate", - "id": "def-public.WorkflowBlock.color", - "type": "CompoundType", - "tags": [], - "label": "color", - "description": [], - "signature": [ - "\"link\" | \"text\" | \"title\" | \"warning\" | \"disabled\" | \"success\" | \"body\" | \"highlight\" | \"primary\" | \"accent\" | \"danger\" | \"primaryText\" | \"accentText\" | \"successText\" | \"warningText\" | \"dangerText\" | \"emptyShade\" | \"lightestShade\" | \"lightShade\" | \"mediumShade\" | \"darkShade\" | \"darkestShade\" | \"fullShade\" | \"disabledText\" | \"shadow\" | \"subduedText\" | \"ghost\" | \"ink\" | undefined" - ], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "investigate", - "id": "def-public.WorkflowBlock.children", - "type": "CompoundType", - "tags": [], - "label": "children", - "description": [], - "signature": [ - "boolean | React.ReactChild | React.ReactFragment | React.ReactPortal | null | undefined" - ], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false } ], "initialIsOpen": false @@ -1125,7 +906,7 @@ "section": "def-common.InvestigateWidget", "text": "InvestigateWidget" }, - "<{}, {}>, \"type\" | \"title\" | \"columns\" | \"description\" | \"rows\" | \"locked\"> & { parameters: ", + "<{}, {}>, \"type\" | \"title\" | \"columns\" | \"description\" | \"rows\"> & { parameters: ", "_DeepPartialObject", "<", "GlobalWidgetParameters", @@ -1175,7 +956,7 @@ "section": "def-common.InvestigateWidget", "text": "InvestigateWidget" }, - "<{}, {}>, \"type\" | \"title\" | \"columns\" | \"description\" | \"rows\" | \"locked\"> & { parameters: ", + "<{}, {}>, \"type\" | \"title\" | \"columns\" | \"description\" | \"rows\"> & { parameters: ", "_DeepPartialObject", "<", "GlobalWidgetParameters", @@ -1824,17 +1605,6 @@ "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", "deprecated": false, "trackAdoption": false - }, - { - "parentPluginId": "investigate", - "id": "def-common.InvestigateWidget.locked", - "type": "boolean", - "tags": [], - "label": "locked", - "description": [], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false } ], "initialIsOpen": false @@ -1892,27 +1662,6 @@ "deprecated": false, "trackAdoption": false }, - { - "parentPluginId": "investigate", - "id": "def-common.Investigation.revisions", - "type": "Array", - "tags": [], - "label": "revisions", - "description": [], - "signature": [ - { - "pluginId": "investigate", - "scope": "common", - "docId": "kibInvestigatePluginApi", - "section": "def-common.InvestigationRevision", - "text": "InvestigationRevision" - }, - "[]" - ], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false - }, { "parentPluginId": "investigate", "id": "def-common.Investigation.title", @@ -1926,43 +1675,7 @@ }, { "parentPluginId": "investigate", - "id": "def-common.Investigation.revision", - "type": "string", - "tags": [], - "label": "revision", - "description": [], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "investigate", - "id": "def-common.InvestigationRevision", - "type": "Interface", - "tags": [], - "label": "InvestigationRevision", - "description": [], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "investigate", - "id": "def-common.InvestigationRevision.id", - "type": "string", - "tags": [], - "label": "id", - "description": [], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "investigate", - "id": "def-common.InvestigationRevision.items", + "id": "def-common.Investigation.items", "type": "Array", "tags": [], "label": "items", @@ -1983,7 +1696,7 @@ }, { "parentPluginId": "investigate", - "id": "def-common.InvestigationRevision.parameters", + "id": "def-common.Investigation.parameters", "type": "Object", "tags": [], "label": "parameters", @@ -1997,114 +1710,6 @@ } ], "initialIsOpen": false - }, - { - "parentPluginId": "investigate", - "id": "def-common.WorkflowBlock", - "type": "Interface", - "tags": [], - "label": "WorkflowBlock", - "description": [], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "investigate", - "id": "def-common.WorkflowBlock.id", - "type": "string", - "tags": [], - "label": "id", - "description": [], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "investigate", - "id": "def-common.WorkflowBlock.content", - "type": "string", - "tags": [], - "label": "content", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "investigate", - "id": "def-common.WorkflowBlock.description", - "type": "string", - "tags": [], - "label": "description", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "investigate", - "id": "def-common.WorkflowBlock.loading", - "type": "boolean", - "tags": [], - "label": "loading", - "description": [], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "investigate", - "id": "def-common.WorkflowBlock.onClick", - "type": "Function", - "tags": [], - "label": "onClick", - "description": [], - "signature": [ - "(() => void) | undefined" - ], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "investigate", - "id": "def-common.WorkflowBlock.color", - "type": "CompoundType", - "tags": [], - "label": "color", - "description": [], - "signature": [ - "\"link\" | \"text\" | \"title\" | \"warning\" | \"disabled\" | \"success\" | \"body\" | \"highlight\" | \"primary\" | \"accent\" | \"danger\" | \"primaryText\" | \"accentText\" | \"successText\" | \"warningText\" | \"dangerText\" | \"emptyShade\" | \"lightestShade\" | \"lightShade\" | \"mediumShade\" | \"darkShade\" | \"darkestShade\" | \"fullShade\" | \"disabledText\" | \"shadow\" | \"subduedText\" | \"ghost\" | \"ink\" | undefined" - ], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "investigate", - "id": "def-common.WorkflowBlock.children", - "type": "CompoundType", - "tags": [], - "label": "children", - "description": [], - "signature": [ - "boolean | React.ReactChild | React.ReactFragment | React.ReactPortal | null | undefined" - ], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false } ], "enums": [ @@ -2138,7 +1743,7 @@ "section": "def-common.InvestigateWidget", "text": "InvestigateWidget" }, - "<{}, {}>, \"type\" | \"title\" | \"columns\" | \"description\" | \"rows\" | \"locked\"> & { parameters: ", + "<{}, {}>, \"type\" | \"title\" | \"columns\" | \"description\" | \"rows\"> & { parameters: ", "_DeepPartialObject", "<", "GlobalWidgetParameters", diff --git a/api_docs/investigate.mdx b/api_docs/investigate.mdx index 1b29e5d06bf4b..6ee8666fcb479 100644 --- a/api_docs/investigate.mdx +++ b/api_docs/investigate.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/investigate title: "investigate" image: https://source.unsplash.com/400x175/?github description: API docs for the investigate plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'investigate'] --- import investigateObj from './investigate.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/ | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 133 | 0 | 133 | 6 | +| 105 | 0 | 105 | 6 | ## Client diff --git a/api_docs/investigate_app.mdx b/api_docs/investigate_app.mdx index 6ce7480be204c..7f32a9e495b69 100644 --- a/api_docs/investigate_app.mdx +++ b/api_docs/investigate_app.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/investigateApp title: "investigateApp" image: https://source.unsplash.com/400x175/?github description: API docs for the investigateApp plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'investigateApp'] --- import investigateAppObj from './investigate_app.devdocs.json'; diff --git a/api_docs/kbn_ace.mdx b/api_docs/kbn_ace.mdx index ccd698d677fb8..8bd5d596a4a1a 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ace'] --- import kbnAceObj from './kbn_ace.devdocs.json'; diff --git a/api_docs/kbn_actions_types.mdx b/api_docs/kbn_actions_types.mdx index c992b7a968d62..28beb5f5e71b9 100644 --- a/api_docs/kbn_actions_types.mdx +++ b/api_docs/kbn_actions_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-actions-types title: "@kbn/actions-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/actions-types plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/actions-types'] --- import kbnActionsTypesObj from './kbn_actions_types.devdocs.json'; diff --git a/api_docs/kbn_aiops_components.mdx b/api_docs/kbn_aiops_components.mdx index 74cf64bd7c89f..1ae57b349f2d7 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-components'] --- import kbnAiopsComponentsObj from './kbn_aiops_components.devdocs.json'; diff --git a/api_docs/kbn_aiops_log_pattern_analysis.mdx b/api_docs/kbn_aiops_log_pattern_analysis.mdx index 7f7df5da22676..ee8b1ca9d51b7 100644 --- a/api_docs/kbn_aiops_log_pattern_analysis.mdx +++ b/api_docs/kbn_aiops_log_pattern_analysis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-log-pattern-analysis title: "@kbn/aiops-log-pattern-analysis" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-log-pattern-analysis plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-log-pattern-analysis'] --- import kbnAiopsLogPatternAnalysisObj from './kbn_aiops_log_pattern_analysis.devdocs.json'; diff --git a/api_docs/kbn_aiops_log_rate_analysis.mdx b/api_docs/kbn_aiops_log_rate_analysis.mdx index 3318b6d4631bc..0f822889bef9c 100644 --- a/api_docs/kbn_aiops_log_rate_analysis.mdx +++ b/api_docs/kbn_aiops_log_rate_analysis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-log-rate-analysis title: "@kbn/aiops-log-rate-analysis" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-log-rate-analysis plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-log-rate-analysis'] --- import kbnAiopsLogRateAnalysisObj from './kbn_aiops_log_rate_analysis.devdocs.json'; diff --git a/api_docs/kbn_alerting_api_integration_helpers.mdx b/api_docs/kbn_alerting_api_integration_helpers.mdx index 84a84401fc17b..91e46ad099474 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: 2024-08-07 +date: 2024-08-08 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_comparators.mdx b/api_docs/kbn_alerting_comparators.mdx index c3ef7950f3f9d..b61f1eb256a65 100644 --- a/api_docs/kbn_alerting_comparators.mdx +++ b/api_docs/kbn_alerting_comparators.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerting-comparators title: "@kbn/alerting-comparators" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerting-comparators plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerting-comparators'] --- import kbnAlertingComparatorsObj from './kbn_alerting_comparators.devdocs.json'; diff --git a/api_docs/kbn_alerting_state_types.mdx b/api_docs/kbn_alerting_state_types.mdx index 50bd82983c171..0028ba5a32acd 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerting-state-types'] --- import kbnAlertingStateTypesObj from './kbn_alerting_state_types.devdocs.json'; diff --git a/api_docs/kbn_alerting_types.mdx b/api_docs/kbn_alerting_types.mdx index bfcc5d708359e..2ab32c9514c0f 100644 --- a/api_docs/kbn_alerting_types.mdx +++ b/api_docs/kbn_alerting_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerting-types title: "@kbn/alerting-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerting-types plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerting-types'] --- import kbnAlertingTypesObj from './kbn_alerting_types.devdocs.json'; diff --git a/api_docs/kbn_alerts_as_data_utils.mdx b/api_docs/kbn_alerts_as_data_utils.mdx index 9843a18950986..f024c69a70e9a 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: 2024-08-07 +date: 2024-08-08 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_grouping.mdx b/api_docs/kbn_alerts_grouping.mdx index 55b283ee8dcfa..c531517955cd9 100644 --- a/api_docs/kbn_alerts_grouping.mdx +++ b/api_docs/kbn_alerts_grouping.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts-grouping title: "@kbn/alerts-grouping" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts-grouping plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts-grouping'] --- import kbnAlertsGroupingObj from './kbn_alerts_grouping.devdocs.json'; diff --git a/api_docs/kbn_alerts_ui_shared.mdx b/api_docs/kbn_alerts_ui_shared.mdx index 7bd331df0780c..b4a8269c8789e 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: 2024-08-07 +date: 2024-08-08 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 8f5a92db940d0..2b965557779f4 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics'] --- import kbnAnalyticsObj from './kbn_analytics.devdocs.json'; diff --git a/api_docs/kbn_analytics_collection_utils.mdx b/api_docs/kbn_analytics_collection_utils.mdx index 4f69e739b0a8a..4063b850862ff 100644 --- a/api_docs/kbn_analytics_collection_utils.mdx +++ b/api_docs/kbn_analytics_collection_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-collection-utils title: "@kbn/analytics-collection-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-collection-utils plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-collection-utils'] --- import kbnAnalyticsCollectionUtilsObj from './kbn_analytics_collection_utils.devdocs.json'; diff --git a/api_docs/kbn_apm_config_loader.mdx b/api_docs/kbn_apm_config_loader.mdx index 9da01e04bf4e8..1d52ef9ba5a2e 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: 2024-08-07 +date: 2024-08-08 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_data_view.mdx b/api_docs/kbn_apm_data_view.mdx index f3bb1285d5840..df6a8f62a8b24 100644 --- a/api_docs/kbn_apm_data_view.mdx +++ b/api_docs/kbn_apm_data_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-data-view title: "@kbn/apm-data-view" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-data-view plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-data-view'] --- import kbnApmDataViewObj from './kbn_apm_data_view.devdocs.json'; diff --git a/api_docs/kbn_apm_synthtrace.mdx b/api_docs/kbn_apm_synthtrace.mdx index 105b072fa0fc1..90519ac3dedf9 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: 2024-08-07 +date: 2024-08-08 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 dac10e0c00be3..db9be7e877731 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: 2024-08-07 +date: 2024-08-08 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_types.mdx b/api_docs/kbn_apm_types.mdx index 4ae7ff8ec57ed..881a5e89da298 100644 --- a/api_docs/kbn_apm_types.mdx +++ b/api_docs/kbn_apm_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-types title: "@kbn/apm-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-types plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-types'] --- import kbnApmTypesObj from './kbn_apm_types.devdocs.json'; diff --git a/api_docs/kbn_apm_utils.mdx b/api_docs/kbn_apm_utils.mdx index b973228823543..2445cfa995ac5 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-utils'] --- import kbnApmUtilsObj from './kbn_apm_utils.devdocs.json'; diff --git a/api_docs/kbn_avc_banner.mdx b/api_docs/kbn_avc_banner.mdx index 8183640afe3f1..ab4e15e990cea 100644 --- a/api_docs/kbn_avc_banner.mdx +++ b/api_docs/kbn_avc_banner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-avc-banner title: "@kbn/avc-banner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/avc-banner plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/avc-banner'] --- import kbnAvcBannerObj from './kbn_avc_banner.devdocs.json'; diff --git a/api_docs/kbn_axe_config.mdx b/api_docs/kbn_axe_config.mdx index 3674d8df40195..cc63e676f4d8f 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/axe-config'] --- import kbnAxeConfigObj from './kbn_axe_config.devdocs.json'; diff --git a/api_docs/kbn_bfetch_error.mdx b/api_docs/kbn_bfetch_error.mdx index 591be343d9ce3..523fced6fc1f9 100644 --- a/api_docs/kbn_bfetch_error.mdx +++ b/api_docs/kbn_bfetch_error.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-bfetch-error title: "@kbn/bfetch-error" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/bfetch-error plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/bfetch-error'] --- import kbnBfetchErrorObj from './kbn_bfetch_error.devdocs.json'; diff --git a/api_docs/kbn_calculate_auto.mdx b/api_docs/kbn_calculate_auto.mdx index 91f392ccd8a39..89be88ede0779 100644 --- a/api_docs/kbn_calculate_auto.mdx +++ b/api_docs/kbn_calculate_auto.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-calculate-auto title: "@kbn/calculate-auto" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/calculate-auto plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/calculate-auto'] --- import kbnCalculateAutoObj from './kbn_calculate_auto.devdocs.json'; diff --git a/api_docs/kbn_calculate_width_from_char_count.mdx b/api_docs/kbn_calculate_width_from_char_count.mdx index b55d0870b34f7..e746686094b1a 100644 --- a/api_docs/kbn_calculate_width_from_char_count.mdx +++ b/api_docs/kbn_calculate_width_from_char_count.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-calculate-width-from-char-count title: "@kbn/calculate-width-from-char-count" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/calculate-width-from-char-count plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/calculate-width-from-char-count'] --- import kbnCalculateWidthFromCharCountObj from './kbn_calculate_width_from_char_count.devdocs.json'; diff --git a/api_docs/kbn_cases_components.mdx b/api_docs/kbn_cases_components.mdx index 303cc9d4f3558..3d00af448b4e4 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: 2024-08-07 +date: 2024-08-08 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 77789ac4a47a3..220ed6a7fcbbf 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: 2024-08-07 +date: 2024-08-08 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 bac1c8d552912..4263a55d33903 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: 2024-08-07 +date: 2024-08-08 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 78b3adb4049cd..4a1b086234b3a 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: 2024-08-07 +date: 2024-08-08 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 873678301fa02..761958a4bd072 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: 2024-08-07 +date: 2024-08-08 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 8389886e1f637..d50286e2ab526 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: 2024-08-07 +date: 2024-08-08 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 b247d03d9ae96..8bcca2c4a7442 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: 2024-08-07 +date: 2024-08-08 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 aeba9dacedc86..38157496f7473 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: 2024-08-07 +date: 2024-08-08 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 3aec0cad819d2..394ad9ee954a5 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/code-editor'] --- import kbnCodeEditorObj from './kbn_code_editor.devdocs.json'; diff --git a/api_docs/kbn_code_editor_mock.mdx b/api_docs/kbn_code_editor_mock.mdx index 2cc03c33e4815..9266414223fd2 100644 --- a/api_docs/kbn_code_editor_mock.mdx +++ b/api_docs/kbn_code_editor_mock.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-code-editor-mock title: "@kbn/code-editor-mock" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/code-editor-mock plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/code-editor-mock'] --- import kbnCodeEditorMockObj from './kbn_code_editor_mock.devdocs.json'; diff --git a/api_docs/kbn_code_owners.mdx b/api_docs/kbn_code_owners.mdx index 6a24653ba8de6..9d6e263d5fd2f 100644 --- a/api_docs/kbn_code_owners.mdx +++ b/api_docs/kbn_code_owners.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-code-owners title: "@kbn/code-owners" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/code-owners plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/code-owners'] --- import kbnCodeOwnersObj from './kbn_code_owners.devdocs.json'; diff --git a/api_docs/kbn_coloring.mdx b/api_docs/kbn_coloring.mdx index ea02a88b5fae6..ca7ea4c6fb050 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: 2024-08-07 +date: 2024-08-08 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 ca0ad4f59cdb2..5dd0d8148b42c 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: 2024-08-07 +date: 2024-08-08 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 6b97335ef3893..acc99c3af289f 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: 2024-08-07 +date: 2024-08-08 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 606761a228347..f4b9f10c0400a 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: 2024-08-07 +date: 2024-08-08 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 77ec00e7b4735..cd842ec41bab6 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: 2024-08-07 +date: 2024-08-08 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 effb5b8fd3a97..1c0e2a901debd 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: 2024-08-07 +date: 2024-08-08 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 d2468d6982cc6..6e3b7c53092fc 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: 2024-08-07 +date: 2024-08-08 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_common.mdx b/api_docs/kbn_content_management_table_list_view_common.mdx index 63527867ac6db..da4fe7f5b0e8b 100644 --- a/api_docs/kbn_content_management_table_list_view_common.mdx +++ b/api_docs/kbn_content_management_table_list_view_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list-view-common title: "@kbn/content-management-table-list-view-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list-view-common plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list-view-common'] --- import kbnContentManagementTableListViewCommonObj from './kbn_content_management_table_list_view_common.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 c575984088da2..ce781c5176dc7 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: 2024-08-07 +date: 2024-08-08 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_user_profiles.mdx b/api_docs/kbn_content_management_user_profiles.mdx index 3c33687948c8f..8de0a676f1e2c 100644 --- a/api_docs/kbn_content_management_user_profiles.mdx +++ b/api_docs/kbn_content_management_user_profiles.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-user-profiles title: "@kbn/content-management-user-profiles" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-user-profiles plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-user-profiles'] --- import kbnContentManagementUserProfilesObj from './kbn_content_management_user_profiles.devdocs.json'; diff --git a/api_docs/kbn_content_management_utils.mdx b/api_docs/kbn_content_management_utils.mdx index 3334cd761b8b3..12f8c8a6de8ad 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: 2024-08-07 +date: 2024-08-08 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 4768e37ff0829..a793479f7602d 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: 2024-08-07 +date: 2024-08-08 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 3f5a50c32bd49..310f4ed18de24 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: 2024-08-07 +date: 2024-08-08 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 49912b98650db..aa06c61584405 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: 2024-08-07 +date: 2024-08-08 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 b4fd7bffd127c..f1ad240a44404 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: 2024-08-07 +date: 2024-08-08 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 bab5d2fb27de7..dc70e9744e58b 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: 2024-08-07 +date: 2024-08-08 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 31866d09667fb..64703de1f77b4 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: 2024-08-07 +date: 2024-08-08 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 adfe2f6523031..9e1690e08ceb2 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: 2024-08-07 +date: 2024-08-08 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 330ba33f0757e..530e0d6bd254b 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: 2024-08-07 +date: 2024-08-08 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 7e363a3bc7b0b..f6deecb7a1562 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: 2024-08-07 +date: 2024-08-08 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 48011079081e1..997e285a0a3a9 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: 2024-08-07 +date: 2024-08-08 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 dc233630ee4e6..8733a70ca8e65 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: 2024-08-07 +date: 2024-08-08 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 dd6daa1aa0638..5825f4565f4df 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: 2024-08-07 +date: 2024-08-08 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 362b0be596228..76e14f23b305d 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: 2024-08-07 +date: 2024-08-08 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 eff8656811063..2e4489f3232f5 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: 2024-08-07 +date: 2024-08-08 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 a8f76247b3fc1..1032bf13903f8 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: 2024-08-07 +date: 2024-08-08 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 4b504f0eddbec..855c432e05a5c 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: 2024-08-07 +date: 2024-08-08 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 5b56f9b451a62..fc371a7da5fea 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: 2024-08-07 +date: 2024-08-08 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 e8a37e677db0b..367401d5465b5 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: 2024-08-07 +date: 2024-08-08 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 6788ca960af62..307eaec8259c9 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: 2024-08-07 +date: 2024-08-08 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 15bd6c799b4e1..e1ed7083d0943 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: 2024-08-07 +date: 2024-08-08 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 47f1945040d54..3e2738a996591 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: 2024-08-07 +date: 2024-08-08 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 3508a0e1fbe92..0e29e08274115 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: 2024-08-07 +date: 2024-08-08 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 a59fe59c42a08..19552d4519ba0 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: 2024-08-07 +date: 2024-08-08 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 bd5e874eed54b..077253d0ed78d 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: 2024-08-07 +date: 2024-08-08 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 db6c453567f8d..140eb76dca0ff 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: 2024-08-07 +date: 2024-08-08 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 74722b999371f..5076b07076fbf 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: 2024-08-07 +date: 2024-08-08 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 897be94eb2a6b..97b1c935abfd1 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: 2024-08-07 +date: 2024-08-08 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 3dd8ba0cbb31f..454677f757d6b 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: 2024-08-07 +date: 2024-08-08 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 1edfea57e645b..960da41c9edaf 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: 2024-08-07 +date: 2024-08-08 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 6954fdeea0808..2d04ef59419cd 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: 2024-08-07 +date: 2024-08-08 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 26c43cf291777..04f38bc565069 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: 2024-08-07 +date: 2024-08-08 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 3b2cc31fa515e..ae19158ad7399 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: 2024-08-07 +date: 2024-08-08 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 7e12572b6b054..2e45bd54c7a03 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: 2024-08-07 +date: 2024-08-08 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 b3ae1964e8de6..2cc2ce8f6017c 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: 2024-08-07 +date: 2024-08-08 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 5ea3420ca7d95..37468710d9b88 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: 2024-08-07 +date: 2024-08-08 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 ff0b727101180..9c73c93d5b8de 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: 2024-08-07 +date: 2024-08-08 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 74440445bb542..e525c802e4e7e 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: 2024-08-07 +date: 2024-08-08 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 b47ca3bc06134..7166a1ce1194f 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: 2024-08-07 +date: 2024-08-08 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 95c6f1fb85391..a490c217c0b4f 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: 2024-08-07 +date: 2024-08-08 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 cefe6d30d756e..343d6a726a624 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: 2024-08-07 +date: 2024-08-08 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 22f458642e7dc..0a05876ee80f2 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: 2024-08-07 +date: 2024-08-08 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 3a5f2a2e5fdc4..8b0e05a0337c4 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: 2024-08-07 +date: 2024-08-08 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 2f16d4ebe8411..a0e3d62763e48 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: 2024-08-07 +date: 2024-08-08 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 153a71ad696ff..84cbfc3d7b0e4 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: 2024-08-07 +date: 2024-08-08 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 c86899be6e4f2..23fe79aafb6da 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: 2024-08-07 +date: 2024-08-08 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 1008904bab3e6..413cf6e4d52d8 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: 2024-08-07 +date: 2024-08-08 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 4c1796475687d..4ba6e885e9836 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: 2024-08-07 +date: 2024-08-08 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 1e774ec849997..dc9cdad4a1b7f 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: 2024-08-07 +date: 2024-08-08 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 b942ec4d1a54d..31ecd53561f36 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: 2024-08-07 +date: 2024-08-08 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 d6b30dd1f719b..8126074467e5d 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: 2024-08-07 +date: 2024-08-08 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 d80956ce98bbe..8c4c54654e9b3 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: 2024-08-07 +date: 2024-08-08 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 9b2f70c1c9ba1..20c2e8fb07a88 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: 2024-08-07 +date: 2024-08-08 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 41407c42ba62e..015a207ae6064 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: 2024-08-07 +date: 2024-08-08 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 a4090339f68d6..a1d355d5be45e 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: 2024-08-07 +date: 2024-08-08 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 d597af2c06e97..16ed3e04515cd 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: 2024-08-07 +date: 2024-08-08 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 8047f05d60655..4ddc1e87119e5 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: 2024-08-07 +date: 2024-08-08 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 15b8a97334453..f62fd6d08b0f2 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: 2024-08-07 +date: 2024-08-08 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 723417fb1b3ef..92ba2d829e8af 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: 2024-08-07 +date: 2024-08-08 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 8eba8ae6c89f1..6be9e99c051b0 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: 2024-08-07 +date: 2024-08-08 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 97d69e1cdf346..7c05687458387 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: 2024-08-07 +date: 2024-08-08 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 96159695cbad5..1a55dc775a017 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: 2024-08-07 +date: 2024-08-08 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 3aceb7f8f7c43..65de75d044b27 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: 2024-08-07 +date: 2024-08-08 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 fc11a11d73662..ac2c8e40b071c 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: 2024-08-07 +date: 2024-08-08 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 be8450bf5f217..93821243288de 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: 2024-08-07 +date: 2024-08-08 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 94f3fcaad4420..b8ba8351950ad 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: 2024-08-07 +date: 2024-08-08 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 a90f773f1f8eb..06f5c5b024a92 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: 2024-08-07 +date: 2024-08-08 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 492ae9d7b58f9..20ed5837f9c1d 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: 2024-08-07 +date: 2024-08-08 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 4db5850ca6236..9ab0ebb9d22b9 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: 2024-08-07 +date: 2024-08-08 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 f1b6b312335d6..c7d858aca4e71 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: 2024-08-07 +date: 2024-08-08 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 4434b3a4e843d..5149ad7026690 100644 --- a/api_docs/kbn_core_http_server.devdocs.json +++ b/api_docs/kbn_core_http_server.devdocs.json @@ -16256,6 +16256,10 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/perform_rule_upgrade_route.ts" }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.ts" + }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/api/create_legacy_notification/route.ts" diff --git a/api_docs/kbn_core_http_server.mdx b/api_docs/kbn_core_http_server.mdx index d6619cddc719a..c692c75e55959 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: 2024-08-07 +date: 2024-08-08 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 94acf6a97f327..9c980bdc6af4d 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: 2024-08-07 +date: 2024-08-08 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 42a67d1a8fb8f..b21cf13bf7240 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: 2024-08-07 +date: 2024-08-08 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 0d424556b669a..9aad28e33b6e5 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: 2024-08-07 +date: 2024-08-08 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 e2e32967897e2..dd023b0bea6eb 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: 2024-08-07 +date: 2024-08-08 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 4240e6410105e..b6ddb752efb91 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: 2024-08-07 +date: 2024-08-08 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 ee69b8a60b455..13f0f8c223382 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: 2024-08-07 +date: 2024-08-08 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 01cad8af14253..c3ccfff0036c5 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: 2024-08-07 +date: 2024-08-08 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 95b827952078e..33d6ca12a78e5 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: 2024-08-07 +date: 2024-08-08 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 2f6bb04d9b85c..b2897c26a6be8 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: 2024-08-07 +date: 2024-08-08 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 892e46019996d..6388a809fdf99 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: 2024-08-07 +date: 2024-08-08 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 6f9361cae0d1a..160be2049ca12 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: 2024-08-07 +date: 2024-08-08 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 fd03bd8833337..b941d5a6a6261 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: 2024-08-07 +date: 2024-08-08 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 639473154e7d9..9c125ded3f1a6 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: 2024-08-07 +date: 2024-08-08 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 0e0600a5510c7..41ce3404a2c4a 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: 2024-08-07 +date: 2024-08-08 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 c22091940475e..dc85930fd990c 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: 2024-08-07 +date: 2024-08-08 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 256d4cc8e2b4f..b2eafcc6a7639 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: 2024-08-07 +date: 2024-08-08 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 538f440978d42..5dfa3a42819a4 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: 2024-08-07 +date: 2024-08-08 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 293d80070451f..46f8a1618a19d 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: 2024-08-07 +date: 2024-08-08 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 ef061c949472f..71979ef44c51b 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: 2024-08-07 +date: 2024-08-08 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 d679291349d5a..f87ac414f2c98 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: 2024-08-07 +date: 2024-08-08 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 864919e815bf7..6291e09855117 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: 2024-08-07 +date: 2024-08-08 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 cbbd92dafd084..fbda7292e618f 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: 2024-08-07 +date: 2024-08-08 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 77c1661fc2e31..89c3071317e64 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: 2024-08-07 +date: 2024-08-08 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 691b01db02342..ec3539518bda0 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: 2024-08-07 +date: 2024-08-08 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 f679e69c11d35..422f46abfca74 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: 2024-08-07 +date: 2024-08-08 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 6c1fa890f6590..65ec6baeabab3 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: 2024-08-07 +date: 2024-08-08 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 0a39e3ae748dc..98affc539016b 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: 2024-08-07 +date: 2024-08-08 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 0bd823aa5da43..65a1ca8d4ecb1 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: 2024-08-07 +date: 2024-08-08 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 856b6a4dcd6e1..b7ff779a30c22 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: 2024-08-07 +date: 2024-08-08 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 74f235d7eba47..7dbf787d93ec6 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: 2024-08-07 +date: 2024-08-08 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 925b955901288..9097db7f7a20f 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: 2024-08-07 +date: 2024-08-08 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 0b736f916cd11..7b887e36ec3c0 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: 2024-08-07 +date: 2024-08-08 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 fc3dc7a8a431a..3e51096b74109 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: 2024-08-07 +date: 2024-08-08 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 c482ffd5785b3..3e94a07faf665 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: 2024-08-07 +date: 2024-08-08 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 aa6a6d5b6b92c..df1748367db1b 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: 2024-08-07 +date: 2024-08-08 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 5f4aff9e49eb8..77a30d20d5162 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: 2024-08-07 +date: 2024-08-08 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_contracts_browser.mdx b/api_docs/kbn_core_plugins_contracts_browser.mdx index 99b6c79f9d089..6252a6f9ea275 100644 --- a/api_docs/kbn_core_plugins_contracts_browser.mdx +++ b/api_docs/kbn_core_plugins_contracts_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-contracts-browser title: "@kbn/core-plugins-contracts-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-contracts-browser plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-contracts-browser'] --- import kbnCorePluginsContractsBrowserObj from './kbn_core_plugins_contracts_browser.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_contracts_server.mdx b/api_docs/kbn_core_plugins_contracts_server.mdx index 521e2a1b134cc..81a2e655fe9ef 100644 --- a/api_docs/kbn_core_plugins_contracts_server.mdx +++ b/api_docs/kbn_core_plugins_contracts_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-contracts-server title: "@kbn/core-plugins-contracts-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-contracts-server plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-contracts-server'] --- import kbnCorePluginsContractsServerObj from './kbn_core_plugins_contracts_server.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_server.mdx b/api_docs/kbn_core_plugins_server.mdx index 3581e014b8ba5..78fb2c07ff576 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: 2024-08-07 +date: 2024-08-08 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 5266e460527c5..6a643acf11c62 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: 2024-08-07 +date: 2024-08-08 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 a7e1b074af5e6..88ae749184503 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: 2024-08-07 +date: 2024-08-08 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 8e7faea580393..c2f1f37181b47 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: 2024-08-07 +date: 2024-08-08 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 d744979f23cf9..a8e11f83270ea 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: 2024-08-07 +date: 2024-08-08 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 b76a33c8cac15..d0a03f4e04c06 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: 2024-08-07 +date: 2024-08-08 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 16c6531517d05..688fb9f3b4c28 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: 2024-08-07 +date: 2024-08-08 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 9dba80615896b..1df480d7154d3 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: 2024-08-07 +date: 2024-08-08 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 4da2589969d9a..124eb03be209b 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: 2024-08-07 +date: 2024-08-08 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 51c7c3b168f16..5db99bcbef48c 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: 2024-08-07 +date: 2024-08-08 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 a70d1b0e42e87..5bb42322a06d5 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: 2024-08-07 +date: 2024-08-08 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 0bab0cb9bbf41..e983c424ebe70 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: 2024-08-07 +date: 2024-08-08 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 985b2209c97a2..ee647d80037f3 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: 2024-08-07 +date: 2024-08-08 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 93003707e2864..22623d9e42aa5 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: 2024-08-07 +date: 2024-08-08 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 0f9d69d450e03..8495eee8c675b 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: 2024-08-07 +date: 2024-08-08 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 cdf5c923f2ded..d5d17c328feee 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: 2024-08-07 +date: 2024-08-08 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 92cb9947f27e1..dd5f4b6d825a9 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: 2024-08-07 +date: 2024-08-08 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 f40e4d4ae49c6..67d9aa4e36f50 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: 2024-08-07 +date: 2024-08-08 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 3dffe34a67ef5..66fbdfff3e06e 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: 2024-08-07 +date: 2024-08-08 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 bdcd9c4db6813..578f863774fd7 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: 2024-08-07 +date: 2024-08-08 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 2b16306fb31c0..01c86eb82bc1d 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: 2024-08-07 +date: 2024-08-08 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 ef569ebc68d9a..6af3fc84b6fbc 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: 2024-08-07 +date: 2024-08-08 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 53302743fe185..049d062806580 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: 2024-08-07 +date: 2024-08-08 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 e2bc6ef0088e9..6d89a1e04b0ad 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: 2024-08-07 +date: 2024-08-08 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 ac0208ff61fe1..23680a2a1e059 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: 2024-08-07 +date: 2024-08-08 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_security_browser.mdx b/api_docs/kbn_core_security_browser.mdx index ef40217a17f95..6032611201c6e 100644 --- a/api_docs/kbn_core_security_browser.mdx +++ b/api_docs/kbn_core_security_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-browser title: "@kbn/core-security-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-browser plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-browser'] --- import kbnCoreSecurityBrowserObj from './kbn_core_security_browser.devdocs.json'; diff --git a/api_docs/kbn_core_security_browser_internal.mdx b/api_docs/kbn_core_security_browser_internal.mdx index 8c08781471ff4..d614db056c796 100644 --- a/api_docs/kbn_core_security_browser_internal.mdx +++ b/api_docs/kbn_core_security_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-browser-internal title: "@kbn/core-security-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-browser-internal plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-browser-internal'] --- import kbnCoreSecurityBrowserInternalObj from './kbn_core_security_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_security_browser_mocks.mdx b/api_docs/kbn_core_security_browser_mocks.mdx index c4122ae5b1991..c53ac907b981a 100644 --- a/api_docs/kbn_core_security_browser_mocks.mdx +++ b/api_docs/kbn_core_security_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-browser-mocks title: "@kbn/core-security-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-browser-mocks plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-browser-mocks'] --- import kbnCoreSecurityBrowserMocksObj from './kbn_core_security_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_security_common.mdx b/api_docs/kbn_core_security_common.mdx index 5d514e39afb8b..6f05f909f33a2 100644 --- a/api_docs/kbn_core_security_common.mdx +++ b/api_docs/kbn_core_security_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-common title: "@kbn/core-security-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-common plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-common'] --- import kbnCoreSecurityCommonObj from './kbn_core_security_common.devdocs.json'; diff --git a/api_docs/kbn_core_security_server.mdx b/api_docs/kbn_core_security_server.mdx index 4c39f069ed171..2ee3d047e1b18 100644 --- a/api_docs/kbn_core_security_server.mdx +++ b/api_docs/kbn_core_security_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-server title: "@kbn/core-security-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-server plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-server'] --- import kbnCoreSecurityServerObj from './kbn_core_security_server.devdocs.json'; diff --git a/api_docs/kbn_core_security_server_internal.mdx b/api_docs/kbn_core_security_server_internal.mdx index 6e573c1294323..b12dbb37f254e 100644 --- a/api_docs/kbn_core_security_server_internal.mdx +++ b/api_docs/kbn_core_security_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-server-internal title: "@kbn/core-security-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-server-internal plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-server-internal'] --- import kbnCoreSecurityServerInternalObj from './kbn_core_security_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_security_server_mocks.mdx b/api_docs/kbn_core_security_server_mocks.mdx index d53053d34c7dd..c4d5ab6a546ad 100644 --- a/api_docs/kbn_core_security_server_mocks.mdx +++ b/api_docs/kbn_core_security_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-server-mocks title: "@kbn/core-security-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-server-mocks plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-server-mocks'] --- import kbnCoreSecurityServerMocksObj from './kbn_core_security_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_status_common.mdx b/api_docs/kbn_core_status_common.mdx index 17a6cd7ea8489..b544592c39ebe 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: 2024-08-07 +date: 2024-08-08 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 8ee8865bab412..9459de56dc967 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: 2024-08-07 +date: 2024-08-08 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 8c71b5811cc88..21afb62ac543f 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: 2024-08-07 +date: 2024-08-08 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 b92cecd5d21e0..e9a49f8b343f0 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: 2024-08-07 +date: 2024-08-08 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 0dbcf370fea52..3f61689026b71 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: 2024-08-07 +date: 2024-08-08 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 2f2e12bd7e4da..dd8798932b574 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: 2024-08-07 +date: 2024-08-08 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 faf194a90fc4f..1d5fcf92cb3d3 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: 2024-08-07 +date: 2024-08-08 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 6de497f3de2c2..43158ffaa622b 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: 2024-08-07 +date: 2024-08-08 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 371b7f6019390..d4f130b1148ea 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: 2024-08-07 +date: 2024-08-08 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 2c530d0fa261f..2dca355e221d3 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: 2024-08-07 +date: 2024-08-08 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 c16581393abd1..f01565ff5ecdc 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: 2024-08-07 +date: 2024-08-08 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 8e4d35e6916b9..f692d9fd2cb47 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: 2024-08-07 +date: 2024-08-08 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 378cea3601120..682e69edf6a2d 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: 2024-08-07 +date: 2024-08-08 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 a11db3e31a131..3e30377fa8757 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: 2024-08-07 +date: 2024-08-08 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 d4f80c59b6beb..0fdc0b3be0fcc 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: 2024-08-07 +date: 2024-08-08 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 b986790ca0e52..97dccbbfe4105 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: 2024-08-07 +date: 2024-08-08 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 34b7173aaeeeb..c11fcfb323cda 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: 2024-08-07 +date: 2024-08-08 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 c479b3e824076..c3d1b9143c822 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: 2024-08-07 +date: 2024-08-08 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 397271bfe183a..2efe024d7317f 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: 2024-08-07 +date: 2024-08-08 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 50e6cd45d94da..37572e60a8970 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: 2024-08-07 +date: 2024-08-08 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 a8f7fbdd140f8..cf037dc1afcbc 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: 2024-08-07 +date: 2024-08-08 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 fa02db4d88ab2..1cd2306ff0716 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: 2024-08-07 +date: 2024-08-08 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 d3de53763336d..9d737f68a867a 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: 2024-08-07 +date: 2024-08-08 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_profile_browser.mdx b/api_docs/kbn_core_user_profile_browser.mdx index 7a701ca3dca85..2e21026c9326f 100644 --- a/api_docs/kbn_core_user_profile_browser.mdx +++ b/api_docs/kbn_core_user_profile_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-browser title: "@kbn/core-user-profile-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-browser plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-browser'] --- import kbnCoreUserProfileBrowserObj from './kbn_core_user_profile_browser.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_browser_internal.mdx b/api_docs/kbn_core_user_profile_browser_internal.mdx index 040d306ceb2a1..79bb962c37d9c 100644 --- a/api_docs/kbn_core_user_profile_browser_internal.mdx +++ b/api_docs/kbn_core_user_profile_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-browser-internal title: "@kbn/core-user-profile-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-browser-internal plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-browser-internal'] --- import kbnCoreUserProfileBrowserInternalObj from './kbn_core_user_profile_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_browser_mocks.mdx b/api_docs/kbn_core_user_profile_browser_mocks.mdx index ee477258908a0..3b3c619b194fb 100644 --- a/api_docs/kbn_core_user_profile_browser_mocks.mdx +++ b/api_docs/kbn_core_user_profile_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-browser-mocks title: "@kbn/core-user-profile-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-browser-mocks plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-browser-mocks'] --- import kbnCoreUserProfileBrowserMocksObj from './kbn_core_user_profile_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_common.mdx b/api_docs/kbn_core_user_profile_common.mdx index 6ce99040351cf..ec3a90c775101 100644 --- a/api_docs/kbn_core_user_profile_common.mdx +++ b/api_docs/kbn_core_user_profile_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-common title: "@kbn/core-user-profile-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-common plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-common'] --- import kbnCoreUserProfileCommonObj from './kbn_core_user_profile_common.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_server.mdx b/api_docs/kbn_core_user_profile_server.mdx index b840d5516bfa3..db4a0516bdfa6 100644 --- a/api_docs/kbn_core_user_profile_server.mdx +++ b/api_docs/kbn_core_user_profile_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-server title: "@kbn/core-user-profile-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-server plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-server'] --- import kbnCoreUserProfileServerObj from './kbn_core_user_profile_server.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_server_internal.mdx b/api_docs/kbn_core_user_profile_server_internal.mdx index e1e3cb8e32112..14f000d7c8fab 100644 --- a/api_docs/kbn_core_user_profile_server_internal.mdx +++ b/api_docs/kbn_core_user_profile_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-server-internal title: "@kbn/core-user-profile-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-server-internal plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-server-internal'] --- import kbnCoreUserProfileServerInternalObj from './kbn_core_user_profile_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_server_mocks.mdx b/api_docs/kbn_core_user_profile_server_mocks.mdx index 25fa2537aa4a1..c0c1f141ffc36 100644 --- a/api_docs/kbn_core_user_profile_server_mocks.mdx +++ b/api_docs/kbn_core_user_profile_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-server-mocks title: "@kbn/core-user-profile-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-server-mocks plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-server-mocks'] --- import kbnCoreUserProfileServerMocksObj from './kbn_core_user_profile_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 449361d51cfe0..f8b8d1631b69a 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: 2024-08-07 +date: 2024-08-08 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_mocks.mdx b/api_docs/kbn_core_user_settings_server_mocks.mdx index 7174e8b04d030..5cc7ea0d2d601 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: 2024-08-07 +date: 2024-08-08 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 19e040684cea1..c96b4764c262c 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: 2024-08-07 +date: 2024-08-08 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 1fcff5f4f1773..836c2475794f6 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto-browser'] --- import kbnCryptoBrowserObj from './kbn_crypto_browser.devdocs.json'; diff --git a/api_docs/kbn_custom_icons.mdx b/api_docs/kbn_custom_icons.mdx index adaedfa8451a8..9ae039d714586 100644 --- a/api_docs/kbn_custom_icons.mdx +++ b/api_docs/kbn_custom_icons.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-custom-icons title: "@kbn/custom-icons" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/custom-icons plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/custom-icons'] --- import kbnCustomIconsObj from './kbn_custom_icons.devdocs.json'; diff --git a/api_docs/kbn_custom_integrations.mdx b/api_docs/kbn_custom_integrations.mdx index 42f14ead2597e..8226142b1cb8c 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: 2024-08-07 +date: 2024-08-08 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 4c5faee558b8f..69eca48ec2380 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cypress-config'] --- import kbnCypressConfigObj from './kbn_cypress_config.devdocs.json'; diff --git a/api_docs/kbn_data_forge.mdx b/api_docs/kbn_data_forge.mdx index a9a92243c577f..509ca548d4926 100644 --- a/api_docs/kbn_data_forge.mdx +++ b/api_docs/kbn_data_forge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-data-forge title: "@kbn/data-forge" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/data-forge plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/data-forge'] --- import kbnDataForgeObj from './kbn_data_forge.devdocs.json'; diff --git a/api_docs/kbn_data_service.mdx b/api_docs/kbn_data_service.mdx index 6e35f51a6a452..555d0dd879966 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/data-service'] --- import kbnDataServiceObj from './kbn_data_service.devdocs.json'; diff --git a/api_docs/kbn_data_stream_adapter.mdx b/api_docs/kbn_data_stream_adapter.mdx index 5474b6d35da28..d2886f7060f10 100644 --- a/api_docs/kbn_data_stream_adapter.mdx +++ b/api_docs/kbn_data_stream_adapter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-data-stream-adapter title: "@kbn/data-stream-adapter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/data-stream-adapter plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/data-stream-adapter'] --- import kbnDataStreamAdapterObj from './kbn_data_stream_adapter.devdocs.json'; diff --git a/api_docs/kbn_data_view_utils.mdx b/api_docs/kbn_data_view_utils.mdx index 33aa8ef98671e..16c548f5329d0 100644 --- a/api_docs/kbn_data_view_utils.mdx +++ b/api_docs/kbn_data_view_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-data-view-utils title: "@kbn/data-view-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/data-view-utils plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/data-view-utils'] --- import kbnDataViewUtilsObj from './kbn_data_view_utils.devdocs.json'; diff --git a/api_docs/kbn_datemath.mdx b/api_docs/kbn_datemath.mdx index a530d56072bb9..bcb6431d30aa1 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: 2024-08-07 +date: 2024-08-08 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 8c9c9312f95ee..8bec8b7018321 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: 2024-08-07 +date: 2024-08-08 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 91c65c29d5d77..8fc14399c24d8 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-devtools'] --- import kbnDeeplinksDevtoolsObj from './kbn_deeplinks_devtools.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_fleet.mdx b/api_docs/kbn_deeplinks_fleet.mdx index de6ecc8c9b539..0db6a0b7844ee 100644 --- a/api_docs/kbn_deeplinks_fleet.mdx +++ b/api_docs/kbn_deeplinks_fleet.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-fleet title: "@kbn/deeplinks-fleet" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-fleet plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-fleet'] --- import kbnDeeplinksFleetObj from './kbn_deeplinks_fleet.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_management.mdx b/api_docs/kbn_deeplinks_management.mdx index 1ecc7db5a0ba7..704827f5339be 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: 2024-08-07 +date: 2024-08-08 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 cd1023a9ca9d5..d800f2d397e1b 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: 2024-08-07 +date: 2024-08-08 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 e6c9a91c6e116..f5cdb192e922a 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: 2024-08-07 +date: 2024-08-08 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 19a3eea207c17..45f4196169034 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-search'] --- import kbnDeeplinksSearchObj from './kbn_deeplinks_search.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_security.mdx b/api_docs/kbn_deeplinks_security.mdx index 8595ffd36bc0d..22f0092e077fa 100644 --- a/api_docs/kbn_deeplinks_security.mdx +++ b/api_docs/kbn_deeplinks_security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-security title: "@kbn/deeplinks-security" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-security plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-security'] --- import kbnDeeplinksSecurityObj from './kbn_deeplinks_security.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_shared.mdx b/api_docs/kbn_deeplinks_shared.mdx index f367b9d3389bf..be50b55a13a8f 100644 --- a/api_docs/kbn_deeplinks_shared.mdx +++ b/api_docs/kbn_deeplinks_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-shared title: "@kbn/deeplinks-shared" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-shared plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-shared'] --- import kbnDeeplinksSharedObj from './kbn_deeplinks_shared.devdocs.json'; diff --git a/api_docs/kbn_default_nav_analytics.mdx b/api_docs/kbn_default_nav_analytics.mdx index bdfac735d0b65..6356d7f131f8d 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: 2024-08-07 +date: 2024-08-08 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 cd46109698633..01453ecb5681f 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: 2024-08-07 +date: 2024-08-08 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 fd36348d22a3f..1d1ca54ef4758 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: 2024-08-07 +date: 2024-08-08 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 ccf7c5b02c2b6..47cfbb922ed05 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: 2024-08-07 +date: 2024-08-08 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 1f0bc36ee1147..0ba93c08205a6 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: 2024-08-07 +date: 2024-08-08 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 94d1c58b7330e..501f3ff24603d 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: 2024-08-07 +date: 2024-08-08 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 81eb9706e3100..9fd07049bee70 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: 2024-08-07 +date: 2024-08-08 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 67669f4170a1f..e3e2eaf529ede 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: 2024-08-07 +date: 2024-08-08 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 1840d21457b18..d7ba299ff0796 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/discover-utils'] --- import kbnDiscoverUtilsObj from './kbn_discover_utils.devdocs.json'; diff --git a/api_docs/kbn_doc_links.mdx b/api_docs/kbn_doc_links.mdx index 45e728b9a0a54..ca931b5a2b4e9 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: 2024-08-07 +date: 2024-08-08 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 689ff37dd9f80..efe5bb9a757d9 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: 2024-08-07 +date: 2024-08-08 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 05b72b3bbef2c..2390fa9fbdac7 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: 2024-08-07 +date: 2024-08-08 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 e48007dbffa70..09a0e076adfdc 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ebt-tools'] --- import kbnEbtToolsObj from './kbn_ebt_tools.devdocs.json'; diff --git a/api_docs/kbn_ecs_data_quality_dashboard.mdx b/api_docs/kbn_ecs_data_quality_dashboard.mdx index f1bfcd4f15224..596c3b16fc82a 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: 2024-08-07 +date: 2024-08-08 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_agent_utils.mdx b/api_docs/kbn_elastic_agent_utils.mdx index 8cd02274cf97f..53b45fc342d46 100644 --- a/api_docs/kbn_elastic_agent_utils.mdx +++ b/api_docs/kbn_elastic_agent_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-elastic-agent-utils title: "@kbn/elastic-agent-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/elastic-agent-utils plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/elastic-agent-utils'] --- import kbnElasticAgentUtilsObj from './kbn_elastic_agent_utils.devdocs.json'; diff --git a/api_docs/kbn_elastic_assistant.mdx b/api_docs/kbn_elastic_assistant.mdx index 055eceb5522e4..0fc3d37b2675e 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/elastic-assistant'] --- import kbnElasticAssistantObj from './kbn_elastic_assistant.devdocs.json'; diff --git a/api_docs/kbn_elastic_assistant_common.mdx b/api_docs/kbn_elastic_assistant_common.mdx index e227d3dc16245..5e90b15ce4a17 100644 --- a/api_docs/kbn_elastic_assistant_common.mdx +++ b/api_docs/kbn_elastic_assistant_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-elastic-assistant-common title: "@kbn/elastic-assistant-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/elastic-assistant-common plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/elastic-assistant-common'] --- import kbnElasticAssistantCommonObj from './kbn_elastic_assistant_common.devdocs.json'; diff --git a/api_docs/kbn_entities_schema.mdx b/api_docs/kbn_entities_schema.mdx index 0108dd151fe7d..3a5daa5b0ba90 100644 --- a/api_docs/kbn_entities_schema.mdx +++ b/api_docs/kbn_entities_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-entities-schema title: "@kbn/entities-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/entities-schema plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/entities-schema'] --- import kbnEntitiesSchemaObj from './kbn_entities_schema.devdocs.json'; diff --git a/api_docs/kbn_es.devdocs.json b/api_docs/kbn_es.devdocs.json index 6fad581f61b92..96bcb65c45764 100644 --- a/api_docs/kbn_es.devdocs.json +++ b/api_docs/kbn_es.devdocs.json @@ -942,6 +942,18 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/es", + "id": "def-common.STATEFUL_ROLES_ROOT_PATH", + "type": "string", + "tags": [], + "label": "STATEFUL_ROLES_ROOT_PATH", + "description": [], + "path": "packages/kbn-es/src/paths.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/es", "id": "def-common.SYSTEM_INDICES_SUPERUSER", diff --git a/api_docs/kbn_es.mdx b/api_docs/kbn_es.mdx index ebbc2cb7ffc30..9a11e984c1167 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es'] --- import kbnEsObj from './kbn_es.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kiban | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 54 | 0 | 39 | 7 | +| 55 | 0 | 40 | 7 | ## Common diff --git a/api_docs/kbn_es_archiver.mdx b/api_docs/kbn_es_archiver.mdx index 62b6418c24d3b..15d5e76d6d7b0 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: 2024-08-07 +date: 2024-08-08 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 d6403b8f2ef83..b9e234b52a368 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: 2024-08-07 +date: 2024-08-08 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 1bd82f19113ef..a2cd69e078253 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: 2024-08-07 +date: 2024-08-08 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 dc0be98566456..a47e034f3145c 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: 2024-08-07 +date: 2024-08-08 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 1bf6abbfcbbc6..43b7c04d55360 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/eslint-plugin-imports'] --- import kbnEslintPluginImportsObj from './kbn_eslint_plugin_imports.devdocs.json'; diff --git a/api_docs/kbn_esql_ast.mdx b/api_docs/kbn_esql_ast.mdx index e3afc1ccfa311..95b954bf9d7c9 100644 --- a/api_docs/kbn_esql_ast.mdx +++ b/api_docs/kbn_esql_ast.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-esql-ast title: "@kbn/esql-ast" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/esql-ast plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/esql-ast'] --- import kbnEsqlAstObj from './kbn_esql_ast.devdocs.json'; diff --git a/api_docs/kbn_esql_utils.mdx b/api_docs/kbn_esql_utils.mdx index 05cd9c06aa0af..19ea1eacd5abb 100644 --- a/api_docs/kbn_esql_utils.mdx +++ b/api_docs/kbn_esql_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-esql-utils title: "@kbn/esql-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/esql-utils plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/esql-utils'] --- import kbnEsqlUtilsObj from './kbn_esql_utils.devdocs.json'; diff --git a/api_docs/kbn_esql_validation_autocomplete.mdx b/api_docs/kbn_esql_validation_autocomplete.mdx index d454e4497712d..4973f2c8fedf4 100644 --- a/api_docs/kbn_esql_validation_autocomplete.mdx +++ b/api_docs/kbn_esql_validation_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-esql-validation-autocomplete title: "@kbn/esql-validation-autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/esql-validation-autocomplete plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/esql-validation-autocomplete'] --- import kbnEsqlValidationAutocompleteObj from './kbn_esql_validation_autocomplete.devdocs.json'; diff --git a/api_docs/kbn_event_annotation_common.mdx b/api_docs/kbn_event_annotation_common.mdx index 793684ebc0d34..cdc5f632b8708 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: 2024-08-07 +date: 2024-08-08 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 79c3ba2da234f..ca49285f48c75 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: 2024-08-07 +date: 2024-08-08 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 dddc35d961acc..59e53d57c40ca 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: 2024-08-07 +date: 2024-08-08 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 9c925d8843d98..e29614df62c0a 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: 2024-08-07 +date: 2024-08-08 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 5d8655834a585..bf1a06302b4df 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: 2024-08-07 +date: 2024-08-08 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 9ecc90616d46c..7842fe4085939 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: 2024-08-07 +date: 2024-08-08 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_formatters.mdx b/api_docs/kbn_formatters.mdx index e5d5f2a064a9c..428a819d7c550 100644 --- a/api_docs/kbn_formatters.mdx +++ b/api_docs/kbn_formatters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-formatters title: "@kbn/formatters" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/formatters plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/formatters'] --- import kbnFormattersObj from './kbn_formatters.devdocs.json'; diff --git a/api_docs/kbn_ftr_common_functional_services.devdocs.json b/api_docs/kbn_ftr_common_functional_services.devdocs.json index 8b01b5e5f2d67..9c00accd18000 100644 --- a/api_docs/kbn_ftr_common_functional_services.devdocs.json +++ b/api_docs/kbn_ftr_common_functional_services.devdocs.json @@ -434,7 +434,64 @@ } ], "functions": [], - "interfaces": [], + "interfaces": [ + { + "parentPluginId": "@kbn/ftr-common-functional-services", + "id": "def-common.RoleCredentials", + "type": "Interface", + "tags": [], + "label": "RoleCredentials", + "description": [], + "path": "packages/kbn-ftr-common-functional-services/services/saml_auth/saml_auth_provider.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ftr-common-functional-services", + "id": "def-common.RoleCredentials.apiKey", + "type": "Object", + "tags": [], + "label": "apiKey", + "description": [], + "signature": [ + "{ id: string; name: string; }" + ], + "path": "packages/kbn-ftr-common-functional-services/services/saml_auth/saml_auth_provider.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ftr-common-functional-services", + "id": "def-common.RoleCredentials.apiKeyHeader", + "type": "Object", + "tags": [], + "label": "apiKeyHeader", + "description": [], + "signature": [ + "{ Authorization: string; }" + ], + "path": "packages/kbn-ftr-common-functional-services/services/saml_auth/saml_auth_provider.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ftr-common-functional-services", + "id": "def-common.RoleCredentials.cookieHeader", + "type": "Object", + "tags": [], + "label": "cookieHeader", + "description": [], + "signature": [ + "{ Cookie: string; }" + ], + "path": "packages/kbn-ftr-common-functional-services/services/saml_auth/saml_auth_provider.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], "enums": [], "misc": [ { @@ -541,7 +598,49 @@ "node_modules/@types/supertest/lib/agent", "<", "SuperTestStatic", - ".Test>; }, {}, ProvidedTypeMap<{ es: ({ getService }: ", + ".Test>; samlAuth: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => Promise<{ getInteractiveUserSessionCookieWithRoleScope(role: string): Promise; getM2MApiCredentialsWithRoleScope(role: string): Promise<{ Cookie: string; }>; getEmail(role: string): Promise; getUserData(role: string): Promise<", + "UserProfile", + ">; createM2mApiKeyWithDefaultRoleScope(): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; createM2mApiKeyWithRoleScope(role: string): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; invalidateM2mApiKeyWithRoleScope(roleCredentials: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + "): Promise; getCommonRequestHeader(): { 'kbn-xsrf': string; }; getInternalRequestHeader(): ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.InternalRequestHeader", + "text": "InternalRequestHeader" + }, + "; DEFAULT_ROLE: string; }>; }, {}, ProvidedTypeMap<{ es: ({ getService }: ", { "pluginId": "@kbn/ftr-common-functional-services", "scope": "common", @@ -603,13 +702,70 @@ "node_modules/@types/supertest/lib/agent", "<", "SuperTestStatic", - ".Test>; }>, ProvidedTypeMap<{}>>" + ".Test>; samlAuth: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => Promise<{ getInteractiveUserSessionCookieWithRoleScope(role: string): Promise; getM2MApiCredentialsWithRoleScope(role: string): Promise<{ Cookie: string; }>; getEmail(role: string): Promise; getUserData(role: string): Promise<", + "UserProfile", + ">; createM2mApiKeyWithDefaultRoleScope(): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; createM2mApiKeyWithRoleScope(role: string): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; invalidateM2mApiKeyWithRoleScope(roleCredentials: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + "): Promise; getCommonRequestHeader(): { 'kbn-xsrf': string; }; getInternalRequestHeader(): ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.InternalRequestHeader", + "text": "InternalRequestHeader" + }, + "; DEFAULT_ROLE: string; }>; }>, ProvidedTypeMap<{}>>" ], "path": "packages/kbn-ftr-common-functional-services/services/ftr_provider_context.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/ftr-common-functional-services", + "id": "def-common.InternalRequestHeader", + "type": "Type", + "tags": [], + "label": "InternalRequestHeader", + "description": [], + "signature": [ + "{ 'kbn-xsrf': string; } | { 'x-elastic-internal-origin': string; 'kbn-xsrf': string; }" + ], + "path": "packages/kbn-ftr-common-functional-services/services/saml_auth/default_request_headers.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/ftr-common-functional-services", "id": "def-common.KibanaServer", @@ -763,7 +919,49 @@ "node_modules/@types/supertest/lib/agent", "<", "SuperTestStatic", - ".Test>; }, {}, ProvidedTypeMap<{ es: ({ getService }: ", + ".Test>; samlAuth: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => Promise<{ getInteractiveUserSessionCookieWithRoleScope(role: string): Promise; getM2MApiCredentialsWithRoleScope(role: string): Promise<{ Cookie: string; }>; getEmail(role: string): Promise; getUserData(role: string): Promise<", + "UserProfile", + ">; createM2mApiKeyWithDefaultRoleScope(): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; createM2mApiKeyWithRoleScope(role: string): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; invalidateM2mApiKeyWithRoleScope(roleCredentials: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + "): Promise; getCommonRequestHeader(): { 'kbn-xsrf': string; }; getInternalRequestHeader(): ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.InternalRequestHeader", + "text": "InternalRequestHeader" + }, + "; DEFAULT_ROLE: string; }>; }, {}, ProvidedTypeMap<{ es: ({ getService }: ", { "pluginId": "@kbn/ftr-common-functional-services", "scope": "common", @@ -825,7 +1023,49 @@ "node_modules/@types/supertest/lib/agent", "<", "SuperTestStatic", - ".Test>; }>, ProvidedTypeMap<{}>>" + ".Test>; samlAuth: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => Promise<{ getInteractiveUserSessionCookieWithRoleScope(role: string): Promise; getM2MApiCredentialsWithRoleScope(role: string): Promise<{ Cookie: string; }>; getEmail(role: string): Promise; getUserData(role: string): Promise<", + "UserProfile", + ">; createM2mApiKeyWithDefaultRoleScope(): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; createM2mApiKeyWithRoleScope(role: string): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; invalidateM2mApiKeyWithRoleScope(roleCredentials: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + "): Promise; getCommonRequestHeader(): { 'kbn-xsrf': string; }; getInternalRequestHeader(): ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.InternalRequestHeader", + "text": "InternalRequestHeader" + }, + "; DEFAULT_ROLE: string; }>; }>, ProvidedTypeMap<{}>>" ], "path": "packages/kbn-ftr-common-functional-services/services/es.ts", "deprecated": false, @@ -940,7 +1180,49 @@ "node_modules/@types/supertest/lib/agent", "<", "SuperTestStatic", - ".Test>; }, {}, ProvidedTypeMap<{ es: ({ getService }: ", + ".Test>; samlAuth: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => Promise<{ getInteractiveUserSessionCookieWithRoleScope(role: string): Promise; getM2MApiCredentialsWithRoleScope(role: string): Promise<{ Cookie: string; }>; getEmail(role: string): Promise; getUserData(role: string): Promise<", + "UserProfile", + ">; createM2mApiKeyWithDefaultRoleScope(): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; createM2mApiKeyWithRoleScope(role: string): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; invalidateM2mApiKeyWithRoleScope(roleCredentials: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + "): Promise; getCommonRequestHeader(): { 'kbn-xsrf': string; }; getInternalRequestHeader(): ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.InternalRequestHeader", + "text": "InternalRequestHeader" + }, + "; DEFAULT_ROLE: string; }>; }, {}, ProvidedTypeMap<{ es: ({ getService }: ", { "pluginId": "@kbn/ftr-common-functional-services", "scope": "common", @@ -1002,7 +1284,49 @@ "node_modules/@types/supertest/lib/agent", "<", "SuperTestStatic", - ".Test>; }>, ProvidedTypeMap<{}>>" + ".Test>; samlAuth: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => Promise<{ getInteractiveUserSessionCookieWithRoleScope(role: string): Promise; getM2MApiCredentialsWithRoleScope(role: string): Promise<{ Cookie: string; }>; getEmail(role: string): Promise; getUserData(role: string): Promise<", + "UserProfile", + ">; createM2mApiKeyWithDefaultRoleScope(): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; createM2mApiKeyWithRoleScope(role: string): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; invalidateM2mApiKeyWithRoleScope(roleCredentials: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + "): Promise; getCommonRequestHeader(): { 'kbn-xsrf': string; }; getInternalRequestHeader(): ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.InternalRequestHeader", + "text": "InternalRequestHeader" + }, + "; DEFAULT_ROLE: string; }>; }>, ProvidedTypeMap<{}>>" ], "path": "packages/kbn-ftr-common-functional-services/services/kibana_server/kibana_server.ts", "deprecated": false, @@ -1117,7 +1441,49 @@ "node_modules/@types/supertest/lib/agent", "<", "SuperTestStatic", - ".Test>; }, {}, ProvidedTypeMap<{ es: ({ getService }: ", + ".Test>; samlAuth: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => Promise<{ getInteractiveUserSessionCookieWithRoleScope(role: string): Promise; getM2MApiCredentialsWithRoleScope(role: string): Promise<{ Cookie: string; }>; getEmail(role: string): Promise; getUserData(role: string): Promise<", + "UserProfile", + ">; createM2mApiKeyWithDefaultRoleScope(): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; createM2mApiKeyWithRoleScope(role: string): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; invalidateM2mApiKeyWithRoleScope(roleCredentials: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + "): Promise; getCommonRequestHeader(): { 'kbn-xsrf': string; }; getInternalRequestHeader(): ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.InternalRequestHeader", + "text": "InternalRequestHeader" + }, + "; DEFAULT_ROLE: string; }>; }, {}, ProvidedTypeMap<{ es: ({ getService }: ", { "pluginId": "@kbn/ftr-common-functional-services", "scope": "common", @@ -1179,21 +1545,63 @@ "node_modules/@types/supertest/lib/agent", "<", "SuperTestStatic", - ".Test>; }>, ProvidedTypeMap<{}>>" - ], - "path": "packages/kbn-ftr-common-functional-services/services/es_archiver.ts", - "deprecated": false, - "trackAdoption": false - } - ] - }, - { - "parentPluginId": "@kbn/ftr-common-functional-services", - "id": "def-common.services.retry", - "type": "Object", - "tags": [], - "label": "retry", - "description": [], + ".Test>; samlAuth: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => Promise<{ getInteractiveUserSessionCookieWithRoleScope(role: string): Promise; getM2MApiCredentialsWithRoleScope(role: string): Promise<{ Cookie: string; }>; getEmail(role: string): Promise; getUserData(role: string): Promise<", + "UserProfile", + ">; createM2mApiKeyWithDefaultRoleScope(): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; createM2mApiKeyWithRoleScope(role: string): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; invalidateM2mApiKeyWithRoleScope(roleCredentials: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + "): Promise; getCommonRequestHeader(): { 'kbn-xsrf': string; }; getInternalRequestHeader(): ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.InternalRequestHeader", + "text": "InternalRequestHeader" + }, + "; DEFAULT_ROLE: string; }>; }>, ProvidedTypeMap<{}>>" + ], + "path": "packages/kbn-ftr-common-functional-services/services/es_archiver.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/ftr-common-functional-services", + "id": "def-common.services.retry", + "type": "Object", + "tags": [], + "label": "retry", + "description": [], "signature": [ "typeof ", { @@ -1312,7 +1720,49 @@ "node_modules/@types/supertest/lib/agent", "<", "SuperTestStatic", - ".Test>; }, {}, ProvidedTypeMap<{ es: ({ getService }: ", + ".Test>; samlAuth: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => Promise<{ getInteractiveUserSessionCookieWithRoleScope(role: string): Promise; getM2MApiCredentialsWithRoleScope(role: string): Promise<{ Cookie: string; }>; getEmail(role: string): Promise; getUserData(role: string): Promise<", + "UserProfile", + ">; createM2mApiKeyWithDefaultRoleScope(): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; createM2mApiKeyWithRoleScope(role: string): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; invalidateM2mApiKeyWithRoleScope(roleCredentials: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + "): Promise; getCommonRequestHeader(): { 'kbn-xsrf': string; }; getInternalRequestHeader(): ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.InternalRequestHeader", + "text": "InternalRequestHeader" + }, + "; DEFAULT_ROLE: string; }>; }, {}, ProvidedTypeMap<{ es: ({ getService }: ", { "pluginId": "@kbn/ftr-common-functional-services", "scope": "common", @@ -1374,13 +1824,343 @@ "node_modules/@types/supertest/lib/agent", "<", "SuperTestStatic", - ".Test>; }>, ProvidedTypeMap<{}>>" + ".Test>; samlAuth: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => Promise<{ getInteractiveUserSessionCookieWithRoleScope(role: string): Promise; getM2MApiCredentialsWithRoleScope(role: string): Promise<{ Cookie: string; }>; getEmail(role: string): Promise; getUserData(role: string): Promise<", + "UserProfile", + ">; createM2mApiKeyWithDefaultRoleScope(): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; createM2mApiKeyWithRoleScope(role: string): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; invalidateM2mApiKeyWithRoleScope(roleCredentials: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + "): Promise; getCommonRequestHeader(): { 'kbn-xsrf': string; }; getInternalRequestHeader(): ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.InternalRequestHeader", + "text": "InternalRequestHeader" + }, + "; DEFAULT_ROLE: string; }>; }>, ProvidedTypeMap<{}>>" ], "path": "packages/kbn-ftr-common-functional-services/services/supertest_without_auth.ts", "deprecated": false, "trackAdoption": false } ] + }, + { + "parentPluginId": "@kbn/ftr-common-functional-services", + "id": "def-common.services.samlAuth", + "type": "Function", + "tags": [], + "label": "samlAuth", + "description": [], + "signature": [ + "({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => Promise<{ getInteractiveUserSessionCookieWithRoleScope(role: string): Promise; getM2MApiCredentialsWithRoleScope(role: string): Promise<{ Cookie: string; }>; getEmail(role: string): Promise; getUserData(role: string): Promise<", + "UserProfile", + ">; createM2mApiKeyWithDefaultRoleScope(): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; createM2mApiKeyWithRoleScope(role: string): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; invalidateM2mApiKeyWithRoleScope(roleCredentials: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + "): Promise; getCommonRequestHeader(): { 'kbn-xsrf': string; }; getInternalRequestHeader(): ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.InternalRequestHeader", + "text": "InternalRequestHeader" + }, + "; DEFAULT_ROLE: string; }>" + ], + "path": "packages/kbn-ftr-common-functional-services/services/all.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/ftr-common-functional-services", + "id": "def-common.services.samlAuth.$1", + "type": "Object", + "tags": [], + "label": "__0", + "description": [], + "signature": [ + { + "pluginId": "@kbn/test", + "scope": "common", + "docId": "kibKbnTestPluginApi", + "section": "def-common.GenericFtrProviderContext", + "text": "GenericFtrProviderContext" + }, + "<{ es: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => ", + "default", + "; kibanaServer: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => ", + { + "pluginId": "@kbn/test", + "scope": "common", + "docId": "kibKbnTestPluginApi", + "section": "def-common.KbnClient", + "text": "KbnClient" + }, + "; esArchiver: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => ", + { + "pluginId": "@kbn/es-archiver", + "scope": "common", + "docId": "kibKbnEsArchiverPluginApi", + "section": "def-common.EsArchiver", + "text": "EsArchiver" + }, + "; retry: typeof ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RetryService", + "text": "RetryService" + }, + "; supertestWithoutAuth: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => ", + "node_modules/@types/supertest/lib/agent", + "<", + "SuperTestStatic", + ".Test>; samlAuth: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => Promise<{ getInteractiveUserSessionCookieWithRoleScope(role: string): Promise; getM2MApiCredentialsWithRoleScope(role: string): Promise<{ Cookie: string; }>; getEmail(role: string): Promise; getUserData(role: string): Promise<", + "UserProfile", + ">; createM2mApiKeyWithDefaultRoleScope(): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; createM2mApiKeyWithRoleScope(role: string): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; invalidateM2mApiKeyWithRoleScope(roleCredentials: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + "): Promise; getCommonRequestHeader(): { 'kbn-xsrf': string; }; getInternalRequestHeader(): ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.InternalRequestHeader", + "text": "InternalRequestHeader" + }, + "; DEFAULT_ROLE: string; }>; }, {}, ProvidedTypeMap<{ es: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => ", + "default", + "; kibanaServer: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => ", + { + "pluginId": "@kbn/test", + "scope": "common", + "docId": "kibKbnTestPluginApi", + "section": "def-common.KbnClient", + "text": "KbnClient" + }, + "; esArchiver: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => ", + { + "pluginId": "@kbn/es-archiver", + "scope": "common", + "docId": "kibKbnEsArchiverPluginApi", + "section": "def-common.EsArchiver", + "text": "EsArchiver" + }, + "; retry: typeof ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RetryService", + "text": "RetryService" + }, + "; supertestWithoutAuth: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => ", + "node_modules/@types/supertest/lib/agent", + "<", + "SuperTestStatic", + ".Test>; samlAuth: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => Promise<{ getInteractiveUserSessionCookieWithRoleScope(role: string): Promise; getM2MApiCredentialsWithRoleScope(role: string): Promise<{ Cookie: string; }>; getEmail(role: string): Promise; getUserData(role: string): Promise<", + "UserProfile", + ">; createM2mApiKeyWithDefaultRoleScope(): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; createM2mApiKeyWithRoleScope(role: string): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; invalidateM2mApiKeyWithRoleScope(roleCredentials: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + "): Promise; getCommonRequestHeader(): { 'kbn-xsrf': string; }; getInternalRequestHeader(): ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.InternalRequestHeader", + "text": "InternalRequestHeader" + }, + "; DEFAULT_ROLE: string; }>; }>, ProvidedTypeMap<{}>>" + ], + "path": "packages/kbn-ftr-common-functional-services/services/saml_auth/saml_auth_provider.ts", + "deprecated": false, + "trackAdoption": false + } + ] } ], "initialIsOpen": false diff --git a/api_docs/kbn_ftr_common_functional_services.mdx b/api_docs/kbn_ftr_common_functional_services.mdx index 2943039f157c4..18dab808bd1d4 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ftr-common-functional-services'] --- import kbnFtrCommonFunctionalServicesObj from './kbn_ftr_common_functional_services.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kiban | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 39 | 0 | 24 | 1 | +| 46 | 0 | 31 | 1 | ## Common @@ -31,6 +31,9 @@ Contact [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kiban ### Classes +### Interfaces + + ### Consts, variables and types diff --git a/api_docs/kbn_ftr_common_functional_ui_services.mdx b/api_docs/kbn_ftr_common_functional_ui_services.mdx index 036ab594963ed..2a318b44c872a 100644 --- a/api_docs/kbn_ftr_common_functional_ui_services.mdx +++ b/api_docs/kbn_ftr_common_functional_ui_services.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ftr-common-functional-ui-services title: "@kbn/ftr-common-functional-ui-services" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ftr-common-functional-ui-services plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ftr-common-functional-ui-services'] --- import kbnFtrCommonFunctionalUiServicesObj from './kbn_ftr_common_functional_ui_services.devdocs.json'; diff --git a/api_docs/kbn_generate.mdx b/api_docs/kbn_generate.mdx index 7fd38c3e4cfd8..b8901fe9608fc 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: 2024-08-07 +date: 2024-08-08 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 acadcebb1e467..cf003b43a8e91 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: 2024-08-07 +date: 2024-08-08 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 e101969f35fb2..8568819720032 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate-csv'] --- import kbnGenerateCsvObj from './kbn_generate_csv.devdocs.json'; diff --git a/api_docs/kbn_grouping.mdx b/api_docs/kbn_grouping.mdx index c66ebf3af6402..4443baabaae68 100644 --- a/api_docs/kbn_grouping.mdx +++ b/api_docs/kbn_grouping.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-grouping title: "@kbn/grouping" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/grouping plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/grouping'] --- import kbnGroupingObj from './kbn_grouping.devdocs.json'; diff --git a/api_docs/kbn_guided_onboarding.mdx b/api_docs/kbn_guided_onboarding.mdx index fb31793c6d291..3f28a58ae2f49 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: 2024-08-07 +date: 2024-08-08 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 44d7870f7ad62..6504d460950f9 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: 2024-08-07 +date: 2024-08-08 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 457a333325581..11725e7233c0e 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: 2024-08-07 +date: 2024-08-08 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 b0d3765b231c6..b15b4e12b47e0 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: 2024-08-07 +date: 2024-08-08 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 e8ae9111027c0..ff731def1a283 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: 2024-08-07 +date: 2024-08-08 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 491eba641b9ac..bf543fa26ee53 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: 2024-08-07 +date: 2024-08-08 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 8e7eba82afbf1..c9d6d8ecb9275 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: 2024-08-07 +date: 2024-08-08 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 b22f1b92ffac5..c589731ba6615 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: 2024-08-07 +date: 2024-08-08 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 21b2953a94a4a..ac1d8fa676612 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/import-resolver'] --- import kbnImportResolverObj from './kbn_import_resolver.devdocs.json'; diff --git a/api_docs/kbn_index_management.mdx b/api_docs/kbn_index_management.mdx index ac2cecfffd76d..33d4d14500c70 100644 --- a/api_docs/kbn_index_management.mdx +++ b/api_docs/kbn_index_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-index-management title: "@kbn/index-management" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/index-management plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/index-management'] --- import kbnIndexManagementObj from './kbn_index_management.devdocs.json'; diff --git a/api_docs/kbn_inference_integration_flyout.mdx b/api_docs/kbn_inference_integration_flyout.mdx index 6c4df8186ef31..ddebb4164fcb7 100644 --- a/api_docs/kbn_inference_integration_flyout.mdx +++ b/api_docs/kbn_inference_integration_flyout.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-inference_integration_flyout title: "@kbn/inference_integration_flyout" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/inference_integration_flyout plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/inference_integration_flyout'] --- import kbnInferenceIntegrationFlyoutObj from './kbn_inference_integration_flyout.devdocs.json'; diff --git a/api_docs/kbn_infra_forge.mdx b/api_docs/kbn_infra_forge.mdx index 5e2fdc0b6021d..6cbd9a39dc92a 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: 2024-08-07 +date: 2024-08-08 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 cddc7309102c7..67048dc4fbf1d 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: 2024-08-07 +date: 2024-08-08 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 9d6b4b8a79a6e..fbea40e454929 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/io-ts-utils'] --- import kbnIoTsUtilsObj from './kbn_io_ts_utils.devdocs.json'; diff --git a/api_docs/kbn_ipynb.mdx b/api_docs/kbn_ipynb.mdx index 76fd5007099b5..f62adb8bf4c67 100644 --- a/api_docs/kbn_ipynb.mdx +++ b/api_docs/kbn_ipynb.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ipynb title: "@kbn/ipynb" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ipynb plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ipynb'] --- import kbnIpynbObj from './kbn_ipynb.devdocs.json'; diff --git a/api_docs/kbn_jest_serializers.mdx b/api_docs/kbn_jest_serializers.mdx index 7e30310f681ca..54f223c041ffb 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: 2024-08-07 +date: 2024-08-08 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 2e11afe629c37..553cb25e9ddba 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: 2024-08-07 +date: 2024-08-08 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 1f8599833d0fe..8938147d160ad 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/json-ast'] --- import kbnJsonAstObj from './kbn_json_ast.devdocs.json'; diff --git a/api_docs/kbn_json_schemas.mdx b/api_docs/kbn_json_schemas.mdx index 9a7dac2e9428e..12123e4afc91b 100644 --- a/api_docs/kbn_json_schemas.mdx +++ b/api_docs/kbn_json_schemas.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-json-schemas title: "@kbn/json-schemas" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/json-schemas plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/json-schemas'] --- import kbnJsonSchemasObj from './kbn_json_schemas.devdocs.json'; diff --git a/api_docs/kbn_kibana_manifest_schema.mdx b/api_docs/kbn_kibana_manifest_schema.mdx index 24a416af94a52..848e59fdc8ef6 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: 2024-08-07 +date: 2024-08-08 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 520cba29a4ea4..907d946361115 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: 2024-08-07 +date: 2024-08-08 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 2323a774a3144..ff83eaf17488b 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/lens-embeddable-utils'] --- import kbnLensEmbeddableUtilsObj from './kbn_lens_embeddable_utils.devdocs.json'; diff --git a/api_docs/kbn_lens_formula_docs.mdx b/api_docs/kbn_lens_formula_docs.mdx index 6cd72b38cabb8..43c92ee278fd1 100644 --- a/api_docs/kbn_lens_formula_docs.mdx +++ b/api_docs/kbn_lens_formula_docs.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-lens-formula-docs title: "@kbn/lens-formula-docs" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/lens-formula-docs plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/lens-formula-docs'] --- import kbnLensFormulaDocsObj from './kbn_lens_formula_docs.devdocs.json'; diff --git a/api_docs/kbn_logging.mdx b/api_docs/kbn_logging.mdx index 740cee132c411..91bcedd4e2543 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: 2024-08-07 +date: 2024-08-08 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 b363e8169dd10..268d61cfecc92 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging-mocks'] --- import kbnLoggingMocksObj from './kbn_logging_mocks.devdocs.json'; diff --git a/api_docs/kbn_managed_content_badge.mdx b/api_docs/kbn_managed_content_badge.mdx index ecb949a72e808..6f6bf2daf3c5f 100644 --- a/api_docs/kbn_managed_content_badge.mdx +++ b/api_docs/kbn_managed_content_badge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-managed-content-badge title: "@kbn/managed-content-badge" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/managed-content-badge plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/managed-content-badge'] --- import kbnManagedContentBadgeObj from './kbn_managed_content_badge.devdocs.json'; diff --git a/api_docs/kbn_managed_vscode_config.mdx b/api_docs/kbn_managed_vscode_config.mdx index 1a540234c7f60..3e756296f06f2 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: 2024-08-07 +date: 2024-08-08 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 549f16efefb70..b4d578c005a8a 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: 2024-08-07 +date: 2024-08-08 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 cab0ce644d8fb..af133c80ff191 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: 2024-08-07 +date: 2024-08-08 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 74d273a77994e..f373d99d76e40 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: 2024-08-07 +date: 2024-08-08 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 ff7b85a170457..2f19b0d35478d 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: 2024-08-07 +date: 2024-08-08 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 41566fa488f6a..20a4676243868 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: 2024-08-07 +date: 2024-08-08 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 48bdc6f87ef6e..4ce6dfb7bd4bf 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: 2024-08-07 +date: 2024-08-08 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 ac81aa6a79707..75118f9d8ac3d 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: 2024-08-07 +date: 2024-08-08 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.mdx b/api_docs/kbn_management_settings_ids.mdx index 93372122094ed..5c96ac7531ab6 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-ids'] --- import kbnManagementSettingsIdsObj from './kbn_management_settings_ids.devdocs.json'; diff --git a/api_docs/kbn_management_settings_section_registry.mdx b/api_docs/kbn_management_settings_section_registry.mdx index 4f760b397621e..81ae22db61c22 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: 2024-08-07 +date: 2024-08-08 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 5735fe3a36d0e..b8f25f9b2ed7f 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: 2024-08-07 +date: 2024-08-08 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 8c31ce8d23cb8..c8442abfd00b0 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: 2024-08-07 +date: 2024-08-08 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 d8557bb44ea78..1ce0dcd3205a6 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: 2024-08-07 +date: 2024-08-08 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 24ec19b983215..463a833ec6d2d 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: 2024-08-07 +date: 2024-08-08 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 b1a4f7d1831ac..0950729a9aa3f 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: 2024-08-07 +date: 2024-08-08 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 2ff1211c2fdac..637b176474ec4 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: 2024-08-07 +date: 2024-08-08 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 cb6a4c68b2143..1c2d02400c863 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: 2024-08-07 +date: 2024-08-08 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_cancellable_search.mdx b/api_docs/kbn_ml_cancellable_search.mdx index 26e401988a66a..523384303a5ae 100644 --- a/api_docs/kbn_ml_cancellable_search.mdx +++ b/api_docs/kbn_ml_cancellable_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-cancellable-search title: "@kbn/ml-cancellable-search" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-cancellable-search plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-cancellable-search'] --- import kbnMlCancellableSearchObj from './kbn_ml_cancellable_search.devdocs.json'; diff --git a/api_docs/kbn_ml_category_validator.mdx b/api_docs/kbn_ml_category_validator.mdx index 173fcf1e3a4b2..ce4aa52996da9 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: 2024-08-07 +date: 2024-08-08 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 c5335cb8508bb..6d0f44ab29b27 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: 2024-08-07 +date: 2024-08-08 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 50b6bebb9e5aa..da5bdb08cff11 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: 2024-08-07 +date: 2024-08-08 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 9eb2ad0599c25..89f81e0315db9 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: 2024-08-07 +date: 2024-08-08 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 e6ae8dce35f41..a8155cb084d60 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: 2024-08-07 +date: 2024-08-08 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 211862725b017..352e21c7b3308 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: 2024-08-07 +date: 2024-08-08 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 33e97540d42e0..8730f6d8bd0ec 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: 2024-08-07 +date: 2024-08-08 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 0018ad75b1bc6..6b3cfbf62a844 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: 2024-08-07 +date: 2024-08-08 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 10d75fc496674..afdd04438d24d 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: 2024-08-07 +date: 2024-08-08 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 7c20f44a62819..e03f335494091 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: 2024-08-07 +date: 2024-08-08 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 db54293063f38..c4a835cba75bc 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: 2024-08-07 +date: 2024-08-08 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 79efe74995457..7c755588467c3 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: 2024-08-07 +date: 2024-08-08 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 96124db01454c..6847177f53739 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: 2024-08-07 +date: 2024-08-08 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 8957afa8fbb2e..594709c18db88 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: 2024-08-07 +date: 2024-08-08 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 f1616e51af8ae..9d9434b02d037 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: 2024-08-07 +date: 2024-08-08 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 bf186ba274155..d91a1ed9948de 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: 2024-08-07 +date: 2024-08-08 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 583bf1595b623..c7bb929f42d47 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: 2024-08-07 +date: 2024-08-08 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 e0ee6b4a1fa5e..f0eae0d2c5d73 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: 2024-08-07 +date: 2024-08-08 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 b746110751a55..9810f2d71e567 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: 2024-08-07 +date: 2024-08-08 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_time_buckets.mdx b/api_docs/kbn_ml_time_buckets.mdx index 25d83a0bd8745..1e8b845e370d6 100644 --- a/api_docs/kbn_ml_time_buckets.mdx +++ b/api_docs/kbn_ml_time_buckets.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-time-buckets title: "@kbn/ml-time-buckets" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-time-buckets plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-time-buckets'] --- import kbnMlTimeBucketsObj from './kbn_ml_time_buckets.devdocs.json'; diff --git a/api_docs/kbn_ml_trained_models_utils.devdocs.json b/api_docs/kbn_ml_trained_models_utils.devdocs.json index ae77700bbd77e..29e317f3d5c28 100644 --- a/api_docs/kbn_ml_trained_models_utils.devdocs.json +++ b/api_docs/kbn_ml_trained_models_utils.devdocs.json @@ -178,6 +178,19 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "@kbn/ml-trained-models-utils", + "id": "def-common.ModelDefinition.supported", + "type": "boolean", + "tags": [], + "label": "supported", + "description": [ + "Indicates if model version is supported by the cluster" + ], + "path": "x-pack/packages/ml/trained_models_utils/src/constants/trained_models.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "@kbn/ml-trained-models-utils", "id": "def-common.ModelDefinition.hidden", @@ -619,7 +632,7 @@ "label": "ELASTIC_MODEL_DEFINITIONS", "description": [], "signature": [ - "{ [x: string]: ", + "{ [x: string]: Omit<", { "pluginId": "@kbn/ml-trained-models-utils", "scope": "common", @@ -627,7 +640,7 @@ "section": "def-common.ModelDefinition", "text": "ModelDefinition" }, - "; }" + ", \"supported\">; }" ], "path": "x-pack/packages/ml/trained_models_utils/src/constants/trained_models.ts", "deprecated": false, diff --git a/api_docs/kbn_ml_trained_models_utils.mdx b/api_docs/kbn_ml_trained_models_utils.mdx index 6798ced77593c..8993e30a00a39 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-trained-models-utils'] --- import kbnMlTrainedModelsUtilsObj from './kbn_ml_trained_models_utils.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) for questi | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 43 | 0 | 38 | 1 | +| 44 | 0 | 38 | 1 | ## Common diff --git a/api_docs/kbn_ml_ui_actions.mdx b/api_docs/kbn_ml_ui_actions.mdx index 3a20459c63036..0422b465dfce8 100644 --- a/api_docs/kbn_ml_ui_actions.mdx +++ b/api_docs/kbn_ml_ui_actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-ui-actions title: "@kbn/ml-ui-actions" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-ui-actions plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-ui-actions'] --- import kbnMlUiActionsObj from './kbn_ml_ui_actions.devdocs.json'; diff --git a/api_docs/kbn_ml_url_state.mdx b/api_docs/kbn_ml_url_state.mdx index a0671608bddf0..e5aa4ab7bc992 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-url-state'] --- import kbnMlUrlStateObj from './kbn_ml_url_state.devdocs.json'; diff --git a/api_docs/kbn_mock_idp_utils.mdx b/api_docs/kbn_mock_idp_utils.mdx index 30f4fb2596e25..bab15c846e2c7 100644 --- a/api_docs/kbn_mock_idp_utils.mdx +++ b/api_docs/kbn_mock_idp_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-mock-idp-utils title: "@kbn/mock-idp-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/mock-idp-utils plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/mock-idp-utils'] --- import kbnMockIdpUtilsObj from './kbn_mock_idp_utils.devdocs.json'; diff --git a/api_docs/kbn_monaco.mdx b/api_docs/kbn_monaco.mdx index 3b7dd0a872544..c4089d57918e8 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: 2024-08-07 +date: 2024-08-08 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 1a6817d8532bb..5fe646445461e 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: 2024-08-07 +date: 2024-08-08 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 c8acf57fc8a09..f852885f817a8 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/observability-alert-details'] --- import kbnObservabilityAlertDetailsObj from './kbn_observability_alert_details.devdocs.json'; diff --git a/api_docs/kbn_observability_alerting_rule_utils.mdx b/api_docs/kbn_observability_alerting_rule_utils.mdx index b6fbe5721ce47..5750ad2c951e6 100644 --- a/api_docs/kbn_observability_alerting_rule_utils.mdx +++ b/api_docs/kbn_observability_alerting_rule_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-observability-alerting-rule-utils title: "@kbn/observability-alerting-rule-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/observability-alerting-rule-utils plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/observability-alerting-rule-utils'] --- import kbnObservabilityAlertingRuleUtilsObj from './kbn_observability_alerting_rule_utils.devdocs.json'; diff --git a/api_docs/kbn_observability_alerting_test_data.mdx b/api_docs/kbn_observability_alerting_test_data.mdx index 3bc708904f291..e3b424459b05a 100644 --- a/api_docs/kbn_observability_alerting_test_data.mdx +++ b/api_docs/kbn_observability_alerting_test_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-observability-alerting-test-data title: "@kbn/observability-alerting-test-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/observability-alerting-test-data plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/observability-alerting-test-data'] --- import kbnObservabilityAlertingTestDataObj from './kbn_observability_alerting_test_data.devdocs.json'; diff --git a/api_docs/kbn_observability_get_padded_alert_time_range_util.mdx b/api_docs/kbn_observability_get_padded_alert_time_range_util.mdx index 0b7d536935864..341b03cf068e2 100644 --- a/api_docs/kbn_observability_get_padded_alert_time_range_util.mdx +++ b/api_docs/kbn_observability_get_padded_alert_time_range_util.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-observability-get-padded-alert-time-range-util title: "@kbn/observability-get-padded-alert-time-range-util" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/observability-get-padded-alert-time-range-util plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/observability-get-padded-alert-time-range-util'] --- import kbnObservabilityGetPaddedAlertTimeRangeUtilObj from './kbn_observability_get_padded_alert_time_range_util.devdocs.json'; diff --git a/api_docs/kbn_openapi_bundler.mdx b/api_docs/kbn_openapi_bundler.mdx index 5e5349349b743..dbcb018644602 100644 --- a/api_docs/kbn_openapi_bundler.mdx +++ b/api_docs/kbn_openapi_bundler.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-openapi-bundler title: "@kbn/openapi-bundler" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/openapi-bundler plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/openapi-bundler'] --- import kbnOpenapiBundlerObj from './kbn_openapi_bundler.devdocs.json'; diff --git a/api_docs/kbn_openapi_generator.mdx b/api_docs/kbn_openapi_generator.mdx index 698a79085d7b9..0afb431ad62fc 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: 2024-08-07 +date: 2024-08-08 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 17babd551a424..ee9922dcf7947 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: 2024-08-07 +date: 2024-08-08 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 ee25b386db4ff..f5a7ad7b1654c 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: 2024-08-07 +date: 2024-08-08 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 47e9f5534127b..ccd944982955d 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: 2024-08-07 +date: 2024-08-08 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_panel_loader.mdx b/api_docs/kbn_panel_loader.mdx index b0a20387a1fae..919b94ce3a1a8 100644 --- a/api_docs/kbn_panel_loader.mdx +++ b/api_docs/kbn_panel_loader.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-panel-loader title: "@kbn/panel-loader" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/panel-loader plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/panel-loader'] --- import kbnPanelLoaderObj from './kbn_panel_loader.devdocs.json'; diff --git a/api_docs/kbn_performance_testing_dataset_extractor.mdx b/api_docs/kbn_performance_testing_dataset_extractor.mdx index 6b4c5a5ae62d2..9ae83db144035 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: 2024-08-07 +date: 2024-08-08 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_check.mdx b/api_docs/kbn_plugin_check.mdx index e55d772096158..a9a6381b459c7 100644 --- a/api_docs/kbn_plugin_check.mdx +++ b/api_docs/kbn_plugin_check.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-check title: "@kbn/plugin-check" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-check plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-check'] --- import kbnPluginCheckObj from './kbn_plugin_check.devdocs.json'; diff --git a/api_docs/kbn_plugin_generator.mdx b/api_docs/kbn_plugin_generator.mdx index 6840bea9b1544..6088a1b02046f 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: 2024-08-07 +date: 2024-08-08 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 2a011fe0eea81..be17ef12a7939 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-helpers'] --- import kbnPluginHelpersObj from './kbn_plugin_helpers.devdocs.json'; diff --git a/api_docs/kbn_presentation_containers.mdx b/api_docs/kbn_presentation_containers.mdx index ff8d58ac53a21..e38e8e7fd049f 100644 --- a/api_docs/kbn_presentation_containers.mdx +++ b/api_docs/kbn_presentation_containers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-presentation-containers title: "@kbn/presentation-containers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/presentation-containers plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/presentation-containers'] --- import kbnPresentationContainersObj from './kbn_presentation_containers.devdocs.json'; diff --git a/api_docs/kbn_presentation_publishing.mdx b/api_docs/kbn_presentation_publishing.mdx index 748cde53949b4..ee999fd7e22c2 100644 --- a/api_docs/kbn_presentation_publishing.mdx +++ b/api_docs/kbn_presentation_publishing.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-presentation-publishing title: "@kbn/presentation-publishing" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/presentation-publishing plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/presentation-publishing'] --- import kbnPresentationPublishingObj from './kbn_presentation_publishing.devdocs.json'; diff --git a/api_docs/kbn_profiling_utils.mdx b/api_docs/kbn_profiling_utils.mdx index 0bfb2537c841c..771c8587cb286 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: 2024-08-07 +date: 2024-08-08 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 3c88ee34244c8..e775402034d52 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: 2024-08-07 +date: 2024-08-08 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 4d019adc68881..f39dbac0a810a 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-field'] --- import kbnReactFieldObj from './kbn_react_field.devdocs.json'; diff --git a/api_docs/kbn_react_hooks.mdx b/api_docs/kbn_react_hooks.mdx index b55ef51f8fbef..bd286282e2ad0 100644 --- a/api_docs/kbn_react_hooks.mdx +++ b/api_docs/kbn_react_hooks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-hooks title: "@kbn/react-hooks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-hooks plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-hooks'] --- import kbnReactHooksObj from './kbn_react_hooks.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_context_common.mdx b/api_docs/kbn_react_kibana_context_common.mdx index b2fd7369a0d4e..81993e0917771 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: 2024-08-07 +date: 2024-08-08 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 73bf3989b2d00..77283ede26138 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: 2024-08-07 +date: 2024-08-08 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 00cd118abddc3..28444b825734c 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: 2024-08-07 +date: 2024-08-08 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 88a4e95e014ab..ed2cadefff94f 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: 2024-08-07 +date: 2024-08-08 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 532af35f0c939..c509d8f64bc20 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: 2024-08-07 +date: 2024-08-08 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 8ceb6fee86764..d8008ad5fe538 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-mount'] --- import kbnReactKibanaMountObj from './kbn_react_kibana_mount.devdocs.json'; diff --git a/api_docs/kbn_recently_accessed.mdx b/api_docs/kbn_recently_accessed.mdx index e44ca75d62e1c..aae8e22d7f51b 100644 --- a/api_docs/kbn_recently_accessed.mdx +++ b/api_docs/kbn_recently_accessed.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-recently-accessed title: "@kbn/recently-accessed" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/recently-accessed plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/recently-accessed'] --- import kbnRecentlyAccessedObj from './kbn_recently_accessed.devdocs.json'; diff --git a/api_docs/kbn_repo_file_maps.mdx b/api_docs/kbn_repo_file_maps.mdx index abac6f5b55af1..4364da4f5e2b2 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: 2024-08-07 +date: 2024-08-08 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 1465e0ee29252..a27c077ae68ff 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: 2024-08-07 +date: 2024-08-08 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 3bf9c18e0c587..eae5097895bf6 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: 2024-08-07 +date: 2024-08-08 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 02c1e538495a3..ab1a3cdcd19e6 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: 2024-08-07 +date: 2024-08-08 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 82806161b59cb..00dce6a151991 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-common'] --- import kbnReportingCommonObj from './kbn_reporting_common.devdocs.json'; diff --git a/api_docs/kbn_reporting_csv_share_panel.mdx b/api_docs/kbn_reporting_csv_share_panel.mdx index 7562ee968765b..157c7253517a8 100644 --- a/api_docs/kbn_reporting_csv_share_panel.mdx +++ b/api_docs/kbn_reporting_csv_share_panel.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-csv-share-panel title: "@kbn/reporting-csv-share-panel" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-csv-share-panel plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-csv-share-panel'] --- import kbnReportingCsvSharePanelObj from './kbn_reporting_csv_share_panel.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_csv.mdx b/api_docs/kbn_reporting_export_types_csv.mdx index 20ad5a85748a6..ef4ecb54af388 100644 --- a/api_docs/kbn_reporting_export_types_csv.mdx +++ b/api_docs/kbn_reporting_export_types_csv.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-csv title: "@kbn/reporting-export-types-csv" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-csv plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-csv'] --- import kbnReportingExportTypesCsvObj from './kbn_reporting_export_types_csv.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_csv_common.mdx b/api_docs/kbn_reporting_export_types_csv_common.mdx index 236e4a34a2d2b..8e301f04dfd0e 100644 --- a/api_docs/kbn_reporting_export_types_csv_common.mdx +++ b/api_docs/kbn_reporting_export_types_csv_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-csv-common title: "@kbn/reporting-export-types-csv-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-csv-common plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-csv-common'] --- import kbnReportingExportTypesCsvCommonObj from './kbn_reporting_export_types_csv_common.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_pdf.mdx b/api_docs/kbn_reporting_export_types_pdf.mdx index 27e710b4d812d..3145ab3e7c451 100644 --- a/api_docs/kbn_reporting_export_types_pdf.mdx +++ b/api_docs/kbn_reporting_export_types_pdf.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-pdf title: "@kbn/reporting-export-types-pdf" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-pdf plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-pdf'] --- import kbnReportingExportTypesPdfObj from './kbn_reporting_export_types_pdf.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_pdf_common.mdx b/api_docs/kbn_reporting_export_types_pdf_common.mdx index 8729c6bec5a27..f43a8e9901be5 100644 --- a/api_docs/kbn_reporting_export_types_pdf_common.mdx +++ b/api_docs/kbn_reporting_export_types_pdf_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-pdf-common title: "@kbn/reporting-export-types-pdf-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-pdf-common plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-pdf-common'] --- import kbnReportingExportTypesPdfCommonObj from './kbn_reporting_export_types_pdf_common.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_png.mdx b/api_docs/kbn_reporting_export_types_png.mdx index d5f36a0d211b0..4b8339f3e812d 100644 --- a/api_docs/kbn_reporting_export_types_png.mdx +++ b/api_docs/kbn_reporting_export_types_png.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-png title: "@kbn/reporting-export-types-png" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-png plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-png'] --- import kbnReportingExportTypesPngObj from './kbn_reporting_export_types_png.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_png_common.mdx b/api_docs/kbn_reporting_export_types_png_common.mdx index 36508d307c6bf..71cf39f741a20 100644 --- a/api_docs/kbn_reporting_export_types_png_common.mdx +++ b/api_docs/kbn_reporting_export_types_png_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-png-common title: "@kbn/reporting-export-types-png-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-png-common plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-png-common'] --- import kbnReportingExportTypesPngCommonObj from './kbn_reporting_export_types_png_common.devdocs.json'; diff --git a/api_docs/kbn_reporting_mocks_server.mdx b/api_docs/kbn_reporting_mocks_server.mdx index 3d63013062ac5..9dee4ca54c204 100644 --- a/api_docs/kbn_reporting_mocks_server.mdx +++ b/api_docs/kbn_reporting_mocks_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-mocks-server title: "@kbn/reporting-mocks-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-mocks-server plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-mocks-server'] --- import kbnReportingMocksServerObj from './kbn_reporting_mocks_server.devdocs.json'; diff --git a/api_docs/kbn_reporting_public.mdx b/api_docs/kbn_reporting_public.mdx index fe7a03d27d8a8..8aff20a5e82bb 100644 --- a/api_docs/kbn_reporting_public.mdx +++ b/api_docs/kbn_reporting_public.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-public title: "@kbn/reporting-public" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-public plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-public'] --- import kbnReportingPublicObj from './kbn_reporting_public.devdocs.json'; diff --git a/api_docs/kbn_reporting_server.mdx b/api_docs/kbn_reporting_server.mdx index 1d0103645e146..2bf3b49a0041e 100644 --- a/api_docs/kbn_reporting_server.mdx +++ b/api_docs/kbn_reporting_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-server title: "@kbn/reporting-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-server plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-server'] --- import kbnReportingServerObj from './kbn_reporting_server.devdocs.json'; diff --git a/api_docs/kbn_resizable_layout.mdx b/api_docs/kbn_resizable_layout.mdx index f26a66a788276..05804a8139e88 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/resizable-layout'] --- import kbnResizableLayoutObj from './kbn_resizable_layout.devdocs.json'; diff --git a/api_docs/kbn_response_ops_feature_flag_service.mdx b/api_docs/kbn_response_ops_feature_flag_service.mdx index 86a44d0eddd3a..d96d0c98b2ee5 100644 --- a/api_docs/kbn_response_ops_feature_flag_service.mdx +++ b/api_docs/kbn_response_ops_feature_flag_service.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-response-ops-feature-flag-service title: "@kbn/response-ops-feature-flag-service" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/response-ops-feature-flag-service plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/response-ops-feature-flag-service'] --- import kbnResponseOpsFeatureFlagServiceObj from './kbn_response_ops_feature_flag_service.devdocs.json'; diff --git a/api_docs/kbn_rison.mdx b/api_docs/kbn_rison.mdx index 4678da355b238..974465f102b6c 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rison'] --- import kbnRisonObj from './kbn_rison.devdocs.json'; diff --git a/api_docs/kbn_rollup.mdx b/api_docs/kbn_rollup.mdx index 50b4ab4e92733..717416a01dbd0 100644 --- a/api_docs/kbn_rollup.mdx +++ b/api_docs/kbn_rollup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rollup title: "@kbn/rollup" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rollup plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rollup'] --- import kbnRollupObj from './kbn_rollup.devdocs.json'; diff --git a/api_docs/kbn_router_to_openapispec.mdx b/api_docs/kbn_router_to_openapispec.mdx index 5a9715787a639..82fc843dea744 100644 --- a/api_docs/kbn_router_to_openapispec.mdx +++ b/api_docs/kbn_router_to_openapispec.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-router-to-openapispec title: "@kbn/router-to-openapispec" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/router-to-openapispec plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/router-to-openapispec'] --- import kbnRouterToOpenapispecObj from './kbn_router_to_openapispec.devdocs.json'; diff --git a/api_docs/kbn_router_utils.mdx b/api_docs/kbn_router_utils.mdx index 5fc0971fc4ad5..9aca2ec33252a 100644 --- a/api_docs/kbn_router_utils.mdx +++ b/api_docs/kbn_router_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-router-utils title: "@kbn/router-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/router-utils plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/router-utils'] --- import kbnRouterUtilsObj from './kbn_router_utils.devdocs.json'; diff --git a/api_docs/kbn_rrule.mdx b/api_docs/kbn_rrule.mdx index 10e4207634123..1a448ec1857b7 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: 2024-08-07 +date: 2024-08-08 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 6ff5dd1f188d5..c019899d6e803 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: 2024-08-07 +date: 2024-08-08 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 7b6e122446a7f..dc6b8c0b1a38d 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: 2024-08-07 +date: 2024-08-08 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 f36a92bebf88f..806bde85577d8 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: 2024-08-07 +date: 2024-08-08 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.mdx b/api_docs/kbn_search_connectors.mdx index 72aa55afd4e9c..5fa0005540305 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-connectors'] --- import kbnSearchConnectorsObj from './kbn_search_connectors.devdocs.json'; diff --git a/api_docs/kbn_search_errors.mdx b/api_docs/kbn_search_errors.mdx index 654a4522136a4..8276b375507f2 100644 --- a/api_docs/kbn_search_errors.mdx +++ b/api_docs/kbn_search_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-errors title: "@kbn/search-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-errors plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-errors'] --- import kbnSearchErrorsObj from './kbn_search_errors.devdocs.json'; diff --git a/api_docs/kbn_search_index_documents.mdx b/api_docs/kbn_search_index_documents.mdx index 744b11186a5b4..8055e39240ba4 100644 --- a/api_docs/kbn_search_index_documents.mdx +++ b/api_docs/kbn_search_index_documents.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-index-documents title: "@kbn/search-index-documents" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-index-documents plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-index-documents'] --- import kbnSearchIndexDocumentsObj from './kbn_search_index_documents.devdocs.json'; diff --git a/api_docs/kbn_search_response_warnings.mdx b/api_docs/kbn_search_response_warnings.mdx index 8cf1a0cd3a221..95e61451f1f67 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-response-warnings'] --- import kbnSearchResponseWarningsObj from './kbn_search_response_warnings.devdocs.json'; diff --git a/api_docs/kbn_search_types.mdx b/api_docs/kbn_search_types.mdx index dca214b31f090..b469025b6d532 100644 --- a/api_docs/kbn_search_types.mdx +++ b/api_docs/kbn_search_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-types title: "@kbn/search-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-types plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-types'] --- import kbnSearchTypesObj from './kbn_search_types.devdocs.json'; diff --git a/api_docs/kbn_security_api_key_management.mdx b/api_docs/kbn_security_api_key_management.mdx index 2b539dfdab67b..233c5119cf596 100644 --- a/api_docs/kbn_security_api_key_management.mdx +++ b/api_docs/kbn_security_api_key_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-api-key-management title: "@kbn/security-api-key-management" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-api-key-management plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-api-key-management'] --- import kbnSecurityApiKeyManagementObj from './kbn_security_api_key_management.devdocs.json'; diff --git a/api_docs/kbn_security_form_components.mdx b/api_docs/kbn_security_form_components.mdx index 0a0f64aa5a632..a24cac184ed26 100644 --- a/api_docs/kbn_security_form_components.mdx +++ b/api_docs/kbn_security_form_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-form-components title: "@kbn/security-form-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-form-components plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-form-components'] --- import kbnSecurityFormComponentsObj from './kbn_security_form_components.devdocs.json'; diff --git a/api_docs/kbn_security_hardening.mdx b/api_docs/kbn_security_hardening.mdx index f1045a3bd5806..2da1623dda034 100644 --- a/api_docs/kbn_security_hardening.mdx +++ b/api_docs/kbn_security_hardening.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-hardening title: "@kbn/security-hardening" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-hardening plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-hardening'] --- import kbnSecurityHardeningObj from './kbn_security_hardening.devdocs.json'; diff --git a/api_docs/kbn_security_plugin_types_common.mdx b/api_docs/kbn_security_plugin_types_common.mdx index ea66a6f1eb27e..6bcceb090ab38 100644 --- a/api_docs/kbn_security_plugin_types_common.mdx +++ b/api_docs/kbn_security_plugin_types_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-plugin-types-common title: "@kbn/security-plugin-types-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-plugin-types-common plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-plugin-types-common'] --- import kbnSecurityPluginTypesCommonObj from './kbn_security_plugin_types_common.devdocs.json'; diff --git a/api_docs/kbn_security_plugin_types_public.mdx b/api_docs/kbn_security_plugin_types_public.mdx index 304e2e3c7be8c..46d4056720b79 100644 --- a/api_docs/kbn_security_plugin_types_public.mdx +++ b/api_docs/kbn_security_plugin_types_public.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-plugin-types-public title: "@kbn/security-plugin-types-public" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-plugin-types-public plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-plugin-types-public'] --- import kbnSecurityPluginTypesPublicObj from './kbn_security_plugin_types_public.devdocs.json'; diff --git a/api_docs/kbn_security_plugin_types_server.mdx b/api_docs/kbn_security_plugin_types_server.mdx index 9984215e88752..c3f11125cc42b 100644 --- a/api_docs/kbn_security_plugin_types_server.mdx +++ b/api_docs/kbn_security_plugin_types_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-plugin-types-server title: "@kbn/security-plugin-types-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-plugin-types-server plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-plugin-types-server'] --- import kbnSecurityPluginTypesServerObj from './kbn_security_plugin_types_server.devdocs.json'; diff --git a/api_docs/kbn_security_solution_distribution_bar.mdx b/api_docs/kbn_security_solution_distribution_bar.mdx index 77a7d684f94a2..388acb35c5d8c 100644 --- a/api_docs/kbn_security_solution_distribution_bar.mdx +++ b/api_docs/kbn_security_solution_distribution_bar.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-distribution-bar title: "@kbn/security-solution-distribution-bar" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-distribution-bar plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-solution-distribution-bar'] --- import kbnSecuritySolutionDistributionBarObj from './kbn_security_solution_distribution_bar.devdocs.json'; diff --git a/api_docs/kbn_security_solution_features.mdx b/api_docs/kbn_security_solution_features.mdx index 35834cbbafb96..e05572fa49949 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: 2024-08-07 +date: 2024-08-08 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 b8df60d866613..b422b473d6036 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: 2024-08-07 +date: 2024-08-08 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 81a9262a4105f..4cf26074672af 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: 2024-08-07 +date: 2024-08-08 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 040f9351a266c..309d62ccaa2d0 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: 2024-08-07 +date: 2024-08-08 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 5173ed25904a2..5714d4778ff73 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: 2024-08-07 +date: 2024-08-08 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 f4f8b8c8b7fea..4c2c7f27a9536 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: 2024-08-07 +date: 2024-08-08 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 664f909f9641f..57d35b97b81b8 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: 2024-08-07 +date: 2024-08-08 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 19450134390c1..52ba6f7c95735 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: 2024-08-07 +date: 2024-08-08 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.devdocs.json b/api_docs/kbn_securitysolution_exception_list_components.devdocs.json index 1f9c44937ea70..fa61ae7f0558f 100644 --- a/api_docs/kbn_securitysolution_exception_list_components.devdocs.json +++ b/api_docs/kbn_securitysolution_exception_list_components.devdocs.json @@ -851,7 +851,7 @@ "label": "formattedDateComponent", "description": [], "signature": [ - "\"symbol\" | \"object\" | \"source\" | \"meta\" | \"desc\" | \"filter\" | \"big\" | \"link\" | \"small\" | \"sub\" | \"sup\" | \"text\" | \"map\" | \"head\" | \"slot\" | \"style\" | \"title\" | \"data\" | \"path\" | \"code\" | \"pattern\" | \"summary\" | \"template\" | \"span\" | \"q\" | \"body\" | \"main\" | \"form\" | \"line\" | \"rect\" | \"html\" | \"stop\" | \"label\" | \"progress\" | \"article\" | \"image\" | \"menu\" | \"base\" | React.ComponentType | \"s\" | \"legend\" | \"canvas\" | \"svg\" | \"h1\" | \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"p\" | \"select\" | \"output\" | \"script\" | \"time\" | \"mask\" | \"input\" | \"table\" | \"a\" | \"abbr\" | \"address\" | \"area\" | \"aside\" | \"audio\" | \"b\" | \"bdi\" | \"bdo\" | \"blockquote\" | \"br\" | \"button\" | \"caption\" | \"cite\" | \"col\" | \"colgroup\" | \"datalist\" | \"dd\" | \"del\" | \"details\" | \"dfn\" | \"dialog\" | \"div\" | \"dl\" | \"dt\" | \"em\" | \"embed\" | \"fieldset\" | \"figcaption\" | \"figure\" | \"footer\" | \"header\" | \"hgroup\" | \"hr\" | \"i\" | \"iframe\" | \"img\" | \"ins\" | \"kbd\" | \"keygen\" | \"li\" | \"mark\" | \"menuitem\" | \"meter\" | \"nav\" | \"noindex\" | \"noscript\" | \"ol\" | \"optgroup\" | \"option\" | \"param\" | \"picture\" | \"pre\" | \"rp\" | \"rt\" | \"ruby\" | \"samp\" | \"section\" | \"strong\" | \"tbody\" | \"td\" | \"textarea\" | \"tfoot\" | \"th\" | \"thead\" | \"tr\" | \"track\" | \"u\" | \"ul\" | \"var\" | \"video\" | \"wbr\" | \"webview\" | \"animate\" | \"animateMotion\" | \"animateTransform\" | \"circle\" | \"clipPath\" | \"defs\" | \"ellipse\" | \"feBlend\" | \"feColorMatrix\" | \"feComponentTransfer\" | \"feComposite\" | \"feConvolveMatrix\" | \"feDiffuseLighting\" | \"feDisplacementMap\" | \"feDistantLight\" | \"feDropShadow\" | \"feFlood\" | \"feFuncA\" | \"feFuncB\" | \"feFuncG\" | \"feFuncR\" | \"feGaussianBlur\" | \"feImage\" | \"feMerge\" | \"feMergeNode\" | \"feMorphology\" | \"feOffset\" | \"fePointLight\" | \"feSpecularLighting\" | \"feSpotLight\" | \"feTile\" | \"feTurbulence\" | \"foreignObject\" | \"g\" | \"linearGradient\" | \"marker\" | \"metadata\" | \"mpath\" | \"polygon\" | \"polyline\" | \"radialGradient\" | \"switch\" | \"textPath\" | \"tspan\" | \"use\" | \"view\"" + "\"symbol\" | \"object\" | \"source\" | \"meta\" | \"desc\" | \"filter\" | \"big\" | \"link\" | \"small\" | \"sub\" | \"sup\" | \"text\" | \"map\" | \"head\" | \"slot\" | \"style\" | \"title\" | \"data\" | \"path\" | \"code\" | \"pattern\" | \"summary\" | \"template\" | \"span\" | \"q\" | \"body\" | \"html\" | \"stop\" | \"main\" | \"form\" | \"line\" | \"rect\" | \"label\" | \"progress\" | \"article\" | \"image\" | \"menu\" | \"base\" | React.ComponentType | \"s\" | \"legend\" | \"canvas\" | \"svg\" | \"h1\" | \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"p\" | \"select\" | \"output\" | \"script\" | \"time\" | \"mask\" | \"input\" | \"table\" | \"a\" | \"abbr\" | \"address\" | \"area\" | \"aside\" | \"audio\" | \"b\" | \"bdi\" | \"bdo\" | \"blockquote\" | \"br\" | \"button\" | \"caption\" | \"cite\" | \"col\" | \"colgroup\" | \"datalist\" | \"dd\" | \"del\" | \"details\" | \"dfn\" | \"dialog\" | \"div\" | \"dl\" | \"dt\" | \"em\" | \"embed\" | \"fieldset\" | \"figcaption\" | \"figure\" | \"footer\" | \"header\" | \"hgroup\" | \"hr\" | \"i\" | \"iframe\" | \"img\" | \"ins\" | \"kbd\" | \"keygen\" | \"li\" | \"mark\" | \"menuitem\" | \"meter\" | \"nav\" | \"noindex\" | \"noscript\" | \"ol\" | \"optgroup\" | \"option\" | \"param\" | \"picture\" | \"pre\" | \"rp\" | \"rt\" | \"ruby\" | \"samp\" | \"section\" | \"strong\" | \"tbody\" | \"td\" | \"textarea\" | \"tfoot\" | \"th\" | \"thead\" | \"tr\" | \"track\" | \"u\" | \"ul\" | \"var\" | \"video\" | \"wbr\" | \"webview\" | \"animate\" | \"animateMotion\" | \"animateTransform\" | \"circle\" | \"clipPath\" | \"defs\" | \"ellipse\" | \"feBlend\" | \"feColorMatrix\" | \"feComponentTransfer\" | \"feComposite\" | \"feConvolveMatrix\" | \"feDiffuseLighting\" | \"feDisplacementMap\" | \"feDistantLight\" | \"feDropShadow\" | \"feFlood\" | \"feFuncA\" | \"feFuncB\" | \"feFuncG\" | \"feFuncR\" | \"feGaussianBlur\" | \"feImage\" | \"feMerge\" | \"feMergeNode\" | \"feMorphology\" | \"feOffset\" | \"fePointLight\" | \"feSpecularLighting\" | \"feSpotLight\" | \"feTile\" | \"feTurbulence\" | \"foreignObject\" | \"g\" | \"linearGradient\" | \"marker\" | \"metadata\" | \"mpath\" | \"polygon\" | \"polyline\" | \"radialGradient\" | \"switch\" | \"textPath\" | \"tspan\" | \"use\" | \"view\"" ], "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/meta/index.tsx", "deprecated": false, @@ -865,7 +865,7 @@ "label": "securityLinkAnchorComponent", "description": [], "signature": [ - "\"symbol\" | \"object\" | \"source\" | \"meta\" | \"desc\" | \"filter\" | \"big\" | \"link\" | \"small\" | \"sub\" | \"sup\" | \"text\" | \"map\" | \"head\" | \"slot\" | \"style\" | \"title\" | \"data\" | \"path\" | \"code\" | \"pattern\" | \"summary\" | \"template\" | \"span\" | \"q\" | \"body\" | \"main\" | \"form\" | \"line\" | \"rect\" | \"html\" | \"stop\" | \"label\" | \"progress\" | \"article\" | \"image\" | \"menu\" | \"base\" | React.ComponentType | \"s\" | \"legend\" | \"canvas\" | \"svg\" | \"h1\" | \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"p\" | \"select\" | \"output\" | \"script\" | \"time\" | \"mask\" | \"input\" | \"table\" | \"a\" | \"abbr\" | \"address\" | \"area\" | \"aside\" | \"audio\" | \"b\" | \"bdi\" | \"bdo\" | \"blockquote\" | \"br\" | \"button\" | \"caption\" | \"cite\" | \"col\" | \"colgroup\" | \"datalist\" | \"dd\" | \"del\" | \"details\" | \"dfn\" | \"dialog\" | \"div\" | \"dl\" | \"dt\" | \"em\" | \"embed\" | \"fieldset\" | \"figcaption\" | \"figure\" | \"footer\" | \"header\" | \"hgroup\" | \"hr\" | \"i\" | \"iframe\" | \"img\" | \"ins\" | \"kbd\" | \"keygen\" | \"li\" | \"mark\" | \"menuitem\" | \"meter\" | \"nav\" | \"noindex\" | \"noscript\" | \"ol\" | \"optgroup\" | \"option\" | \"param\" | \"picture\" | \"pre\" | \"rp\" | \"rt\" | \"ruby\" | \"samp\" | \"section\" | \"strong\" | \"tbody\" | \"td\" | \"textarea\" | \"tfoot\" | \"th\" | \"thead\" | \"tr\" | \"track\" | \"u\" | \"ul\" | \"var\" | \"video\" | \"wbr\" | \"webview\" | \"animate\" | \"animateMotion\" | \"animateTransform\" | \"circle\" | \"clipPath\" | \"defs\" | \"ellipse\" | \"feBlend\" | \"feColorMatrix\" | \"feComponentTransfer\" | \"feComposite\" | \"feConvolveMatrix\" | \"feDiffuseLighting\" | \"feDisplacementMap\" | \"feDistantLight\" | \"feDropShadow\" | \"feFlood\" | \"feFuncA\" | \"feFuncB\" | \"feFuncG\" | \"feFuncR\" | \"feGaussianBlur\" | \"feImage\" | \"feMerge\" | \"feMergeNode\" | \"feMorphology\" | \"feOffset\" | \"fePointLight\" | \"feSpecularLighting\" | \"feSpotLight\" | \"feTile\" | \"feTurbulence\" | \"foreignObject\" | \"g\" | \"linearGradient\" | \"marker\" | \"metadata\" | \"mpath\" | \"polygon\" | \"polyline\" | \"radialGradient\" | \"switch\" | \"textPath\" | \"tspan\" | \"use\" | \"view\"" + "\"symbol\" | \"object\" | \"source\" | \"meta\" | \"desc\" | \"filter\" | \"big\" | \"link\" | \"small\" | \"sub\" | \"sup\" | \"text\" | \"map\" | \"head\" | \"slot\" | \"style\" | \"title\" | \"data\" | \"path\" | \"code\" | \"pattern\" | \"summary\" | \"template\" | \"span\" | \"q\" | \"body\" | \"html\" | \"stop\" | \"main\" | \"form\" | \"line\" | \"rect\" | \"label\" | \"progress\" | \"article\" | \"image\" | \"menu\" | \"base\" | React.ComponentType | \"s\" | \"legend\" | \"canvas\" | \"svg\" | \"h1\" | \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"p\" | \"select\" | \"output\" | \"script\" | \"time\" | \"mask\" | \"input\" | \"table\" | \"a\" | \"abbr\" | \"address\" | \"area\" | \"aside\" | \"audio\" | \"b\" | \"bdi\" | \"bdo\" | \"blockquote\" | \"br\" | \"button\" | \"caption\" | \"cite\" | \"col\" | \"colgroup\" | \"datalist\" | \"dd\" | \"del\" | \"details\" | \"dfn\" | \"dialog\" | \"div\" | \"dl\" | \"dt\" | \"em\" | \"embed\" | \"fieldset\" | \"figcaption\" | \"figure\" | \"footer\" | \"header\" | \"hgroup\" | \"hr\" | \"i\" | \"iframe\" | \"img\" | \"ins\" | \"kbd\" | \"keygen\" | \"li\" | \"mark\" | \"menuitem\" | \"meter\" | \"nav\" | \"noindex\" | \"noscript\" | \"ol\" | \"optgroup\" | \"option\" | \"param\" | \"picture\" | \"pre\" | \"rp\" | \"rt\" | \"ruby\" | \"samp\" | \"section\" | \"strong\" | \"tbody\" | \"td\" | \"textarea\" | \"tfoot\" | \"th\" | \"thead\" | \"tr\" | \"track\" | \"u\" | \"ul\" | \"var\" | \"video\" | \"wbr\" | \"webview\" | \"animate\" | \"animateMotion\" | \"animateTransform\" | \"circle\" | \"clipPath\" | \"defs\" | \"ellipse\" | \"feBlend\" | \"feColorMatrix\" | \"feComponentTransfer\" | \"feComposite\" | \"feConvolveMatrix\" | \"feDiffuseLighting\" | \"feDisplacementMap\" | \"feDistantLight\" | \"feDropShadow\" | \"feFlood\" | \"feFuncA\" | \"feFuncB\" | \"feFuncG\" | \"feFuncR\" | \"feGaussianBlur\" | \"feImage\" | \"feMerge\" | \"feMergeNode\" | \"feMorphology\" | \"feOffset\" | \"fePointLight\" | \"feSpecularLighting\" | \"feSpotLight\" | \"feTile\" | \"feTurbulence\" | \"foreignObject\" | \"g\" | \"linearGradient\" | \"marker\" | \"metadata\" | \"mpath\" | \"polygon\" | \"polyline\" | \"radialGradient\" | \"switch\" | \"textPath\" | \"tspan\" | \"use\" | \"view\"" ], "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/meta/index.tsx", "deprecated": false, @@ -1004,7 +1004,7 @@ "label": "securityLinkAnchorComponent", "description": [], "signature": [ - "\"symbol\" | \"object\" | \"source\" | \"meta\" | \"desc\" | \"filter\" | \"big\" | \"link\" | \"small\" | \"sub\" | \"sup\" | \"text\" | \"map\" | \"head\" | \"slot\" | \"style\" | \"title\" | \"data\" | \"path\" | \"code\" | \"pattern\" | \"summary\" | \"template\" | \"span\" | \"q\" | \"body\" | \"main\" | \"form\" | \"line\" | \"rect\" | \"html\" | \"stop\" | \"label\" | \"progress\" | \"article\" | \"image\" | \"menu\" | \"base\" | React.ComponentType | \"s\" | \"legend\" | \"canvas\" | \"svg\" | \"h1\" | \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"p\" | \"select\" | \"output\" | \"script\" | \"time\" | \"mask\" | \"input\" | \"table\" | \"a\" | \"abbr\" | \"address\" | \"area\" | \"aside\" | \"audio\" | \"b\" | \"bdi\" | \"bdo\" | \"blockquote\" | \"br\" | \"button\" | \"caption\" | \"cite\" | \"col\" | \"colgroup\" | \"datalist\" | \"dd\" | \"del\" | \"details\" | \"dfn\" | \"dialog\" | \"div\" | \"dl\" | \"dt\" | \"em\" | \"embed\" | \"fieldset\" | \"figcaption\" | \"figure\" | \"footer\" | \"header\" | \"hgroup\" | \"hr\" | \"i\" | \"iframe\" | \"img\" | \"ins\" | \"kbd\" | \"keygen\" | \"li\" | \"mark\" | \"menuitem\" | \"meter\" | \"nav\" | \"noindex\" | \"noscript\" | \"ol\" | \"optgroup\" | \"option\" | \"param\" | \"picture\" | \"pre\" | \"rp\" | \"rt\" | \"ruby\" | \"samp\" | \"section\" | \"strong\" | \"tbody\" | \"td\" | \"textarea\" | \"tfoot\" | \"th\" | \"thead\" | \"tr\" | \"track\" | \"u\" | \"ul\" | \"var\" | \"video\" | \"wbr\" | \"webview\" | \"animate\" | \"animateMotion\" | \"animateTransform\" | \"circle\" | \"clipPath\" | \"defs\" | \"ellipse\" | \"feBlend\" | \"feColorMatrix\" | \"feComponentTransfer\" | \"feComposite\" | \"feConvolveMatrix\" | \"feDiffuseLighting\" | \"feDisplacementMap\" | \"feDistantLight\" | \"feDropShadow\" | \"feFlood\" | \"feFuncA\" | \"feFuncB\" | \"feFuncG\" | \"feFuncR\" | \"feGaussianBlur\" | \"feImage\" | \"feMerge\" | \"feMergeNode\" | \"feMorphology\" | \"feOffset\" | \"fePointLight\" | \"feSpecularLighting\" | \"feSpotLight\" | \"feTile\" | \"feTurbulence\" | \"foreignObject\" | \"g\" | \"linearGradient\" | \"marker\" | \"metadata\" | \"mpath\" | \"polygon\" | \"polyline\" | \"radialGradient\" | \"switch\" | \"textPath\" | \"tspan\" | \"use\" | \"view\"" + "\"symbol\" | \"object\" | \"source\" | \"meta\" | \"desc\" | \"filter\" | \"big\" | \"link\" | \"small\" | \"sub\" | \"sup\" | \"text\" | \"map\" | \"head\" | \"slot\" | \"style\" | \"title\" | \"data\" | \"path\" | \"code\" | \"pattern\" | \"summary\" | \"template\" | \"span\" | \"q\" | \"body\" | \"html\" | \"stop\" | \"main\" | \"form\" | \"line\" | \"rect\" | \"label\" | \"progress\" | \"article\" | \"image\" | \"menu\" | \"base\" | React.ComponentType | \"s\" | \"legend\" | \"canvas\" | \"svg\" | \"h1\" | \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"p\" | \"select\" | \"output\" | \"script\" | \"time\" | \"mask\" | \"input\" | \"table\" | \"a\" | \"abbr\" | \"address\" | \"area\" | \"aside\" | \"audio\" | \"b\" | \"bdi\" | \"bdo\" | \"blockquote\" | \"br\" | \"button\" | \"caption\" | \"cite\" | \"col\" | \"colgroup\" | \"datalist\" | \"dd\" | \"del\" | \"details\" | \"dfn\" | \"dialog\" | \"div\" | \"dl\" | \"dt\" | \"em\" | \"embed\" | \"fieldset\" | \"figcaption\" | \"figure\" | \"footer\" | \"header\" | \"hgroup\" | \"hr\" | \"i\" | \"iframe\" | \"img\" | \"ins\" | \"kbd\" | \"keygen\" | \"li\" | \"mark\" | \"menuitem\" | \"meter\" | \"nav\" | \"noindex\" | \"noscript\" | \"ol\" | \"optgroup\" | \"option\" | \"param\" | \"picture\" | \"pre\" | \"rp\" | \"rt\" | \"ruby\" | \"samp\" | \"section\" | \"strong\" | \"tbody\" | \"td\" | \"textarea\" | \"tfoot\" | \"th\" | \"thead\" | \"tr\" | \"track\" | \"u\" | \"ul\" | \"var\" | \"video\" | \"wbr\" | \"webview\" | \"animate\" | \"animateMotion\" | \"animateTransform\" | \"circle\" | \"clipPath\" | \"defs\" | \"ellipse\" | \"feBlend\" | \"feColorMatrix\" | \"feComponentTransfer\" | \"feComposite\" | \"feConvolveMatrix\" | \"feDiffuseLighting\" | \"feDisplacementMap\" | \"feDistantLight\" | \"feDropShadow\" | \"feFlood\" | \"feFuncA\" | \"feFuncB\" | \"feFuncG\" | \"feFuncR\" | \"feGaussianBlur\" | \"feImage\" | \"feMerge\" | \"feMergeNode\" | \"feMorphology\" | \"feOffset\" | \"fePointLight\" | \"feSpecularLighting\" | \"feSpotLight\" | \"feTile\" | \"feTurbulence\" | \"foreignObject\" | \"g\" | \"linearGradient\" | \"marker\" | \"metadata\" | \"mpath\" | \"polygon\" | \"polyline\" | \"radialGradient\" | \"switch\" | \"textPath\" | \"tspan\" | \"use\" | \"view\"" ], "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/exception_item_card.tsx", "deprecated": false, @@ -1018,7 +1018,7 @@ "label": "formattedDateComponent", "description": [], "signature": [ - "\"symbol\" | \"object\" | \"source\" | \"meta\" | \"desc\" | \"filter\" | \"big\" | \"link\" | \"small\" | \"sub\" | \"sup\" | \"text\" | \"map\" | \"head\" | \"slot\" | \"style\" | \"title\" | \"data\" | \"path\" | \"code\" | \"pattern\" | \"summary\" | \"template\" | \"span\" | \"q\" | \"body\" | \"main\" | \"form\" | \"line\" | \"rect\" | \"html\" | \"stop\" | \"label\" | \"progress\" | \"article\" | \"image\" | \"menu\" | \"base\" | React.ComponentType | \"s\" | \"legend\" | \"canvas\" | \"svg\" | \"h1\" | \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"p\" | \"select\" | \"output\" | \"script\" | \"time\" | \"mask\" | \"input\" | \"table\" | \"a\" | \"abbr\" | \"address\" | \"area\" | \"aside\" | \"audio\" | \"b\" | \"bdi\" | \"bdo\" | \"blockquote\" | \"br\" | \"button\" | \"caption\" | \"cite\" | \"col\" | \"colgroup\" | \"datalist\" | \"dd\" | \"del\" | \"details\" | \"dfn\" | \"dialog\" | \"div\" | \"dl\" | \"dt\" | \"em\" | \"embed\" | \"fieldset\" | \"figcaption\" | \"figure\" | \"footer\" | \"header\" | \"hgroup\" | \"hr\" | \"i\" | \"iframe\" | \"img\" | \"ins\" | \"kbd\" | \"keygen\" | \"li\" | \"mark\" | \"menuitem\" | \"meter\" | \"nav\" | \"noindex\" | \"noscript\" | \"ol\" | \"optgroup\" | \"option\" | \"param\" | \"picture\" | \"pre\" | \"rp\" | \"rt\" | \"ruby\" | \"samp\" | \"section\" | \"strong\" | \"tbody\" | \"td\" | \"textarea\" | \"tfoot\" | \"th\" | \"thead\" | \"tr\" | \"track\" | \"u\" | \"ul\" | \"var\" | \"video\" | \"wbr\" | \"webview\" | \"animate\" | \"animateMotion\" | \"animateTransform\" | \"circle\" | \"clipPath\" | \"defs\" | \"ellipse\" | \"feBlend\" | \"feColorMatrix\" | \"feComponentTransfer\" | \"feComposite\" | \"feConvolveMatrix\" | \"feDiffuseLighting\" | \"feDisplacementMap\" | \"feDistantLight\" | \"feDropShadow\" | \"feFlood\" | \"feFuncA\" | \"feFuncB\" | \"feFuncG\" | \"feFuncR\" | \"feGaussianBlur\" | \"feImage\" | \"feMerge\" | \"feMergeNode\" | \"feMorphology\" | \"feOffset\" | \"fePointLight\" | \"feSpecularLighting\" | \"feSpotLight\" | \"feTile\" | \"feTurbulence\" | \"foreignObject\" | \"g\" | \"linearGradient\" | \"marker\" | \"metadata\" | \"mpath\" | \"polygon\" | \"polyline\" | \"radialGradient\" | \"switch\" | \"textPath\" | \"tspan\" | \"use\" | \"view\"" + "\"symbol\" | \"object\" | \"source\" | \"meta\" | \"desc\" | \"filter\" | \"big\" | \"link\" | \"small\" | \"sub\" | \"sup\" | \"text\" | \"map\" | \"head\" | \"slot\" | \"style\" | \"title\" | \"data\" | \"path\" | \"code\" | \"pattern\" | \"summary\" | \"template\" | \"span\" | \"q\" | \"body\" | \"html\" | \"stop\" | \"main\" | \"form\" | \"line\" | \"rect\" | \"label\" | \"progress\" | \"article\" | \"image\" | \"menu\" | \"base\" | React.ComponentType | \"s\" | \"legend\" | \"canvas\" | \"svg\" | \"h1\" | \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"p\" | \"select\" | \"output\" | \"script\" | \"time\" | \"mask\" | \"input\" | \"table\" | \"a\" | \"abbr\" | \"address\" | \"area\" | \"aside\" | \"audio\" | \"b\" | \"bdi\" | \"bdo\" | \"blockquote\" | \"br\" | \"button\" | \"caption\" | \"cite\" | \"col\" | \"colgroup\" | \"datalist\" | \"dd\" | \"del\" | \"details\" | \"dfn\" | \"dialog\" | \"div\" | \"dl\" | \"dt\" | \"em\" | \"embed\" | \"fieldset\" | \"figcaption\" | \"figure\" | \"footer\" | \"header\" | \"hgroup\" | \"hr\" | \"i\" | \"iframe\" | \"img\" | \"ins\" | \"kbd\" | \"keygen\" | \"li\" | \"mark\" | \"menuitem\" | \"meter\" | \"nav\" | \"noindex\" | \"noscript\" | \"ol\" | \"optgroup\" | \"option\" | \"param\" | \"picture\" | \"pre\" | \"rp\" | \"rt\" | \"ruby\" | \"samp\" | \"section\" | \"strong\" | \"tbody\" | \"td\" | \"textarea\" | \"tfoot\" | \"th\" | \"thead\" | \"tr\" | \"track\" | \"u\" | \"ul\" | \"var\" | \"video\" | \"wbr\" | \"webview\" | \"animate\" | \"animateMotion\" | \"animateTransform\" | \"circle\" | \"clipPath\" | \"defs\" | \"ellipse\" | \"feBlend\" | \"feColorMatrix\" | \"feComponentTransfer\" | \"feComposite\" | \"feConvolveMatrix\" | \"feDiffuseLighting\" | \"feDisplacementMap\" | \"feDistantLight\" | \"feDropShadow\" | \"feFlood\" | \"feFuncA\" | \"feFuncB\" | \"feFuncG\" | \"feFuncR\" | \"feGaussianBlur\" | \"feImage\" | \"feMerge\" | \"feMergeNode\" | \"feMorphology\" | \"feOffset\" | \"fePointLight\" | \"feSpecularLighting\" | \"feSpotLight\" | \"feTile\" | \"feTurbulence\" | \"foreignObject\" | \"g\" | \"linearGradient\" | \"marker\" | \"metadata\" | \"mpath\" | \"polygon\" | \"polyline\" | \"radialGradient\" | \"switch\" | \"textPath\" | \"tspan\" | \"use\" | \"view\"" ], "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/exception_item_card.tsx", "deprecated": false, @@ -1144,7 +1144,7 @@ "label": "showValueListModal", "description": [], "signature": [ - "\"symbol\" | \"object\" | \"source\" | \"meta\" | \"desc\" | \"filter\" | \"big\" | \"link\" | \"small\" | \"sub\" | \"sup\" | \"text\" | \"map\" | \"head\" | \"slot\" | \"style\" | \"title\" | \"data\" | \"path\" | \"code\" | \"pattern\" | \"summary\" | \"template\" | \"span\" | \"q\" | \"body\" | \"main\" | \"form\" | \"line\" | \"rect\" | \"html\" | \"stop\" | \"label\" | \"progress\" | \"article\" | \"image\" | \"menu\" | \"base\" | React.ComponentType | \"s\" | \"legend\" | \"canvas\" | \"svg\" | \"h1\" | \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"p\" | \"select\" | \"output\" | \"script\" | \"time\" | \"mask\" | \"input\" | \"table\" | \"a\" | \"abbr\" | \"address\" | \"area\" | \"aside\" | \"audio\" | \"b\" | \"bdi\" | \"bdo\" | \"blockquote\" | \"br\" | \"button\" | \"caption\" | \"cite\" | \"col\" | \"colgroup\" | \"datalist\" | \"dd\" | \"del\" | \"details\" | \"dfn\" | \"dialog\" | \"div\" | \"dl\" | \"dt\" | \"em\" | \"embed\" | \"fieldset\" | \"figcaption\" | \"figure\" | \"footer\" | \"header\" | \"hgroup\" | \"hr\" | \"i\" | \"iframe\" | \"img\" | \"ins\" | \"kbd\" | \"keygen\" | \"li\" | \"mark\" | \"menuitem\" | \"meter\" | \"nav\" | \"noindex\" | \"noscript\" | \"ol\" | \"optgroup\" | \"option\" | \"param\" | \"picture\" | \"pre\" | \"rp\" | \"rt\" | \"ruby\" | \"samp\" | \"section\" | \"strong\" | \"tbody\" | \"td\" | \"textarea\" | \"tfoot\" | \"th\" | \"thead\" | \"tr\" | \"track\" | \"u\" | \"ul\" | \"var\" | \"video\" | \"wbr\" | \"webview\" | \"animate\" | \"animateMotion\" | \"animateTransform\" | \"circle\" | \"clipPath\" | \"defs\" | \"ellipse\" | \"feBlend\" | \"feColorMatrix\" | \"feComponentTransfer\" | \"feComposite\" | \"feConvolveMatrix\" | \"feDiffuseLighting\" | \"feDisplacementMap\" | \"feDistantLight\" | \"feDropShadow\" | \"feFlood\" | \"feFuncA\" | \"feFuncB\" | \"feFuncG\" | \"feFuncR\" | \"feGaussianBlur\" | \"feImage\" | \"feMerge\" | \"feMergeNode\" | \"feMorphology\" | \"feOffset\" | \"fePointLight\" | \"feSpecularLighting\" | \"feSpotLight\" | \"feTile\" | \"feTurbulence\" | \"foreignObject\" | \"g\" | \"linearGradient\" | \"marker\" | \"metadata\" | \"mpath\" | \"polygon\" | \"polyline\" | \"radialGradient\" | \"switch\" | \"textPath\" | \"tspan\" | \"use\" | \"view\"" + "\"symbol\" | \"object\" | \"source\" | \"meta\" | \"desc\" | \"filter\" | \"big\" | \"link\" | \"small\" | \"sub\" | \"sup\" | \"text\" | \"map\" | \"head\" | \"slot\" | \"style\" | \"title\" | \"data\" | \"path\" | \"code\" | \"pattern\" | \"summary\" | \"template\" | \"span\" | \"q\" | \"body\" | \"html\" | \"stop\" | \"main\" | \"form\" | \"line\" | \"rect\" | \"label\" | \"progress\" | \"article\" | \"image\" | \"menu\" | \"base\" | React.ComponentType | \"s\" | \"legend\" | \"canvas\" | \"svg\" | \"h1\" | \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"p\" | \"select\" | \"output\" | \"script\" | \"time\" | \"mask\" | \"input\" | \"table\" | \"a\" | \"abbr\" | \"address\" | \"area\" | \"aside\" | \"audio\" | \"b\" | \"bdi\" | \"bdo\" | \"blockquote\" | \"br\" | \"button\" | \"caption\" | \"cite\" | \"col\" | \"colgroup\" | \"datalist\" | \"dd\" | \"del\" | \"details\" | \"dfn\" | \"dialog\" | \"div\" | \"dl\" | \"dt\" | \"em\" | \"embed\" | \"fieldset\" | \"figcaption\" | \"figure\" | \"footer\" | \"header\" | \"hgroup\" | \"hr\" | \"i\" | \"iframe\" | \"img\" | \"ins\" | \"kbd\" | \"keygen\" | \"li\" | \"mark\" | \"menuitem\" | \"meter\" | \"nav\" | \"noindex\" | \"noscript\" | \"ol\" | \"optgroup\" | \"option\" | \"param\" | \"picture\" | \"pre\" | \"rp\" | \"rt\" | \"ruby\" | \"samp\" | \"section\" | \"strong\" | \"tbody\" | \"td\" | \"textarea\" | \"tfoot\" | \"th\" | \"thead\" | \"tr\" | \"track\" | \"u\" | \"ul\" | \"var\" | \"video\" | \"wbr\" | \"webview\" | \"animate\" | \"animateMotion\" | \"animateTransform\" | \"circle\" | \"clipPath\" | \"defs\" | \"ellipse\" | \"feBlend\" | \"feColorMatrix\" | \"feComponentTransfer\" | \"feComposite\" | \"feConvolveMatrix\" | \"feDiffuseLighting\" | \"feDisplacementMap\" | \"feDistantLight\" | \"feDropShadow\" | \"feFlood\" | \"feFuncA\" | \"feFuncB\" | \"feFuncG\" | \"feFuncR\" | \"feGaussianBlur\" | \"feImage\" | \"feMerge\" | \"feMergeNode\" | \"feMorphology\" | \"feOffset\" | \"fePointLight\" | \"feSpecularLighting\" | \"feSpotLight\" | \"feTile\" | \"feTurbulence\" | \"foreignObject\" | \"g\" | \"linearGradient\" | \"marker\" | \"metadata\" | \"mpath\" | \"polygon\" | \"polyline\" | \"radialGradient\" | \"switch\" | \"textPath\" | \"tspan\" | \"use\" | \"view\"" ], "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/exception_item_card.tsx", "deprecated": false, diff --git a/api_docs/kbn_securitysolution_exception_list_components.mdx b/api_docs/kbn_securitysolution_exception_list_components.mdx index de3dccd97d1c4..a170d02cfa40d 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: 2024-08-07 +date: 2024-08-08 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_hook_utils.mdx b/api_docs/kbn_securitysolution_hook_utils.mdx index 72ecc721d8f37..a8c25aa4741c0 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: 2024-08-07 +date: 2024-08-08 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 fb2da044633d0..87ee6dac77884 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: 2024-08-07 +date: 2024-08-08 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 130678cae8218..405bb509c707d 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: 2024-08-07 +date: 2024-08-08 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 8713523144fc5..61eae47af1347 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: 2024-08-07 +date: 2024-08-08 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 95c1aa4c154c5..e0b21eb792a2f 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: 2024-08-07 +date: 2024-08-08 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 40c0cf9e935bc..99cb8d1dfa5c2 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: 2024-08-07 +date: 2024-08-08 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 bc72db5e380a2..a1ca045acad6e 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: 2024-08-07 +date: 2024-08-08 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 b9582a0fd2144..a843756e300ab 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: 2024-08-07 +date: 2024-08-08 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 b569552ed2a64..3b0038d07dbd5 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: 2024-08-07 +date: 2024-08-08 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 ca9884950e2d6..f9e79f761ad33 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: 2024-08-07 +date: 2024-08-08 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 4c199c4b6bf73..40f19b0f2122a 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: 2024-08-07 +date: 2024-08-08 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.devdocs.json b/api_docs/kbn_securitysolution_utils.devdocs.json index d36fe39b6481f..7f9eeb7c9d58c 100644 --- a/api_docs/kbn_securitysolution_utils.devdocs.json +++ b/api_docs/kbn_securitysolution_utils.devdocs.json @@ -251,6 +251,53 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/securitysolution-utils", + "id": "def-common.isAggregatingQuery", + "type": "Function", + "tags": [], + "label": "isAggregatingQuery", + "description": [], + "signature": [ + "(ast: ", + { + "pluginId": "@kbn/esql-ast", + "scope": "common", + "docId": "kibKbnEsqlAstPluginApi", + "section": "def-common.ESQLAst", + "text": "ESQLAst" + }, + ") => boolean" + ], + "path": "packages/kbn-securitysolution-utils/src/esql/compute_if_esql_query_aggregating.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/securitysolution-utils", + "id": "def-common.isAggregatingQuery.$1", + "type": "Array", + "tags": [], + "label": "ast", + "description": [], + "signature": [ + { + "pluginId": "@kbn/esql-ast", + "scope": "common", + "docId": "kibKbnEsqlAstPluginApi", + "section": "def-common.ESQLAst", + "text": "ESQLAst" + } + ], + "path": "packages/kbn-securitysolution-utils/src/esql/compute_if_esql_query_aggregating.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/securitysolution-utils", "id": "def-common.isPathValid", diff --git a/api_docs/kbn_securitysolution_utils.mdx b/api_docs/kbn_securitysolution_utils.mdx index 39e5c5d55a138..582a92a2e4b42 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-utils'] --- import kbnSecuritysolutionUtilsObj from './kbn_securitysolution_utils.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/security-detection-engine](https://github.com/orgs/elastic/tea | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 49 | 0 | 44 | 0 | +| 51 | 0 | 46 | 0 | ## Common diff --git a/api_docs/kbn_server_http_tools.mdx b/api_docs/kbn_server_http_tools.mdx index 425aff9a46c91..e1ee7439b9b27 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: 2024-08-07 +date: 2024-08-08 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 746d9122cb34e..e41e9b0900d4e 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-route-repository'] --- import kbnServerRouteRepositoryObj from './kbn_server_route_repository.devdocs.json'; diff --git a/api_docs/kbn_server_route_repository_utils.mdx b/api_docs/kbn_server_route_repository_utils.mdx index 1a68ae7843f61..64bcfa7677127 100644 --- a/api_docs/kbn_server_route_repository_utils.mdx +++ b/api_docs/kbn_server_route_repository_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-route-repository-utils title: "@kbn/server-route-repository-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-route-repository-utils plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-route-repository-utils'] --- import kbnServerRouteRepositoryUtilsObj from './kbn_server_route_repository_utils.devdocs.json'; diff --git a/api_docs/kbn_serverless_common_settings.mdx b/api_docs/kbn_serverless_common_settings.mdx index 14e0099f22655..a8f2ac795ebb3 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: 2024-08-07 +date: 2024-08-08 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 8937a2fb75b01..82655cae0866f 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: 2024-08-07 +date: 2024-08-08 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 22aeab4b567a0..e78845c52d974 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: 2024-08-07 +date: 2024-08-08 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 747c3fd2ecdc6..3a0c8737238cd 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: 2024-08-07 +date: 2024-08-08 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 06d1e020228cc..2fd1795ff01cd 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: 2024-08-07 +date: 2024-08-08 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 0832794d892b3..a2e322532f7fe 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: 2024-08-07 +date: 2024-08-08 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 73b553d872d27..2958c334b5d2b 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: 2024-08-07 +date: 2024-08-08 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 ca2fd0d4782f9..aaa806fbafe5b 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: 2024-08-07 +date: 2024-08-08 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_button_exit_full_screen.mdx b/api_docs/kbn_shared_ux_button_exit_full_screen.mdx index a4202291c2929..569cba45daf73 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: 2024-08-07 +date: 2024-08-08 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_toolbar.mdx b/api_docs/kbn_shared_ux_button_toolbar.mdx index 9bf689199a9ab..4bbce6fd5be83 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: 2024-08-07 +date: 2024-08-08 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 362ffc5b90ab9..0232688b5c7a2 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: 2024-08-07 +date: 2024-08-08 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 de6f9a301b620..8dfda9d2dcc01 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: 2024-08-07 +date: 2024-08-08 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 e25bb16b3dc2b..85fd8e27ed66f 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: 2024-08-07 +date: 2024-08-08 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_error_boundary.mdx b/api_docs/kbn_shared_ux_error_boundary.mdx index 33227327f80f8..55955ca38da63 100644 --- a/api_docs/kbn_shared_ux_error_boundary.mdx +++ b/api_docs/kbn_shared_ux_error_boundary.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-error-boundary title: "@kbn/shared-ux-error-boundary" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-error-boundary plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-error-boundary'] --- import kbnSharedUxErrorBoundaryObj from './kbn_shared_ux_error_boundary.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_context.mdx b/api_docs/kbn_shared_ux_file_context.mdx index 1481843dbf957..db6f363b3e76a 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: 2024-08-07 +date: 2024-08-08 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 6621be39f8f10..37acae655f400 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: 2024-08-07 +date: 2024-08-08 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 ede9d509a6c75..e91a962f90792 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: 2024-08-07 +date: 2024-08-08 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 d3a08f690b139..e0ffe21f66ec8 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: 2024-08-07 +date: 2024-08-08 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 3aca4546e2064..cbbde620ba593 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: 2024-08-07 +date: 2024-08-08 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 ac5779bab5179..31b11254fddec 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: 2024-08-07 +date: 2024-08-08 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 db13e6f9106db..e180968d95a76 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: 2024-08-07 +date: 2024-08-08 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 5428b13254c19..9408bfe76143c 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: 2024-08-07 +date: 2024-08-08 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 5a693b4b62ad5..1802cb8e2af6d 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: 2024-08-07 +date: 2024-08-08 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 bd14577dd508a..14ac0aeb3d679 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: 2024-08-07 +date: 2024-08-08 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 4ba3341f62ba9..afbe5b45c7c0b 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: 2024-08-07 +date: 2024-08-08 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 b47ae9cd12678..553fbd004694b 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: 2024-08-07 +date: 2024-08-08 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 0f0aa093345b0..b18286fc4bfee 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: 2024-08-07 +date: 2024-08-08 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 673596225c2c0..a95cfe896f4cf 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: 2024-08-07 +date: 2024-08-08 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 155254ad58aa7..baaae833f7fbe 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: 2024-08-07 +date: 2024-08-08 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 47ae1def7644d..0f3e321c1f0b6 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: 2024-08-07 +date: 2024-08-08 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 7d8c39faaac54..6fff6b2fcc4fa 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: 2024-08-07 +date: 2024-08-08 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 a3bee026d01aa..94cf259e55575 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: 2024-08-07 +date: 2024-08-08 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.mdx b/api_docs/kbn_shared_ux_page_no_data.mdx index d8d844a28ed20..6c217da430dbd 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data'] --- import kbnSharedUxPageNoDataObj from './kbn_shared_ux_page_no_data.devdocs.json'; 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 26ae370e79a91..49c0b4fd9c8a8 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: 2024-08-07 +date: 2024-08-08 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 e68fe53bc2269..416ab1b14135b 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: 2024-08-07 +date: 2024-08-08 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 a8ba55391fc10..a5184f221df7b 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: 2024-08-07 +date: 2024-08-08 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 512343708b539..effc761116b21 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: 2024-08-07 +date: 2024-08-08 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 0cc7fca096b6d..39f9c8ceda85c 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: 2024-08-07 +date: 2024-08-08 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 98c34d3270f1b..045d44840c05d 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: 2024-08-07 +date: 2024-08-08 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 d24e2c571f25e..0991c30ea0482 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: 2024-08-07 +date: 2024-08-08 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 1acf5f35a4e9b..6b30ce494f0a6 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: 2024-08-07 +date: 2024-08-08 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 e673c66bad911..990f362356522 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: 2024-08-07 +date: 2024-08-08 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 5927d224a729e..190dd3edd0425 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: 2024-08-07 +date: 2024-08-08 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 d6dd30872efa2..86b8e1ac93218 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: 2024-08-07 +date: 2024-08-08 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_tabbed_modal.mdx b/api_docs/kbn_shared_ux_tabbed_modal.mdx index bfad61a118a1d..62bbf8aff5ab2 100644 --- a/api_docs/kbn_shared_ux_tabbed_modal.mdx +++ b/api_docs/kbn_shared_ux_tabbed_modal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-tabbed-modal title: "@kbn/shared-ux-tabbed-modal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-tabbed-modal plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-tabbed-modal'] --- import kbnSharedUxTabbedModalObj from './kbn_shared_ux_tabbed_modal.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_utility.mdx b/api_docs/kbn_shared_ux_utility.mdx index 6f6d18cdeac1b..dc96e32137628 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: 2024-08-07 +date: 2024-08-08 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.mdx b/api_docs/kbn_slo_schema.mdx index e9af93a2f8979..359807de14b5e 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/slo-schema'] --- import kbnSloSchemaObj from './kbn_slo_schema.devdocs.json'; diff --git a/api_docs/kbn_some_dev_log.mdx b/api_docs/kbn_some_dev_log.mdx index cee33264d0196..7b1df1741bdad 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/some-dev-log'] --- import kbnSomeDevLogObj from './kbn_some_dev_log.devdocs.json'; diff --git a/api_docs/kbn_sort_predicates.mdx b/api_docs/kbn_sort_predicates.mdx index 07913e5d57d71..4d54b3fa28666 100644 --- a/api_docs/kbn_sort_predicates.mdx +++ b/api_docs/kbn_sort_predicates.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-sort-predicates title: "@kbn/sort-predicates" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/sort-predicates plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/sort-predicates'] --- import kbnSortPredicatesObj from './kbn_sort_predicates.devdocs.json'; diff --git a/api_docs/kbn_std.mdx b/api_docs/kbn_std.mdx index bb8a6221357ab..43b5de7f747ea 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: 2024-08-07 +date: 2024-08-08 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 8911ac28be550..f1f0cb01a2123 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: 2024-08-07 +date: 2024-08-08 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 1432cedd829ff..92a2718ced9da 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/storybook'] --- import kbnStorybookObj from './kbn_storybook.devdocs.json'; diff --git a/api_docs/kbn_synthetics_e2e.mdx b/api_docs/kbn_synthetics_e2e.mdx index 3bd47abeaf85f..7e910866ef549 100644 --- a/api_docs/kbn_synthetics_e2e.mdx +++ b/api_docs/kbn_synthetics_e2e.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-synthetics-e2e title: "@kbn/synthetics-e2e" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/synthetics-e2e plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/synthetics-e2e'] --- import kbnSyntheticsE2eObj from './kbn_synthetics_e2e.devdocs.json'; diff --git a/api_docs/kbn_telemetry_tools.mdx b/api_docs/kbn_telemetry_tools.mdx index 2645588274ca1..55443d5e20bdc 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/telemetry-tools'] --- import kbnTelemetryToolsObj from './kbn_telemetry_tools.devdocs.json'; diff --git a/api_docs/kbn_test.devdocs.json b/api_docs/kbn_test.devdocs.json index 50380d3b663e5..a8df40b09e765 100644 --- a/api_docs/kbn_test.devdocs.json +++ b/api_docs/kbn_test.devdocs.json @@ -1699,21 +1699,6 @@ "deprecated": false, "trackAdoption": false, "isRequired": true - }, - { - "parentPluginId": "@kbn/test", - "id": "def-common.SamlSessionManager.Unnamed.$2", - "type": "string", - "tags": [], - "label": "rolesFilename", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "packages/kbn-test/src/auth/session_manager.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": false } ], "returnComment": [] @@ -5077,17 +5062,29 @@ { "parentPluginId": "@kbn/test", "id": "def-common.SamlSessionManagerOptions.supportedRoles", - "type": "Array", + "type": "Object", "tags": [], "label": "supportedRoles", "description": [], "signature": [ - "string[] | undefined" + "SupportedRoles", + " | undefined" ], "path": "packages/kbn-test/src/auth/session_manager.ts", "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "@kbn/test", + "id": "def-common.SamlSessionManagerOptions.cloudUsersFilePath", + "type": "string", + "tags": [], + "label": "cloudUsersFilePath", + "description": [], + "path": "packages/kbn-test/src/auth/session_manager.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "@kbn/test", "id": "def-common.SamlSessionManagerOptions.log", diff --git a/api_docs/kbn_test.mdx b/api_docs/kbn_test.mdx index 2976e80138be3..bb773205983e2 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test'] --- import kbnTestObj from './kbn_test.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kiban | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 315 | 4 | 267 | 13 | +| 315 | 4 | 267 | 14 | ## Common diff --git a/api_docs/kbn_test_eui_helpers.mdx b/api_docs/kbn_test_eui_helpers.mdx index a646100ec1706..8849b900ecab1 100644 --- a/api_docs/kbn_test_eui_helpers.mdx +++ b/api_docs/kbn_test_eui_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-eui-helpers title: "@kbn/test-eui-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-eui-helpers plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-eui-helpers'] --- import kbnTestEuiHelpersObj from './kbn_test_eui_helpers.devdocs.json'; diff --git a/api_docs/kbn_test_jest_helpers.mdx b/api_docs/kbn_test_jest_helpers.mdx index e1e10fb5b1cfb..aa63c17a859bb 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: 2024-08-07 +date: 2024-08-08 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 f84f9acb67c54..d44f7e1e72d7c 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: 2024-08-07 +date: 2024-08-08 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 044005597d375..f4f616919047b 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/text-based-editor'] --- import kbnTextBasedEditorObj from './kbn_text_based_editor.devdocs.json'; diff --git a/api_docs/kbn_timerange.mdx b/api_docs/kbn_timerange.mdx index 91791f06878ef..b955552a4bc68 100644 --- a/api_docs/kbn_timerange.mdx +++ b/api_docs/kbn_timerange.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-timerange title: "@kbn/timerange" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/timerange plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/timerange'] --- import kbnTimerangeObj from './kbn_timerange.devdocs.json'; diff --git a/api_docs/kbn_tooling_log.mdx b/api_docs/kbn_tooling_log.mdx index a385c52d365a6..515767c2bde02 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/tooling-log'] --- import kbnToolingLogObj from './kbn_tooling_log.devdocs.json'; diff --git a/api_docs/kbn_triggers_actions_ui_types.mdx b/api_docs/kbn_triggers_actions_ui_types.mdx index b29afa2e8b936..6000486ec41e1 100644 --- a/api_docs/kbn_triggers_actions_ui_types.mdx +++ b/api_docs/kbn_triggers_actions_ui_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-triggers-actions-ui-types title: "@kbn/triggers-actions-ui-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/triggers-actions-ui-types plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/triggers-actions-ui-types'] --- import kbnTriggersActionsUiTypesObj from './kbn_triggers_actions_ui_types.devdocs.json'; diff --git a/api_docs/kbn_try_in_console.mdx b/api_docs/kbn_try_in_console.mdx index 73760c5351c8d..a7ea664586f4a 100644 --- a/api_docs/kbn_try_in_console.mdx +++ b/api_docs/kbn_try_in_console.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-try-in-console title: "@kbn/try-in-console" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/try-in-console plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/try-in-console'] --- import kbnTryInConsoleObj from './kbn_try_in_console.devdocs.json'; diff --git a/api_docs/kbn_ts_projects.mdx b/api_docs/kbn_ts_projects.mdx index 0dbd743d29385..6019e351ecb2c 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: 2024-08-07 +date: 2024-08-08 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 b28bb3f6f897a..494cce9a2b37b 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: 2024-08-07 +date: 2024-08-08 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 024f4f807176f..590f740824a41 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: 2024-08-07 +date: 2024-08-08 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 08891cf6b82a2..6bea7d88435f3 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: 2024-08-07 +date: 2024-08-08 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 8a42da71913a7..1b487a89f29bf 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: 2024-08-07 +date: 2024-08-08 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.devdocs.json b/api_docs/kbn_unified_data_table.devdocs.json index c778b833f799d..00dea271a943c 100644 --- a/api_docs/kbn_unified_data_table.devdocs.json +++ b/api_docs/kbn_unified_data_table.devdocs.json @@ -11,7 +11,9 @@ "label": "DataTableRowControl", "description": [], "signature": [ - "({ children }: { children: React.ReactNode; }) => JSX.Element" + "({ size, children }: React.PropsWithChildren<{ size?: ", + "Size", + " | undefined; }>) => JSX.Element" ], "path": "packages/kbn-unified-data-table/src/components/data_table_row_control.tsx", "deprecated": false, @@ -20,29 +22,19 @@ { "parentPluginId": "@kbn/unified-data-table", "id": "def-public.DataTableRowControl.$1", - "type": "Object", + "type": "CompoundType", "tags": [], - "label": "{ children }", + "label": "{ size, children }", "description": [], + "signature": [ + "React.PropsWithChildren<{ size?: ", + "Size", + " | undefined; }>" + ], "path": "packages/kbn-unified-data-table/src/components/data_table_row_control.tsx", "deprecated": false, "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/unified-data-table", - "id": "def-public.DataTableRowControl.$1.children", - "type": "CompoundType", - "tags": [], - "label": "children", - "description": [], - "signature": [ - "boolean | React.ReactChild | React.ReactFragment | React.ReactPortal | null | undefined" - ], - "path": "packages/kbn-unified-data-table/src/components/data_table_row_control.tsx", - "deprecated": false, - "trackAdoption": false - } - ] + "isRequired": true } ], "returnComment": [], @@ -516,7 +508,7 @@ "label": "UnifiedDataTable", "description": [], "signature": [ - "({ ariaLabelledBy, columns, columnsMeta, showColumnTokens, configHeaderRowHeight, headerRowHeightState, onUpdateHeaderRowHeight, controlColumnIds, dataView, loadingState, onFilter, onResize, onSetColumns, onSort, rows, searchDescription, searchTitle, settings, showTimeCol, showFullScreenButton, sort, useNewFieldsApi, isSortEnabled, isPaginationEnabled, cellActionsTriggerId, className, rowHeightState, onUpdateRowHeight, maxAllowedSampleSize, sampleSizeState, onUpdateSampleSize, isPlainRecord, rowsPerPageState, onUpdateRowsPerPage, onFieldEdited, services, renderCustomGridBody, renderCustomToolbar, trailingControlColumns, totalHits, onFetchMoreRecords, renderDocumentView, setExpandedDoc, expandedDoc, configRowHeight, showMultiFields, maxDocFieldsDisplayed, externalControlColumns, externalAdditionalControls, rowsPerPageOptions, visibleCellActions, externalCustomRenderers, additionalFieldGroups, consumer, componentsTourSteps, gridStyleOverride, rowLineHeightOverride, cellActionsMetadata, customGridColumnsConfiguration, customControlColumnsConfiguration, enableComparisonMode, cellContext, renderCellPopover, getRowIndicator, }: ", + "({ ariaLabelledBy, columns, columnsMeta, showColumnTokens, configHeaderRowHeight, headerRowHeightState, onUpdateHeaderRowHeight, controlColumnIds, rowAdditionalLeadingControls, dataView, loadingState, onFilter, onResize, onSetColumns, onSort, rows, searchDescription, searchTitle, settings, showTimeCol, showFullScreenButton, sort, useNewFieldsApi, isSortEnabled, isPaginationEnabled, cellActionsTriggerId, className, rowHeightState, onUpdateRowHeight, maxAllowedSampleSize, sampleSizeState, onUpdateSampleSize, isPlainRecord, rowsPerPageState, onUpdateRowsPerPage, onFieldEdited, services, renderCustomGridBody, renderCustomToolbar, externalControlColumns, trailingControlColumns, totalHits, onFetchMoreRecords, renderDocumentView, setExpandedDoc, expandedDoc, configRowHeight, showMultiFields, maxDocFieldsDisplayed, externalAdditionalControls, rowsPerPageOptions, visibleCellActions, externalCustomRenderers, additionalFieldGroups, consumer, componentsTourSteps, gridStyleOverride, rowLineHeightOverride, cellActionsMetadata, customGridColumnsConfiguration, enableComparisonMode, cellContext, renderCellPopover, getRowIndicator, }: ", { "pluginId": "@kbn/unified-data-table", "scope": "public", @@ -535,7 +527,7 @@ "id": "def-public.UnifiedDataTable.$1", "type": "Object", "tags": [], - "label": "{\n ariaLabelledBy,\n columns,\n columnsMeta,\n showColumnTokens,\n configHeaderRowHeight,\n headerRowHeightState,\n onUpdateHeaderRowHeight,\n controlColumnIds = CONTROL_COLUMN_IDS_DEFAULT,\n dataView,\n loadingState,\n onFilter,\n onResize,\n onSetColumns,\n onSort,\n rows,\n searchDescription,\n searchTitle,\n settings,\n showTimeCol,\n showFullScreenButton = true,\n sort,\n useNewFieldsApi,\n isSortEnabled = true,\n isPaginationEnabled = true,\n cellActionsTriggerId,\n className,\n rowHeightState,\n onUpdateRowHeight,\n maxAllowedSampleSize,\n sampleSizeState,\n onUpdateSampleSize,\n isPlainRecord = false,\n rowsPerPageState,\n onUpdateRowsPerPage,\n onFieldEdited,\n services,\n renderCustomGridBody,\n renderCustomToolbar,\n trailingControlColumns,\n totalHits,\n onFetchMoreRecords,\n renderDocumentView,\n setExpandedDoc,\n expandedDoc,\n configRowHeight,\n showMultiFields = true,\n maxDocFieldsDisplayed = 50,\n externalControlColumns,\n externalAdditionalControls,\n rowsPerPageOptions,\n visibleCellActions,\n externalCustomRenderers,\n additionalFieldGroups,\n consumer = 'discover',\n componentsTourSteps,\n gridStyleOverride,\n rowLineHeightOverride,\n cellActionsMetadata,\n customGridColumnsConfiguration,\n customControlColumnsConfiguration,\n enableComparisonMode,\n cellContext,\n renderCellPopover,\n getRowIndicator,\n}", + "label": "{\n ariaLabelledBy,\n columns,\n columnsMeta,\n showColumnTokens,\n configHeaderRowHeight,\n headerRowHeightState,\n onUpdateHeaderRowHeight,\n controlColumnIds = CONTROL_COLUMN_IDS_DEFAULT,\n rowAdditionalLeadingControls,\n dataView,\n loadingState,\n onFilter,\n onResize,\n onSetColumns,\n onSort,\n rows,\n searchDescription,\n searchTitle,\n settings,\n showTimeCol,\n showFullScreenButton = true,\n sort,\n useNewFieldsApi,\n isSortEnabled = true,\n isPaginationEnabled = true,\n cellActionsTriggerId,\n className,\n rowHeightState,\n onUpdateRowHeight,\n maxAllowedSampleSize,\n sampleSizeState,\n onUpdateSampleSize,\n isPlainRecord = false,\n rowsPerPageState,\n onUpdateRowsPerPage,\n onFieldEdited,\n services,\n renderCustomGridBody,\n renderCustomToolbar,\n externalControlColumns, // TODO: deprecate in favor of rowAdditionalLeadingControls\n trailingControlColumns, // TODO: deprecate in favor of rowAdditionalLeadingControls\n totalHits,\n onFetchMoreRecords,\n renderDocumentView,\n setExpandedDoc,\n expandedDoc,\n configRowHeight,\n showMultiFields = true,\n maxDocFieldsDisplayed = 50,\n externalAdditionalControls,\n rowsPerPageOptions,\n visibleCellActions,\n externalCustomRenderers,\n additionalFieldGroups,\n consumer = 'discover',\n componentsTourSteps,\n gridStyleOverride,\n rowLineHeightOverride,\n cellActionsMetadata,\n customGridColumnsConfiguration,\n enableComparisonMode,\n cellContext,\n renderCellPopover,\n getRowIndicator,\n}", "description": [], "signature": [ { @@ -592,10 +584,10 @@ "interfaces": [ { "parentPluginId": "@kbn/unified-data-table", - "id": "def-public.ControlColumns", + "id": "def-public.CustomGridColumnProps", "type": "Interface", "tags": [], - "label": "ControlColumns", + "label": "CustomGridColumnProps", "description": [], "path": "packages/kbn-unified-data-table/src/types.ts", "deprecated": false, @@ -603,13 +595,13 @@ "children": [ { "parentPluginId": "@kbn/unified-data-table", - "id": "def-public.ControlColumns.select", + "id": "def-public.CustomGridColumnProps.column", "type": "Object", "tags": [], - "label": "select", + "label": "column", "description": [], "signature": [ - "EuiDataGridControlColumn" + "EuiDataGridColumn" ], "path": "packages/kbn-unified-data-table/src/types.ts", "deprecated": false, @@ -617,13 +609,13 @@ }, { "parentPluginId": "@kbn/unified-data-table", - "id": "def-public.ControlColumns.openDetails", - "type": "Object", + "id": "def-public.CustomGridColumnProps.headerRowHeight", + "type": "number", "tags": [], - "label": "openDetails", + "label": "headerRowHeight", "description": [], "signature": [ - "EuiDataGridControlColumn" + "number | undefined" ], "path": "packages/kbn-unified-data-table/src/types.ts", "deprecated": false, @@ -634,10 +626,10 @@ }, { "parentPluginId": "@kbn/unified-data-table", - "id": "def-public.ControlColumnsProps", + "id": "def-public.RowControlColumn", "type": "Interface", "tags": [], - "label": "ControlColumnsProps", + "label": "RowControlColumn", "description": [], "path": "packages/kbn-unified-data-table/src/types.ts", "deprecated": false, @@ -645,33 +637,124 @@ "children": [ { "parentPluginId": "@kbn/unified-data-table", - "id": "def-public.ControlColumnsProps.controlColumns", - "type": "Object", + "id": "def-public.RowControlColumn.id", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "path": "packages/kbn-unified-data-table/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-public.RowControlColumn.headerAriaLabel", + "type": "string", "tags": [], - "label": "controlColumns", + "label": "headerAriaLabel", + "description": [], + "path": "packages/kbn-unified-data-table/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-public.RowControlColumn.headerCellRender", + "type": "CompoundType", + "tags": [], + "label": "headerCellRender", + "description": [], + "signature": [ + "React.ComponentType<{}> | undefined" + ], + "path": "packages/kbn-unified-data-table/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-public.RowControlColumn.renderControl", + "type": "Function", + "tags": [], + "label": "renderControl", "description": [], "signature": [ + "(Control: ", { "pluginId": "@kbn/unified-data-table", "scope": "public", "docId": "kibKbnUnifiedDataTablePluginApi", - "section": "def-public.ControlColumns", - "text": "ControlColumns" - } + "section": "def-public.RowControlComponent", + "text": "RowControlComponent" + }, + ", props: ", + { + "pluginId": "@kbn/unified-data-table", + "scope": "public", + "docId": "kibKbnUnifiedDataTablePluginApi", + "section": "def-public.RowControlRowProps", + "text": "RowControlRowProps" + }, + ") => React.ReactElement>" ], "path": "packages/kbn-unified-data-table/src/types.ts", "deprecated": false, - "trackAdoption": false + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-public.RowControlColumn.renderControl.$1", + "type": "Function", + "tags": [], + "label": "Control", + "description": [], + "signature": [ + { + "pluginId": "@kbn/unified-data-table", + "scope": "public", + "docId": "kibKbnUnifiedDataTablePluginApi", + "section": "def-public.RowControlComponent", + "text": "RowControlComponent" + } + ], + "path": "packages/kbn-unified-data-table/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-public.RowControlColumn.renderControl.$2", + "type": "Object", + "tags": [], + "label": "props", + "description": [], + "signature": [ + { + "pluginId": "@kbn/unified-data-table", + "scope": "public", + "docId": "kibKbnUnifiedDataTablePluginApi", + "section": "def-public.RowControlRowProps", + "text": "RowControlRowProps" + } + ], + "path": "packages/kbn-unified-data-table/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] } ], "initialIsOpen": false }, { "parentPluginId": "@kbn/unified-data-table", - "id": "def-public.CustomGridColumnProps", + "id": "def-public.RowControlProps", "type": "Interface", "tags": [], - "label": "CustomGridColumnProps", + "label": "RowControlProps", "description": [], "path": "packages/kbn-unified-data-table/src/types.ts", "deprecated": false, @@ -679,13 +762,13 @@ "children": [ { "parentPluginId": "@kbn/unified-data-table", - "id": "def-public.CustomGridColumnProps.column", - "type": "Object", + "id": "def-public.RowControlProps.datatestsubj", + "type": "string", "tags": [], - "label": "column", + "label": "'data-test-subj'", "description": [], "signature": [ - "EuiDataGridColumn" + "string | undefined" ], "path": "packages/kbn-unified-data-table/src/types.ts", "deprecated": false, @@ -693,13 +776,119 @@ }, { "parentPluginId": "@kbn/unified-data-table", - "id": "def-public.CustomGridColumnProps.headerRowHeight", + "id": "def-public.RowControlProps.color", + "type": "CompoundType", + "tags": [], + "label": "color", + "description": [], + "signature": [ + "\"text\" | \"warning\" | \"success\" | \"primary\" | \"accent\" | \"danger\" | undefined" + ], + "path": "packages/kbn-unified-data-table/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-public.RowControlProps.disabled", + "type": "CompoundType", + "tags": [], + "label": "disabled", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "packages/kbn-unified-data-table/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-public.RowControlProps.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-unified-data-table/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-public.RowControlProps.iconType", + "type": "CompoundType", + "tags": [], + "label": "iconType", + "description": [], + "signature": [ + "string | React.ComponentType<{}>" + ], + "path": "packages/kbn-unified-data-table/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-public.RowControlProps.onClick", + "type": "Function", + "tags": [], + "label": "onClick", + "description": [], + "signature": [ + "((props: ", + { + "pluginId": "@kbn/unified-data-table", + "scope": "public", + "docId": "kibKbnUnifiedDataTablePluginApi", + "section": "def-public.RowControlRowProps", + "text": "RowControlRowProps" + }, + ") => void) | undefined" + ], + "path": "packages/kbn-unified-data-table/src/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-public.RowControlRowProps", + "type": "Interface", + "tags": [], + "label": "RowControlRowProps", + "description": [], + "path": "packages/kbn-unified-data-table/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-public.RowControlRowProps.rowIndex", "type": "number", "tags": [], - "label": "headerRowHeight", + "label": "rowIndex", + "description": [], + "path": "packages/kbn-unified-data-table/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-public.RowControlRowProps.record", + "type": "Object", + "tags": [], + "label": "record", "description": [], "signature": [ - "number | undefined" + { + "pluginId": "@kbn/discover-utils", + "scope": "common", + "docId": "kibKbnDiscoverUtilsPluginApi", + "section": "def-common.DataTableRecord", + "text": "DataTableRecord" + } ], "path": "packages/kbn-unified-data-table/src/types.ts", "deprecated": false, @@ -1955,10 +2144,37 @@ "parentPluginId": "@kbn/unified-data-table", "id": "def-public.UnifiedDataTableProps.externalControlColumns", "type": "Array", - "tags": [], + "tags": [ + "deprecated" + ], "label": "externalControlColumns", + "description": [], + "signature": [ + "EuiDataGridControlColumn", + "[] | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": true, + "trackAdoption": false, + "references": [ + { + "plugin": "cloudSecurityPosture", + "path": "x-pack/plugins/cloud_security_posture/public/components/cloud_security_data_table/cloud_security_data_table.tsx" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/index.tsx" + } + ] + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-public.UnifiedDataTableProps.trailingControlColumns", + "type": "Array", + "tags": [], + "label": "trailingControlColumns", "description": [ - "\nOptional value for providing EuiDataGridControlColumn list of the additional leading control columns. UnifiedDataTable includes two control columns: Open Details and Select." + "\nAn optional list of the EuiDataGridControlColumn type for setting trailing control columns standard for EuiDataGrid.\nWe recommend to rather position all controls in the beginning of rows and use `rowAdditionalLeadingControls` for that\nas number of columns can be dynamically changed and we don't want the controls to become hidden due to horizontal scroll." ], "signature": [ "EuiDataGridControlColumn", @@ -1968,6 +2184,29 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-public.UnifiedDataTableProps.rowAdditionalLeadingControls", + "type": "Array", + "tags": [], + "label": "rowAdditionalLeadingControls", + "description": [ + "\nOptional value to extend the list of default row actions" + ], + "signature": [ + { + "pluginId": "@kbn/unified-data-table", + "scope": "public", + "docId": "kibKbnUnifiedDataTablePluginApi", + "section": "def-public.RowControlColumn", + "text": "RowControlColumn" + }, + "[] | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "@kbn/unified-data-table", "id": "def-public.UnifiedDataTableProps.totalHits", @@ -2093,23 +2332,6 @@ "deprecated": false, "trackAdoption": false }, - { - "parentPluginId": "@kbn/unified-data-table", - "id": "def-public.UnifiedDataTableProps.trailingControlColumns", - "type": "Array", - "tags": [], - "label": "trailingControlColumns", - "description": [ - "\nAn optional list of the EuiDataGridControlColumn type for setting trailing control columns standard for EuiDataGrid." - ], - "signature": [ - "EuiDataGridControlColumn", - "[] | undefined" - ], - "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", - "deprecated": false, - "trackAdoption": false - }, { "parentPluginId": "@kbn/unified-data-table", "id": "def-public.UnifiedDataTableProps.visibleCellActions", @@ -2203,29 +2425,6 @@ "deprecated": false, "trackAdoption": false }, - { - "parentPluginId": "@kbn/unified-data-table", - "id": "def-public.UnifiedDataTableProps.customControlColumnsConfiguration", - "type": "Function", - "tags": [], - "label": "customControlColumnsConfiguration", - "description": [ - "\nAn optional settings to control which columns to render as trailing and leading control columns" - ], - "signature": [ - { - "pluginId": "@kbn/unified-data-table", - "scope": "public", - "docId": "kibKbnUnifiedDataTablePluginApi", - "section": "def-public.CustomControlColumnConfiguration", - "text": "CustomControlColumnConfiguration" - }, - " | undefined" - ], - "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", - "deprecated": false, - "trackAdoption": false - }, { "parentPluginId": "@kbn/unified-data-table", "id": "def-public.UnifiedDataTableProps.consumer", @@ -2553,56 +2752,6 @@ "trackAdoption": false, "initialIsOpen": false }, - { - "parentPluginId": "@kbn/unified-data-table", - "id": "def-public.CustomControlColumnConfiguration", - "type": "Type", - "tags": [], - "label": "CustomControlColumnConfiguration", - "description": [], - "signature": [ - "(props: ", - { - "pluginId": "@kbn/unified-data-table", - "scope": "public", - "docId": "kibKbnUnifiedDataTablePluginApi", - "section": "def-public.ControlColumnsProps", - "text": "ControlColumnsProps" - }, - ") => { leadingControlColumns: ", - "EuiDataGridControlColumn", - "[]; trailingControlColumns?: ", - "EuiDataGridControlColumn", - "[] | undefined; }" - ], - "path": "packages/kbn-unified-data-table/src/types.ts", - "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ - { - "parentPluginId": "@kbn/unified-data-table", - "id": "def-public.CustomControlColumnConfiguration.$1", - "type": "Object", - "tags": [], - "label": "props", - "description": [], - "signature": [ - { - "pluginId": "@kbn/unified-data-table", - "scope": "public", - "docId": "kibKbnUnifiedDataTablePluginApi", - "section": "def-public.ControlColumnsProps", - "text": "ControlColumnsProps" - } - ], - "path": "packages/kbn-unified-data-table/src/types.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, { "parentPluginId": "@kbn/unified-data-table", "id": "def-public.CustomGridColumnsConfiguration", @@ -2708,6 +2857,60 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-public.RowControlComponent", + "type": "Type", + "tags": [], + "label": "RowControlComponent", + "description": [], + "signature": [ + "React.FunctionComponent<", + { + "pluginId": "@kbn/unified-data-table", + "scope": "public", + "docId": "kibKbnUnifiedDataTablePluginApi", + "section": "def-public.RowControlProps", + "text": "RowControlProps" + }, + ">" + ], + "path": "packages/kbn-unified-data-table/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-public.RowControlComponent.$1", + "type": "CompoundType", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "P & { children?: React.ReactNode; }" + ], + "path": "node_modules/@types/react/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-public.RowControlComponent.$2", + "type": "Any", + "tags": [], + "label": "context", + "description": [], + "signature": [ + "any" + ], + "path": "node_modules/@types/react/index.d.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/unified-data-table", "id": "def-public.SELECT_ROW", diff --git a/api_docs/kbn_unified_data_table.mdx b/api_docs/kbn_unified_data_table.mdx index bd3e38eb6b241..70a60fabe0e0e 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unified-data-table'] --- import kbnUnifiedDataTableObj from './kbn_unified_data_table.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/k | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 154 | 0 | 81 | 1 | +| 166 | 0 | 92 | 2 | ## Client diff --git a/api_docs/kbn_unified_doc_viewer.mdx b/api_docs/kbn_unified_doc_viewer.mdx index 13e4466a2d47d..23c0a849e7305 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: 2024-08-07 +date: 2024-08-08 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.devdocs.json b/api_docs/kbn_unified_field_list.devdocs.json index 408aed745f51c..ccbed1bcf4edb 100644 --- a/api_docs/kbn_unified_field_list.devdocs.json +++ b/api_docs/kbn_unified_field_list.devdocs.json @@ -2203,7 +2203,7 @@ "section": "def-public.CoreStart", "text": "CoreStart" }, - ", \"uiSettings\" | \"analytics\">; data: ", + ", \"analytics\" | \"uiSettings\">; data: ", { "pluginId": "data", "scope": "public", diff --git a/api_docs/kbn_unified_field_list.mdx b/api_docs/kbn_unified_field_list.mdx index 99c68aa553f8d..f5585995ea34c 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unified-field-list'] --- import kbnUnifiedFieldListObj from './kbn_unified_field_list.devdocs.json'; diff --git a/api_docs/kbn_unsaved_changes_badge.mdx b/api_docs/kbn_unsaved_changes_badge.mdx index 9e9f365037869..dba8d0e1c6c36 100644 --- a/api_docs/kbn_unsaved_changes_badge.mdx +++ b/api_docs/kbn_unsaved_changes_badge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-unsaved-changes-badge title: "@kbn/unsaved-changes-badge" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/unsaved-changes-badge plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unsaved-changes-badge'] --- import kbnUnsavedChangesBadgeObj from './kbn_unsaved_changes_badge.devdocs.json'; diff --git a/api_docs/kbn_unsaved_changes_prompt.mdx b/api_docs/kbn_unsaved_changes_prompt.mdx index 862a07e1dd180..1184618de6465 100644 --- a/api_docs/kbn_unsaved_changes_prompt.mdx +++ b/api_docs/kbn_unsaved_changes_prompt.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-unsaved-changes-prompt title: "@kbn/unsaved-changes-prompt" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/unsaved-changes-prompt plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unsaved-changes-prompt'] --- import kbnUnsavedChangesPromptObj from './kbn_unsaved_changes_prompt.devdocs.json'; diff --git a/api_docs/kbn_use_tracked_promise.mdx b/api_docs/kbn_use_tracked_promise.mdx index a60f76f18f035..9f5b88f3fe7c4 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: 2024-08-07 +date: 2024-08-08 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 d026b03d53f4e..fdf7c9c0821b6 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: 2024-08-07 +date: 2024-08-08 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 82b808c0b82c9..22fe3638109b3 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: 2024-08-07 +date: 2024-08-08 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 0d7cea5a23c63..4c92217fde3ad 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: 2024-08-07 +date: 2024-08-08 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 7ed5b8b43ea1b..9f72af7215f17 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: 2024-08-07 +date: 2024-08-08 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 fa3e694a439ba..d066d3c12f39a 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/visualization-ui-components'] --- import kbnVisualizationUiComponentsObj from './kbn_visualization_ui_components.devdocs.json'; diff --git a/api_docs/kbn_visualization_utils.mdx b/api_docs/kbn_visualization_utils.mdx index 261d94735ddc8..48fdc8ea7c184 100644 --- a/api_docs/kbn_visualization_utils.mdx +++ b/api_docs/kbn_visualization_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-visualization-utils title: "@kbn/visualization-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/visualization-utils plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/visualization-utils'] --- import kbnVisualizationUtilsObj from './kbn_visualization_utils.devdocs.json'; diff --git a/api_docs/kbn_xstate_utils.mdx b/api_docs/kbn_xstate_utils.mdx index e7c3cd176fc3f..253f1c414c92e 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: 2024-08-07 +date: 2024-08-08 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 9f9440e8906ec..2d4ba03b6d2ec 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/yarn-lock-validator'] --- import kbnYarnLockValidatorObj from './kbn_yarn_lock_validator.devdocs.json'; diff --git a/api_docs/kbn_zod.mdx b/api_docs/kbn_zod.mdx index eb97efe281fcd..3113fa897f7bf 100644 --- a/api_docs/kbn_zod.mdx +++ b/api_docs/kbn_zod.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-zod title: "@kbn/zod" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/zod plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/zod'] --- import kbnZodObj from './kbn_zod.devdocs.json'; diff --git a/api_docs/kbn_zod_helpers.mdx b/api_docs/kbn_zod_helpers.mdx index 48444a6e2a0cd..aa5f1c4c6f77b 100644 --- a/api_docs/kbn_zod_helpers.mdx +++ b/api_docs/kbn_zod_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-zod-helpers title: "@kbn/zod-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/zod-helpers plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/zod-helpers'] --- import kbnZodHelpersObj from './kbn_zod_helpers.devdocs.json'; diff --git a/api_docs/kibana_overview.mdx b/api_docs/kibana_overview.mdx index f81f0e00b4e3b..b6b25b7dec58e 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: 2024-08-07 +date: 2024-08-08 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 c315fa94ff100..75974d909acf5 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: 2024-08-07 +date: 2024-08-08 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 065f8db62dbca..3d93f1a16183a 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: 2024-08-07 +date: 2024-08-08 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 87d098c77122d..245c7ce391f44 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: 2024-08-07 +date: 2024-08-08 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 300a27421b55d..a67015d3e7aa4 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: 2024-08-07 +date: 2024-08-08 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 79ebc01041ff4..2f15b54f16f78 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: 2024-08-07 +date: 2024-08-08 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 ea9261cb2e9b5..dbca4e021fd73 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: 2024-08-07 +date: 2024-08-08 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 69fd0e93b708c..95098191a82ae 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: 2024-08-07 +date: 2024-08-08 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 95d2b1655be0e..3fb1feaed3c47 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: 2024-08-07 +date: 2024-08-08 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 9694521f9fa3a..8689e19f3b26b 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lists'] --- import listsObj from './lists.devdocs.json'; diff --git a/api_docs/logs_data_access.devdocs.json b/api_docs/logs_data_access.devdocs.json index c695eedcacdbf..4098b0950c6f1 100644 --- a/api_docs/logs_data_access.devdocs.json +++ b/api_docs/logs_data_access.devdocs.json @@ -6,7 +6,41 @@ "interfaces": [], "enums": [], "misc": [], - "objects": [] + "objects": [], + "setup": { + "parentPluginId": "logsDataAccess", + "id": "def-public.LogsDataAccessPluginSetup", + "type": "Type", + "tags": [], + "label": "LogsDataAccessPluginSetup", + "description": [], + "signature": [ + "void" + ], + "path": "x-pack/plugins/observability_solution/logs_data_access/public/plugin.ts", + "deprecated": false, + "trackAdoption": false, + "lifecycle": "setup", + "initialIsOpen": true + }, + "start": { + "parentPluginId": "logsDataAccess", + "id": "def-public.LogsDataAccessPluginStart", + "type": "Type", + "tags": [], + "label": "LogsDataAccessPluginStart", + "description": [], + "signature": [ + "{ services: { logSourcesService: ", + "LogSourcesService", + "; }; }" + ], + "path": "x-pack/plugins/observability_solution/logs_data_access/public/plugin.ts", + "deprecated": false, + "trackAdoption": false, + "lifecycle": "start", + "initialIsOpen": true + } }, "server": { "classes": [], @@ -132,7 +166,17 @@ "LogsErrorRateTimeseries", ") => Promise<", "LogsErrorRateTimeseriesReturnType", - ">; getLogSourcesService: (request: ", + ">; logSourcesServiceFactory: { getLogSourcesService(savedObjectsClient: ", + { + "pluginId": "@kbn/core-saved-objects-api-server", + "scope": "server", + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-server.SavedObjectsClientContract", + "text": "SavedObjectsClientContract" + }, + "): Promise<", + "LogSourcesService", + ">; getScopedLogSourcesService(request: ", { "pluginId": "@kbn/core-http-server", "scope": "server", @@ -140,11 +184,9 @@ "section": "def-server.KibanaRequest", "text": "KibanaRequest" }, - ") => Promise<{ getLogSources: () => Promise<", - "LogSource", - "[]>; setLogSources: (sources: ", - "LogSource", - "[]) => Promise; }>; }; }" + "): Promise<", + "LogSourcesService", + ">; }; }; }" ], "path": "x-pack/plugins/observability_solution/logs_data_access/server/plugin.ts", "deprecated": false, diff --git a/api_docs/logs_data_access.mdx b/api_docs/logs_data_access.mdx index 115ccf995831d..ddd656d98573f 100644 --- a/api_docs/logs_data_access.mdx +++ b/api_docs/logs_data_access.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/logsDataAccess title: "logsDataAccess" image: https://source.unsplash.com/400x175/?github description: API docs for the logsDataAccess plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'logsDataAccess'] --- import logsDataAccessObj from './logs_data_access.devdocs.json'; @@ -21,7 +21,15 @@ Contact [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs- | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 7 | 0 | 7 | 6 | +| 9 | 0 | 9 | 6 | + +## Client + +### Setup + + +### Start + ## Server diff --git a/api_docs/logs_explorer.mdx b/api_docs/logs_explorer.mdx index 37a71675f5e66..b9b15ad1bd39c 100644 --- a/api_docs/logs_explorer.mdx +++ b/api_docs/logs_explorer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/logsExplorer title: "logsExplorer" image: https://source.unsplash.com/400x175/?github description: API docs for the logsExplorer plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'logsExplorer'] --- import logsExplorerObj from './logs_explorer.devdocs.json'; diff --git a/api_docs/logs_shared.devdocs.json b/api_docs/logs_shared.devdocs.json index d0c2a280e1eab..32758ecf4558d 100644 --- a/api_docs/logs_shared.devdocs.json +++ b/api_docs/logs_shared.devdocs.json @@ -11,7 +11,7 @@ "label": "getLogViewReferenceFromUrl", "description": [], "signature": [ - "({ logViewKey, sourceIdKey, toastsService, urlStateStorage, }: LogViewUrlStateDependencies) => { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; } | null" + "({ logViewKey, sourceIdKey, toastsService, urlStateStorage, }: LogViewUrlStateDependencies) => { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; } | null" ], "path": "x-pack/plugins/observability_solution/logs_shared/public/observability_logs/log_view_state/src/url_state_storage_service.ts", "deprecated": false, @@ -840,7 +840,7 @@ "label": "LogViewProvider", "description": [], "signature": [ - "React.FunctionComponent; hasFailedLoading: boolean; hasFailedLoadingLogView: boolean; hasFailedLoadingLogViewStatus: boolean; hasFailedResolvingLogView: boolean; latestLoadLogViewFailures: Error[]; isUninitialized: boolean; isLoading: boolean; isLoadingLogView: boolean; isLoadingLogViewStatus: boolean; isResolvingLogView: boolean; logViewReference: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }; logView: ({ id: string; origin: \"internal\" | \"inline\" | \"stored\" | \"infra-source-stored\" | \"infra-source-internal\" | \"infra-source-fallback\"; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; } & { updatedAt?: number | undefined; version?: string | undefined; }) | undefined; resolvedLogView: ", + ">; hasFailedLoading: boolean; hasFailedLoadingLogView: boolean; hasFailedLoadingLogViewStatus: boolean; hasFailedResolvingLogView: boolean; latestLoadLogViewFailures: Error[]; isUninitialized: boolean; isLoading: boolean; isLoadingLogView: boolean; isLoadingLogViewStatus: boolean; isResolvingLogView: boolean; logViewReference: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }; logView: ({ id: string; origin: \"internal\" | \"inline\" | \"stored\" | \"infra-source-stored\" | \"infra-source-internal\" | \"infra-source-fallback\"; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; } & { updatedAt?: number | undefined; version?: string | undefined; }) | undefined; resolvedLogView: ", { "pluginId": "logsShared", "scope": "common", @@ -1918,7 +1918,7 @@ "BaseActionObject", ", ", "ServiceMap", - ">>; update: (logViewAttributes: Partial<{ name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }>) => Promise; changeLogViewReference: (logViewReference: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }) => void; revertToDefaultLogView: () => void; }" + ">>; update: (logViewAttributes: Partial<{ name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }>) => Promise; changeLogViewReference: (logViewReference: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }) => void; revertToDefaultLogView: () => void; }" ], "path": "x-pack/plugins/observability_solution/logs_shared/public/hooks/use_log_view.ts", "deprecated": false, @@ -1943,7 +1943,7 @@ "label": "initialLogViewReference", "description": [], "signature": [ - "{ logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; } | undefined" + "{ logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; } | undefined" ], "path": "x-pack/plugins/observability_solution/logs_shared/public/hooks/use_log_view.ts", "deprecated": false, @@ -2343,7 +2343,7 @@ "section": "def-public.LogViewNotificationEvent", "text": "LogViewNotificationEvent" }, - ">; hasFailedLoading: boolean; hasFailedLoadingLogView: boolean; hasFailedLoadingLogViewStatus: boolean; hasFailedResolvingLogView: boolean; latestLoadLogViewFailures: Error[]; isUninitialized: boolean; isLoading: boolean; isLoadingLogView: boolean; isLoadingLogViewStatus: boolean; isResolvingLogView: boolean; logViewReference: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }; logView: ({ id: string; origin: \"internal\" | \"inline\" | \"stored\" | \"infra-source-stored\" | \"infra-source-internal\" | \"infra-source-fallback\"; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; } & { updatedAt?: number | undefined; version?: string | undefined; }) | undefined; resolvedLogView: ", + ">; hasFailedLoading: boolean; hasFailedLoadingLogView: boolean; hasFailedLoadingLogViewStatus: boolean; hasFailedResolvingLogView: boolean; latestLoadLogViewFailures: Error[]; isUninitialized: boolean; isLoading: boolean; isLoadingLogView: boolean; isLoadingLogViewStatus: boolean; isResolvingLogView: boolean; logViewReference: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }; logView: ({ id: string; origin: \"internal\" | \"inline\" | \"stored\" | \"infra-source-stored\" | \"infra-source-internal\" | \"infra-source-fallback\"; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; } & { updatedAt?: number | undefined; version?: string | undefined; }) | undefined; resolvedLogView: ", { "pluginId": "logsShared", "scope": "common", @@ -2499,7 +2499,7 @@ "BaseActionObject", ", ", "ServiceMap", - ">>; update: (logViewAttributes: Partial<{ name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }>) => Promise; changeLogViewReference: (logViewReference: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }) => void; revertToDefaultLogView: () => void; }" + ">>; update: (logViewAttributes: Partial<{ name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }>) => Promise; changeLogViewReference: (logViewReference: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }) => void; revertToDefaultLogView: () => void; }" ], "path": "x-pack/plugins/observability_solution/logs_shared/public/hooks/use_log_view.ts", "deprecated": false, @@ -2905,6 +2905,22 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "logsShared", + "id": "def-public.LogsSharedClientStartDeps.logsDataAccess", + "type": "Object", + "tags": [], + "label": "logsDataAccess", + "description": [], + "signature": [ + "{ services: { logSourcesService: ", + "LogSourcesService", + "; }; }" + ], + "path": "x-pack/plugins/observability_solution/logs_shared/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "logsShared", "id": "def-public.LogsSharedClientStartDeps.observabilityAIAssistant", @@ -3345,7 +3361,7 @@ "label": "LogViewNotificationEvent", "description": [], "signature": [ - "{ type: \"LOADING_LOG_VIEW_STARTED\"; logViewReference: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }; } | { type: \"LOADING_LOG_VIEW_SUCCEEDED\"; resolvedLogView: ", + "{ type: \"LOADING_LOG_VIEW_STARTED\"; logViewReference: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }; } | { type: \"LOADING_LOG_VIEW_SUCCEEDED\"; resolvedLogView: ", { "pluginId": "logsShared", "scope": "common", @@ -3578,7 +3594,7 @@ "section": "def-server.RequestHandlerContext", "text": "RequestHandlerContext" }, - ", logView: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }, params: ", + ", logView: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }, params: ", "LogEntriesAroundParams", ", columnOverrides?: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[] | undefined) => Promise<{ entries: { id: string; index: string; cursor: { time: string; tiebreaker: number; }; columns: ({ columnId: string; time: string; } | { columnId: string; field: string; value: ", { @@ -3631,7 +3647,7 @@ "label": "logView", "description": [], "signature": [ - "{ logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }" + "{ logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }" ], "path": "x-pack/plugins/observability_solution/logs_shared/server/lib/domains/log_entries_domain/log_entries_domain.ts", "deprecated": false, @@ -3687,7 +3703,7 @@ "section": "def-server.RequestHandlerContext", "text": "RequestHandlerContext" }, - ", logView: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }, params: ", + ", logView: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }, params: ", "LogEntriesParams", ", columnOverrides?: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[] | undefined) => Promise<{ entries: { id: string; index: string; cursor: { time: string; tiebreaker: number; }; columns: ({ columnId: string; time: string; } | { columnId: string; field: string; value: ", { @@ -3740,7 +3756,7 @@ "label": "logView", "description": [], "signature": [ - "{ logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }" + "{ logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }" ], "path": "x-pack/plugins/observability_solution/logs_shared/server/lib/domains/log_entries_domain/log_entries_domain.ts", "deprecated": false, @@ -3796,7 +3812,7 @@ "section": "def-server.RequestHandlerContext", "text": "RequestHandlerContext" }, - ", logView: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }, start: number, end: number, bucketSize: number, filterQuery?: ", + ", logView: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }, start: number, end: number, bucketSize: number, filterQuery?: ", { "pluginId": "@kbn/utility-types", "scope": "common", @@ -3839,7 +3855,7 @@ "label": "logView", "description": [], "signature": [ - "{ logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }" + "{ logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }" ], "path": "x-pack/plugins/observability_solution/logs_shared/server/lib/domains/log_entries_domain/log_entries_domain.ts", "deprecated": false, @@ -3932,7 +3948,7 @@ "section": "def-server.RequestHandlerContext", "text": "RequestHandlerContext" }, - ", logView: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }, startTimestamp: number, endTimestamp: number, bucketSize: number, highlightQueries: string[], filterQuery?: ", + ", logView: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }, startTimestamp: number, endTimestamp: number, bucketSize: number, highlightQueries: string[], filterQuery?: ", { "pluginId": "@kbn/utility-types", "scope": "common", @@ -3975,7 +3991,7 @@ "label": "logView", "description": [], "signature": [ - "{ logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }" + "{ logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }" ], "path": "x-pack/plugins/observability_solution/logs_shared/server/lib/domains/log_entries_domain/log_entries_domain.ts", "deprecated": false, @@ -4223,7 +4239,7 @@ "section": "def-server.RequestHandlerContext", "text": "RequestHandlerContext" }, - ", logView: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }, params: ", + ", logView: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }, params: ", "LogEntriesAroundParams", ", columnOverrides?: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[] | undefined) => Promise<{ entries: { id: string; index: string; cursor: { time: string; tiebreaker: number; }; columns: ({ columnId: string; time: string; } | { columnId: string; field: string; value: ", { @@ -4276,7 +4292,7 @@ "label": "logView", "description": [], "signature": [ - "{ logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }" + "{ logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }" ], "path": "x-pack/plugins/observability_solution/logs_shared/server/lib/domains/log_entries_domain/log_entries_domain.ts", "deprecated": false, @@ -4332,7 +4348,7 @@ "section": "def-server.RequestHandlerContext", "text": "RequestHandlerContext" }, - ", logView: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }, params: ", + ", logView: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }, params: ", "LogEntriesParams", ", columnOverrides?: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[] | undefined) => Promise<{ entries: { id: string; index: string; cursor: { time: string; tiebreaker: number; }; columns: ({ columnId: string; time: string; } | { columnId: string; field: string; value: ", { @@ -4385,7 +4401,7 @@ "label": "logView", "description": [], "signature": [ - "{ logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }" + "{ logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }" ], "path": "x-pack/plugins/observability_solution/logs_shared/server/lib/domains/log_entries_domain/log_entries_domain.ts", "deprecated": false, @@ -4441,7 +4457,7 @@ "section": "def-server.RequestHandlerContext", "text": "RequestHandlerContext" }, - ", logView: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }, start: number, end: number, bucketSize: number, filterQuery?: ", + ", logView: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }, start: number, end: number, bucketSize: number, filterQuery?: ", { "pluginId": "@kbn/utility-types", "scope": "common", @@ -4484,7 +4500,7 @@ "label": "logView", "description": [], "signature": [ - "{ logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }" + "{ logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }" ], "path": "x-pack/plugins/observability_solution/logs_shared/server/lib/domains/log_entries_domain/log_entries_domain.ts", "deprecated": false, @@ -4577,7 +4593,7 @@ "section": "def-server.RequestHandlerContext", "text": "RequestHandlerContext" }, - ", logView: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }, startTimestamp: number, endTimestamp: number, bucketSize: number, highlightQueries: string[], filterQuery?: ", + ", logView: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }, startTimestamp: number, endTimestamp: number, bucketSize: number, highlightQueries: string[], filterQuery?: ", { "pluginId": "@kbn/utility-types", "scope": "common", @@ -4620,7 +4636,7 @@ "label": "logView", "description": [], "signature": [ - "{ logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }" + "{ logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }" ], "path": "x-pack/plugins/observability_solution/logs_shared/server/lib/domains/log_entries_domain/log_entries_domain.ts", "deprecated": false, @@ -5476,7 +5492,7 @@ "label": "logView", "description": [], "signature": [ - "{ logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; } | undefined" + "{ logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; } | undefined" ], "path": "x-pack/plugins/observability_solution/logs_shared/common/locators/types.ts", "deprecated": false, @@ -6093,7 +6109,7 @@ "label": "LogIndexReference", "description": [], "signature": [ - "{ type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }" + "{ type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }" ], "path": "x-pack/plugins/observability_solution/logs_shared/common/log_views/types.ts", "deprecated": false, @@ -6199,6 +6215,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "logsShared", + "id": "def-common.LogSourcesKibanaAdvancedSettingReference", + "type": "Type", + "tags": [], + "label": "LogSourcesKibanaAdvancedSettingReference", + "description": [], + "signature": [ + "{ type: \"kibana_advanced_setting\"; }" + ], + "path": "x-pack/plugins/observability_solution/logs_shared/common/log_views/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "logsShared", "id": "def-common.LogTimestampColumn", @@ -6222,7 +6253,7 @@ "label": "LogView", "description": [], "signature": [ - "{ id: string; origin: \"internal\" | \"inline\" | \"stored\" | \"infra-source-stored\" | \"infra-source-internal\" | \"infra-source-fallback\"; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; } & { updatedAt?: number | undefined; version?: string | undefined; }" + "{ id: string; origin: \"internal\" | \"inline\" | \"stored\" | \"infra-source-stored\" | \"infra-source-internal\" | \"infra-source-fallback\"; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; } & { updatedAt?: number | undefined; version?: string | undefined; }" ], "path": "x-pack/plugins/observability_solution/logs_shared/common/log_views/types.ts", "deprecated": false, @@ -6237,7 +6268,7 @@ "label": "LogViewAttributes", "description": [], "signature": [ - "{ name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }" + "{ name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }" ], "path": "x-pack/plugins/observability_solution/logs_shared/common/log_views/types.ts", "deprecated": false, @@ -6267,7 +6298,7 @@ "label": "LogViewReference", "description": [], "signature": [ - "{ logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }" + "{ logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }" ], "path": "x-pack/plugins/observability_solution/logs_shared/common/log_views/types.ts", "deprecated": false, @@ -6704,7 +6735,11 @@ "LiteralC", "<\"index_name\">; indexName: ", "StringC", - "; }>]>; logColumns: ", + "; }>, ", + "TypeC", + "<{ type: ", + "LiteralC", + "<\"kibana_advanced_setting\">; }>]>; logColumns: ", "ArrayC", "<", "UnionC", @@ -6848,7 +6883,11 @@ "LiteralC", "<\"index_name\">; indexName: ", "StringC", - "; }>]>; logColumns: ", + "; }>, ", + "TypeC", + "<{ type: ", + "LiteralC", + "<\"kibana_advanced_setting\">; }>]>; logColumns: ", "ArrayC", "<", "UnionC", @@ -7004,7 +7043,11 @@ "LiteralC", "<\"index_name\">; indexName: ", "StringC", - "; }>]>; logColumns: ", + "; }>, ", + "TypeC", + "<{ type: ", + "LiteralC", + "<\"kibana_advanced_setting\">; }>]>; logColumns: ", "ArrayC", "<", "UnionC", @@ -7160,7 +7203,11 @@ "LiteralC", "<\"index_name\">; indexName: ", "StringC", - "; }>]>; logColumns: ", + "; }>, ", + "TypeC", + "<{ type: ", + "LiteralC", + "<\"kibana_advanced_setting\">; }>]>; logColumns: ", "ArrayC", "<", "UnionC", @@ -7570,7 +7617,11 @@ "LiteralC", "<\"index_name\">; indexName: ", "StringC", - "; }>]>; logColumns: ", + "; }>, ", + "TypeC", + "<{ type: ", + "LiteralC", + "<\"kibana_advanced_setting\">; }>]>; logColumns: ", "ArrayC", "<", "UnionC", @@ -8163,6 +8214,24 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "logsShared", + "id": "def-common.logSourcesKibanaAdvancedSettingRT", + "type": "Object", + "tags": [], + "label": "logSourcesKibanaAdvancedSettingRT", + "description": [], + "signature": [ + "TypeC", + "<{ type: ", + "LiteralC", + "<\"kibana_advanced_setting\">; }>" + ], + "path": "x-pack/plugins/observability_solution/logs_shared/common/log_views/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "logsShared", "id": "def-common.logTimestampColumnRT", @@ -8282,7 +8351,11 @@ "LiteralC", "<\"index_name\">; indexName: ", "StringC", - "; }>]>; logColumns: ", + "; }>, ", + "TypeC", + "<{ type: ", + "LiteralC", + "<\"kibana_advanced_setting\">; }>]>; logColumns: ", "ArrayC", "<", "UnionC", diff --git a/api_docs/logs_shared.mdx b/api_docs/logs_shared.mdx index d5e04e15cf194..ee624af4f8b1b 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'logsShared'] --- import logsSharedObj from './logs_shared.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 297 | 0 | 269 | 32 | +| 300 | 0 | 272 | 32 | ## Client diff --git a/api_docs/management.mdx b/api_docs/management.mdx index bfe61eedb26f6..9832b8c90b504 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: 2024-08-07 +date: 2024-08-08 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 406f6466732db..0a4f9a2cabe26 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'maps'] --- import mapsObj from './maps.devdocs.json'; diff --git a/api_docs/maps_ems.mdx b/api_docs/maps_ems.mdx index 35a1e0b739bf7..8658b91a7f81c 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'mapsEms'] --- import mapsEmsObj from './maps_ems.devdocs.json'; diff --git a/api_docs/metrics_data_access.devdocs.json b/api_docs/metrics_data_access.devdocs.json index 0b6a3be970952..786c41c318074 100644 --- a/api_docs/metrics_data_access.devdocs.json +++ b/api_docs/metrics_data_access.devdocs.json @@ -745,7 +745,7 @@ "label": "hostSnapshotMetricTypes", "description": [], "signature": [ - "(\"memory\" | \"logRate\" | \"rx\" | \"normalizedLoad1m\" | \"memoryFree\" | \"tx\" | \"cpu\" | \"diskLatency\" | \"diskSpaceUsage\" | \"load\" | \"memoryTotal\" | \"rxV2\" | \"txV2\")[]" + "(\"memory\" | \"logRate\" | \"rx\" | \"normalizedLoad1m\" | \"memoryFree\" | \"tx\" | \"cpu\" | \"cpuTotal\" | \"diskLatency\" | \"diskSpaceUsage\" | \"load\" | \"memoryTotal\" | \"rxV2\" | \"txV2\")[]" ], "path": "x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/index.ts", "deprecated": false, @@ -790,7 +790,7 @@ "label": "InventoryMetric", "description": [], "signature": [ - "\"custom\" | \"hostSystemOverview\" | \"hostCpuUsage\" | \"hostFilesystem\" | \"hostK8sOverview\" | \"hostK8sCpuCap\" | \"hostK8sDiskCap\" | \"hostK8sMemoryCap\" | \"hostK8sPodCap\" | \"hostLoad\" | \"hostMemoryUsage\" | \"hostNetworkTraffic\" | \"hostDockerOverview\" | \"hostDockerInfo\" | \"hostDockerTop5ByCpu\" | \"hostDockerTop5ByMemory\" | \"podOverview\" | \"podCpuUsage\" | \"podMemoryUsage\" | \"podLogUsage\" | \"podNetworkTraffic\" | \"containerOverview\" | \"containerCpuKernel\" | \"containerCpuUsage\" | \"containerDiskIOOps\" | \"containerDiskIOBytes\" | \"containerMemory\" | \"containerNetworkTraffic\" | \"containerK8sOverview\" | \"containerK8sCpuUsage\" | \"containerK8sMemoryUsage\" | \"nginxHits\" | \"nginxRequestRate\" | \"nginxActiveConnections\" | \"nginxRequestsPerConnection\" | \"awsOverview\" | \"awsCpuUtilization\" | \"awsNetworkBytes\" | \"awsNetworkPackets\" | \"awsDiskioBytes\" | \"awsDiskioOps\" | \"awsEC2CpuUtilization\" | \"awsEC2NetworkTraffic\" | \"awsEC2DiskIOBytes\" | \"awsS3TotalRequests\" | \"awsS3NumberOfObjects\" | \"awsS3BucketSize\" | \"awsS3DownloadBytes\" | \"awsS3UploadBytes\" | \"awsRDSCpuTotal\" | \"awsRDSConnections\" | \"awsRDSQueriesExecuted\" | \"awsRDSActiveTransactions\" | \"awsRDSLatency\" | \"awsSQSMessagesVisible\" | \"awsSQSMessagesDelayed\" | \"awsSQSMessagesSent\" | \"awsSQSMessagesEmpty\" | \"awsSQSOldestMessage\"" + "\"custom\" | \"hostSystemOverview\" | \"hostCpuUsageTotal\" | \"hostCpuUsage\" | \"hostFilesystem\" | \"hostK8sOverview\" | \"hostK8sCpuCap\" | \"hostK8sDiskCap\" | \"hostK8sMemoryCap\" | \"hostK8sPodCap\" | \"hostLoad\" | \"hostMemoryUsage\" | \"hostNetworkTraffic\" | \"hostDockerOverview\" | \"hostDockerInfo\" | \"hostDockerTop5ByCpu\" | \"hostDockerTop5ByMemory\" | \"podOverview\" | \"podCpuUsage\" | \"podMemoryUsage\" | \"podLogUsage\" | \"podNetworkTraffic\" | \"containerOverview\" | \"containerCpuKernel\" | \"containerCpuUsage\" | \"containerDiskIOOps\" | \"containerDiskIOBytes\" | \"containerMemory\" | \"containerNetworkTraffic\" | \"containerK8sOverview\" | \"containerK8sCpuUsage\" | \"containerK8sMemoryUsage\" | \"nginxHits\" | \"nginxRequestRate\" | \"nginxActiveConnections\" | \"nginxRequestsPerConnection\" | \"awsOverview\" | \"awsCpuUtilization\" | \"awsNetworkBytes\" | \"awsNetworkPackets\" | \"awsDiskioBytes\" | \"awsDiskioOps\" | \"awsEC2CpuUtilization\" | \"awsEC2NetworkTraffic\" | \"awsEC2DiskIOBytes\" | \"awsS3TotalRequests\" | \"awsS3NumberOfObjects\" | \"awsS3BucketSize\" | \"awsS3DownloadBytes\" | \"awsS3UploadBytes\" | \"awsRDSCpuTotal\" | \"awsRDSConnections\" | \"awsRDSQueriesExecuted\" | \"awsRDSActiveTransactions\" | \"awsRDSLatency\" | \"awsSQSMessagesVisible\" | \"awsSQSMessagesDelayed\" | \"awsSQSMessagesSent\" | \"awsSQSMessagesEmpty\" | \"awsSQSOldestMessage\"" ], "path": "x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/types.ts", "deprecated": false, @@ -1522,7 +1522,7 @@ "label": "SnapshotMetricType", "description": [], "signature": [ - "\"count\" | \"memory\" | \"custom\" | \"logRate\" | \"rx\" | \"normalizedLoad1m\" | \"memoryFree\" | \"tx\" | \"cpu\" | \"s3BucketSize\" | \"s3NumberOfObjects\" | \"s3TotalRequests\" | \"s3UploadBytes\" | \"s3DownloadBytes\" | \"diskLatency\" | \"diskSpaceUsage\" | \"load\" | \"memoryTotal\" | \"rxV2\" | \"txV2\" | \"diskIOReadBytes\" | \"diskIOWriteBytes\" | \"rdsLatency\" | \"rdsConnections\" | \"rdsQueriesExecuted\" | \"rdsActiveTransactions\" | \"sqsMessagesVisible\" | \"sqsMessagesDelayed\" | \"sqsMessagesEmpty\" | \"sqsMessagesSent\" | \"sqsOldestMessage\"" + "\"count\" | \"memory\" | \"custom\" | \"logRate\" | \"rx\" | \"normalizedLoad1m\" | \"memoryFree\" | \"tx\" | \"cpu\" | \"s3BucketSize\" | \"s3NumberOfObjects\" | \"s3TotalRequests\" | \"s3UploadBytes\" | \"s3DownloadBytes\" | \"cpuTotal\" | \"diskLatency\" | \"diskSpaceUsage\" | \"load\" | \"memoryTotal\" | \"rxV2\" | \"txV2\" | \"diskIOReadBytes\" | \"diskIOWriteBytes\" | \"rdsLatency\" | \"rdsConnections\" | \"rdsQueriesExecuted\" | \"rdsActiveTransactions\" | \"sqsMessagesVisible\" | \"sqsMessagesDelayed\" | \"sqsMessagesEmpty\" | \"sqsMessagesSent\" | \"sqsOldestMessage\"" ], "path": "x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/types.ts", "deprecated": false, @@ -1537,7 +1537,7 @@ "label": "TSVBMetricModel", "description": [], "signature": [ - "{ id: \"custom\" | \"hostSystemOverview\" | \"hostCpuUsage\" | \"hostFilesystem\" | \"hostK8sOverview\" | \"hostK8sCpuCap\" | \"hostK8sDiskCap\" | \"hostK8sMemoryCap\" | \"hostK8sPodCap\" | \"hostLoad\" | \"hostMemoryUsage\" | \"hostNetworkTraffic\" | \"hostDockerOverview\" | \"hostDockerInfo\" | \"hostDockerTop5ByCpu\" | \"hostDockerTop5ByMemory\" | \"podOverview\" | \"podCpuUsage\" | \"podMemoryUsage\" | \"podLogUsage\" | \"podNetworkTraffic\" | \"containerOverview\" | \"containerCpuKernel\" | \"containerCpuUsage\" | \"containerDiskIOOps\" | \"containerDiskIOBytes\" | \"containerMemory\" | \"containerNetworkTraffic\" | \"containerK8sOverview\" | \"containerK8sCpuUsage\" | \"containerK8sMemoryUsage\" | \"nginxHits\" | \"nginxRequestRate\" | \"nginxActiveConnections\" | \"nginxRequestsPerConnection\" | \"awsOverview\" | \"awsCpuUtilization\" | \"awsNetworkBytes\" | \"awsNetworkPackets\" | \"awsDiskioBytes\" | \"awsDiskioOps\" | \"awsEC2CpuUtilization\" | \"awsEC2NetworkTraffic\" | \"awsEC2DiskIOBytes\" | \"awsS3TotalRequests\" | \"awsS3NumberOfObjects\" | \"awsS3BucketSize\" | \"awsS3DownloadBytes\" | \"awsS3UploadBytes\" | \"awsRDSCpuTotal\" | \"awsRDSConnections\" | \"awsRDSQueriesExecuted\" | \"awsRDSActiveTransactions\" | \"awsRDSLatency\" | \"awsSQSMessagesVisible\" | \"awsSQSMessagesDelayed\" | \"awsSQSMessagesSent\" | \"awsSQSMessagesEmpty\" | \"awsSQSOldestMessage\"; requires: string[]; index_pattern: string | string[]; interval: string; time_field: string; type: string; series: ({ id: string; metrics: ({ id: string; type: \"count\"; } | ({ id: string; type: \"min\" | \"max\" | \"sum\" | \"avg\" | \"count\" | \"cardinality\" | \"cumulative_sum\" | \"derivative\" | \"calculation\" | \"series_agg\" | \"positive_only\"; } & { field?: string | undefined; }) | { id: string; script: string; type: \"calculation\"; variables: { field: string; id: string; name: string; }[]; } | { id: string; field: string; unit: string; type: \"derivative\"; } | ({ id: string; type: \"percentile\"; percentiles: { id: string; value: number; }[]; } & { field?: string | undefined; }) | { id: string; function: string; type: \"series_agg\"; })[]; split_mode: string; } & { terms_field?: string | undefined; terms_size?: number | undefined; terms_order_by?: string | undefined; filter?: { query: string; language: \"kuery\" | \"lucene\"; } | undefined; })[]; } & { filter?: string | undefined; map_field_to?: string | undefined; id_type?: \"cloud\" | \"node\" | undefined; drop_last_bucket?: boolean | undefined; }" + "{ id: \"custom\" | \"hostSystemOverview\" | \"hostCpuUsageTotal\" | \"hostCpuUsage\" | \"hostFilesystem\" | \"hostK8sOverview\" | \"hostK8sCpuCap\" | \"hostK8sDiskCap\" | \"hostK8sMemoryCap\" | \"hostK8sPodCap\" | \"hostLoad\" | \"hostMemoryUsage\" | \"hostNetworkTraffic\" | \"hostDockerOverview\" | \"hostDockerInfo\" | \"hostDockerTop5ByCpu\" | \"hostDockerTop5ByMemory\" | \"podOverview\" | \"podCpuUsage\" | \"podMemoryUsage\" | \"podLogUsage\" | \"podNetworkTraffic\" | \"containerOverview\" | \"containerCpuKernel\" | \"containerCpuUsage\" | \"containerDiskIOOps\" | \"containerDiskIOBytes\" | \"containerMemory\" | \"containerNetworkTraffic\" | \"containerK8sOverview\" | \"containerK8sCpuUsage\" | \"containerK8sMemoryUsage\" | \"nginxHits\" | \"nginxRequestRate\" | \"nginxActiveConnections\" | \"nginxRequestsPerConnection\" | \"awsOverview\" | \"awsCpuUtilization\" | \"awsNetworkBytes\" | \"awsNetworkPackets\" | \"awsDiskioBytes\" | \"awsDiskioOps\" | \"awsEC2CpuUtilization\" | \"awsEC2NetworkTraffic\" | \"awsEC2DiskIOBytes\" | \"awsS3TotalRequests\" | \"awsS3NumberOfObjects\" | \"awsS3BucketSize\" | \"awsS3DownloadBytes\" | \"awsS3UploadBytes\" | \"awsRDSCpuTotal\" | \"awsRDSConnections\" | \"awsRDSQueriesExecuted\" | \"awsRDSActiveTransactions\" | \"awsRDSLatency\" | \"awsSQSMessagesVisible\" | \"awsSQSMessagesDelayed\" | \"awsSQSMessagesSent\" | \"awsSQSMessagesEmpty\" | \"awsSQSOldestMessage\"; requires: string[]; index_pattern: string | string[]; interval: string; time_field: string; type: string; series: ({ id: string; metrics: ({ id: string; type: \"count\"; } | ({ id: string; type: \"min\" | \"max\" | \"sum\" | \"avg\" | \"count\" | \"cardinality\" | \"cumulative_sum\" | \"derivative\" | \"calculation\" | \"series_agg\" | \"positive_only\"; } & { field?: string | undefined; }) | { id: string; script: string; type: \"calculation\"; variables: { field: string; id: string; name: string; }[]; } | { id: string; field: string; unit: string; type: \"derivative\"; } | ({ id: string; type: \"percentile\"; percentiles: { id: string; value: number; }[]; } & { field?: string | undefined; }) | { id: string; function: string; type: \"series_agg\"; })[]; split_mode: string; } & { terms_field?: string | undefined; terms_size?: number | undefined; terms_order_by?: string | undefined; filter?: { query: string; language: \"kuery\" | \"lucene\"; } | undefined; })[]; } & { filter?: string | undefined; map_field_to?: string | undefined; id_type?: \"cloud\" | \"node\" | undefined; drop_last_bucket?: boolean | undefined; }" ], "path": "x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/types.ts", "deprecated": false, @@ -1552,7 +1552,7 @@ "label": "TSVBMetricModelCreator", "description": [], "signature": [ - "(timeField: string, indexPattern: string | string[], interval: string) => { id: \"custom\" | \"hostSystemOverview\" | \"hostCpuUsage\" | \"hostFilesystem\" | \"hostK8sOverview\" | \"hostK8sCpuCap\" | \"hostK8sDiskCap\" | \"hostK8sMemoryCap\" | \"hostK8sPodCap\" | \"hostLoad\" | \"hostMemoryUsage\" | \"hostNetworkTraffic\" | \"hostDockerOverview\" | \"hostDockerInfo\" | \"hostDockerTop5ByCpu\" | \"hostDockerTop5ByMemory\" | \"podOverview\" | \"podCpuUsage\" | \"podMemoryUsage\" | \"podLogUsage\" | \"podNetworkTraffic\" | \"containerOverview\" | \"containerCpuKernel\" | \"containerCpuUsage\" | \"containerDiskIOOps\" | \"containerDiskIOBytes\" | \"containerMemory\" | \"containerNetworkTraffic\" | \"containerK8sOverview\" | \"containerK8sCpuUsage\" | \"containerK8sMemoryUsage\" | \"nginxHits\" | \"nginxRequestRate\" | \"nginxActiveConnections\" | \"nginxRequestsPerConnection\" | \"awsOverview\" | \"awsCpuUtilization\" | \"awsNetworkBytes\" | \"awsNetworkPackets\" | \"awsDiskioBytes\" | \"awsDiskioOps\" | \"awsEC2CpuUtilization\" | \"awsEC2NetworkTraffic\" | \"awsEC2DiskIOBytes\" | \"awsS3TotalRequests\" | \"awsS3NumberOfObjects\" | \"awsS3BucketSize\" | \"awsS3DownloadBytes\" | \"awsS3UploadBytes\" | \"awsRDSCpuTotal\" | \"awsRDSConnections\" | \"awsRDSQueriesExecuted\" | \"awsRDSActiveTransactions\" | \"awsRDSLatency\" | \"awsSQSMessagesVisible\" | \"awsSQSMessagesDelayed\" | \"awsSQSMessagesSent\" | \"awsSQSMessagesEmpty\" | \"awsSQSOldestMessage\"; requires: string[]; index_pattern: string | string[]; interval: string; time_field: string; type: string; series: ({ id: string; metrics: ({ id: string; type: \"count\"; } | ({ id: string; type: \"min\" | \"max\" | \"sum\" | \"avg\" | \"count\" | \"cardinality\" | \"cumulative_sum\" | \"derivative\" | \"calculation\" | \"series_agg\" | \"positive_only\"; } & { field?: string | undefined; }) | { id: string; script: string; type: \"calculation\"; variables: { field: string; id: string; name: string; }[]; } | { id: string; field: string; unit: string; type: \"derivative\"; } | ({ id: string; type: \"percentile\"; percentiles: { id: string; value: number; }[]; } & { field?: string | undefined; }) | { id: string; function: string; type: \"series_agg\"; })[]; split_mode: string; } & { terms_field?: string | undefined; terms_size?: number | undefined; terms_order_by?: string | undefined; filter?: { query: string; language: \"kuery\" | \"lucene\"; } | undefined; })[]; } & { filter?: string | undefined; map_field_to?: string | undefined; id_type?: \"cloud\" | \"node\" | undefined; drop_last_bucket?: boolean | undefined; }" + "(timeField: string, indexPattern: string | string[], interval: string) => { id: \"custom\" | \"hostSystemOverview\" | \"hostCpuUsageTotal\" | \"hostCpuUsage\" | \"hostFilesystem\" | \"hostK8sOverview\" | \"hostK8sCpuCap\" | \"hostK8sDiskCap\" | \"hostK8sMemoryCap\" | \"hostK8sPodCap\" | \"hostLoad\" | \"hostMemoryUsage\" | \"hostNetworkTraffic\" | \"hostDockerOverview\" | \"hostDockerInfo\" | \"hostDockerTop5ByCpu\" | \"hostDockerTop5ByMemory\" | \"podOverview\" | \"podCpuUsage\" | \"podMemoryUsage\" | \"podLogUsage\" | \"podNetworkTraffic\" | \"containerOverview\" | \"containerCpuKernel\" | \"containerCpuUsage\" | \"containerDiskIOOps\" | \"containerDiskIOBytes\" | \"containerMemory\" | \"containerNetworkTraffic\" | \"containerK8sOverview\" | \"containerK8sCpuUsage\" | \"containerK8sMemoryUsage\" | \"nginxHits\" | \"nginxRequestRate\" | \"nginxActiveConnections\" | \"nginxRequestsPerConnection\" | \"awsOverview\" | \"awsCpuUtilization\" | \"awsNetworkBytes\" | \"awsNetworkPackets\" | \"awsDiskioBytes\" | \"awsDiskioOps\" | \"awsEC2CpuUtilization\" | \"awsEC2NetworkTraffic\" | \"awsEC2DiskIOBytes\" | \"awsS3TotalRequests\" | \"awsS3NumberOfObjects\" | \"awsS3BucketSize\" | \"awsS3DownloadBytes\" | \"awsS3UploadBytes\" | \"awsRDSCpuTotal\" | \"awsRDSConnections\" | \"awsRDSQueriesExecuted\" | \"awsRDSActiveTransactions\" | \"awsRDSLatency\" | \"awsSQSMessagesVisible\" | \"awsSQSMessagesDelayed\" | \"awsSQSMessagesSent\" | \"awsSQSMessagesEmpty\" | \"awsSQSOldestMessage\"; requires: string[]; index_pattern: string | string[]; interval: string; time_field: string; type: string; series: ({ id: string; metrics: ({ id: string; type: \"count\"; } | ({ id: string; type: \"min\" | \"max\" | \"sum\" | \"avg\" | \"count\" | \"cardinality\" | \"cumulative_sum\" | \"derivative\" | \"calculation\" | \"series_agg\" | \"positive_only\"; } & { field?: string | undefined; }) | { id: string; script: string; type: \"calculation\"; variables: { field: string; id: string; name: string; }[]; } | { id: string; field: string; unit: string; type: \"derivative\"; } | ({ id: string; type: \"percentile\"; percentiles: { id: string; value: number; }[]; } & { field?: string | undefined; }) | { id: string; function: string; type: \"series_agg\"; })[]; split_mode: string; } & { terms_field?: string | undefined; terms_size?: number | undefined; terms_order_by?: string | undefined; filter?: { query: string; language: \"kuery\" | \"lucene\"; } | undefined; })[]; } & { filter?: string | undefined; map_field_to?: string | undefined; id_type?: \"cloud\" | \"node\" | undefined; drop_last_bucket?: boolean | undefined; }" ], "path": "x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/types.ts", "deprecated": false, @@ -1715,7 +1715,7 @@ "description": [], "signature": [ "KeyofC", - "<{ hostSystemOverview: null; hostCpuUsage: null; hostFilesystem: null; hostK8sOverview: null; hostK8sCpuCap: null; hostK8sDiskCap: null; hostK8sMemoryCap: null; hostK8sPodCap: null; hostLoad: null; hostMemoryUsage: null; hostNetworkTraffic: null; hostDockerOverview: null; hostDockerInfo: null; hostDockerTop5ByCpu: null; hostDockerTop5ByMemory: null; podOverview: null; podCpuUsage: null; podMemoryUsage: null; podLogUsage: null; podNetworkTraffic: null; containerOverview: null; containerCpuKernel: null; containerCpuUsage: null; containerDiskIOOps: null; containerDiskIOBytes: null; containerMemory: null; containerNetworkTraffic: null; containerK8sOverview: null; containerK8sCpuUsage: null; containerK8sMemoryUsage: null; nginxHits: null; nginxRequestRate: null; nginxActiveConnections: null; nginxRequestsPerConnection: null; awsOverview: null; awsCpuUtilization: null; awsNetworkBytes: null; awsNetworkPackets: null; awsDiskioBytes: null; awsDiskioOps: null; awsEC2CpuUtilization: null; awsEC2NetworkTraffic: null; awsEC2DiskIOBytes: null; awsS3TotalRequests: null; awsS3NumberOfObjects: null; awsS3BucketSize: null; awsS3DownloadBytes: null; awsS3UploadBytes: null; awsRDSCpuTotal: null; awsRDSConnections: null; awsRDSQueriesExecuted: null; awsRDSActiveTransactions: null; awsRDSLatency: null; awsSQSMessagesVisible: null; awsSQSMessagesDelayed: null; awsSQSMessagesSent: null; awsSQSMessagesEmpty: null; awsSQSOldestMessage: null; custom: null; }>" + "<{ hostSystemOverview: null; hostCpuUsageTotal: null; hostCpuUsage: null; hostFilesystem: null; hostK8sOverview: null; hostK8sCpuCap: null; hostK8sDiskCap: null; hostK8sMemoryCap: null; hostK8sPodCap: null; hostLoad: null; hostMemoryUsage: null; hostNetworkTraffic: null; hostDockerOverview: null; hostDockerInfo: null; hostDockerTop5ByCpu: null; hostDockerTop5ByMemory: null; podOverview: null; podCpuUsage: null; podMemoryUsage: null; podLogUsage: null; podNetworkTraffic: null; containerOverview: null; containerCpuKernel: null; containerCpuUsage: null; containerDiskIOOps: null; containerDiskIOBytes: null; containerMemory: null; containerNetworkTraffic: null; containerK8sOverview: null; containerK8sCpuUsage: null; containerK8sMemoryUsage: null; nginxHits: null; nginxRequestRate: null; nginxActiveConnections: null; nginxRequestsPerConnection: null; awsOverview: null; awsCpuUtilization: null; awsNetworkBytes: null; awsNetworkPackets: null; awsDiskioBytes: null; awsDiskioOps: null; awsEC2CpuUtilization: null; awsEC2NetworkTraffic: null; awsEC2DiskIOBytes: null; awsS3TotalRequests: null; awsS3NumberOfObjects: null; awsS3BucketSize: null; awsS3DownloadBytes: null; awsS3UploadBytes: null; awsRDSCpuTotal: null; awsRDSConnections: null; awsRDSQueriesExecuted: null; awsRDSActiveTransactions: null; awsRDSLatency: null; awsSQSMessagesVisible: null; awsSQSMessagesDelayed: null; awsSQSMessagesSent: null; awsSQSMessagesEmpty: null; awsSQSOldestMessage: null; custom: null; }>" ], "path": "x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/types.ts", "deprecated": false, @@ -2064,6 +2064,20 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "metricsDataAccess", + "id": "def-common.SnapshotMetricTypeKeys.cpuTotal", + "type": "Uncategorized", + "tags": [], + "label": "cpuTotal", + "description": [], + "signature": [ + "null" + ], + "path": "x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/types.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "metricsDataAccess", "id": "def-common.SnapshotMetricTypeKeys.cpu", @@ -2496,7 +2510,7 @@ "description": [], "signature": [ "KeyofC", - "<{ count: null; cpu: null; diskLatency: null; diskSpaceUsage: null; load: null; memory: null; memoryFree: null; memoryTotal: null; normalizedLoad1m: null; tx: null; rx: null; txV2: null; rxV2: null; logRate: null; diskIOReadBytes: null; diskIOWriteBytes: null; s3TotalRequests: null; s3NumberOfObjects: null; s3BucketSize: null; s3DownloadBytes: null; s3UploadBytes: null; rdsConnections: null; rdsQueriesExecuted: null; rdsActiveTransactions: null; rdsLatency: null; sqsMessagesVisible: null; sqsMessagesDelayed: null; sqsMessagesSent: null; sqsMessagesEmpty: null; sqsOldestMessage: null; custom: null; }>" + "<{ count: null; cpuTotal: null; cpu: null; diskLatency: null; diskSpaceUsage: null; load: null; memory: null; memoryFree: null; memoryTotal: null; normalizedLoad1m: null; tx: null; rx: null; txV2: null; rxV2: null; logRate: null; diskIOReadBytes: null; diskIOWriteBytes: null; s3TotalRequests: null; s3NumberOfObjects: null; s3BucketSize: null; s3DownloadBytes: null; s3UploadBytes: null; rdsConnections: null; rdsQueriesExecuted: null; rdsActiveTransactions: null; rdsLatency: null; sqsMessagesVisible: null; sqsMessagesDelayed: null; sqsMessagesSent: null; sqsMessagesEmpty: null; sqsOldestMessage: null; custom: null; }>" ], "path": "x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/types.ts", "deprecated": false, diff --git a/api_docs/metrics_data_access.mdx b/api_docs/metrics_data_access.mdx index eb2f54bf25244..0d2da80237b1b 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'metricsDataAccess'] --- import metricsDataAccessObj from './metrics_data_access.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs- | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 107 | 8 | 107 | 6 | +| 108 | 8 | 108 | 6 | ## Client diff --git a/api_docs/ml.mdx b/api_docs/ml.mdx index 86d52cfd786cd..68b2be50e796e 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ml'] --- import mlObj from './ml.devdocs.json'; diff --git a/api_docs/mock_idp_plugin.mdx b/api_docs/mock_idp_plugin.mdx index 547d6e04d5121..193cbb48605fc 100644 --- a/api_docs/mock_idp_plugin.mdx +++ b/api_docs/mock_idp_plugin.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/mockIdpPlugin title: "mockIdpPlugin" image: https://source.unsplash.com/400x175/?github description: API docs for the mockIdpPlugin plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'mockIdpPlugin'] --- import mockIdpPluginObj from './mock_idp_plugin.devdocs.json'; diff --git a/api_docs/monitoring.mdx b/api_docs/monitoring.mdx index 3c803928dfdaf..78365923fe83e 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: 2024-08-07 +date: 2024-08-08 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 9b1342bae78a8..25295ffca87b5 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: 2024-08-07 +date: 2024-08-08 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 7baf4f4294995..ff5166a8b85c5 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: 2024-08-07 +date: 2024-08-08 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 209b91b66e255..d1dc0f3bad91d 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: 2024-08-07 +date: 2024-08-08 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 f6c2f9690a995..ced4481f4a592 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: 2024-08-07 +date: 2024-08-08 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 a5f3b6a29f00e..b1fce344186ac 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'notifications'] --- import notificationsObj from './notifications.devdocs.json'; diff --git a/api_docs/observability.mdx b/api_docs/observability.mdx index d966f15257411..a5cb3732f6cd5 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: 2024-08-07 +date: 2024-08-08 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 f3c4d2f7d6abe..dc7d5febf9597 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityAIAssistant'] --- import observabilityAIAssistantObj from './observability_a_i_assistant.devdocs.json'; diff --git a/api_docs/observability_a_i_assistant_app.mdx b/api_docs/observability_a_i_assistant_app.mdx index ea81cc6dd7f26..bff2968ffbe84 100644 --- a/api_docs/observability_a_i_assistant_app.mdx +++ b/api_docs/observability_a_i_assistant_app.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityAIAssistantApp title: "observabilityAIAssistantApp" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityAIAssistantApp plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityAIAssistantApp'] --- import observabilityAIAssistantAppObj from './observability_a_i_assistant_app.devdocs.json'; diff --git a/api_docs/observability_ai_assistant_management.mdx b/api_docs/observability_ai_assistant_management.mdx index 2637d8df8131e..7ee81fde42030 100644 --- a/api_docs/observability_ai_assistant_management.mdx +++ b/api_docs/observability_ai_assistant_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityAiAssistantManagement title: "observabilityAiAssistantManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityAiAssistantManagement plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityAiAssistantManagement'] --- import observabilityAiAssistantManagementObj from './observability_ai_assistant_management.devdocs.json'; diff --git a/api_docs/observability_logs_explorer.mdx b/api_docs/observability_logs_explorer.mdx index 5e2ee9959a196..ea89389c52fa2 100644 --- a/api_docs/observability_logs_explorer.mdx +++ b/api_docs/observability_logs_explorer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityLogsExplorer title: "observabilityLogsExplorer" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityLogsExplorer plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityLogsExplorer'] --- import observabilityLogsExplorerObj from './observability_logs_explorer.devdocs.json'; diff --git a/api_docs/observability_onboarding.mdx b/api_docs/observability_onboarding.mdx index 621c2922a05e7..b5a4c40daf7fc 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: 2024-08-07 +date: 2024-08-08 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 c6086be316e89..3f50d3fd415a0 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: 2024-08-07 +date: 2024-08-08 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 f3713070bc2b0..025a24c5e51fd 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: 2024-08-07 +date: 2024-08-08 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 77ad6cd0bf304..30bc08f8cc7a2 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: 2024-08-07 +date: 2024-08-08 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 1d313dc29b06f..95802ed12b23a 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -21,7 +21,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | API Count | Any Count | Missing comments | Missing exports | |--------------|----------|-----------------|--------| -| 51986 | 241 | 38930 | 1902 | +| 51991 | 241 | 38932 | 1909 | ## Plugin Directory @@ -32,8 +32,8 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | - | 4 | 0 | 4 | 1 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | AIOps plugin maintained by ML team. | 74 | 0 | 9 | 2 | | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 871 | 1 | 839 | 52 | -| | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | The user interface for Elastic APM | 29 | 0 | 29 | 118 | -| | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | - | 74 | 0 | 74 | 0 | +| | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | The user interface for Elastic APM | 29 | 0 | 29 | 119 | +| | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | - | 74 | 0 | 74 | 1 | | | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | - | 2 | 0 | 2 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 9 | 0 | 9 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Considering using bfetch capabilities when fetching large amounts of data. This services supports batching HTTP requests and streaming responses back. | 83 | 1 | 73 | 2 | @@ -102,7 +102,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. | 84 | 0 | 84 | 8 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | File upload, download, sharing, and serving over HTTP implementation in Kibana. | 240 | 0 | 24 | 9 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Simple UI for managing files in Kibana | 3 | 0 | 3 | 0 | -| | [@elastic/fleet](https://github.com/orgs/elastic/teams/fleet) | - | 1351 | 5 | 1229 | 73 | +| | [@elastic/fleet](https://github.com/orgs/elastic/teams/fleet) | - | 1351 | 5 | 1229 | 74 | | 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) | - | 72 | 0 | 14 | 5 | | globalSearchBar | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 0 | 0 | 0 | 0 | @@ -121,7 +121,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | - | 127 | 2 | 100 | 4 | | | [@elastic/security-scalability](https://github.com/orgs/elastic/teams/security-scalability) | Plugin implementing the Integration Assistant API and UI | 47 | 0 | 40 | 3 | | | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides UI and APIs for the interactive setup mode. | 28 | 0 | 18 | 0 | -| | [@elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/obs-ux-management-team) | - | 133 | 0 | 133 | 6 | +| | [@elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/obs-ux-management-team) | - | 105 | 0 | 105 | 6 | | | [@elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/obs-ux-management-team) | - | 5 | 0 | 5 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 6 | 0 | 6 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 153 | 0 | 121 | 3 | @@ -134,14 +134,14 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 117 | 0 | 42 | 10 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | A dashboard panel for creating links to dashboards or external links. | 5 | 0 | 5 | 0 | | | [@elastic/security-detection-engine](https://github.com/orgs/elastic/teams/security-detection-engine) | - | 226 | 0 | 97 | 52 | -| | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | - | 7 | 0 | 7 | 6 | +| | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | - | 9 | 0 | 9 | 6 | | | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | This plugin provides a LogsExplorer component using the Discover customization framework, offering several affordances specifically designed for log consumption. | 117 | 4 | 117 | 22 | -| | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | Exposes the shared components and APIs to access and visualize logs. | 297 | 0 | 269 | 32 | +| | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | Exposes the shared components and APIs to access and visualize logs. | 300 | 0 | 272 | 32 | | logstash | [@elastic/logstash](https://github.com/orgs/elastic/teams/logstash) | - | 0 | 0 | 0 | 0 | | | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 44 | 0 | 44 | 7 | | | [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | - | 209 | 0 | 205 | 28 | | | [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | - | 60 | 0 | 60 | 0 | -| | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | Exposes utilities for accessing metrics data | 107 | 8 | 107 | 6 | +| | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | Exposes utilities for accessing metrics data | 108 | 8 | 108 | 6 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | This plugin provides access to the machine learning features provided by Elastic. | 154 | 3 | 67 | 102 | | | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | - | 2 | 0 | 2 | 0 | | | [@elastic/stack-monitoring](https://github.com/orgs/elastic/teams/stack-monitoring) | - | 15 | 3 | 13 | 1 | @@ -183,7 +183,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) | - | 15 | 0 | 9 | 1 | | searchprofiler | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 0 | 0 | 0 | 0 | | | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides authentication and authorization features, and exposes functionality to understand the capabilities of the currently authenticated user. | 447 | 0 | 231 | 1 | -| | [@elastic/security-solution](https://github.com/orgs/elastic/teams/security-solution) | - | 190 | 0 | 121 | 33 | +| | [@elastic/security-solution](https://github.com/orgs/elastic/teams/security-solution) | - | 192 | 0 | 123 | 33 | | | [@elastic/security-solution](https://github.com/orgs/elastic/teams/security-solution) | ESS customizations for Security Solution. | 6 | 0 | 6 | 0 | | | [@elastic/security-solution](https://github.com/orgs/elastic/teams/security-solution) | Serverless customizations for security. | 7 | 0 | 7 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | The core Serverless plugin, providing APIs to Serverless Project plugins. | 25 | 0 | 24 | 0 | @@ -197,7 +197,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 25 | 0 | 25 | 3 | | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 10 | 0 | 10 | 0 | | synthetics | [@elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/obs-ux-management-team) | This plugin visualizes data from Synthetics and Heartbeat, and integrates with other Observability solutions. | 0 | 0 | 0 | 1 | -| | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 105 | 0 | 62 | 5 | +| | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 107 | 0 | 63 | 7 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 45 | 0 | 1 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 31 | 0 | 26 | 6 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 1 | 0 | 1 | 0 | @@ -494,7 +494,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/security-generative-ai](https://github.com/orgs/elastic/teams/security-generative-ai) | - | 156 | 0 | 130 | 9 | | | [@elastic/security-generative-ai](https://github.com/orgs/elastic/teams/security-generative-ai) | - | 354 | 0 | 328 | 0 | | | [@elastic/obs-entities](https://github.com/orgs/elastic/teams/obs-entities) | - | 26 | 0 | 26 | 0 | -| | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 54 | 0 | 39 | 7 | +| | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 55 | 0 | 40 | 7 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 32 | 0 | 19 | 1 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 11 | 0 | 6 | 0 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 269 | 1 | 209 | 15 | @@ -510,7 +510,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 49 | 0 | 40 | 2 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 2 | 0 | 0 | 0 | | | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | - | 3 | 0 | 3 | 0 | -| | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 39 | 0 | 24 | 1 | +| | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 46 | 0 | 31 | 1 | | | [@elastic/appex-qa](https://github.com/orgs/elastic/teams/appex-qa) | - | 551 | 6 | 511 | 3 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 1 | 0 | 0 | 0 | | | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 1 | 0 | 1 | 0 | @@ -580,7 +580,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 8 | 0 | 0 | 0 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 2 | 0 | 1 | 0 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 34 | 0 | 0 | 0 | -| | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 43 | 0 | 38 | 1 | +| | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 44 | 0 | 38 | 1 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 18 | 0 | 18 | 0 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 31 | 1 | 24 | 1 | | | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | - | 22 | 0 | 16 | 0 | @@ -670,7 +670,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/security-detection-engine](https://github.com/orgs/elastic/teams/security-detection-engine) | - | 209 | 0 | 161 | 0 | | | [@elastic/security-detection-engine](https://github.com/orgs/elastic/teams/security-detection-engine) | - | 28 | 0 | 25 | 0 | | | [@elastic/security-detection-engine](https://github.com/orgs/elastic/teams/security-detection-engine) | - | 120 | 0 | 116 | 0 | -| | [@elastic/security-detection-engine](https://github.com/orgs/elastic/teams/security-detection-engine) | - | 49 | 0 | 44 | 0 | +| | [@elastic/security-detection-engine](https://github.com/orgs/elastic/teams/security-detection-engine) | - | 51 | 0 | 46 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 69 | 0 | 64 | 0 | | | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | - | 31 | 0 | 30 | 1 | | | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | - | 5 | 0 | 5 | 0 | @@ -728,7 +728,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 41 | 2 | 21 | 0 | | | [@elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/obs-ux-management-team) | - | 32 | 2 | 32 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 7 | 0 | 5 | 1 | -| | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 315 | 4 | 267 | 13 | +| | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 315 | 4 | 267 | 14 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 36 | 0 | 18 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 131 | 3 | 98 | 2 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 2 | 0 | 1 | 0 | @@ -742,7 +742,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 42 | 0 | 28 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 57 | 0 | 48 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 9 | 0 | 8 | 0 | -| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Contains functionality for the unified data table which can be integrated into apps | 154 | 0 | 81 | 1 | +| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Contains functionality for the unified data table which can be integrated into apps | 166 | 0 | 92 | 2 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 18 | 0 | 17 | 5 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Contains functionality for the field list and field stats which can be integrated into apps | 314 | 0 | 285 | 8 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 13 | 0 | 9 | 0 | diff --git a/api_docs/presentation_panel.mdx b/api_docs/presentation_panel.mdx index f21fa9b4ab7fe..39f680b354142 100644 --- a/api_docs/presentation_panel.mdx +++ b/api_docs/presentation_panel.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/presentationPanel title: "presentationPanel" image: https://source.unsplash.com/400x175/?github description: API docs for the presentationPanel plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'presentationPanel'] --- import presentationPanelObj from './presentation_panel.devdocs.json'; diff --git a/api_docs/presentation_util.mdx b/api_docs/presentation_util.mdx index 354d08537b759..9f2ec5fc497a5 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'presentationUtil'] --- import presentationUtilObj from './presentation_util.devdocs.json'; diff --git a/api_docs/profiling.mdx b/api_docs/profiling.mdx index 1cbf191997f4f..c0f536704ca49 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: 2024-08-07 +date: 2024-08-08 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 f82f565eeff15..d26bbabd2ce07 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: 2024-08-07 +date: 2024-08-08 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 f553f20e8eb38..d63a01170e220 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: 2024-08-07 +date: 2024-08-08 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 f0d27ad231850..274439a9eed20 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: 2024-08-07 +date: 2024-08-08 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 d9c543cef788f..3f8dbe1736cb1 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: 2024-08-07 +date: 2024-08-08 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 90614c69c5d99..a6bcbd10bfe54 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: 2024-08-07 +date: 2024-08-08 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 b2edb0f9deaf2..898c63a048a6e 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: 2024-08-07 +date: 2024-08-08 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 e807c6d6b6e16..3c200fdf07ef9 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: 2024-08-07 +date: 2024-08-08 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 4e979594a4246..b3040b7273fca 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: 2024-08-07 +date: 2024-08-08 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 25778bc4c154d..910d4acc1d6f1 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: 2024-08-07 +date: 2024-08-08 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 e420e71dce89e..bae1bce7bafda 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: 2024-08-07 +date: 2024-08-08 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 5a8883cb85788..b30d6fb256995 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: 2024-08-07 +date: 2024-08-08 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 164a1e74715e1..86c92cb9a8bb2 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: 2024-08-07 +date: 2024-08-08 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 c26457287a12b..10cf5df7a0190 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: 2024-08-07 +date: 2024-08-08 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 ba0df32aefc9d..0044c885eb623 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotting'] --- import screenshottingObj from './screenshotting.devdocs.json'; diff --git a/api_docs/search_connectors.mdx b/api_docs/search_connectors.mdx index 572b581eee2c8..aea1b99981212 100644 --- a/api_docs/search_connectors.mdx +++ b/api_docs/search_connectors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/searchConnectors title: "searchConnectors" image: https://source.unsplash.com/400x175/?github description: API docs for the searchConnectors plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'searchConnectors'] --- import searchConnectorsObj from './search_connectors.devdocs.json'; diff --git a/api_docs/search_homepage.mdx b/api_docs/search_homepage.mdx index 87fab1d0c7322..bbe352871685e 100644 --- a/api_docs/search_homepage.mdx +++ b/api_docs/search_homepage.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/searchHomepage title: "searchHomepage" image: https://source.unsplash.com/400x175/?github description: API docs for the searchHomepage plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'searchHomepage'] --- import searchHomepageObj from './search_homepage.devdocs.json'; diff --git a/api_docs/search_inference_endpoints.mdx b/api_docs/search_inference_endpoints.mdx index ea61205b339ec..924dfac2865c4 100644 --- a/api_docs/search_inference_endpoints.mdx +++ b/api_docs/search_inference_endpoints.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/searchInferenceEndpoints title: "searchInferenceEndpoints" image: https://source.unsplash.com/400x175/?github description: API docs for the searchInferenceEndpoints plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'searchInferenceEndpoints'] --- import searchInferenceEndpointsObj from './search_inference_endpoints.devdocs.json'; diff --git a/api_docs/search_notebooks.mdx b/api_docs/search_notebooks.mdx index e36b931e02b5b..2e0ad8d29c6f6 100644 --- a/api_docs/search_notebooks.mdx +++ b/api_docs/search_notebooks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/searchNotebooks title: "searchNotebooks" image: https://source.unsplash.com/400x175/?github description: API docs for the searchNotebooks plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'searchNotebooks'] --- import searchNotebooksObj from './search_notebooks.devdocs.json'; diff --git a/api_docs/search_playground.mdx b/api_docs/search_playground.mdx index 39011ccb1c8ac..e5770230187d0 100644 --- a/api_docs/search_playground.mdx +++ b/api_docs/search_playground.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/searchPlayground title: "searchPlayground" image: https://source.unsplash.com/400x175/?github description: API docs for the searchPlayground plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'searchPlayground'] --- import searchPlaygroundObj from './search_playground.devdocs.json'; diff --git a/api_docs/security.mdx b/api_docs/security.mdx index bba1523894f68..c000c37ab9a59 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'security'] --- import securityObj from './security.devdocs.json'; diff --git a/api_docs/security_solution.devdocs.json b/api_docs/security_solution.devdocs.json index a23dcd8993720..fef443a955c1e 100644 --- a/api_docs/security_solution.devdocs.json +++ b/api_docs/security_solution.devdocs.json @@ -390,7 +390,7 @@ "label": "data", "description": [], "signature": [ - "({ id: string; type: \"eql\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; action_type_id: string; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; uuid?: string | undefined; group?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; }[]; tags: string[]; setup: string; description: string; enabled: boolean; revision: number; query: string; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; risk_score: number; from: string; to: string; language: \"eql\"; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; index?: string[] | undefined; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; filters?: unknown[] | undefined; tiebreaker_field?: string | undefined; timestamp_field?: string | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; total_enrichment_duration_ms?: number | undefined; }; status_order: number; }; } | undefined; data_view_id?: string | undefined; event_category_override?: string | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; } | { id: string; type: \"query\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; action_type_id: string; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; uuid?: string | undefined; group?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; }[]; tags: string[]; setup: string; description: string; enabled: boolean; revision: number; query: string; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; risk_score: number; from: string; to: string; language: \"kuery\" | \"lucene\"; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; index?: string[] | undefined; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; filters?: unknown[] | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; total_enrichment_duration_ms?: number | undefined; }; status_order: number; }; } | undefined; data_view_id?: string | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; saved_id?: string | undefined; response_actions?: ({ params: { query?: string | undefined; timeout?: number | undefined; queries?: { id: string; query: string; version?: string | undefined; snapshot?: boolean | undefined; platform?: string | undefined; ecs_mapping?: Zod.objectOutputType<{}, Zod.ZodObject<{ field: Zod.ZodOptional; value: Zod.ZodOptional]>>; }, \"strip\", Zod.ZodTypeAny, { value?: string | string[] | undefined; field?: string | undefined; }, { value?: string | string[] | undefined; field?: string | undefined; }>, \"strip\"> | undefined; removed?: boolean | undefined; }[] | undefined; ecs_mapping?: Zod.objectOutputType<{}, Zod.ZodObject<{ field: Zod.ZodOptional; value: Zod.ZodOptional]>>; }, \"strip\", Zod.ZodTypeAny, { value?: string | string[] | undefined; field?: string | undefined; }, { value?: string | string[] | undefined; field?: string | undefined; }>, \"strip\"> | undefined; saved_query_id?: string | undefined; pack_id?: string | undefined; }; action_type_id: \".osquery\"; } | { params: { command: \"isolate\"; comment?: string | undefined; } | { config: { field: string; overwrite: boolean; }; command: \"kill-process\" | \"suspend-process\"; comment?: string | undefined; }; action_type_id: \".endpoint\"; })[] | undefined; } | { id: string; type: \"saved_query\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; action_type_id: string; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; uuid?: string | undefined; group?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; }[]; tags: string[]; setup: string; description: string; enabled: boolean; revision: number; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; risk_score: number; from: string; to: string; language: \"kuery\" | \"lucene\"; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; saved_id: string; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; index?: string[] | undefined; license?: string | undefined; throttle?: string | undefined; query?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; filters?: unknown[] | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; total_enrichment_duration_ms?: number | undefined; }; status_order: number; }; } | undefined; data_view_id?: string | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; response_actions?: ({ params: { query?: string | undefined; timeout?: number | undefined; queries?: { id: string; query: string; version?: string | undefined; snapshot?: boolean | undefined; platform?: string | undefined; ecs_mapping?: Zod.objectOutputType<{}, Zod.ZodObject<{ field: Zod.ZodOptional; value: Zod.ZodOptional]>>; }, \"strip\", Zod.ZodTypeAny, { value?: string | string[] | undefined; field?: string | undefined; }, { value?: string | string[] | undefined; field?: string | undefined; }>, \"strip\"> | undefined; removed?: boolean | undefined; }[] | undefined; ecs_mapping?: Zod.objectOutputType<{}, Zod.ZodObject<{ field: Zod.ZodOptional; value: Zod.ZodOptional]>>; }, \"strip\", Zod.ZodTypeAny, { value?: string | string[] | undefined; field?: string | undefined; }, { value?: string | string[] | undefined; field?: string | undefined; }>, \"strip\"> | undefined; saved_query_id?: string | undefined; pack_id?: string | undefined; }; action_type_id: \".osquery\"; } | { params: { command: \"isolate\"; comment?: string | undefined; } | { config: { field: string; overwrite: boolean; }; command: \"kill-process\" | \"suspend-process\"; comment?: string | undefined; }; action_type_id: \".endpoint\"; })[] | undefined; } | { id: string; type: \"threshold\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; action_type_id: string; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; uuid?: string | undefined; group?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; }[]; tags: string[]; setup: string; description: string; enabled: boolean; revision: number; query: string; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; risk_score: number; from: string; to: string; language: \"kuery\" | \"lucene\"; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threshold: { value: number; field: string | string[]; cardinality?: { value: number; field: string; }[] | undefined; }; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; index?: string[] | undefined; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; filters?: unknown[] | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; total_enrichment_duration_ms?: number | undefined; }; status_order: number; }; } | undefined; data_view_id?: string | undefined; alert_suppression?: { duration: { value: number; unit: \"m\" | \"h\" | \"s\"; }; } | undefined; saved_id?: string | undefined; } | { id: string; type: \"threat_match\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; action_type_id: string; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; uuid?: string | undefined; group?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; }[]; tags: string[]; setup: string; description: string; enabled: boolean; revision: number; query: string; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; risk_score: number; from: string; to: string; language: \"kuery\" | \"lucene\"; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; threat_query: string; threat_mapping: { entries: { value: string; type: \"mapping\"; field: string; }[]; }[]; threat_index: string[]; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; index?: string[] | undefined; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; filters?: unknown[] | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; total_enrichment_duration_ms?: number | undefined; }; status_order: number; }; } | undefined; data_view_id?: string | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; saved_id?: string | undefined; threat_filters?: unknown[] | undefined; threat_indicator_path?: string | undefined; threat_language?: \"kuery\" | \"lucene\" | undefined; concurrent_searches?: number | undefined; items_per_search?: number | undefined; } | { id: string; type: \"machine_learning\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; action_type_id: string; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; uuid?: string | undefined; group?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; }[]; tags: string[]; setup: string; description: string; enabled: boolean; revision: number; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; risk_score: number; from: string; to: string; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; anomaly_threshold: number; machine_learning_job_id: string | string[]; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; total_enrichment_duration_ms?: number | undefined; }; status_order: number; }; } | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; } | { id: string; type: \"new_terms\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; action_type_id: string; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; uuid?: string | undefined; group?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; }[]; tags: string[]; setup: string; description: string; enabled: boolean; revision: number; query: string; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; risk_score: number; from: string; to: string; language: \"kuery\" | \"lucene\"; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; new_terms_fields: string[]; history_window_start: string; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; index?: string[] | undefined; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; filters?: unknown[] | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; total_enrichment_duration_ms?: number | undefined; }; status_order: number; }; } | undefined; data_view_id?: string | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; } | { id: string; type: \"esql\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; action_type_id: string; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; uuid?: string | undefined; group?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; }[]; tags: string[]; setup: string; description: string; enabled: boolean; revision: number; query: string; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; risk_score: number; from: string; to: string; language: \"esql\"; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; total_enrichment_duration_ms?: number | undefined; }; status_order: number; }; } | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; })[]" + "({ id: string; type: \"eql\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; action_type_id: string; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; uuid?: string | undefined; group?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; }[]; tags: string[]; setup: string; description: string; enabled: boolean; revision: number; query: string; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; risk_score: number; language: \"eql\"; from: string; to: string; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; index?: string[] | undefined; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; filters?: unknown[] | undefined; tiebreaker_field?: string | undefined; timestamp_field?: string | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; total_enrichment_duration_ms?: number | undefined; }; status_order: number; }; } | undefined; data_view_id?: string | undefined; event_category_override?: string | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; } | { id: string; type: \"query\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; action_type_id: string; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; uuid?: string | undefined; group?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; }[]; tags: string[]; setup: string; description: string; enabled: boolean; revision: number; query: string; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; risk_score: number; language: \"kuery\" | \"lucene\"; from: string; to: string; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; index?: string[] | undefined; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; filters?: unknown[] | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; total_enrichment_duration_ms?: number | undefined; }; status_order: number; }; } | undefined; data_view_id?: string | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; saved_id?: string | undefined; response_actions?: ({ params: { query?: string | undefined; timeout?: number | undefined; queries?: { id: string; query: string; version?: string | undefined; snapshot?: boolean | undefined; platform?: string | undefined; ecs_mapping?: Zod.objectOutputType<{}, Zod.ZodObject<{ field: Zod.ZodOptional; value: Zod.ZodOptional]>>; }, \"strip\", Zod.ZodTypeAny, { value?: string | string[] | undefined; field?: string | undefined; }, { value?: string | string[] | undefined; field?: string | undefined; }>, \"strip\"> | undefined; removed?: boolean | undefined; }[] | undefined; ecs_mapping?: Zod.objectOutputType<{}, Zod.ZodObject<{ field: Zod.ZodOptional; value: Zod.ZodOptional]>>; }, \"strip\", Zod.ZodTypeAny, { value?: string | string[] | undefined; field?: string | undefined; }, { value?: string | string[] | undefined; field?: string | undefined; }>, \"strip\"> | undefined; saved_query_id?: string | undefined; pack_id?: string | undefined; }; action_type_id: \".osquery\"; } | { params: { command: \"isolate\"; comment?: string | undefined; } | { config: { field: string; overwrite: boolean; }; command: \"kill-process\" | \"suspend-process\"; comment?: string | undefined; }; action_type_id: \".endpoint\"; })[] | undefined; } | { id: string; type: \"saved_query\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; action_type_id: string; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; uuid?: string | undefined; group?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; }[]; tags: string[]; setup: string; description: string; enabled: boolean; revision: number; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; risk_score: number; language: \"kuery\" | \"lucene\"; from: string; to: string; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; saved_id: string; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; index?: string[] | undefined; license?: string | undefined; throttle?: string | undefined; query?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; filters?: unknown[] | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; total_enrichment_duration_ms?: number | undefined; }; status_order: number; }; } | undefined; data_view_id?: string | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; response_actions?: ({ params: { query?: string | undefined; timeout?: number | undefined; queries?: { id: string; query: string; version?: string | undefined; snapshot?: boolean | undefined; platform?: string | undefined; ecs_mapping?: Zod.objectOutputType<{}, Zod.ZodObject<{ field: Zod.ZodOptional; value: Zod.ZodOptional]>>; }, \"strip\", Zod.ZodTypeAny, { value?: string | string[] | undefined; field?: string | undefined; }, { value?: string | string[] | undefined; field?: string | undefined; }>, \"strip\"> | undefined; removed?: boolean | undefined; }[] | undefined; ecs_mapping?: Zod.objectOutputType<{}, Zod.ZodObject<{ field: Zod.ZodOptional; value: Zod.ZodOptional]>>; }, \"strip\", Zod.ZodTypeAny, { value?: string | string[] | undefined; field?: string | undefined; }, { value?: string | string[] | undefined; field?: string | undefined; }>, \"strip\"> | undefined; saved_query_id?: string | undefined; pack_id?: string | undefined; }; action_type_id: \".osquery\"; } | { params: { command: \"isolate\"; comment?: string | undefined; } | { config: { field: string; overwrite: boolean; }; command: \"kill-process\" | \"suspend-process\"; comment?: string | undefined; }; action_type_id: \".endpoint\"; })[] | undefined; } | { id: string; type: \"threshold\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; action_type_id: string; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; uuid?: string | undefined; group?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; }[]; tags: string[]; setup: string; description: string; enabled: boolean; revision: number; query: string; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; risk_score: number; language: \"kuery\" | \"lucene\"; from: string; to: string; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threshold: { value: number; field: string | string[]; cardinality?: { value: number; field: string; }[] | undefined; }; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; index?: string[] | undefined; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; filters?: unknown[] | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; total_enrichment_duration_ms?: number | undefined; }; status_order: number; }; } | undefined; data_view_id?: string | undefined; alert_suppression?: { duration: { value: number; unit: \"m\" | \"h\" | \"s\"; }; } | undefined; saved_id?: string | undefined; } | { id: string; type: \"threat_match\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; action_type_id: string; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; uuid?: string | undefined; group?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; }[]; tags: string[]; setup: string; description: string; enabled: boolean; revision: number; query: string; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; risk_score: number; language: \"kuery\" | \"lucene\"; from: string; to: string; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; threat_query: string; threat_mapping: { entries: { value: string; type: \"mapping\"; field: string; }[]; }[]; threat_index: string[]; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; index?: string[] | undefined; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; filters?: unknown[] | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; total_enrichment_duration_ms?: number | undefined; }; status_order: number; }; } | undefined; data_view_id?: string | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; saved_id?: string | undefined; threat_filters?: unknown[] | undefined; threat_indicator_path?: string | undefined; threat_language?: \"kuery\" | \"lucene\" | undefined; concurrent_searches?: number | undefined; items_per_search?: number | undefined; } | { id: string; type: \"machine_learning\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; action_type_id: string; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; uuid?: string | undefined; group?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; }[]; tags: string[]; setup: string; description: string; enabled: boolean; revision: number; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; risk_score: number; from: string; to: string; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; anomaly_threshold: number; machine_learning_job_id: string | string[]; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; total_enrichment_duration_ms?: number | undefined; }; status_order: number; }; } | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; } | { id: string; type: \"new_terms\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; action_type_id: string; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; uuid?: string | undefined; group?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; }[]; tags: string[]; setup: string; description: string; enabled: boolean; revision: number; query: string; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; risk_score: number; language: \"kuery\" | \"lucene\"; from: string; to: string; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; new_terms_fields: string[]; history_window_start: string; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; index?: string[] | undefined; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; filters?: unknown[] | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; total_enrichment_duration_ms?: number | undefined; }; status_order: number; }; } | undefined; data_view_id?: string | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; } | { id: string; type: \"esql\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; action_type_id: string; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; uuid?: string | undefined; group?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; }[]; tags: string[]; setup: string; description: string; enabled: boolean; revision: number; query: string; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; risk_score: number; language: \"esql\"; from: string; to: string; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; total_enrichment_duration_ms?: number | undefined; }; status_order: number; }; } | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; })[]" ], "path": "x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts", "deprecated": false, @@ -2201,6 +2201,27 @@ "deprecated": false, "trackAdoption": false, "isRequired": true + }, + { + "parentPluginId": "securitySolution", + "id": "def-server.AppClient.Unnamed.$5", + "type": "CompoundType", + "tags": [], + "label": "buildFlavor", + "description": [], + "signature": [ + { + "pluginId": "@kbn/config", + "scope": "server", + "docId": "kibKbnConfigPluginApi", + "section": "def-server.BuildFlavor", + "text": "BuildFlavor" + } + ], + "path": "x-pack/plugins/security_solution/server/client/client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true } ], "returnComment": [] @@ -2316,6 +2337,29 @@ "trackAdoption": false, "children": [], "returnComment": [] + }, + { + "parentPluginId": "securitySolution", + "id": "def-server.AppClient.getBuildFlavor", + "type": "Function", + "tags": [], + "label": "getBuildFlavor", + "description": [], + "signature": [ + "() => ", + { + "pluginId": "@kbn/config", + "scope": "server", + "docId": "kibKbnConfigPluginApi", + "section": "def-server.BuildFlavor", + "text": "BuildFlavor" + } + ], + "path": "x-pack/plugins/security_solution/server/client/client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] } ], "initialIsOpen": false diff --git a/api_docs/security_solution.mdx b/api_docs/security_solution.mdx index 92ee1f8b470d0..faac33f2f5eb7 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolution'] --- import securitySolutionObj from './security_solution.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/security-solution](https://github.com/orgs/elastic/teams/secur | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 190 | 0 | 121 | 33 | +| 192 | 0 | 123 | 33 | ## Client diff --git a/api_docs/security_solution_ess.mdx b/api_docs/security_solution_ess.mdx index 8116d7e7aa5b5..211066386212f 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: 2024-08-07 +date: 2024-08-08 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 6b4ff508c0b6a..7e9da161c7c43 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: 2024-08-07 +date: 2024-08-08 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 55578db0e8424..280f36400d10e 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: 2024-08-07 +date: 2024-08-08 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 549d230fd0e16..f25512f73e45d 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: 2024-08-07 +date: 2024-08-08 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 fa1b9cc64125e..38c2c94000c5e 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: 2024-08-07 +date: 2024-08-08 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 b85f0413f266f..7472f66790dec 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: 2024-08-07 +date: 2024-08-08 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 1679d464845fe..f36058b2be1d4 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'share'] --- import shareObj from './share.devdocs.json'; diff --git a/api_docs/slo.mdx b/api_docs/slo.mdx index 75f58389f0c2e..7de0907e55c0f 100644 --- a/api_docs/slo.mdx +++ b/api_docs/slo.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/slo title: "slo" image: https://source.unsplash.com/400x175/?github description: API docs for the slo plugin -date: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'slo'] --- import sloObj from './slo.devdocs.json'; diff --git a/api_docs/snapshot_restore.mdx b/api_docs/snapshot_restore.mdx index 8bb82168ffbbc..071acbb356988 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: 2024-08-07 +date: 2024-08-08 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 7f3831062a1b1..cf39690fd8e55 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: 2024-08-07 +date: 2024-08-08 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 e4ec4f51a79f7..b71a7ae4c36af 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: 2024-08-07 +date: 2024-08-08 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 fa60e705e1059..f2020c9c0d8d9 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackConnectors'] --- import stackConnectorsObj from './stack_connectors.devdocs.json'; diff --git a/api_docs/task_manager.devdocs.json b/api_docs/task_manager.devdocs.json index 1b43d99d8b0be..6ed73468daeea 100644 --- a/api_docs/task_manager.devdocs.json +++ b/api_docs/task_manager.devdocs.json @@ -230,7 +230,7 @@ "label": "start", "description": [], "signature": [ - "({ savedObjects, elasticsearch, executionContext, docLinks, }: ", + "({ savedObjects, elasticsearch, executionContext, docLinks }: ", { "pluginId": "@kbn/core-lifecycle-server", "scope": "server", @@ -238,6 +238,8 @@ "section": "def-server.CoreStart", "text": "CoreStart" }, + ", { cloud }: ", + "TaskManagerPluginStart", ") => ", { "pluginId": "taskManager", @@ -256,7 +258,7 @@ "id": "def-server.TaskManagerPlugin.start.$1", "type": "Object", "tags": [], - "label": "{\n savedObjects,\n elasticsearch,\n executionContext,\n docLinks,\n }", + "label": "{ savedObjects, elasticsearch, executionContext, docLinks }", "description": [], "signature": [ { @@ -271,6 +273,21 @@ "deprecated": false, "trackAdoption": false, "isRequired": true + }, + { + "parentPluginId": "taskManager", + "id": "def-server.TaskManagerPlugin.start.$2", + "type": "Object", + "tags": [], + "label": "{ cloud }", + "description": [], + "signature": [ + "TaskManagerPluginStart" + ], + "path": "x-pack/plugins/task_manager/server/plugin.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true } ], "returnComment": [] @@ -1437,6 +1454,23 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "taskManager", + "id": "def-server.TaskRegisterDefinition.cost", + "type": "CompoundType", + "tags": [], + "label": "cost", + "description": [ + "\nAn optional definition of the cost associated with running the task." + ], + "signature": [ + "TaskCost", + " | undefined" + ], + "path": "x-pack/plugins/task_manager/server/task_type_dictionary.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "taskManager", "id": "def-server.TaskRegisterDefinition.description", diff --git a/api_docs/task_manager.mdx b/api_docs/task_manager.mdx index 758e6144b59dd..c8ec229776f2a 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'taskManager'] --- import taskManagerObj from './task_manager.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-o | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 105 | 0 | 62 | 5 | +| 107 | 0 | 63 | 7 | ## Server diff --git a/api_docs/telemetry.mdx b/api_docs/telemetry.mdx index a1dd9b23fedd5..1a006ed707680 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: 2024-08-07 +date: 2024-08-08 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 2b093f087945d..1f04de17c164d 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: 2024-08-07 +date: 2024-08-08 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 c824a9d0df371..1c66d152df6eb 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: 2024-08-07 +date: 2024-08-08 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 d1655bf996ab3..88daa1085358e 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryManagementSection'] --- import telemetryManagementSectionObj from './telemetry_management_section.devdocs.json'; diff --git a/api_docs/threat_intelligence.mdx b/api_docs/threat_intelligence.mdx index b248ec1b6dc43..0a3d44483ccac 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: 2024-08-07 +date: 2024-08-08 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 6bfc6d886c5a6..ffaa53d997ba5 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: 2024-08-07 +date: 2024-08-08 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 4f993fb97a090..78a08c46494e0 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: 2024-08-07 +date: 2024-08-08 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 0ecb013e39ac3..846fa43855ed7 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: 2024-08-07 +date: 2024-08-08 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 d35950d11c37e..14faecc1b008b 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: 2024-08-07 +date: 2024-08-08 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 28a65ee53d3cf..0b656b0223982 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: 2024-08-07 +date: 2024-08-08 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 1ae05a4e2d11f..0341174380b36 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: 2024-08-07 +date: 2024-08-08 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 4e60d04926682..27d6c562fe7ad 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: 2024-08-07 +date: 2024-08-08 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 2d8c7bdd9e6b5..3ab0dcf1e5403 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: 2024-08-07 +date: 2024-08-08 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 67edea51adf5b..d3a8e4d5f3648 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: 2024-08-07 +date: 2024-08-08 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 7d4b86a72a19b..23c1e763645f7 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: 2024-08-07 +date: 2024-08-08 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 3b4c5e7160e8d..361f67a81f9cc 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: 2024-08-07 +date: 2024-08-08 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 ca2fffc381dfa..ff05a389ec2c2 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: 2024-08-07 +date: 2024-08-08 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 0b2eaf1c126f6..de09b62ae5c0e 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: 2024-08-07 +date: 2024-08-08 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 a4851a20f1df3..31bba38b5475e 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: 2024-08-07 +date: 2024-08-08 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 6e10edf52f475..030603954b21d 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: 2024-08-07 +date: 2024-08-08 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 a6ef77f0fff35..20bf2f6a0cc9d 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: 2024-08-07 +date: 2024-08-08 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 8579c69be2c73..32010837ac2c4 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: 2024-08-07 +date: 2024-08-08 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 81eb10ef50560..d7253de28fcf6 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: 2024-08-07 +date: 2024-08-08 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 f5dfb33730ef1..aa19fda274b2e 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: 2024-08-07 +date: 2024-08-08 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 6fa0d00c3160e..d7f05ff1044fb 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: 2024-08-07 +date: 2024-08-08 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 356b67432f80b..f3c76f010502f 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: 2024-08-07 +date: 2024-08-08 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 106ce90085f33..304a8c1ea8b4f 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: 2024-08-07 +date: 2024-08-08 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 f97e3e25845ce..13c1fa62e1f10 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: 2024-08-07 +date: 2024-08-08 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 b66825868faa5..0a4684c1fb556 100644 --- a/api_docs/visualizations.devdocs.json +++ b/api_docs/visualizations.devdocs.json @@ -15659,7 +15659,7 @@ "label": "GaugeCentralMajorMode", "description": [], "signature": [ - "\"none\" | \"auto\" | \"custom\"" + "\"none\" | \"custom\" | \"auto\"" ], "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", "deprecated": false, @@ -15689,7 +15689,7 @@ "label": "GaugeLabelMajorMode", "description": [], "signature": [ - "\"none\" | \"auto\" | \"custom\"" + "\"none\" | \"custom\" | \"auto\"" ], "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", "deprecated": false, @@ -16105,7 +16105,7 @@ "label": "Operation", "description": [], "signature": [ - "\"min\" | \"max\" | \"sum\" | \"median\" | \"count\" | \"filters\" | \"range\" | \"cumulative_sum\" | \"date_histogram\" | \"terms\" | \"average\" | \"percentile\" | \"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\" | \"terms\" | \"range\" | \"cumulative_sum\" | \"date_histogram\" | \"average\" | \"percentile\" | \"moving_average\" | \"unique_count\" | \"standard_deviation\" | \"percentile_rank\" | \"last_value\" | \"counter_rate\" | \"differences\" | \"formula\" | \"static_value\" | \"normalize_by_unit\"" ], "path": "src/plugins/visualizations/common/convert_to_lens/types/operations.ts", "deprecated": false, @@ -16135,7 +16135,7 @@ "label": "OperationWithSourceField", "description": [], "signature": [ - "\"min\" | \"max\" | \"sum\" | \"median\" | \"count\" | \"filters\" | \"range\" | \"date_histogram\" | \"terms\" | \"average\" | \"percentile\" | \"unique_count\" | \"standard_deviation\" | \"percentile_rank\" | \"last_value\"" + "\"min\" | \"max\" | \"sum\" | \"median\" | \"count\" | \"filters\" | \"terms\" | \"range\" | \"date_histogram\" | \"average\" | \"percentile\" | \"unique_count\" | \"standard_deviation\" | \"percentile_rank\" | \"last_value\"" ], "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 54e4c54c49869..a51fec67c7dda 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: 2024-08-07 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visualizations'] --- import visualizationsObj from './visualizations.devdocs.json'; From a5900591f87ab76eccde10d702925ff6ea9630d0 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Thu, 8 Aug 2024 09:50:19 +0200 Subject: [PATCH 39/44] [ES|QL][Discover] Adds an explanatory tooltip in the navigation switch (#190036) ## Summary image image --- .../main/components/top_nav/get_top_nav_links.test.ts | 2 ++ .../main/components/top_nav/get_top_nav_links.tsx | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.test.ts b/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.test.ts index 2e9b6481b1081..79ef9fe6b3dc9 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.test.ts +++ b/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.test.ts @@ -46,6 +46,7 @@ test('getTopNavLinks result', () => { "label": "Try ES|QL", "run": [Function], "testId": "select-text-based-language-btn", + "tooltip": "ES|QL is Elastic's powerful new piped query language.", }, Object { "description": "New Search", @@ -110,6 +111,7 @@ test('getTopNavLinks result for ES|QL mode', () => { "label": "Switch to classic", "run": [Function], "testId": "switch-to-dataviews", + "tooltip": "Switch to KQL or Lucene syntax.", }, Object { "description": "New Search", diff --git a/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.tsx b/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.tsx index a333bde02fd89..72cd9d71d12df 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.tsx @@ -81,6 +81,13 @@ export const getTopNavLinks = ({ iconType: 'editorRedo', fill: false, color: 'text', + tooltip: isEsqlMode + ? i18n.translate('discover.localMenu.switchToClassicTooltipLabel', { + defaultMessage: 'Switch to KQL or Lucene syntax.', + }) + : i18n.translate('discover.localMenu.esqlTooltipLabel', { + defaultMessage: `ES|QL is Elastic's powerful new piped query language.`, + }), run: () => { if (dataView) { if (isEsqlMode) { From 66458ac4918ea99a0d48c35f8cbd64534f9372d9 Mon Sep 17 00:00:00 2001 From: "Eyo O. Eyo" <7893459+eokoneyo@users.noreply.github.com> Date: Thu, 8 Aug 2024 10:09:46 +0200 Subject: [PATCH 40/44] Welcome interstitial not displayed (#190045) ## Summary Closes https://github.com/elastic/kibana/issues/186168 The tests fails because for some reason the welcome interstitial animation doesn't complete before the timeout set for the target element to be visible, it's interesting that despite waiting for the set duration of the animation for the property, with a timeout that quadruples the animation duration this tests fails still. It's worth pointing out that in all referenced instances of failure in the issue, on the second attempt the test actually passes, hence this PR only introduces a retry to the assertion that the target element is displayed. Furthermore this PR was ran through the flaky test runner [see here](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/6699) with success. --------- Co-authored-by: Elastic Machine --- test/functional/apps/home/_welcome.ts | 3 +-- test/functional/page_objects/home_page.ts | 11 ++++++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/test/functional/apps/home/_welcome.ts b/test/functional/apps/home/_welcome.ts index 6c8bda90de699..d61afd879090e 100644 --- a/test/functional/apps/home/_welcome.ts +++ b/test/functional/apps/home/_welcome.ts @@ -15,8 +15,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['common', 'home']); const deployment = getService('deployment'); - // FLAKY: https://github.com/elastic/kibana/issues/186168 - describe.skip('Welcome interstitial', () => { + describe('Welcome interstitial', () => { beforeEach(async () => { // Need to navigate to page first to clear storage before test can be run await PageObjects.common.navigateToUrl('home', undefined); diff --git a/test/functional/page_objects/home_page.ts b/test/functional/page_objects/home_page.ts index 0c85c51381b94..7dd5ee171afe6 100644 --- a/test/functional/page_objects/home_page.ts +++ b/test/functional/page_objects/home_page.ts @@ -57,9 +57,14 @@ export class HomePageObject extends FtrService { } async isWelcomeInterstitialDisplayed() { - // give the interstitial enough time to fade in - await new Promise((resolve) => setTimeout(resolve, 500)); - return await this.testSubjects.isDisplayed('homeWelcomeInterstitial', 2000); + // This element inherits style defined {@link https://github.com/elastic/kibana/blob/v8.14.3/src/core/public/styles/core_app/_mixins.scss#L72|here} + // with an animation duration set to $euiAnimSpeedExtraSlow {@see https://eui.elastic.co/#/theming/more-tokens#animation}, + // hence we setup a delay so the interstitial has enough time to fade in + const animSpeedExtraSlow = 500; + await new Promise((resolve) => setTimeout(resolve, animSpeedExtraSlow)); + return this.retry.try(async () => { + return await this.testSubjects.isDisplayed('homeWelcomeInterstitial', animSpeedExtraSlow * 4); + }); } async isGuidedOnboardingLandingDisplayed() { From 4f6a936de54bed00af678ccfcb6d45cc2f97c9b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= Date: Thu, 8 Aug 2024 10:18:42 +0200 Subject: [PATCH 41/44] [Dynamic Config Overrides] Add persistence (#189912) Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../src/core_app.test.ts | 80 ++++++++---- .../core-apps-server-internal/src/core_app.ts | 121 ++++++++++++++++-- .../core-apps-server-internal/tsconfig.json | 3 + .../core-root-server-internal/src/server.ts | 3 + .../current_fields.json | 1 + .../current_mappings.json | 4 + .../kbn-config/src/config_service.test.ts | 15 +++ packages/kbn-config/src/config_service.ts | 32 +++-- .../check_registered_types.test.ts | 1 + .../config/check_dynamic_config.test.ts | 68 +++++++++- .../group3/type_registrations.test.ts | 1 + .../common/telemetry/telemetry_config.ts | 15 +++ 12 files changed, 299 insertions(+), 45 deletions(-) diff --git a/packages/core/apps/core-apps-server-internal/src/core_app.test.ts b/packages/core/apps/core-apps-server-internal/src/core_app.test.ts index 3c5cfa068765c..b4b1837cad756 100644 --- a/packages/core/apps/core-apps-server-internal/src/core_app.test.ts +++ b/packages/core/apps/core-apps-server-internal/src/core_app.test.ts @@ -34,6 +34,7 @@ describe('CoreApp', () => { let httpResourcesRegistrar: ReturnType; beforeEach(() => { + jest.useFakeTimers(); coreContext = mockCoreContext.create(); internalCorePreboot = coreInternalLifecycleMock.createInternalPreboot(); @@ -55,37 +56,70 @@ describe('CoreApp', () => { afterEach(() => { registerBundleRoutesMock.mockReset(); + coreApp.stop(); + jest.clearAllTimers(); }); - describe('`/internal/core/_settings` route', () => { - it('is not registered by default', async () => { - const routerMock = mockRouter.create(); - internalCoreSetup.http.createRouter.mockReturnValue(routerMock); + describe('Dynamic Config feature', () => { + describe('`/internal/core/_settings` route', () => { + it('is not registered by default', async () => { + const routerMock = mockRouter.create(); + internalCoreSetup.http.createRouter.mockReturnValue(routerMock); + + const localCoreApp = new CoreAppsService(coreContext); + await localCoreApp.setup(internalCoreSetup, emptyPlugins()); + + expect(routerMock.versioned.put).not.toHaveBeenCalledWith( + expect.objectContaining({ + path: '/internal/core/_settings', + }) + ); + + // But the Saved Object is still registered + expect(internalCoreSetup.savedObjects.registerType).toHaveBeenCalledWith( + expect.objectContaining({ name: 'dynamic-config-overrides' }) + ); + }); + + it('is registered when enabled', async () => { + const routerMock = mockRouter.create(); + internalCoreSetup.http.createRouter.mockReturnValue(routerMock); - const localCoreApp = new CoreAppsService(coreContext); - await localCoreApp.setup(internalCoreSetup, emptyPlugins()); + coreContext.configService.atPath.mockReturnValue(of({ allowDynamicConfigOverrides: true })); + const localCoreApp = new CoreAppsService(coreContext); + await localCoreApp.setup(internalCoreSetup, emptyPlugins()); - expect(routerMock.versioned.put).not.toHaveBeenCalledWith( - expect.objectContaining({ + expect(routerMock.versioned.put).toHaveBeenCalledWith({ path: '/internal/core/_settings', - }) - ); - }); + access: 'internal', + options: { + tags: ['access:updateDynamicConfig'], + }, + }); + }); - it('is registered when enabled', async () => { - const routerMock = mockRouter.create(); - internalCoreSetup.http.createRouter.mockReturnValue(routerMock); + it('it fetches the persisted document when enabled', async () => { + const routerMock = mockRouter.create(); + internalCoreSetup.http.createRouter.mockReturnValue(routerMock); - coreContext.configService.atPath.mockReturnValue(of({ allowDynamicConfigOverrides: true })); - const localCoreApp = new CoreAppsService(coreContext); - await localCoreApp.setup(internalCoreSetup, emptyPlugins()); + coreContext.configService.atPath.mockReturnValue(of({ allowDynamicConfigOverrides: true })); + const localCoreApp = new CoreAppsService(coreContext); + await localCoreApp.setup(internalCoreSetup, emptyPlugins()); - expect(routerMock.versioned.put).toHaveBeenCalledWith({ - path: '/internal/core/_settings', - access: 'internal', - options: { - tags: ['access:updateDynamicConfig'], - }, + const internalCoreStart = coreInternalLifecycleMock.createInternalStart(); + localCoreApp.start(internalCoreStart); + + expect(internalCoreStart.savedObjects.createInternalRepository).toHaveBeenCalledWith([ + 'dynamic-config-overrides', + ]); + + const repository = + internalCoreStart.savedObjects.createInternalRepository.mock.results[0].value; + await jest.advanceTimersByTimeAsync(0); // "Advancing" 0ms is enough, but necessary to trigger the `timer` observable + expect(repository.get).toHaveBeenCalledWith( + 'dynamic-config-overrides', + 'dynamic-config-overrides' + ); }); }); }); diff --git a/packages/core/apps/core-apps-server-internal/src/core_app.ts b/packages/core/apps/core-apps-server-internal/src/core_app.ts index e6b7349d2edff..e9676c792292a 100644 --- a/packages/core/apps/core-apps-server-internal/src/core_app.ts +++ b/packages/core/apps/core-apps-server-internal/src/core_app.ts @@ -21,9 +21,27 @@ import type { } from '@kbn/core-http-server'; import type { UiPlugins } from '@kbn/core-plugins-base-server-internal'; import type { HttpResources, HttpResourcesServiceToolkit } from '@kbn/core-http-resources-server'; -import type { InternalCorePreboot, InternalCoreSetup } from '@kbn/core-lifecycle-server-internal'; +import type { + InternalCorePreboot, + InternalCoreSetup, + InternalCoreStart, +} from '@kbn/core-lifecycle-server-internal'; import type { InternalStaticAssets } from '@kbn/core-http-server-internal'; -import { firstValueFrom, map, type Observable } from 'rxjs'; +import { + combineLatest, + concatMap, + firstValueFrom, + map, + type Observable, + ReplaySubject, + shareReplay, + Subject, + takeUntil, + timer, +} from 'rxjs'; +import type { InternalSavedObjectsServiceStart } from '@kbn/core-saved-objects-server-internal'; +import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import { SavedObjectsErrorHelpers } from '@kbn/core-saved-objects-server'; import { CoreAppConfig, type CoreAppConfigType, CoreAppPath } from './core_app_config'; import { registerBundleRoutes } from './bundle_routes'; import type { InternalCoreAppsServiceRequestHandlerContext } from './internal_types'; @@ -41,12 +59,17 @@ interface CommonRoutesParams { ) => Promise; } +const DYNAMIC_CONFIG_OVERRIDES_SO_TYPE = 'dynamic-config-overrides'; +const DYNAMIC_CONFIG_OVERRIDES_SO_ID = 'dynamic-config-overrides'; + /** @internal */ export class CoreAppsService { private readonly logger: Logger; private readonly env: Env; private readonly configService: IConfigService; private readonly config$: Observable; + private readonly savedObjectsStart$ = new ReplaySubject(1); + private readonly stop$ = new Subject(); constructor(core: CoreContext) { this.logger = core.logger.get('core-app'); @@ -70,8 +93,21 @@ export class CoreAppsService { async setup(coreSetup: InternalCoreSetup, uiPlugins: UiPlugins) { this.logger.debug('Setting up core app.'); const config = await firstValueFrom(this.config$); - this.registerDefaultRoutes(coreSetup, uiPlugins, config); + this.registerDefaultRoutes(coreSetup, uiPlugins); this.registerStaticDirs(coreSetup, uiPlugins); + this.maybeRegisterDynamicConfigurationFeature({ + config, + coreSetup, + savedObjectsStart$: this.savedObjectsStart$, + }); + } + + start(coreStart: InternalCoreStart) { + this.savedObjectsStart$.next(coreStart.savedObjects); + } + + stop() { + this.stop$.next(); } private registerPrebootDefaultRoutes(corePreboot: InternalCorePreboot, uiPlugins: UiPlugins) { @@ -100,11 +136,7 @@ export class CoreAppsService { }); } - private registerDefaultRoutes( - coreSetup: InternalCoreSetup, - uiPlugins: UiPlugins, - config: CoreAppConfig - ) { + private registerDefaultRoutes(coreSetup: InternalCoreSetup, uiPlugins: UiPlugins) { const httpSetup = coreSetup.http; const router = httpSetup.createRouter(''); const resources = coreSetup.httpResources.createRegistrar(router); @@ -167,18 +199,80 @@ export class CoreAppsService { } } ); + } + + private maybeRegisterDynamicConfigurationFeature({ + config, + coreSetup, + savedObjectsStart$, + }: { + config: CoreAppConfig; + coreSetup: InternalCoreSetup; + savedObjectsStart$: Observable; + }) { + // Always registering the Saved Objects to avoid ON/OFF conflicts in the migrations + coreSetup.savedObjects.registerType({ + name: DYNAMIC_CONFIG_OVERRIDES_SO_TYPE, + hidden: true, + hiddenFromHttpApis: true, + namespaceType: 'agnostic', + mappings: { + dynamic: false, + properties: {}, + }, + }); if (config.allowDynamicConfigOverrides) { - this.registerInternalCoreSettingsRoute(router); + const savedObjectsClient$ = savedObjectsStart$.pipe( + map((savedObjectsStart) => + savedObjectsStart.createInternalRepository([DYNAMIC_CONFIG_OVERRIDES_SO_TYPE]) + ), + shareReplay(1) + ); + + // Register the HTTP route + const router = coreSetup.http.createRouter(''); + this.registerInternalCoreSettingsRoute(router, savedObjectsClient$); + + let latestOverrideVersion: string | undefined; // Use the document version to avoid calling override on every poll + // Poll for updates + combineLatest([savedObjectsClient$, timer(0, 10_000)]) + .pipe( + concatMap(async ([soClient]) => { + try { + const persistedOverrides = await soClient.get>( + DYNAMIC_CONFIG_OVERRIDES_SO_TYPE, + DYNAMIC_CONFIG_OVERRIDES_SO_ID + ); + if (latestOverrideVersion !== persistedOverrides.version) { + this.configService.setDynamicConfigOverrides(persistedOverrides.attributes); + latestOverrideVersion = persistedOverrides.version; + } + } catch (err) { + // Potential failures: + // - The SO document does not exist (404 error) => no need to log + // - The configuration overrides are invalid => they won't be applied and the validation error will be logged. + if (!SavedObjectsErrorHelpers.isNotFoundError(err)) { + this.logger.warn(`Failed to apply the persisted dynamic config overrides: ${err}`); + } + } + }), + takeUntil(this.stop$) + ) + .subscribe(); } } /** * Registers the HTTP API that allows updating in-memory the settings that opted-in to be dynamically updatable. * @param router {@link IRouter} + * @param savedObjectClient$ An observable of a {@link SavedObjectsClientContract | savedObjects client} that will be used to update the document * @private */ - private registerInternalCoreSettingsRoute(router: IRouter) { + private registerInternalCoreSettingsRoute( + router: IRouter, + savedObjectClient$: Observable + ) { router.versioned .put({ path: '/internal/core/_settings', @@ -201,7 +295,12 @@ export class CoreAppsService { }, async (context, req, res) => { try { - this.configService.setDynamicConfigOverrides(req.body); + const newGlobalOverrides = this.configService.setDynamicConfigOverrides(req.body); + const soClient = await firstValueFrom(savedObjectClient$); + await soClient.create(DYNAMIC_CONFIG_OVERRIDES_SO_TYPE, newGlobalOverrides, { + id: DYNAMIC_CONFIG_OVERRIDES_SO_ID, + overwrite: true, + }); } catch (err) { if (err instanceof ValidationError) { return res.badRequest({ body: err }); diff --git a/packages/core/apps/core-apps-server-internal/tsconfig.json b/packages/core/apps/core-apps-server-internal/tsconfig.json index 35698e91f6ddb..cc84d62f4faa4 100644 --- a/packages/core/apps/core-apps-server-internal/tsconfig.json +++ b/packages/core/apps/core-apps-server-internal/tsconfig.json @@ -34,6 +34,9 @@ "@kbn/monaco", "@kbn/core-http-server-internal", "@kbn/core-http-router-server-internal", + "@kbn/core-saved-objects-server-internal", + "@kbn/core-saved-objects-api-server", + "@kbn/core-saved-objects-server", ], "exclude": [ "target/**/*", diff --git a/packages/core/root/core-root-server-internal/src/server.ts b/packages/core/root/core-root-server-internal/src/server.ts index d380bffd8b359..35acaf8324e1c 100644 --- a/packages/core/root/core-root-server-internal/src/server.ts +++ b/packages/core/root/core-root-server-internal/src/server.ts @@ -450,6 +450,8 @@ export class Server { userProfile: userProfileStart, }; + this.coreApp.start(this.coreStart); + await this.plugins.start(this.coreStart); await this.http.start(); @@ -469,6 +471,7 @@ export class Server { public async stop() { this.log.debug('stopping server'); + this.coreApp.stop(); await this.analytics.stop(); await this.http.stop(); // HTTP server has to stop before savedObjects and ES clients are closed to be able to gracefully attempt to resolve any pending requests await this.plugins.stop(); diff --git a/packages/kbn-check-mappings-update-cli/current_fields.json b/packages/kbn-check-mappings-update-cli/current_fields.json index 4cec00e68dc42..d891c7ae5d095 100644 --- a/packages/kbn-check-mappings-update-cli/current_fields.json +++ b/packages/kbn-check-mappings-update-cli/current_fields.json @@ -284,6 +284,7 @@ "title", "version" ], + "dynamic-config-overrides": [], "endpoint:unified-user-artifact-manifest": [ "artifactIds", "policyId", diff --git a/packages/kbn-check-mappings-update-cli/current_mappings.json b/packages/kbn-check-mappings-update-cli/current_mappings.json index 9242f775f7094..8bec33bcda085 100644 --- a/packages/kbn-check-mappings-update-cli/current_mappings.json +++ b/packages/kbn-check-mappings-update-cli/current_mappings.json @@ -975,6 +975,10 @@ } } }, + "dynamic-config-overrides": { + "dynamic": false, + "properties": {} + }, "endpoint:unified-user-artifact-manifest": { "dynamic": false, "properties": { diff --git a/packages/kbn-config/src/config_service.test.ts b/packages/kbn-config/src/config_service.test.ts index 67a8aa0c3d75d..e6a5d3dc331a5 100644 --- a/packages/kbn-config/src/config_service.test.ts +++ b/packages/kbn-config/src/config_service.test.ts @@ -726,4 +726,19 @@ describe('Dynamic Overrides', () => { await firstValueFrom(configService.getConfig$().pipe(map((cfg) => cfg.toRaw()))) ).toStrictEqual({ namespace1: { key: 'another-value' } }); }); + + test('is able to remove a field when setting it to `null`', async () => { + configService.addDynamicConfigPaths('namespace1', ['key']); + configService.setDynamicConfigOverrides({ 'namespace1.key': 'another-value' }); + + expect( + await firstValueFrom(configService.getConfig$().pipe(map((cfg) => cfg.toRaw()))) + ).toStrictEqual({ namespace1: { key: 'another-value' } }); + + configService.setDynamicConfigOverrides({ 'namespace1.key': null }); + + expect( + await firstValueFrom(configService.getConfig$().pipe(map((cfg) => cfg.toRaw()))) + ).toStrictEqual({ namespace1: {} }); + }); }); diff --git a/packages/kbn-config/src/config_service.ts b/packages/kbn-config/src/config_service.ts index 51a83f82664e2..7aba4861c7423 100644 --- a/packages/kbn-config/src/config_service.ts +++ b/packages/kbn-config/src/config_service.ts @@ -8,7 +8,7 @@ import type { PublicMethodsOf } from '@kbn/utility-types'; import { SchemaTypeError, Type, ValidationError } from '@kbn/config-schema'; -import { cloneDeep, isEqual, merge } from 'lodash'; +import { cloneDeep, isEqual, merge, unset } from 'lodash'; import { set } from '@kbn/safer-lodash-set'; import { BehaviorSubject, combineLatest, firstValueFrom, Observable, identity } from 'rxjs'; import { distinctUntilChanged, first, map, shareReplay, tap } from 'rxjs'; @@ -64,7 +64,10 @@ export class ConfigService { private readonly schemas = new Map>(); private readonly deprecations = new BehaviorSubject([]); private readonly dynamicPaths = new Map(); - private readonly overrides$ = new BehaviorSubject>({}); + private readonly overrides$ = new BehaviorSubject<{ + additions: Record; + removals: string[]; + }>({ additions: {}, removals: [] }); private readonly handledDeprecatedConfigs = new Map(); constructor( @@ -85,7 +88,8 @@ export class ConfigService { this.overrides$, ]).pipe( map(([rawConfig, deprecations, overrides]) => { - const overridden = merge(rawConfig, overrides); + const overridden = merge(rawConfig, overrides.additions); + overrides.removals.forEach((key) => unset(overridden, key)); const migrated = applyDeprecations(overridden, deprecations); this.deprecatedConfigPaths.next(migrated.changedPaths); return new ObjectToConfigAdapter(migrated.config); @@ -254,15 +258,23 @@ export class ConfigService { * @param newOverrides */ public setDynamicConfigOverrides(newOverrides: Record) { - const globalOverrides = cloneDeep(this.overrides$.value); + const globalOverrides = cloneDeep(this.overrides$.value.additions); const flattenedOverrides = getFlattenedObject(newOverrides); const validateWithNamespace = new Set(); + const flattenedKeysToRemove: string[] = []; // We don't want to remove keys until all the validations have been applied. + keyLoop: for (const key in flattenedOverrides) { // this if is enforced by an eslint rule :shrug: if (key in flattenedOverrides) { + // If set to `null`, delete the config from the overrides. + if (flattenedOverrides[key] === null) { + flattenedKeysToRemove.push(key); + continue; + } + for (const [configPath, dynamicConfigKeys] of this.dynamicPaths.entries()) { if ( key.startsWith(`${configPath}.`) && @@ -282,13 +294,17 @@ export class ConfigService { } } - const globalOverridesAsConfig = new ObjectToConfigAdapter( - merge({}, this.lastConfig, globalOverrides) - ); + const rawConfig = merge({}, this.lastConfig, globalOverrides); + flattenedKeysToRemove.forEach((key) => { + unset(globalOverrides, key); + unset(rawConfig, key); + }); + const globalOverridesAsConfig = new ObjectToConfigAdapter(rawConfig); validateWithNamespace.forEach((ns) => this.validateAtPath(ns, globalOverridesAsConfig.get(ns))); - this.overrides$.next(globalOverrides); + this.overrides$.next({ additions: globalOverrides, removals: flattenedKeysToRemove }); + return globalOverrides; } private async logDeprecation() { diff --git a/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts b/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts index 02ad163c43931..90f020a42e278 100644 --- a/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts +++ b/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts @@ -86,6 +86,7 @@ describe('checking migration metadata changes on all registered SO types', () => "core-usage-stats": "b3c04da317c957741ebcdedfea4524049fdc79ff", "csp-rule-template": "c151324d5f85178169395eecb12bac6b96064654", "dashboard": "211e9ca30f5a95d5f3c27b1bf2b58e6cfa0c9ae9", + "dynamic-config-overrides": "eb3ec7d96a42991068eda5421eecba9349c82d2b", "endpoint:unified-user-artifact-manifest": "71c7fcb52c658b21ea2800a6b6a76972ae1c776e", "endpoint:user-artifact-manifest": "1c3533161811a58772e30cdc77bac4631da3ef2b", "enterprise_search_telemetry": "9ac912e1417fc8681e0cd383775382117c9e3d3d", diff --git a/src/core/server/integration_tests/config/check_dynamic_config.test.ts b/src/core/server/integration_tests/config/check_dynamic_config.test.ts index 7239f051f41e7..335b688d5b2f4 100644 --- a/src/core/server/integration_tests/config/check_dynamic_config.test.ts +++ b/src/core/server/integration_tests/config/check_dynamic_config.test.ts @@ -7,11 +7,73 @@ */ import { set } from '@kbn/safer-lodash-set'; -import { Root } from '@kbn/core-root-server-internal'; -import { createRootWithCorePlugins } from '@kbn/core-test-helpers-kbn-server'; +import type { Root } from '@kbn/core-root-server-internal'; +import { + createTestServers, + createRootWithCorePlugins, + type TestElasticsearchUtils, + request, +} from '@kbn/core-test-helpers-kbn-server'; import { PLUGIN_SYSTEM_ENABLE_ALL_PLUGINS_CONFIG_PATH } from '@kbn/core-plugins-server-internal/src/constants'; -describe('checking migration metadata changes on all registered SO types', () => { +describe('PUT /internal/core/_settings', () => { + let esServer: TestElasticsearchUtils; + let root: Root; + + const loggerName = 'my-test-logger'; + + beforeAll(async () => { + const settings = { + coreApp: { allowDynamicConfigOverrides: true }, + logging: { + loggers: [{ name: loggerName, level: 'error', appenders: ['console'] }], + }, + }; + const { startES, startKibana } = createTestServers({ + adjustTimeout: (t: number) => jest.setTimeout(t), + settings: { + kbn: settings, + }, + }); + + esServer = await startES(); + + const kbnUtils = await startKibana(); + root = kbnUtils.root; + + // eslint-disable-next-line dot-notation + root['server'].configService.addDynamicConfigPaths('logging', ['loggers']); // just for the sake of being able to change something easy to test + }); + + afterAll(async () => { + await root?.shutdown(); + await esServer?.stop(); + }); + + test('should update the log level', async () => { + const logger = root.logger.get(loggerName); + expect(logger.isLevelEnabled('info')).toBe(false); + await request + .put(root, '/internal/core/_settings') + .set('Elastic-Api-Version', '1') + .send({ 'logging.loggers': [{ name: loggerName, level: 'debug', appenders: ['console'] }] }) + .expect(200); + expect(logger.isLevelEnabled('info')).toBe(true); + }); + + test('should remove the setting', async () => { + const logger = root.logger.get(loggerName); + expect(logger.isLevelEnabled('info')).toBe(true); // still true from the previous test + await request + .put(root, '/internal/core/_settings') + .set('Elastic-Api-Version', '1') + .send({ 'logging.loggers': null }) + .expect(200); + expect(logger.isLevelEnabled('info')).toBe(false); + }); +}); + +describe('checking all opted-in dynamic config settings', () => { let root: Root; beforeAll(async () => { diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/type_registrations.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/type_registrations.test.ts index eebeaf9673973..6235f95147639 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group3/type_registrations.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/type_registrations.test.ts @@ -47,6 +47,7 @@ const previouslyRegisteredTypes = [ 'csp-rule-template', 'csp_rule', 'dashboard', + 'dynamic-config-overrides', // Added in 8.16 to persist the dynamic config overrides and share it with other nodes 'event-annotation-group', 'endpoint:user-artifact', 'endpoint:user-artifact-manifest', diff --git a/x-pack/test_serverless/api_integration/test_suites/common/telemetry/telemetry_config.ts b/x-pack/test_serverless/api_integration/test_suites/common/telemetry/telemetry_config.ts index 2bf5eda659da4..6518dcd30f147 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/telemetry/telemetry_config.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/telemetry/telemetry_config.ts @@ -68,6 +68,21 @@ export default function telemetryConfigTest({ getService }: FtrProviderContext) journeyName: 'my-ftr-test', }, }); + + // Sends "null" to remove the label + await supertestWithoutAuth + .put('/internal/core/_settings') + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .set('elastic-api-version', '1') + .send({ 'telemetry.labels.journeyName': null }) + .expect(200, { ok: true }); + + await supertestWithoutAuth + .get('/api/telemetry/v2/config') + .set(svlCommonApi.getCommonRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .expect(200, initialConfig); }); }); } From 6edf6f1373b5ae9386ba0550f2d541265c046a56 Mon Sep 17 00:00:00 2001 From: Sander Philipse <94373878+sphilipse@users.noreply.github.com> Date: Thu, 8 Aug 2024 16:20:16 +0800 Subject: [PATCH 42/44] [Inference] Improve no inference ID error (#189598) ## Summary This improves the error message shown when an index has a semantic text field with an inference ID referencing a non-existing inference endpoint. --- .../public/application/hooks/use_index_errors.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/index_management/public/application/hooks/use_index_errors.ts b/x-pack/plugins/index_management/public/application/hooks/use_index_errors.ts index f6c7767b4e2d0..c808123b14a76 100644 --- a/x-pack/plugins/index_management/public/application/hooks/use_index_errors.ts +++ b/x-pack/plugins/index_management/public/application/hooks/use_index_errors.ts @@ -45,12 +45,15 @@ export const useIndexErrors = ( if (!model) { return { field, - error: i18n.translate('xpack.idxMgmt.indexOverview.indexErrors.missingModelError', { - defaultMessage: 'Model not found for inference endpoint {inferenceId}', - values: { - inferenceId: field.source.inference_id as string, - }, - }), + error: i18n.translate( + 'xpack.idxMgmt.indexOverview.indexErrors.missingInferenceEndpointError', + { + defaultMessage: 'Inference endpoint {inferenceId} not found', + values: { + inferenceId: field.source.inference_id as string, + }, + } + ), }; } if (isLocalModel(model)) { From 87bf9b230d8129ec1a5ad24fb61bbc7d3c3d79f1 Mon Sep 17 00:00:00 2001 From: Agustina Nahir Ruidiaz <61565784+agusruidiazgd@users.noreply.github.com> Date: Thu, 8 Aug 2024 10:40:23 +0200 Subject: [PATCH 43/44] [Security Solution] Add content to the new Data Ingestion Hub header (#189969) ## Summary Align the header styles with the latest design: Screenshot 2024-08-07 at 10 23 45 Screenshot 2024-08-07 at 10 24 53 ### 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 --- .../data_ingestion_hub_header/index.test.tsx | 77 ++++++++++++++++++ .../data_ingestion_hub_header/index.tsx | 56 ++++++++++++- .../landing_page/onboarding/helpers.ts | 2 + .../onboarding/images/dark_rocket.png | Bin 0 -> 27638 bytes .../landing_page/onboarding/images/rocket.png | Bin 0 -> 24547 bytes .../landing_page/onboarding/onboarding.tsx | 15 +++- .../data_ingestion_hub_header.styles.ts | 68 ++++++++++++++++ .../onboarding/styles/onboarding.styles.ts | 8 +- .../landing_page/onboarding/translations.ts | 14 ++++ 9 files changed, 232 insertions(+), 8 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/data_ingestion_hub_header/index.test.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/images/dark_rocket.png create mode 100644 x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/images/rocket.png create mode 100644 x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/styles/data_ingestion_hub_header.styles.ts diff --git a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/data_ingestion_hub_header/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/data_ingestion_hub_header/index.test.tsx new file mode 100644 index 0000000000000..e9a9c068b0926 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/data_ingestion_hub_header/index.test.tsx @@ -0,0 +1,77 @@ +/* + * 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 { DataIngestionHubHeader } from '.'; +import darkRocket from '../images/dark_rocket.png'; +import { useCurrentUser } from '../../../../lib/kibana'; + +jest.mock('../../../../lib/kibana', () => ({ + useCurrentUser: jest.fn(), + useEuiTheme: jest.fn(() => ({ euiTheme: { colorTheme: 'DARK' } })), +})); + +const mockUseCurrentUser = useCurrentUser as jest.Mock; + +describe('WelcomeHeaderComponent', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should render fullName when fullName is provided', () => { + const fullName = 'John Doe'; + mockUseCurrentUser.mockReturnValue({ fullName }); + const { getByText } = render(); + const titleElement = getByText(`Hi ${fullName}!`); + expect(titleElement).toBeInTheDocument(); + }); + + it('should render username when fullName is an empty string', () => { + const fullName = ''; + const username = 'jd'; + mockUseCurrentUser.mockReturnValue({ fullName, username }); + + const { getByText } = render(); + const titleElement = getByText(`Hi ${username}!`); + expect(titleElement).toBeInTheDocument(); + }); + + it('should render username when fullName is not provided', () => { + const username = 'jd'; + mockUseCurrentUser.mockReturnValue({ username }); + + const { getByText } = render(); + const titleElement = getByText(`Hi ${username}!`); + expect(titleElement).toBeInTheDocument(); + }); + + it('should not render the greeting message if both fullName and username are not available', () => { + mockUseCurrentUser.mockReturnValue({}); + + const { queryByTestId } = render(); + const greetings = queryByTestId('data-ingestion-hub-header-greetings'); + expect(greetings).not.toBeInTheDocument(); + }); + + it('should render subtitle', () => { + const { getByText } = render(); + const subtitleElement = getByText('Welcome to Elastic Security'); + expect(subtitleElement).toBeInTheDocument(); + }); + + it('should render description', () => { + const { getByText } = render(); + const descriptionElement = getByText('Follow these steps to set up your workspace.'); + expect(descriptionElement).toBeInTheDocument(); + }); + + it('should render the rocket dark image when the theme is DARK', () => { + const { queryByTestId } = render(); + const image = queryByTestId('data-ingestion-hub-header-image'); + expect(image).toHaveStyle({ backgroundImage: `url(${darkRocket})` }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/data_ingestion_hub_header/index.tsx b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/data_ingestion_hub_header/index.tsx index 02d7fb6a26c96..167029c5cc431 100644 --- a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/data_ingestion_hub_header/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/data_ingestion_hub_header/index.tsx @@ -6,9 +6,63 @@ */ import React from 'react'; +import classnames from 'classnames'; +import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle } from '@elastic/eui'; +import { + GET_STARTED_PAGE_TITLE, + GET_STARTED_DATA_INGESTION_HUB_DESCRIPTION, + GET_STARTED_DATA_INGESTION_HUB_SUBTITLE, +} from '../translations'; +import { useCurrentUser } from '../../../../lib/kibana'; +import { useDataIngestionHubHeaderStyles } from '../styles/data_ingestion_hub_header.styles'; const DataIngestionHubHeaderComponent: React.FC = () => { - return
      ; + const userName = useCurrentUser(); + + const { + headerContentStyles, + headerImageStyles, + headerTitleStyles, + headerSubtitleStyles, + headerDescriptionStyles, + } = useDataIngestionHubHeaderStyles(); + + // Full name could be null, user name should always exist + const name = userName?.fullName || userName?.username; + + const headerSubtitleClassNames = classnames('eui-displayBlock', headerSubtitleStyles); + const headerDescriptionClassNames = classnames('eui-displayBlock', headerDescriptionStyles); + + return ( + + + + {name && ( + + {GET_STARTED_PAGE_TITLE(name)} + + )} + +

      {GET_STARTED_DATA_INGESTION_HUB_SUBTITLE}

      + + + {GET_STARTED_DATA_INGESTION_HUB_DESCRIPTION} + +
      +
      + ); }; export const DataIngestionHubHeader = React.memo(DataIngestionHubHeaderComponent); diff --git a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/helpers.ts b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/helpers.ts index 6bf7ff9cced91..a99cdea4faf28 100644 --- a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/helpers.ts +++ b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/helpers.ts @@ -21,6 +21,8 @@ import { CreateProjectSteps, QuickStartSectionCardsId } from './types'; export const CONTENT_WIDTH = 1150; +export const IMG_HEADER_WIDTH = 128; + export const DEFAULT_FINISHED_STEPS: Partial> = { [QuickStartSectionCardsId.createFirstProject]: [CreateProjectSteps.createFirstProject], }; diff --git a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/images/dark_rocket.png b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/images/dark_rocket.png new file mode 100644 index 0000000000000000000000000000000000000000..a4d3ba73a8a02c116555c9a69ebb81acfade3c46 GIT binary patch literal 27638 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 literal 0 HcmV?d00001 diff --git a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/images/rocket.png b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/images/rocket.png new file mode 100644 index 0000000000000000000000000000000000000000..0d899f1c5e20118ae270108f61dd550dbc7a4f22 GIT binary patch literal 24547 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 Sw`x)V0000 = ({ productTypes?.find((product) => product.product_line === ProductLine.security)?.product_tier, [productTypes] ); - const { wrapperStyles, progressSectionStyles, stepsSectionStyles, bannerStyles } = + const { wrapperStyles, headerSectionStyles, progressSectionStyles, stepsSectionStyles } = useOnboardingStyles(); const { telemetry, storage } = useKibana().services; const onStepLinkClicked = useCallback( @@ -89,14 +89,23 @@ export const OnboardingComponent: React.FC = ({ [isDataIngestionHubEnabled, productTier] ); + const kibanaPageTemplateSectionStyles = useMemo( + () => (isDataIngestionHubEnabled ? headerSectionStyles : ''), + [headerSectionStyles, isDataIngestionHubEnabled] + ); + return (
      {useIsStillYear2024() && showAVCBanner && ( - + )} - + {renderDataIngestionHubHeader} { + const { euiTheme, colorMode } = useEuiTheme(); + + const headerBackgroundImage = useMemo( + () => (colorMode === COLOR_MODES_STANDARD.dark ? darkRocket : rocket), + [colorMode] + ); + + const dataIngestionHubHeaderStyles = useMemo(() => { + return { + headerImageStyles: css({ + backgroundImage: `url(${headerBackgroundImage})`, + backgroundSize: 'contain', + backgroundRepeat: 'no-repeat', + backgroundPositionX: 'center', + backgroundPositionY: 'center', + width: `${IMG_HEADER_WIDTH}px`, + height: `${IMG_HEADER_WIDTH}px`, + }), + headerTitleStyles: css({ + fontSize: `${euiTheme.base}px`, + color: euiTheme.colors.darkShade, + fontWeight: euiTheme.font.weight.bold, + lineHeight: euiTheme.size.l, + }), + headerSubtitleStyles: css({ + fontSize: `${euiTheme.base * 2.125}px`, + color: euiTheme.colors.title, + fontWeight: euiTheme.font.weight.bold, + lineHeight: euiTheme.size.xxl, + }), + headerDescriptionStyles: css({ + fontSize: `${euiTheme.base}px`, + color: euiTheme.colors.subduedText, + lineHeight: euiTheme.size.l, + fontWeight: euiTheme.font.weight.regular, + }), + headerContentStyles: css({ + width: `${CONTENT_WIDTH / 2}px`, + }), + }; + }, [ + euiTheme.base, + euiTheme.colors.darkShade, + euiTheme.colors.subduedText, + euiTheme.colors.title, + euiTheme.font.weight.bold, + euiTheme.font.weight.regular, + euiTheme.size.l, + euiTheme.size.xxl, + headerBackgroundImage, + ]); + return dataIngestionHubHeaderStyles; +}; diff --git a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/styles/onboarding.styles.ts b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/styles/onboarding.styles.ts index 4f2eb59f06bf2..a16b736dd7efd 100644 --- a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/styles/onboarding.styles.ts +++ b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/styles/onboarding.styles.ts @@ -15,7 +15,10 @@ export const useOnboardingStyles = () => { return useMemo( () => ({ wrapperStyles: css({ - margin: `0 -${euiTheme.size.l}`, + margin: `-${euiTheme.size.l} -${euiTheme.size.l}`, + }), + headerSectionStyles: css({ + backgroundColor: euiTheme.colors.lightestShade, }), progressSectionStyles: css({ backgroundColor: euiTheme.colors.lightestShade, @@ -25,9 +28,6 @@ export const useOnboardingStyles = () => { padding: `0 ${euiTheme.size.xxl} ${euiTheme.size.xxxl}`, backgroundColor: euiTheme.colors.lightestShade, }), - bannerStyles: css({ - margin: `-${euiTheme.size.l} 0`, - }), }), [ euiTheme.colors.lightestShade, diff --git a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/translations.ts b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/translations.ts index bad766fc41a4d..27471327b72fe 100644 --- a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/translations.ts +++ b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/translations.ts @@ -27,6 +27,20 @@ export const GET_STARTED_PAGE_DESCRIPTION = i18n.translate( } ); +export const GET_STARTED_DATA_INGESTION_HUB_SUBTITLE = i18n.translate( + 'xpack.securitySolution.onboarding.subTitle', + { + defaultMessage: `Welcome to Elastic Security`, + } +); + +export const GET_STARTED_DATA_INGESTION_HUB_DESCRIPTION = i18n.translate( + 'xpack.securitySolution.onboarding.description', + { + defaultMessage: `Follow these steps to set up your workspace.`, + } +); + export const CURRENT_PLAN_LABEL = i18n.translate( 'xpack.securitySolution.onboarding.currentPlan.label', { From 1b85455d27fb0c94099b174da0b7e4304bd1961c Mon Sep 17 00:00:00 2001 From: Milton Hultgren Date: Thu, 8 Aug 2024 11:08:13 +0200 Subject: [PATCH 44/44] [EEM] Add entities aliases (#190055) ## Summary When an entity definition is installed and the transforms write the first documents to the `.entities-*` indices, the index templates applied also set up an alias like `entities-{type}-latest` to make it easier to query data by entity type. ## How to test Ingest some data using data forge, install a definition of a given type, try to query for the data via the new alias. ## Open question Do we need to do anything related to users/roles/privileges for the entities data, to make it easier for admins to create their users with the right access? The built in `viewer` role has read access to all indices and it seems trivial to create a new role that limits that down to `entities-*`. --- .../create_assets_es_clients.ts | 8 +++++--- .../lib/entities/install_entity_definition.ts | 4 ++-- .../entities_history_template.test.ts.snap | 3 +++ .../entities_latest_template.test.ts.snap | 3 +++ .../templates/entities_history_template.test.ts | 2 +- .../templates/entities_history_template.ts | 16 +++++++++++----- .../templates/entities_latest_template.test.ts | 2 +- .../templates/entities_latest_template.ts | 16 +++++++++++----- 8 files changed, 37 insertions(+), 17 deletions(-) diff --git a/x-pack/plugins/observability_solution/apm/server/lib/helpers/create_es_client/create_assets_es_client/create_assets_es_clients.ts b/x-pack/plugins/observability_solution/apm/server/lib/helpers/create_es_client/create_assets_es_client/create_assets_es_clients.ts index 17193a6d508b6..4ddd64fd0a3bc 100644 --- a/x-pack/plugins/observability_solution/apm/server/lib/helpers/create_es_client/create_assets_es_client/create_assets_es_clients.ts +++ b/x-pack/plugins/observability_solution/apm/server/lib/helpers/create_es_client/create_assets_es_client/create_assets_es_clients.ts @@ -13,9 +13,10 @@ import { MsearchMultisearchHeader, } from '@elastic/elasticsearch/lib/api/types'; import { withApmSpan } from '../../../../utils/with_apm_span'; +import { EntityType } from '../../../../routes/entities/types'; -const ENTITIES_LATEST_INDEX_NAME = '.entities.v1.latest.builtin_services*'; -const ENTITIES_HISTORY_INDEX_NAME = '.entities.v1.history.builtin_services*'; +const ENTITIES_LATEST_INDEX_NAME = `entities-${EntityType.SERVICE}-latest`; +const ENTITIES_HISTORY_INDEX_NAME = `entities-${EntityType.SERVICE}-history`; export function cancelEsRequestOnAbort>( promise: T, @@ -60,7 +61,7 @@ export async function createEntitiesESClient({ const promise = withApmSpan(operationName, () => { return cancelEsRequestOnAbort( esClient.search( - { ...searchRequest, index: [indexName] }, + { ...searchRequest, index: [indexName], ignore_unavailable: true }, { signal: controller.signal, meta: true, @@ -99,6 +100,7 @@ export async function createEntitiesESClient({ const searchParams: [MsearchMultisearchHeader, MsearchMultisearchBody] = [ { index: [ENTITIES_LATEST_INDEX_NAME], + ignore_unavailable: true, }, { ...params.body, diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/install_entity_definition.ts b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/install_entity_definition.ts index a58019bf236ae..5c3c954b45333 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/install_entity_definition.ts +++ b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/install_entity_definition.ts @@ -81,13 +81,13 @@ export async function installEntityDefinition({ await upsertTemplate({ esClient, logger, - template: getEntitiesHistoryIndexTemplateConfig(definition.id), + template: getEntitiesHistoryIndexTemplateConfig(definition), }); installState.indexTemplates.history = true; await upsertTemplate({ esClient, logger, - template: getEntitiesLatestIndexTemplateConfig(definition.id), + template: getEntitiesLatestIndexTemplateConfig(definition), }); installState.indexTemplates.latest = true; diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/__snapshots__/entities_history_template.test.ts.snap b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/__snapshots__/entities_history_template.test.ts.snap index 94af9f3307f04..464293cc1bca8 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/__snapshots__/entities_history_template.test.ts.snap +++ b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/__snapshots__/entities_history_template.test.ts.snap @@ -29,6 +29,9 @@ Object { "name": "entities_v1_history_admin-console-services_index_template", "priority": 200, "template": Object { + "aliases": Object { + "entities-service-history": Object {}, + }, "mappings": Object { "_meta": Object { "version": "1.6.0", diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/__snapshots__/entities_latest_template.test.ts.snap b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/__snapshots__/entities_latest_template.test.ts.snap index b4247098d9498..448286b16d84a 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/__snapshots__/entities_latest_template.test.ts.snap +++ b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/__snapshots__/entities_latest_template.test.ts.snap @@ -29,6 +29,9 @@ Object { "name": "entities_v1_latest_admin-console-services_index_template", "priority": 200, "template": Object { + "aliases": Object { + "entities-service-latest": Object {}, + }, "mappings": Object { "_meta": Object { "version": "1.6.0", diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/entities_history_template.test.ts b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/entities_history_template.test.ts index 11aad78741020..055ebe75cd608 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/entities_history_template.test.ts +++ b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/entities_history_template.test.ts @@ -10,7 +10,7 @@ import { getEntitiesHistoryIndexTemplateConfig } from './entities_history_templa describe('getEntitiesHistoryIndexTemplateConfig(definitionId)', () => { it('should generate a valid index template', () => { - const template = getEntitiesHistoryIndexTemplateConfig(entityDefinition.id); + const template = getEntitiesHistoryIndexTemplateConfig(entityDefinition); expect(template).toMatchSnapshot(); }); }); diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/entities_history_template.ts b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/entities_history_template.ts index a0fb4b032a6e1..03f3a3510f627 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/entities_history_template.ts +++ b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/entities_history_template.ts @@ -6,19 +6,22 @@ */ import { IndicesPutIndexTemplateRequest } from '@elastic/elasticsearch/lib/api/types'; +import { EntityDefinition } from '@kbn/entities-schema'; import { getEntityHistoryIndexTemplateV1 } from '../../../../common/helpers'; import { + ENTITY_BASE_PREFIX, ENTITY_ENTITY_COMPONENT_TEMPLATE_V1, ENTITY_EVENT_COMPONENT_TEMPLATE_V1, + ENTITY_HISTORY, ENTITY_HISTORY_BASE_COMPONENT_TEMPLATE_V1, ENTITY_HISTORY_INDEX_PREFIX_V1, } from '../../../../common/constants_entities'; import { getCustomHistoryTemplateComponents } from '../../../templates/components/helpers'; export const getEntitiesHistoryIndexTemplateConfig = ( - definitionId: string + definition: EntityDefinition ): IndicesPutIndexTemplateRequest => ({ - name: getEntityHistoryIndexTemplateV1(definitionId), + name: getEntityHistoryIndexTemplateV1(definition.id), _meta: { description: "Index template for indices managed by the Elastic Entity Model's entity discovery framework for the history dataset", @@ -26,16 +29,19 @@ export const getEntitiesHistoryIndexTemplateConfig = ( managed: true, managed_by: 'elastic_entity_model', }, - ignore_missing_component_templates: getCustomHistoryTemplateComponents(definitionId), + ignore_missing_component_templates: getCustomHistoryTemplateComponents(definition.id), composed_of: [ ENTITY_HISTORY_BASE_COMPONENT_TEMPLATE_V1, ENTITY_ENTITY_COMPONENT_TEMPLATE_V1, ENTITY_EVENT_COMPONENT_TEMPLATE_V1, - ...getCustomHistoryTemplateComponents(definitionId), + ...getCustomHistoryTemplateComponents(definition.id), ], - index_patterns: [`${ENTITY_HISTORY_INDEX_PREFIX_V1}.${definitionId}.*`], + index_patterns: [`${ENTITY_HISTORY_INDEX_PREFIX_V1}.${definition.id}.*`], priority: 200, template: { + aliases: { + [`${ENTITY_BASE_PREFIX}-${definition.type}-${ENTITY_HISTORY}`]: {}, + }, mappings: { _meta: { version: '1.6.0', diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/entities_latest_template.test.ts b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/entities_latest_template.test.ts index 72583d941492c..f1012d0bcb7f7 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/entities_latest_template.test.ts +++ b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/entities_latest_template.test.ts @@ -10,7 +10,7 @@ import { getEntitiesLatestIndexTemplateConfig } from './entities_latest_template describe('getEntitiesLatestIndexTemplateConfig(definitionId)', () => { it('should generate a valid index template', () => { - const template = getEntitiesLatestIndexTemplateConfig(entityDefinition.id); + const template = getEntitiesLatestIndexTemplateConfig(entityDefinition); expect(template).toMatchSnapshot(); }); }); diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/entities_latest_template.ts b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/entities_latest_template.ts index 466346f86b44d..7b0093bb4b83f 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/entities_latest_template.ts +++ b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/entities_latest_template.ts @@ -6,19 +6,22 @@ */ import { IndicesPutIndexTemplateRequest } from '@elastic/elasticsearch/lib/api/types'; +import { EntityDefinition } from '@kbn/entities-schema'; import { getEntityLatestIndexTemplateV1 } from '../../../../common/helpers'; import { + ENTITY_BASE_PREFIX, ENTITY_ENTITY_COMPONENT_TEMPLATE_V1, ENTITY_EVENT_COMPONENT_TEMPLATE_V1, + ENTITY_LATEST, ENTITY_LATEST_BASE_COMPONENT_TEMPLATE_V1, ENTITY_LATEST_INDEX_PREFIX_V1, } from '../../../../common/constants_entities'; import { getCustomLatestTemplateComponents } from '../../../templates/components/helpers'; export const getEntitiesLatestIndexTemplateConfig = ( - definitionId: string + definition: EntityDefinition ): IndicesPutIndexTemplateRequest => ({ - name: getEntityLatestIndexTemplateV1(definitionId), + name: getEntityLatestIndexTemplateV1(definition.id), _meta: { description: "Index template for indices managed by the Elastic Entity Model's entity discovery framework for the latest dataset", @@ -26,16 +29,19 @@ export const getEntitiesLatestIndexTemplateConfig = ( managed: true, managed_by: 'elastic_entity_model', }, - ignore_missing_component_templates: getCustomLatestTemplateComponents(definitionId), + ignore_missing_component_templates: getCustomLatestTemplateComponents(definition.id), composed_of: [ ENTITY_LATEST_BASE_COMPONENT_TEMPLATE_V1, ENTITY_ENTITY_COMPONENT_TEMPLATE_V1, ENTITY_EVENT_COMPONENT_TEMPLATE_V1, - ...getCustomLatestTemplateComponents(definitionId), + ...getCustomLatestTemplateComponents(definition.id), ], - index_patterns: [`${ENTITY_LATEST_INDEX_PREFIX_V1}.${definitionId}`], + index_patterns: [`${ENTITY_LATEST_INDEX_PREFIX_V1}.${definition.id}`], priority: 200, template: { + aliases: { + [`${ENTITY_BASE_PREFIX}-${definition.type}-${ENTITY_LATEST}`]: {}, + }, mappings: { _meta: { version: '1.6.0',