From 29a10fddc9af9246f6a329a9aa2018b0a907505d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loix?= Date: Mon, 24 Apr 2023 16:45:41 +0100 Subject: [PATCH 01/52] [Files] Allow option to disable delete action in mgt UI (#155179) --- .../content-management/table_list/index.ts | 2 +- .../table_list/src/components/table.tsx | 33 +++++- .../table_list/src/index.ts | 2 + .../table_list/src/table_list_view.test.tsx | 105 ++++++++++++++++++ .../table_list/src/table_list_view.tsx | 35 +++++- .../table_list/src/types.ts | 13 +++ .../shared-ux/file/types/base_file_client.ts | 1 + packages/shared-ux/file/types/index.ts | 16 +++ src/plugins/files/public/plugin.ts | 11 +- .../adapters/query_filters.ts | 16 +++ .../server/file_service/file_action_types.ts | 4 + .../integration_tests/file_service.test.ts | 21 +++- src/plugins/files/server/routes/find.ts | 4 +- src/plugins/files_management/public/app.tsx | 28 ++++- .../files_management/public/context.tsx | 11 +- .../files_management/public/i18n_texts.ts | 3 + .../public/mount_management_section.tsx | 8 +- src/plugins/files_management/public/types.ts | 2 + 18 files changed, 294 insertions(+), 21 deletions(-) diff --git a/packages/content-management/table_list/index.ts b/packages/content-management/table_list/index.ts index 532b35450d541..9a608b2d6dda3 100644 --- a/packages/content-management/table_list/index.ts +++ b/packages/content-management/table_list/index.ts @@ -8,5 +8,5 @@ export { TableListView, TableListViewProvider, TableListViewKibanaProvider } from './src'; -export type { UserContentCommonSchema } from './src'; +export type { UserContentCommonSchema, RowActions } from './src'; export type { TableListViewKibanaDependencies } from './src/services'; diff --git a/packages/content-management/table_list/src/components/table.tsx b/packages/content-management/table_list/src/components/table.tsx index 330eb67be4278..3214e7bf00a72 100644 --- a/packages/content-management/table_list/src/components/table.tsx +++ b/packages/content-management/table_list/src/components/table.tsx @@ -17,7 +17,9 @@ import { SearchFilterConfig, Direction, Query, + type EuiTableSelectionType, } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { useServices } from '../services'; import type { Action } from '../actions'; @@ -26,6 +28,7 @@ import type { Props as TableListViewProps, UserContentCommonSchema, } from '../table_list_view'; +import type { TableItemsRowActions } from '../types'; import { TableSortSelect } from './table_sort_select'; import { TagFilterPanel } from './tag_filter_panel'; import { useTagFilterPanel } from './use_tag_filter_panel'; @@ -51,6 +54,7 @@ interface Props extends State, TagManageme tableColumns: Array>; hasUpdatedAtMetadata: boolean; deleteItems: TableListViewProps['deleteItems']; + tableItemsRowActions: TableItemsRowActions; onSortChange: (column: SortColumnField, direction: Direction) => void; onTableChange: (criteria: CriteriaWithPagination) => void; onTableSearchChange: (arg: { query: Query | null; queryText: string }) => void; @@ -70,6 +74,7 @@ export function Table({ entityName, entityNamePlural, tagsToTableItemMap, + tableItemsRowActions, deleteItems, tableCaption, onTableChange, @@ -105,13 +110,32 @@ export function Table({ ); }, [deleteItems, dispatch, entityName, entityNamePlural, selectedIds.length]); - const selection = deleteItems - ? { + const selection = useMemo | undefined>(() => { + if (deleteItems) { + return { onSelectionChange: (obj: T[]) => { dispatch({ type: 'onSelectionChange', data: obj }); }, - } - : undefined; + selectable: (obj) => { + const actions = tableItemsRowActions[obj.id]; + return actions?.delete?.enabled !== false; + }, + selectableMessage: (selectable, obj) => { + if (!selectable) { + const actions = tableItemsRowActions[obj.id]; + return ( + actions?.delete?.reason ?? + i18n.translate('contentManagement.tableList.actionsDisabledLabel', { + defaultMessage: 'Actions disabled for this item', + }) + ); + } + return ''; + }, + initialSelected: [], + }; + } + }, [deleteItems, dispatch, tableItemsRowActions]); const { isPopoverOpen, @@ -214,6 +238,7 @@ export function Table({ data-test-subj="itemsInMemTable" rowHeader="attributes.title" tableCaption={tableCaption} + isSelectable /> ); } diff --git a/packages/content-management/table_list/src/index.ts b/packages/content-management/table_list/src/index.ts index df0d1e22bc106..d1e83d7dd2e93 100644 --- a/packages/content-management/table_list/src/index.ts +++ b/packages/content-management/table_list/src/index.ts @@ -15,3 +15,5 @@ export type { } from './table_list_view'; export { TableListViewProvider, TableListViewKibanaProvider } from './services'; + +export type { RowActions } from './types'; diff --git a/packages/content-management/table_list/src/table_list_view.test.tsx b/packages/content-management/table_list/src/table_list_view.test.tsx index 62c83fb5b9454..0245af450fb8a 100644 --- a/packages/content-management/table_list/src/table_list_view.test.tsx +++ b/packages/content-management/table_list/src/table_list_view.test.tsx @@ -1067,4 +1067,109 @@ describe('TableListView', () => { expect(router?.history.location?.search).toBe('?sort=title&sortdir=desc'); }); }); + + describe('row item actions', () => { + const hits: UserContentCommonSchema[] = [ + { + id: '123', + updatedAt: twoDaysAgo.toISOString(), + type: 'dashboard', + attributes: { + title: 'Item 1', + description: 'Item 1 description', + }, + references: [], + }, + { + id: '456', + updatedAt: yesterday.toISOString(), + type: 'dashboard', + attributes: { + title: 'Item 2', + description: 'Item 2 description', + }, + references: [], + }, + ]; + + const setupTest = async (props?: Partial) => { + let testBed: TestBed | undefined; + const deleteItems = jest.fn(); + await act(async () => { + testBed = await setup({ + findItems: jest.fn().mockResolvedValue({ total: hits.length, hits }), + deleteItems, + ...props, + }); + }); + + testBed!.component.update(); + return { testBed: testBed!, deleteItems }; + }; + + test('should allow select items to be deleted', async () => { + const { + testBed: { table, find, exists, component, form }, + deleteItems, + } = await setupTest(); + + const { tableCellsValues } = table.getMetaData('itemsInMemTable'); + + expect(tableCellsValues).toEqual([ + ['', 'Item 2Item 2 description', yesterdayToString], // First empty col is the "checkbox" + ['', 'Item 1Item 1 description', twoDaysAgoToString], + ]); + + const selectedHit = hits[1]; + + expect(exists('deleteSelectedItems')).toBe(false); + act(() => { + // Select the second item + form.selectCheckBox(`checkboxSelectRow-${selectedHit.id}`); + }); + component.update(); + // Delete button is now visible + expect(exists('deleteSelectedItems')).toBe(true); + + // Click delete and validate that confirm modal opens + expect(component.exists('.euiModal--confirmation')).toBe(false); + act(() => { + find('deleteSelectedItems').simulate('click'); + }); + component.update(); + expect(component.exists('.euiModal--confirmation')).toBe(true); + + await act(async () => { + find('confirmModalConfirmButton').simulate('click'); + }); + expect(deleteItems).toHaveBeenCalledWith([selectedHit]); + }); + + test('should allow to disable the "delete" action for a row', async () => { + const reasonMessage = 'This file cannot be deleted.'; + + const { + testBed: { find }, + } = await setupTest({ + rowItemActions: (obj) => { + if (obj.id === hits[1].id) { + return { + delete: { + enabled: false, + reason: reasonMessage, + }, + }; + } + }, + }); + + const firstCheckBox = find(`checkboxSelectRow-${hits[0].id}`); + const secondCheckBox = find(`checkboxSelectRow-${hits[1].id}`); + + expect(firstCheckBox.props().disabled).toBe(false); + expect(secondCheckBox.props().disabled).toBe(true); + // EUI changes the check "title" from "Select this row" to the reason to disable the checkbox + expect(secondCheckBox.props().title).toBe(reasonMessage); + }); + }); }); diff --git a/packages/content-management/table_list/src/table_list_view.tsx b/packages/content-management/table_list/src/table_list_view.tsx index 1612649f80bda..2191a3c9b7eee 100644 --- a/packages/content-management/table_list/src/table_list_view.tsx +++ b/packages/content-management/table_list/src/table_list_view.tsx @@ -42,6 +42,7 @@ import { getReducer } from './reducer'; import type { SortColumnField } from './components'; import { useTags } from './use_tags'; import { useInRouterContext, useUrlState } from './use_url_state'; +import { RowActions, TableItemsRowActions } from './types'; interface ContentEditorConfig extends Pick { @@ -67,6 +68,11 @@ export interface Props RowActions | undefined; children?: ReactNode | undefined; findItems( searchQuery: string, @@ -241,6 +247,7 @@ function TableListViewComp({ urlStateEnabled = true, customTableColumn, emptyPrompt, + rowItemActions, findItems, createItem, editItem, @@ -580,6 +587,15 @@ function TableListViewComp({ return selectedIds.map((selectedId) => itemsById[selectedId]); }, [selectedIds, itemsById]); + const tableItemsRowActions = useMemo(() => { + return items.reduce((acc, item) => { + return { + ...acc, + [item.id]: rowItemActions ? rowItemActions(item) : undefined, + }; + }, {}); + }, [items, rowItemActions]); + // ------------ // Callbacks // ------------ @@ -854,6 +870,20 @@ function TableListViewComp({ }; }, []); + const PageTemplate = useMemo(() => { + return withoutPageTemplateWrapper + ? ((({ + children: _children, + 'data-test-subj': dataTestSubj, + }: { + children: React.ReactNode; + ['data-test-subj']?: string; + }) => ( +
{_children}
+ )) as unknown as typeof KibanaPageTemplate) + : KibanaPageTemplate; + }, [withoutPageTemplateWrapper]); + // ------------ // Render // ------------ @@ -861,10 +891,6 @@ function TableListViewComp({ return null; } - const PageTemplate = withoutPageTemplateWrapper - ? (React.Fragment as unknown as typeof KibanaPageTemplate) - : KibanaPageTemplate; - if (!showFetchError && hasNoItems) { return ( @@ -929,6 +955,7 @@ function TableListViewComp({ tagsToTableItemMap={tagsToTableItemMap} deleteItems={deleteItems} tableCaption={tableListTitle} + tableItemsRowActions={tableItemsRowActions} onTableChange={onTableChange} onTableSearchChange={onTableSearchChange} onSortChange={onSortChange} diff --git a/packages/content-management/table_list/src/types.ts b/packages/content-management/table_list/src/types.ts index 0e716e6d59cf3..c8e734a289451 100644 --- a/packages/content-management/table_list/src/types.ts +++ b/packages/content-management/table_list/src/types.ts @@ -12,3 +12,16 @@ export interface Tag { description: string; color: string; } + +export type TableRowAction = 'delete'; + +export type RowActions = { + [action in TableRowAction]?: { + enabled: boolean; + reason?: string; + }; +}; + +export interface TableItemsRowActions { + [id: string]: RowActions | undefined; +} diff --git a/packages/shared-ux/file/types/base_file_client.ts b/packages/shared-ux/file/types/base_file_client.ts index 4a00f2de00516..52d1ca09fd170 100644 --- a/packages/shared-ux/file/types/base_file_client.ts +++ b/packages/shared-ux/file/types/base_file_client.ts @@ -27,6 +27,7 @@ export interface BaseFilesClient { find: ( args: { kind?: string | string[]; + kindToExclude?: string | string[]; status?: string | string[]; extension?: string | string[]; name?: string | string[]; diff --git a/packages/shared-ux/file/types/index.ts b/packages/shared-ux/file/types/index.ts index 4c49124f7149f..86b9e47fdab43 100644 --- a/packages/shared-ux/file/types/index.ts +++ b/packages/shared-ux/file/types/index.ts @@ -250,6 +250,22 @@ export interface FileKindBrowser extends FileKindBase { * @default 4MiB */ maxSizeBytes?: number; + /** + * Allowed actions that can be done in the File Management UI. If not provided, all actions are allowed + * + */ + managementUiActions?: { + /** Allow files to be listed in management UI */ + list?: { + enabled: boolean; + }; + /** Allow files to be deleted in management UI */ + delete?: { + enabled: boolean; + /** If delete is not enabled in management UI, specify the reason (will appear in a tooltip). */ + reason?: string; + }; + }; } /** diff --git a/src/plugins/files/public/plugin.ts b/src/plugins/files/public/plugin.ts index 54646e9199f9a..13828d0ee366c 100644 --- a/src/plugins/files/public/plugin.ts +++ b/src/plugins/files/public/plugin.ts @@ -35,7 +35,10 @@ export interface FilesSetup { registerFileKind(fileKind: FileKindBrowser): void; } -export type FilesStart = Pick; +export type FilesStart = Pick & { + getFileKindDefinition: (id: string) => FileKindBrowser; + getAllFindKindDefinitions: () => FileKindBrowser[]; +}; /** * Bringing files to Kibana @@ -77,6 +80,12 @@ export class FilesPlugin implements Plugin { start(core: CoreStart): FilesStart { return { filesClientFactory: this.filesClientFactory!, + getFileKindDefinition: (id: string): FileKindBrowser => { + return this.registry.get(id); + }, + getAllFindKindDefinitions: (): FileKindBrowser[] => { + return this.registry.getAll(); + }, }; } } diff --git a/src/plugins/files/server/file_client/file_metadata_client/adapters/query_filters.ts b/src/plugins/files/server/file_client/file_metadata_client/adapters/query_filters.ts index 0f453a1b81e6a..014e57b41d2b1 100644 --- a/src/plugins/files/server/file_client/file_metadata_client/adapters/query_filters.ts +++ b/src/plugins/files/server/file_client/file_metadata_client/adapters/query_filters.ts @@ -24,6 +24,7 @@ export function filterArgsToKuery({ extension, mimeType, kind, + kindToExclude, meta, name, status, @@ -50,12 +51,27 @@ export function filterArgsToKuery({ } }; + const addExcludeFilters = (fieldName: keyof FileMetadata | string, values: string[] = []) => { + if (values.length) { + const andExpressions = values + .filter(Boolean) + .map((value) => + nodeTypes.function.buildNode( + 'not', + nodeBuilder.is(`${attrPrefix}.${fieldName}`, escapeKuery(value)) + ) + ); + kueryExpressions.push(nodeBuilder.and(andExpressions)); + } + }; + addFilters('name', name, true); addFilters('FileKind', kind); addFilters('Status', status); addFilters('extension', extension); addFilters('mime_type', mimeType); addFilters('user.id', user); + addExcludeFilters('FileKind', kindToExclude); if (meta) { const addMetaFilters = pipe( diff --git a/src/plugins/files/server/file_service/file_action_types.ts b/src/plugins/files/server/file_service/file_action_types.ts index 4247f567802ed..96795ac93b387 100644 --- a/src/plugins/files/server/file_service/file_action_types.ts +++ b/src/plugins/files/server/file_service/file_action_types.ts @@ -82,6 +82,10 @@ export interface FindFileArgs extends Pagination { * File kind(s), see {@link FileKind}. */ kind?: string[]; + /** + * File kind(s) to exclude from search, see {@link FileKind}. + */ + kindToExclude?: string[]; /** * File name(s). */ diff --git a/src/plugins/files/server/integration_tests/file_service.test.ts b/src/plugins/files/server/integration_tests/file_service.test.ts index 25d7f463de03a..3492eb8e5f12c 100644 --- a/src/plugins/files/server/integration_tests/file_service.test.ts +++ b/src/plugins/files/server/integration_tests/file_service.test.ts @@ -157,26 +157,39 @@ describe('FileService', () => { createDisposableFile({ fileKind, name: 'foo-2' }), createDisposableFile({ fileKind, name: 'foo-3' }), createDisposableFile({ fileKind, name: 'test-3' }), + createDisposableFile({ fileKind: fileKindNonDefault, name: 'foo-1' }), ]); { const { files, total } = await fileService.find({ - kind: [fileKind], + kind: [fileKind, fileKindNonDefault], name: ['foo*'], perPage: 2, page: 1, }); expect(files.length).toBe(2); - expect(total).toBe(3); + expect(total).toBe(4); } { const { files, total } = await fileService.find({ - kind: [fileKind], + kind: [fileKind, fileKindNonDefault], name: ['foo*'], perPage: 2, page: 2, }); - expect(files.length).toBe(1); + expect(files.length).toBe(2); + expect(total).toBe(4); + } + + // Filter out fileKind + { + const { files, total } = await fileService.find({ + kindToExclude: [fileKindNonDefault], + name: ['foo*'], + perPage: 10, + page: 1, + }); + expect(files.length).toBe(3); // foo-1 from fileKindNonDefault not returned expect(total).toBe(3); } }); diff --git a/src/plugins/files/server/routes/find.ts b/src/plugins/files/server/routes/find.ts index a81a9d2ea5220..6749e06254100 100644 --- a/src/plugins/files/server/routes/find.ts +++ b/src/plugins/files/server/routes/find.ts @@ -30,6 +30,7 @@ export function toArrayOrUndefined(val?: string | string[]): undefined | string[ const rt = { body: schema.object({ kind: schema.maybe(stringOrArrayOfStrings), + kindToExclude: schema.maybe(stringOrArrayOfStrings), status: schema.maybe(stringOrArrayOfStrings), extension: schema.maybe(stringOrArrayOfStrings), name: schema.maybe(nameStringOrArrayOfNameStrings), @@ -50,12 +51,13 @@ export type Endpoint = CreateRouteDefinition< const handler: CreateHandler = async ({ files }, req, res) => { const { fileService } = await files; const { - body: { meta, extension, kind, name, status }, + body: { meta, extension, kind, name, status, kindToExclude }, query, } = req; const { files: results, total } = await fileService.asCurrentUser().find({ kind: toArrayOrUndefined(kind), + kindToExclude: toArrayOrUndefined(kindToExclude), name: toArrayOrUndefined(name), status: toArrayOrUndefined(status), extension: toArrayOrUndefined(extension), diff --git a/src/plugins/files_management/public/app.tsx b/src/plugins/files_management/public/app.tsx index becdd05fa0e2c..3ee4e5f52720c 100644 --- a/src/plugins/files_management/public/app.tsx +++ b/src/plugins/files_management/public/app.tsx @@ -12,20 +12,33 @@ import { EuiButtonEmpty } from '@elastic/eui'; import { TableListView, UserContentCommonSchema } from '@kbn/content-management-table-list'; import numeral from '@elastic/numeral'; import type { FileJSON } from '@kbn/files-plugin/common'; + import { useFilesManagementContext } from './context'; import { i18nTexts } from './i18n_texts'; import { EmptyPrompt, DiagnosticsFlyout, FileFlyout } from './components'; -type FilesUserContentSchema = UserContentCommonSchema; +type FilesUserContentSchema = Omit & { + attributes: { + title: string; + description?: string; + fileKind: string; + }; +}; function naivelyFuzzify(query: string): string { return query.includes('*') ? query : `*${query}*`; } export const App: FunctionComponent = () => { - const { filesClient } = useFilesManagementContext(); + const { filesClient, getFileKindDefinition, getAllFindKindDefinitions } = + useFilesManagementContext(); const [showDiagnosticsFlyout, setShowDiagnosticsFlyout] = useState(false); const [selectedFile, setSelectedFile] = useState(undefined); + + const kindToExcludeFromSearch = getAllFindKindDefinitions() + .filter(({ managementUiActions }) => managementUiActions?.list?.enabled === false) + .map(({ id }) => id); + return (
@@ -37,7 +50,10 @@ export const App: FunctionComponent = () => { entityNamePlural={i18nTexts.entityNamePlural} findItems={(searchQuery) => filesClient - .find({ name: searchQuery ? naivelyFuzzify(searchQuery) : undefined }) + .find({ + name: searchQuery ? naivelyFuzzify(searchQuery) : undefined, + kindToExclude: kindToExcludeFromSearch, + }) .then(({ files, total }) => ({ hits: files.map((file) => ({ id: file.id, @@ -71,6 +87,12 @@ export const App: FunctionComponent = () => { {i18nTexts.diagnosticsFlyoutTitle} , ]} + rowItemActions={({ attributes }) => { + const definition = getFileKindDefinition(attributes.fileKind); + return { + delete: definition?.managementUiActions?.delete, + }; + }} /> {showDiagnosticsFlyout && ( setShowDiagnosticsFlyout(false)} /> diff --git a/src/plugins/files_management/public/context.tsx b/src/plugins/files_management/public/context.tsx index 18f031b84e5c1..0688c5a7edecb 100644 --- a/src/plugins/files_management/public/context.tsx +++ b/src/plugins/files_management/public/context.tsx @@ -12,9 +12,16 @@ import type { AppContext } from './types'; const FilesManagementAppContext = createContext(null as unknown as AppContext); -export const FilesManagementAppContextProvider: FC = ({ children, filesClient }) => { +export const FilesManagementAppContextProvider: FC = ({ + children, + filesClient, + getFileKindDefinition, + getAllFindKindDefinitions, +}) => { return ( - + {children} ); diff --git a/src/plugins/files_management/public/i18n_texts.ts b/src/plugins/files_management/public/i18n_texts.ts index c5f4956af372f..d430038dcdddc 100644 --- a/src/plugins/files_management/public/i18n_texts.ts +++ b/src/plugins/files_management/public/i18n_texts.ts @@ -101,4 +101,7 @@ export const i18nTexts = { defaultMessage: 'Upload error', }), } as Record, + rowCheckboxDisabled: i18n.translate('filesManagement.table.checkBoxDisabledLabel', { + defaultMessage: 'This file cannot be deleted.', + }), }; diff --git a/src/plugins/files_management/public/mount_management_section.tsx b/src/plugins/files_management/public/mount_management_section.tsx index 7dce1986237a7..9c7091516d46e 100755 --- a/src/plugins/files_management/public/mount_management_section.tsx +++ b/src/plugins/files_management/public/mount_management_section.tsx @@ -30,6 +30,10 @@ export const mountManagementSection = ( startDeps: StartDependencies, { element, history }: ManagementAppMountParams ) => { + const { + files: { filesClientFactory, getAllFindKindDefinitions, getFileKindDefinition }, + } = startDeps; + ReactDOM.render( @@ -41,7 +45,9 @@ export const mountManagementSection = ( }} > diff --git a/src/plugins/files_management/public/types.ts b/src/plugins/files_management/public/types.ts index 2a73b69bea017..303d5e1c5d1a7 100755 --- a/src/plugins/files_management/public/types.ts +++ b/src/plugins/files_management/public/types.ts @@ -11,6 +11,8 @@ import { ManagementSetup } from '@kbn/management-plugin/public'; export interface AppContext { filesClient: FilesClient; + getFileKindDefinition: FilesStart['getFileKindDefinition']; + getAllFindKindDefinitions: FilesStart['getAllFindKindDefinitions']; } export interface SetupDependencies { From e951205f7e488ce0d2f53bae504c7f014a3bece3 Mon Sep 17 00:00:00 2001 From: Paul Tavares <56442535+paul-tavares@users.noreply.github.com> Date: Mon, 24 Apr 2023 12:00:32 -0400 Subject: [PATCH 02/52] [Security Solution][Endpoint] Define possible `execute` response failure codes for use in the UI (#155316) ## Summary - Updates the list of known Response Actions response codes with codes for the `execute` response action --- .../endpoint_action_generator.ts | 65 ++++++------- .../common/endpoint/types/actions.ts | 1 + .../integration_tests/execute_action.test.tsx | 34 +++++++ .../lib/endpoint_action_response_codes.ts | 91 +++++++++++++++++++ .../response_actions_log.test.tsx | 31 +++---- 5 files changed, 173 insertions(+), 49 deletions(-) diff --git a/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_action_generator.ts b/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_action_generator.ts index 855dd3a3fe439..fdf75da0a134e 100644 --- a/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_action_generator.ts +++ b/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_action_generator.ts @@ -155,38 +155,37 @@ export class EndpointActionGenerator extends BaseDataGenerator { TOutputType extends object = object, TParameters extends EndpointActionDataParameterTypes = EndpointActionDataParameterTypes >( - overrides: Partial> = {} + overrides: DeepPartial> = {} ): ActionDetails { - const details: ActionDetails = merge( - { - agents: ['agent-a'], - command: 'isolate', - completedAt: '2022-04-30T16:08:47.449Z', - hosts: { 'agent-a': { name: 'Host-agent-a' } }, - id: '123', - isCompleted: true, - isExpired: false, - wasSuccessful: true, - errors: undefined, - startedAt: '2022-04-27T16:08:47.449Z', - status: 'successful', - comment: 'thisisacomment', - createdBy: 'auserid', - parameters: undefined, - outputs: {}, - agentState: { - 'agent-a': { - errors: undefined, - isCompleted: true, - completedAt: '2022-04-30T16:08:47.449Z', - wasSuccessful: true, - }, + const details: ActionDetails = { + agents: ['agent-a'], + command: 'isolate', + completedAt: '2022-04-30T16:08:47.449Z', + hosts: { 'agent-a': { name: 'Host-agent-a' } }, + id: '123', + isCompleted: true, + isExpired: false, + wasSuccessful: true, + errors: undefined, + startedAt: '2022-04-27T16:08:47.449Z', + status: 'successful', + comment: 'thisisacomment', + createdBy: 'auserid', + parameters: undefined, + outputs: {}, + agentState: { + 'agent-a': { + errors: undefined, + isCompleted: true, + completedAt: '2022-04-30T16:08:47.449Z', + wasSuccessful: true, }, }, - overrides - ); + }; - if (details.command === 'get-file') { + const command = overrides.command ?? details.command; + + if (command === 'get-file') { if (!details.parameters) { ( details as ActionDetails< @@ -213,7 +212,7 @@ export class EndpointActionGenerator extends BaseDataGenerator { } } - if (details.command === 'execute') { + if (command === 'execute') { if (!details.parameters) { ( details as ActionDetails< @@ -233,14 +232,17 @@ export class EndpointActionGenerator extends BaseDataGenerator { [details.agents[0]]: this.generateExecuteActionResponseOutput({ content: { output_file_id: getFileDownloadId(details, details.agents[0]), - ...overrides.outputs?.[details.agents[0]].content, + ...(overrides.outputs?.[details.agents[0]]?.content ?? {}), }, }), }; } } - return details as unknown as ActionDetails; + return merge(details, overrides as ActionDetails) as unknown as ActionDetails< + TOutputType, + TParameters + >; } randomGetFileFailureCode(): string { @@ -310,6 +312,7 @@ export class EndpointActionGenerator extends BaseDataGenerator { { type: 'json', content: { + code: 'ra_execute_success_done', stdout: this.randomChoice([ this.randomString(1280), this.randomString(3580), 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 d9082f8aa1d8c..f8f5da28943b8 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types/actions.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types/actions.ts @@ -65,6 +65,7 @@ export interface ResponseActionGetFileOutputContent { } export interface ResponseActionExecuteOutputContent { + code: string; /* The truncated 'tail' output of the command */ stdout: string; /* The truncated 'tail' of any errors generated by the command */ diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/execute_action.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/execute_action.test.tsx index c3f94871c8f67..8d5d8907924f6 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/execute_action.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/execute_action.test.tsx @@ -23,6 +23,8 @@ import { getEndpointAuthzInitialStateMock } from '../../../../../../common/endpo import type { EndpointPrivileges } from '../../../../../../common/endpoint/types'; import { INSUFFICIENT_PRIVILEGES_FOR_COMMAND } from '../../../../../common/translations'; import type { HttpFetchOptionsWithPath } from '@kbn/core-http-browser'; +import { endpointActionResponseCodes } from '../../lib/endpoint_action_response_codes'; +import { EndpointActionGenerator } from '../../../../../../common/endpoint/data_generators/endpoint_action_generator'; jest.mock('../../../../../common/components/user_privileges'); jest.mock('../../../../../common/experimental_features_service'); @@ -180,4 +182,36 @@ describe('When using execute action from response actions console', () => { ); }); }); + + it.each( + Object.keys(endpointActionResponseCodes).filter((key) => key.startsWith('ra_execute_error')) + )('should display known error message for response failure: %s', async (errorCode) => { + apiMocks.responseProvider.actionDetails.mockReturnValue({ + data: new EndpointActionGenerator('seed').generateActionDetails({ + command: 'execute', + errors: ['some error happen in endpoint'], + wasSuccessful: false, + outputs: { + 'agent-a': { + content: { + code: errorCode, + }, + }, + }, + }), + }); + + const { getByTestId } = await render(); + enterConsoleCommand(renderResult, 'execute --command="ls -l"'); + + await waitFor(() => { + expect(apiMocks.responseProvider.actionDetails).toHaveBeenCalled(); + }); + + await waitFor(() => { + expect(getByTestId('execute-actionFailure')).toHaveTextContent( + endpointActionResponseCodes[errorCode] + ); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/lib/endpoint_action_response_codes.ts b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/lib/endpoint_action_response_codes.ts index 746b9201cd200..c2595c625b7fa 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/lib/endpoint_action_response_codes.ts +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/lib/endpoint_action_response_codes.ts @@ -123,6 +123,97 @@ const CODES = Object.freeze({ 'xpack.securitySolution.endpointActionResponseCodes.killProcess.notPermittedSuccess', { defaultMessage: 'The provided process cannot be killed' } ), + + // ----------------------------------------------------------------- + // EXECUTE CODES + // ----------------------------------------------------------------- + + // Dev: + // Something interrupted preparing the zip: file read error, zip error. I think these should be rare, + // and should succeed on retry by the user or result in file-not-found. We might implement some retries + // internally but I'm leaning to the opinion that we should rather quickly send the feedback to the + // user to let them decide. + ra_execute_error_processing: i18n.translate( + 'xpack.securitySolution.endpointActionResponseCodes.execute.processingError', + { + defaultMessage: 'Unable to create execution output zip file.', + } + ), + + // Dev: + // Executing timeout has been reached, the command was killed. + 'ra_execute_error_processing-timeout': i18n.translate( + 'xpack.securitySolution.endpointActionResponseCodes.execute.processingTimeout', + { defaultMessage: 'Command execution was terminated. It exceeded the provided timeout.' } + ), + + // Dev: + // Execution was interrupted, for example: system shutdown, endpoint service stop/restart. + 'ra_execute_error_processing-interrupted': i18n.translate( + 'xpack.securitySolution.endpointActionResponseCodes.execute.processingInterrupted', + { + defaultMessage: 'Command execution was absolutely interrupted.', + } + ), + + // Dev: + // Too many active execute actions, limit 10. Execute actions are allowed to run in parallel, we must + // take into account resource use impact on endpoint as customers are piky about CPU/MEM utilization. + 'ra_execute_error_to-many-requests': i18n.translate( + 'xpack.securitySolution.endpointActionResponseCodes.execute.toManyRequests', + { + defaultMessage: 'Too many concurrent command execution actions.', + } + ), + + // Dev: + // generic failure (rare corner case, software bug, etc) + ra_execute_error_failure: i18n.translate( + 'xpack.securitySolution.endpointActionResponseCodes.execute.failure', + { defaultMessage: 'Unknown failure while executing command.' } + ), + + // Dev: + // Max pending response zip uploads has been reached, limit 10. Endpoint can't use unlimited disk space. + 'ra_execute_error_disk-quota': i18n.translate( + 'xpack.securitySolution.endpointActionResponseCodes.execute.diskQuotaError', + { + defaultMessage: 'Too many pending command execution output zip files.', + } + ), + + // Dev: + // The fleet upload API was unreachable (not just busy). This may mean policy misconfiguration, in which + // case health status in Kibana should indicate degraded, or maybe network configuration problems, or fleet + // server problems HTTP 500. This excludes offline status, where endpoint should just wait for network connection. + 'ra_execute_error_upload-api-unreachable': i18n.translate( + 'xpack.securitySolution.endpointActionResponseCodes.execute.uploadApiUnreachable', + { + defaultMessage: + 'Failed to upload command execution output zip file. Unable to reach Fleet Server upload API.', + } + ), + + // Dev: + // Perhaps internet connection was too slow or unstable to upload all chunks before unique + // upload-id expired. Endpoint will re-try a bit, max 3 times. + 'ra_execute_error_upload-timeout': i18n.translate( + 'xpack.securitySolution.endpointActionResponseCodes.execute.outputUploadTimeout', + { + defaultMessage: 'Failed to upload command execution output zip file. Upload timed out', + } + ), + + // DEV: + // Upload API could be busy, endpoint should periodically re-try (2 days = 192 x 15min, assuming + // that with 1Mbps 15min is enough to upload 100MB) + 'ra_execute_error_queue-timeout': i18n.translate( + 'xpack.securitySolution.endpointActionResponseCodes.execute.queueTimeout', + { + defaultMessage: + 'Failed to upload command execution output zip file. Timed out while queued waiting for Fleet Server', + } + ), }); /** diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/integration_tests/response_actions_log.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/integration_tests/response_actions_log.test.tsx index 6360b55b1c06f..6fc62dd6cf851 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/integration_tests/response_actions_log.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/integration_tests/response_actions_log.test.tsx @@ -652,10 +652,14 @@ describe('Response actions history', () => { }); it('should contain expected output accordions for `execute` action WITH execute operation privilege', async () => { - const actionDetails = await getActionListMock({ actionCount: 1, commands: ['execute'] }); + const actionListApiResponse = await getActionListMock({ + actionCount: 1, + agentIds: ['agent-a'], + commands: ['execute'], + }); useGetEndpointActionListMock.mockReturnValue({ ...getBaseMockedActionList(), - data: actionDetails, + data: actionListApiResponse, }); mockUseGetFileInfo = { @@ -669,17 +673,7 @@ describe('Response actions history', () => { isFetched: true, error: null, data: { - data: { - ...apiMocks.responseProvider.actionDetails({ - path: `/api/endpoint/action/${actionDetails.data[0].id}`, - }).data, - outputs: { - [actionDetails.data[0].agents[0]]: { - content: {}, - type: 'json', - }, - }, - }, + data: actionListApiResponse.data[0], }, }; @@ -714,7 +708,11 @@ describe('Response actions history', () => { }); useGetEndpointActionListMock.mockReturnValue({ ...getBaseMockedActionList(), - data: await getActionListMock({ actionCount: 1, commands: ['execute'] }), + data: await getActionListMock({ + actionCount: 1, + commands: ['execute'], + agentIds: ['agent-a'], + }), }); render(); @@ -723,10 +721,7 @@ describe('Response actions history', () => { const expandButton = getByTestId(`${testPrefix}-expand-button`); userEvent.click(expandButton); - const executeAccordions = getByTestId( - `${testPrefix}-actionsLogTray-executeResponseOutput-output` - ); - expect(executeAccordions).toBeTruthy(); + expect(getByTestId(`${testPrefix}-actionsLogTray-executeResponseOutput-output`)); }); it('should not contain full output download link in expanded row for `execute` action WITHOUT Actions Log privileges', async () => { From 50e3ff2f23ea38396c58961d4282674b9e2da0dd Mon Sep 17 00:00:00 2001 From: Shahzad Date: Mon, 24 Apr 2023 18:17:23 +0200 Subject: [PATCH 03/52] [Synthetics] Add license issued_to value in request (#155600) --- .../service_api_client.test.ts | 40 +++++++++++++++---- .../synthetics_service/service_api_client.ts | 12 +++--- .../synthetics_service/synthetics_service.ts | 12 +++--- 3 files changed, 45 insertions(+), 19 deletions(-) diff --git a/x-pack/plugins/synthetics/server/synthetics_service/service_api_client.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/service_api_client.test.ts index 22ec8e8a3213e..2ed86eb91ae52 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/service_api_client.test.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/service_api_client.test.ts @@ -14,6 +14,24 @@ import { UptimeServerSetup } from '../legacy_uptime/lib/adapters'; import { ServiceConfig } from '../../common/config'; import axios from 'axios'; import { LocationStatus, PublicLocations } from '../../common/runtime_types'; +import { LicenseGetResponse } from '@elastic/elasticsearch/lib/api/types'; + +const licenseMock: LicenseGetResponse = { + license: { + status: 'active', + uid: '1d34eb9f-e66f-47d1-8d24-cd60d187587a', + type: 'trial', + issue_date: '2022-05-05T14:25:00.732Z', + issue_date_in_millis: 165176070074432, + expiry_date: '2022-06-04T14:25:00.732Z', + expiry_date_in_millis: 165435270073332, + max_nodes: 1000, + max_resource_units: null, + issued_to: '2c515bd215ce444441f83ffd36a9d3d2546', + issuer: 'elasticsearch', + start_date_in_millis: -1, + }, +}; jest.mock('axios', () => jest.fn()); jest.mock('@kbn/server-http-tools', () => ({ @@ -167,7 +185,7 @@ describe('callAPI', () => { await apiClient.callAPI('POST', { monitors: testMonitors, output, - licenseLevel: 'trial', + license: licenseMock.license, }); expect(spy).toHaveBeenCalledTimes(3); @@ -181,7 +199,7 @@ describe('callAPI', () => { monitor.locations.some((loc: any) => loc.id === 'us_central') ), output, - licenseLevel: 'trial', + license: licenseMock.license, }, 'POST', devUrl @@ -195,7 +213,7 @@ describe('callAPI', () => { monitor.locations.some((loc: any) => loc.id === 'us_central_qa') ), output, - licenseLevel: 'trial', + license: licenseMock.license, }, 'POST', 'https://qa.service.elstc.co' @@ -209,7 +227,7 @@ describe('callAPI', () => { monitor.locations.some((loc: any) => loc.id === 'us_central_staging') ), output, - licenseLevel: 'trial', + license: licenseMock.license, }, 'POST', 'https://qa.service.stg.co' @@ -223,6 +241,7 @@ describe('callAPI', () => { output, stack_version: '8.7.0', license_level: 'trial', + license_issued_to: '2c515bd215ce444441f83ffd36a9d3d2546', }, headers: { Authorization: 'Basic ZGV2OjEyMzQ1', @@ -242,6 +261,7 @@ describe('callAPI', () => { output, stack_version: '8.7.0', license_level: 'trial', + license_issued_to: '2c515bd215ce444441f83ffd36a9d3d2546', }, headers: { Authorization: 'Basic ZGV2OjEyMzQ1', @@ -261,6 +281,7 @@ describe('callAPI', () => { output, stack_version: '8.7.0', license_level: 'trial', + license_issued_to: '2c515bd215ce444441f83ffd36a9d3d2546', }, headers: { Authorization: 'Basic ZGV2OjEyMzQ1', @@ -324,7 +345,7 @@ describe('callAPI', () => { await apiClient.callAPI('POST', { monitors: testMonitors, output, - licenseLevel: 'platinum', + license: licenseMock.license, }); expect(axiosSpy).toHaveBeenNthCalledWith(1, { @@ -333,7 +354,8 @@ describe('callAPI', () => { is_edit: undefined, output, stack_version: '8.7.0', - license_level: 'platinum', + license_level: 'trial', + license_issued_to: '2c515bd215ce444441f83ffd36a9d3d2546', }, headers: { 'x-kibana-version': '8.7.0', @@ -376,7 +398,7 @@ describe('callAPI', () => { await apiClient.runOnce({ monitors: testMonitors, output, - licenseLevel: 'trial', + license: licenseMock.license, }); expect(axiosSpy).toHaveBeenNthCalledWith(1, { @@ -386,6 +408,7 @@ describe('callAPI', () => { output, stack_version: '8.7.0', license_level: 'trial', + license_issued_to: '2c515bd215ce444441f83ffd36a9d3d2546', }, headers: { 'x-kibana-version': '8.7.0', @@ -428,7 +451,7 @@ describe('callAPI', () => { await apiClient.syncMonitors({ monitors: testMonitors, output, - licenseLevel: 'trial', + license: licenseMock.license, }); expect(axiosSpy).toHaveBeenNthCalledWith(1, { @@ -438,6 +461,7 @@ describe('callAPI', () => { output, stack_version: '8.7.0', license_level: 'trial', + license_issued_to: '2c515bd215ce444441f83ffd36a9d3d2546', }, headers: { 'x-kibana-version': '8.7.0', diff --git a/x-pack/plugins/synthetics/server/synthetics_service/service_api_client.ts b/x-pack/plugins/synthetics/server/synthetics_service/service_api_client.ts index bfc872f3680a8..4fea1d04b64e4 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/service_api_client.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/service_api_client.ts @@ -11,6 +11,7 @@ import { catchError, tap } from 'rxjs/operators'; import * as https from 'https'; import { SslConfig } from '@kbn/server-http-tools'; import { Logger } from '@kbn/core/server'; +import { LicenseGetLicenseInformation } from '@elastic/elasticsearch/lib/api/types'; import { UptimeServerSetup } from '../legacy_uptime/lib/adapters'; import { sendErrorTelemetryEvents } from '../routes/telemetry/monitor_upgrade_sender'; import { MonitorFields, PublicLocations, ServiceLocationErrors } from '../../common/runtime_types'; @@ -27,7 +28,7 @@ export interface ServiceData { }; endpoint?: 'monitors' | 'runOnce' | 'sync'; isEdit?: boolean; - licenseLevel: string; + license: LicenseGetLicenseInformation; } export class ServiceAPIClient { @@ -142,7 +143,7 @@ export class ServiceAPIClient { async callAPI( method: 'POST' | 'PUT' | 'DELETE', - { monitors: allMonitors, output, endpoint, isEdit, licenseLevel }: ServiceData + { monitors: allMonitors, output, endpoint, isEdit, license }: ServiceData ) { if (this.username === TEST_SERVICE_USERNAME) { // we don't want to call service while local integration tests are running @@ -159,7 +160,7 @@ export class ServiceAPIClient { ); if (locMonitors.length > 0) { const promise = this.callServiceEndpoint( - { monitors: locMonitors, isEdit, endpoint, output, licenseLevel }, + { monitors: locMonitors, isEdit, endpoint, output, license }, method, url ); @@ -200,7 +201,7 @@ export class ServiceAPIClient { } async callServiceEndpoint( - { monitors, output, endpoint = 'monitors', isEdit, licenseLevel }: ServiceData, + { monitors, output, endpoint = 'monitors', isEdit, license }: ServiceData, method: 'POST' | 'PUT' | 'DELETE', baseUrl: string ) { @@ -233,7 +234,8 @@ export class ServiceAPIClient { output, stack_version: this.stackVersion, is_edit: isEdit, - license_level: licenseLevel, + license_level: license.type, + license_issued_to: license.issued_to, }, headers: authHeader, httpsAgent: this.getHttpsAgent(baseUrl), diff --git a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts index bd9c0032984c3..e4ef7cbbb6e67 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts @@ -307,7 +307,7 @@ export class SyntheticsService { this.syncErrors = await this.apiClient.post({ monitors, output, - licenseLevel: license.type, + license, }); } return this.syncErrors; @@ -329,7 +329,7 @@ export class SyntheticsService { monitors, output, isEdit, - licenseLevel: license.type, + license, }; this.syncErrors = await this.apiClient.put(data); @@ -372,7 +372,7 @@ export class SyntheticsService { service.syncErrors = await this.apiClient.syncMonitors({ monitors, output, - licenseLevel: license.type, + license, }); } catch (e) { sendErrorTelemetryEvents(service.logger, service.server.telemetry, { @@ -406,7 +406,7 @@ export class SyntheticsService { return await this.apiClient.runOnce({ monitors, output, - licenseLevel: license.type, + license, }); } catch (e) { this.logger.error(e); @@ -429,7 +429,7 @@ export class SyntheticsService { const data = { output, monitors: this.formatConfigs(configs), - licenseLevel: license.type, + license, }; return await this.apiClient.delete(data); } @@ -453,7 +453,7 @@ export class SyntheticsService { const data = { output, monitors, - licenseLevel: license.type, + license, }; return await this.apiClient.delete(data); } From 3e94b43aa28f12538547217a0eeec224e7fa4263 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Mon, 24 Apr 2023 18:47:44 +0200 Subject: [PATCH 04/52] [ML] AIOps: Link from Explain Log Rate Spikes to Log Pattern Analysis (#155121) Adds table actions to Explain Log Rate Spikes to be able to drill down to Log Pattern Analysis. --- .../public/application/utils/url_state.ts | 45 +++++ .../explain_log_rate_spikes_app_state.tsx | 30 ---- .../explain_log_rate_spikes_page.tsx | 10 +- .../category_table/category_table.tsx | 4 +- .../log_categorization_page.tsx | 22 ++- .../log_categorization/use_discover_links.ts | 2 +- .../spike_analysis_table.tsx | 4 +- .../spike_analysis_table_groups.tsx | 4 +- .../table_action_button.tsx | 62 +++++++ .../use_copy_to_clipboard_action.test.tsx | 26 ++- .../use_copy_to_clipboard_action.tsx | 20 ++- .../use_view_in_discover_action.tsx | 36 ++-- ...se_view_in_log_pattern_analysis_action.tsx | 108 ++++++++++++ x-pack/plugins/aiops/public/hooks/use_data.ts | 2 +- x-pack/plugins/aiops/tsconfig.json | 1 + .../apps/aiops/explain_log_rate_spikes.ts | 158 +++++++++++------- .../test/functional/apps/aiops/test_data.ts | 106 ++++++++++++ x-pack/test/functional/apps/aiops/types.ts | 12 +- ...n_log_rate_spikes_analysis_groups_table.ts | 50 ++++++ .../explain_log_rate_spikes_data_generator.ts | 8 + .../aiops/explain_log_rate_spikes_page.ts | 8 +- .../test/functional/services/aiops/index.ts | 3 + .../aiops/log_pattern_analysis_page.ts | 42 +++++ 23 files changed, 631 insertions(+), 132 deletions(-) create mode 100644 x-pack/plugins/aiops/public/application/utils/url_state.ts create mode 100644 x-pack/plugins/aiops/public/components/spike_analysis_table/table_action_button.tsx create mode 100644 x-pack/plugins/aiops/public/components/spike_analysis_table/use_view_in_log_pattern_analysis_action.tsx create mode 100644 x-pack/test/functional/services/aiops/log_pattern_analysis_page.ts diff --git a/x-pack/plugins/aiops/public/application/utils/url_state.ts b/x-pack/plugins/aiops/public/application/utils/url_state.ts new file mode 100644 index 0000000000000..9fdaa443f4c75 --- /dev/null +++ b/x-pack/plugins/aiops/public/application/utils/url_state.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; + +import type { Filter, Query } from '@kbn/es-query'; +import { isPopulatedObject } from '@kbn/ml-is-populated-object'; + +import { SEARCH_QUERY_LANGUAGE, SearchQueryLanguage } from './search_utils'; + +const defaultSearchQuery = { + match_all: {}, +}; + +export interface AiOpsPageUrlState { + pageKey: 'AIOPS_INDEX_VIEWER'; + pageUrlState: AiOpsIndexBasedAppState; +} + +export interface AiOpsIndexBasedAppState { + searchString?: Query['query']; + searchQuery?: estypes.QueryDslQueryContainer; + searchQueryLanguage: SearchQueryLanguage; + filters?: Filter[]; +} + +export type AiOpsFullIndexBasedAppState = Required; + +export const getDefaultAiOpsListState = ( + overrides?: Partial +): AiOpsFullIndexBasedAppState => ({ + searchString: '', + searchQuery: defaultSearchQuery, + searchQueryLanguage: SEARCH_QUERY_LANGUAGE.KUERY, + filters: [], + ...overrides, +}); + +export const isFullAiOpsListState = (arg: unknown): arg is AiOpsFullIndexBasedAppState => { + return isPopulatedObject(arg, Object.keys(getDefaultAiOpsListState())); +}; diff --git a/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_app_state.tsx b/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_app_state.tsx index ce69ea9fea3ae..b30e3a7d1e6fb 100644 --- a/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_app_state.tsx +++ b/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_app_state.tsx @@ -10,7 +10,6 @@ import { pick } from 'lodash'; import { EuiCallOut } from '@elastic/eui'; -import type { Filter, Query } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; import type { SavedSearch } from '@kbn/discover-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public'; @@ -21,7 +20,6 @@ import { DatePickerContextProvider } from '@kbn/ml-date-picker'; import { UI_SETTINGS } from '@kbn/data-plugin/common'; import { toMountPoint, wrapWithTheme } from '@kbn/kibana-react-plugin/public'; -import { SEARCH_QUERY_LANGUAGE, SearchQueryLanguage } from '../../application/utils/search_utils'; import type { AiopsAppDependencies } from '../../hooks/use_aiops_app_context'; import { AiopsAppContext } from '../../hooks/use_aiops_app_context'; import { DataSourceContext } from '../../hooks/use_data_source'; @@ -42,34 +40,6 @@ export interface ExplainLogRateSpikesAppStateProps { appDependencies: AiopsAppDependencies; } -const defaultSearchQuery = { - match_all: {}, -}; - -export interface AiOpsPageUrlState { - pageKey: 'AIOPS_INDEX_VIEWER'; - pageUrlState: AiOpsIndexBasedAppState; -} - -export interface AiOpsIndexBasedAppState { - searchString?: Query['query']; - searchQuery?: Query['query']; - searchQueryLanguage: SearchQueryLanguage; - filters?: Filter[]; -} - -export const getDefaultAiOpsListState = ( - overrides?: Partial -): Required => ({ - searchString: '', - searchQuery: defaultSearchQuery, - searchQueryLanguage: SEARCH_QUERY_LANGUAGE.KUERY, - filters: [], - ...overrides, -}); - -export const restorableDefaults = getDefaultAiOpsListState(); - export const ExplainLogRateSpikesAppState: FC = ({ dataView, savedSearch, diff --git a/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_page.tsx b/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_page.tsx index 80640f59901bc..fb9ce01c63391 100644 --- a/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_page.tsx +++ b/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_page.tsx @@ -7,6 +7,7 @@ import React, { useCallback, useEffect, useState, FC } from 'react'; +import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { EuiEmptyPrompt, EuiFlexGroup, @@ -28,6 +29,10 @@ import { useDataSource } from '../../hooks/use_data_source'; import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; import { SearchQueryLanguage } from '../../application/utils/search_utils'; import { useData } from '../../hooks/use_data'; +import { + getDefaultAiOpsListState, + type AiOpsPageUrlState, +} from '../../application/utils/url_state'; import { DocumentCountContent } from '../document_count_content/document_count_content'; import { SearchPanel } from '../search_panel'; @@ -35,7 +40,6 @@ import type { GroupTableItem } from '../spike_analysis_table/types'; import { useSpikeAnalysisTableRowContext } from '../spike_analysis_table/spike_analysis_table_row_provider'; import { PageHeader } from '../page_header'; -import { restorableDefaults, type AiOpsPageUrlState } from './explain_log_rate_spikes_app_state'; import { ExplainLogRateSpikesAnalysis } from './explain_log_rate_spikes_analysis'; function getDocumentCountStatsSplitLabel( @@ -66,7 +70,7 @@ export const ExplainLogRateSpikesPage: FC = () => { const [aiopsListState, setAiopsListState] = usePageUrlState( 'AIOPS_INDEX_VIEWER', - restorableDefaults + getDefaultAiOpsListState() ); const [globalState, setGlobalState] = useUrlState('_g'); @@ -80,7 +84,7 @@ export const ExplainLogRateSpikesPage: FC = () => { const setSearchParams = useCallback( (searchParams: { - searchQuery: Query['query']; + searchQuery: estypes.QueryDslQueryContainer; searchString: Query['query']; queryLanguage: SearchQueryLanguage; filters: Filter[]; diff --git a/x-pack/plugins/aiops/public/components/log_categorization/category_table/category_table.tsx b/x-pack/plugins/aiops/public/components/log_categorization/category_table/category_table.tsx index c888694a7b0c3..747f90d542354 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/category_table/category_table.tsx +++ b/x-pack/plugins/aiops/public/components/log_categorization/category_table/category_table.tsx @@ -25,7 +25,7 @@ import { import { useDiscoverLinks } from '../use_discover_links'; import { MiniHistogram } from '../../mini_histogram'; import { useEuiTheme } from '../../../hooks/use_eui_theme'; -import type { AiOpsIndexBasedAppState } from '../../explain_log_rate_spikes/explain_log_rate_spikes_app_state'; +import type { AiOpsFullIndexBasedAppState } from '../../../application/utils/url_state'; import type { EventRate, Category, SparkLinesPerCategory } from '../use_categorize_request'; import { useTableState } from './use_table_state'; @@ -42,7 +42,7 @@ interface Props { dataViewId: string; selectedField: string | undefined; timefilter: TimefilterContract; - aiopsListState: Required; + aiopsListState: AiOpsFullIndexBasedAppState; pinnedCategory: Category | null; setPinnedCategory: (category: Category | null) => void; selectedCategory: Category | null; diff --git a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx index b673d8a498a21..7c7d0001aea42 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx +++ b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx @@ -4,8 +4,10 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ + import React, { FC, useState, useEffect, useCallback, useMemo } from 'react'; +import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { EuiButton, EuiSpacer, @@ -21,14 +23,18 @@ import { import { Filter, Query } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { useUrlState } from '@kbn/ml-url-state'; +import { usePageUrlState, useUrlState } from '@kbn/ml-url-state'; import { useDataSource } from '../../hooks/use_data_source'; import { useData } from '../../hooks/use_data'; import type { SearchQueryLanguage } from '../../application/utils/search_utils'; import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; +import { + getDefaultAiOpsListState, + isFullAiOpsListState, + type AiOpsPageUrlState, +} from '../../application/utils/url_state'; -import { restorableDefaults } from '../explain_log_rate_spikes/explain_log_rate_spikes_app_state'; import { SearchPanel } from '../search_panel'; import { PageHeader } from '../page_header'; @@ -47,7 +53,10 @@ export const LogCategorizationPage: FC = () => { const { dataView, savedSearch } = useDataSource(); const { runCategorizeRequest, cancelRequest } = useCategorizeRequest(); - const [aiopsListState, setAiopsListState] = useState(restorableDefaults); + const [aiopsListState, setAiopsListState] = usePageUrlState( + 'AIOPS_INDEX_VIEWER', + getDefaultAiOpsListState() + ); const [globalState, setGlobalState] = useUrlState('_g'); const [selectedField, setSelectedField] = useState(); const [selectedCategory, setSelectedCategory] = useState(null); @@ -76,7 +85,7 @@ export const LogCategorizationPage: FC = () => { const setSearchParams = useCallback( (searchParams: { - searchQuery: Query['query']; + searchQuery: estypes.QueryDslQueryContainer; searchString: Query['query']; queryLanguage: SearchQueryLanguage; filters: Filter[]; @@ -289,7 +298,10 @@ export const LogCategorizationPage: FC = () => { fieldSelected={selectedField !== null} /> - {selectedField !== undefined && categories !== null && categories.length > 0 ? ( + {selectedField !== undefined && + categories !== null && + categories.length > 0 && + isFullAiOpsListState(aiopsListState) ? ( = ({ const copyToClipBoardAction = useCopyToClipboardAction(); const viewInDiscoverAction = useViewInDiscoverAction(dataViewId); + const viewInLogPatternAnalysisAction = useViewInLogPatternAnalysisAction(dataViewId); const columns: Array> = [ { @@ -238,7 +240,7 @@ export const SpikeAnalysisTable: FC = ({ name: i18n.translate('xpack.aiops.spikeAnalysisTable.actionsColumnName', { defaultMessage: 'Actions', }), - actions: [viewInDiscoverAction, copyToClipBoardAction], + actions: [viewInDiscoverAction, viewInLogPatternAnalysisAction, copyToClipBoardAction], width: ACTIONS_COLUMN_WIDTH, valign: 'middle', }, diff --git a/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table_groups.tsx b/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table_groups.tsx index b319db0088d4d..ca55b43907bd6 100644 --- a/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table_groups.tsx +++ b/x-pack/plugins/aiops/public/components/spike_analysis_table/spike_analysis_table_groups.tsx @@ -40,6 +40,7 @@ import { useSpikeAnalysisTableRowContext } from './spike_analysis_table_row_prov import type { GroupTableItem } from './types'; import { useCopyToClipboardAction } from './use_copy_to_clipboard_action'; import { useViewInDiscoverAction } from './use_view_in_discover_action'; +import { useViewInLogPatternAnalysisAction } from './use_view_in_log_pattern_analysis_action'; const NARROW_COLUMN_WIDTH = '120px'; const EXPAND_COLUMN_WIDTH = '40px'; @@ -121,6 +122,7 @@ export const SpikeAnalysisGroupsTable: FC = ({ const copyToClipBoardAction = useCopyToClipboardAction(); const viewInDiscoverAction = useViewInDiscoverAction(dataViewId); + const viewInLogPatternAnalysisAction = useViewInLogPatternAnalysisAction(dataViewId); const columns: Array> = [ { @@ -355,7 +357,7 @@ export const SpikeAnalysisGroupsTable: FC = ({ name: i18n.translate('xpack.aiops.spikeAnalysisTable.actionsColumnName', { defaultMessage: 'Actions', }), - actions: [viewInDiscoverAction, copyToClipBoardAction], + actions: [viewInDiscoverAction, viewInLogPatternAnalysisAction, copyToClipBoardAction], width: ACTIONS_COLUMN_WIDTH, valign: 'top', }, diff --git a/x-pack/plugins/aiops/public/components/spike_analysis_table/table_action_button.tsx b/x-pack/plugins/aiops/public/components/spike_analysis_table/table_action_button.tsx new file mode 100644 index 0000000000000..16c91f8d3851f --- /dev/null +++ b/x-pack/plugins/aiops/public/components/spike_analysis_table/table_action_button.tsx @@ -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 React, { type FC } from 'react'; + +import { EuiLink, EuiIcon, EuiText, EuiToolTip, type IconType } from '@elastic/eui'; + +interface TableActionButtonProps { + iconType: IconType; + dataTestSubjPostfix: string; + isDisabled: boolean; + label: string; + tooltipText?: string; + onClick: () => void; +} + +export const TableActionButton: FC = ({ + iconType, + dataTestSubjPostfix, + isDisabled, + label, + tooltipText, + onClick, +}) => { + const buttonContent = ( + <> + + {label} + + ); + + const unwrappedButton = !isDisabled ? ( + + {buttonContent} + + ) : ( + + {buttonContent} + + ); + + if (tooltipText) { + return {unwrappedButton}; + } + + return unwrappedButton; +}; diff --git a/x-pack/plugins/aiops/public/components/spike_analysis_table/use_copy_to_clipboard_action.test.tsx b/x-pack/plugins/aiops/public/components/spike_analysis_table/use_copy_to_clipboard_action.test.tsx index 82359b3d2b1aa..0984c76a4b170 100644 --- a/x-pack/plugins/aiops/public/components/spike_analysis_table/use_copy_to_clipboard_action.test.tsx +++ b/x-pack/plugins/aiops/public/components/spike_analysis_table/use_copy_to_clipboard_action.test.tsx @@ -30,11 +30,19 @@ describe('useCopyToClipboardAction', () => { it('renders the action for a single significant term', async () => { execCommandMock.mockImplementationOnce(() => true); const { result } = renderHook(() => useCopyToClipboardAction()); - const { getByLabelText } = render((result.current as Action).render(significantTerms[0])); + const { findByText, getByTestId } = render( + (result.current as Action).render(significantTerms[0]) + ); - const button = getByLabelText('Copy field/value pair as KQL syntax to clipboard'); + const button = getByTestId('aiopsTableActionButtonCopyToClipboard enabled'); - expect(button).toBeInTheDocument(); + userEvent.hover(button); + + // The tooltip from EUI takes 250ms to appear, so we must + // use a `find*` query to asynchronously poll for it. + expect( + await findByText('Copy field/value pair as KQL syntax to clipboard') + ).toBeInTheDocument(); await act(async () => { await userEvent.click(button); @@ -50,12 +58,16 @@ describe('useCopyToClipboardAction', () => { it('renders the action for a group of items', async () => { execCommandMock.mockImplementationOnce(() => true); const groupTableItems = getGroupTableItems(finalSignificantTermGroups); - const { result } = renderHook(() => useCopyToClipboardAction()); - const { getByLabelText } = render((result.current as Action).render(groupTableItems[0])); + const { result } = renderHook(useCopyToClipboardAction); + const { findByText, getByText } = render((result.current as Action).render(groupTableItems[0])); + + const button = getByText('Copy to clipboard'); - const button = getByLabelText('Copy group items as KQL syntax to clipboard'); + userEvent.hover(button); - expect(button).toBeInTheDocument(); + // The tooltip from EUI takes 250ms to appear, so we must + // use a `find*` query to asynchronously poll for it. + expect(await findByText('Copy group items as KQL syntax to clipboard')).toBeInTheDocument(); await act(async () => { await userEvent.click(button); diff --git a/x-pack/plugins/aiops/public/components/spike_analysis_table/use_copy_to_clipboard_action.tsx b/x-pack/plugins/aiops/public/components/spike_analysis_table/use_copy_to_clipboard_action.tsx index e9924307c1e27..1b906eb56e988 100644 --- a/x-pack/plugins/aiops/public/components/spike_analysis_table/use_copy_to_clipboard_action.tsx +++ b/x-pack/plugins/aiops/public/components/spike_analysis_table/use_copy_to_clipboard_action.tsx @@ -7,14 +7,22 @@ import React from 'react'; -import { EuiCopy, EuiToolTip, EuiButtonIcon } from '@elastic/eui'; +import { EuiCopy, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { isSignificantTerm, type SignificantTerm } from '@kbn/ml-agg-utils'; +import { TableActionButton } from './table_action_button'; import { getTableItemAsKQL } from './get_table_item_as_kql'; import type { GroupTableItem, TableItemAction } from './types'; +const copyToClipboardButtonLabel = i18n.translate( + 'xpack.aiops.spikeAnalysisTable.linksMenu.copyToClipboardButtonLabel', + { + defaultMessage: 'Copy to clipboard', + } +); + const copyToClipboardSignificantTermMessage = i18n.translate( 'xpack.aiops.spikeAnalysisTable.linksMenu.copyToClipboardSignificantTermMessage', { @@ -37,7 +45,15 @@ export const useCopyToClipboardAction = (): TableItemAction => ({ return ( - {(copy) => } + {(copy) => ( + + )} ); diff --git a/x-pack/plugins/aiops/public/components/spike_analysis_table/use_view_in_discover_action.tsx b/x-pack/plugins/aiops/public/components/spike_analysis_table/use_view_in_discover_action.tsx index 5f30abb2f6cec..bd7741bb452bf 100644 --- a/x-pack/plugins/aiops/public/components/spike_analysis_table/use_view_in_discover_action.tsx +++ b/x-pack/plugins/aiops/public/components/spike_analysis_table/use_view_in_discover_action.tsx @@ -7,14 +7,13 @@ import React, { useMemo } from 'react'; -import { EuiIcon, EuiToolTip } from '@elastic/eui'; - import { i18n } from '@kbn/i18n'; import type { SignificantTerm } from '@kbn/ml-agg-utils'; import { SEARCH_QUERY_LANGUAGE } from '../../application/utils/search_utils'; import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; +import { TableActionButton } from './table_action_button'; import { getTableItemAsKQL } from './get_table_item_as_kql'; import type { GroupTableItem, TableItemAction } from './types'; @@ -83,19 +82,26 @@ export const useViewInDiscoverAction = (dataViewId?: string): TableItemAction => }; return { - name: () => ( - - - - ), - description: viewInDiscoverMessage, - type: 'button', - onClick: async (tableItem) => { - const openInDiscoverUrl = await generateDiscoverUrl(tableItem); - if (typeof openInDiscoverUrl === 'string') { - await application.navigateToUrl(openInDiscoverUrl); - } + render: (tableItem: SignificantTerm | GroupTableItem) => { + const tooltipText = discoverUrlError ? discoverUrlError : viewInDiscoverMessage; + + const clickHandler = async () => { + const openInDiscoverUrl = await generateDiscoverUrl(tableItem); + if (typeof openInDiscoverUrl === 'string') { + await application.navigateToUrl(openInDiscoverUrl); + } + }; + + return ( + + ); }, - enabled: () => discoverUrlError === undefined, }; }; diff --git a/x-pack/plugins/aiops/public/components/spike_analysis_table/use_view_in_log_pattern_analysis_action.tsx b/x-pack/plugins/aiops/public/components/spike_analysis_table/use_view_in_log_pattern_analysis_action.tsx new file mode 100644 index 0000000000000..9388cf147c8ff --- /dev/null +++ b/x-pack/plugins/aiops/public/components/spike_analysis_table/use_view_in_log_pattern_analysis_action.tsx @@ -0,0 +1,108 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useMemo } from 'react'; + +import { SerializableRecord } from '@kbn/utility-types'; +import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; +import { i18n } from '@kbn/i18n'; +import type { SignificantTerm } from '@kbn/ml-agg-utils'; + +import { SEARCH_QUERY_LANGUAGE } from '../../application/utils/search_utils'; +import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; + +import { TableActionButton } from './table_action_button'; +import { getTableItemAsKQL } from './get_table_item_as_kql'; +import type { GroupTableItem, TableItemAction } from './types'; + +const viewInLogPatternAnalysisMessage = i18n.translate( + 'xpack.aiops.spikeAnalysisTable.linksMenu.viewInLogPatternAnalysis', + { + defaultMessage: 'View in Log Pattern Analysis', + } +); + +export const useViewInLogPatternAnalysisAction = (dataViewId?: string): TableItemAction => { + const { application, share, data } = useAiopsAppContext(); + + const mlLocator = useMemo(() => share.url.locators.get('ML_APP_LOCATOR'), [share.url.locators]); + + const generateLogPatternAnalysisUrl = async ( + groupTableItem: GroupTableItem | SignificantTerm + ) => { + if (mlLocator !== undefined) { + const searchString = getTableItemAsKQL(groupTableItem); + const ast = fromKueryExpression(searchString); + const searchQuery = toElasticsearchQuery(ast); + + const appState = { + AIOPS_INDEX_VIEWER: { + filters: data.query.filterManager.getFilters(), + // QueryDslQueryContainer type triggers an error as being + // not working with SerializableRecord, however, it works as expected. + searchQuery: searchQuery as unknown, + searchQueryLanguage: SEARCH_QUERY_LANGUAGE.KUERY, + searchString: getTableItemAsKQL(groupTableItem), + }, + } as SerializableRecord; + + return await mlLocator.getUrl({ + page: 'aiops/log_categorization', + pageState: { + index: dataViewId, + timeRange: data.query.timefilter.timefilter.getTime(), + appState, + }, + }); + } + }; + + const logPatternAnalysisUrlError = useMemo(() => { + if (!mlLocator) { + return i18n.translate('xpack.aiops.spikeAnalysisTable.mlLocatorMissingErrorMessage', { + defaultMessage: 'No locator for Log Pattern Analysis detected', + }); + } + if (!dataViewId) { + return i18n.translate( + 'xpack.aiops.spikeAnalysisTable.autoGeneratedLogPatternAnalysisLinkErrorMessage', + { + defaultMessage: + 'Unable to link to Log Pattern Analysis; no data view exists for this index', + } + ); + } + }, [dataViewId, mlLocator]); + + return { + render: (tableItem: SignificantTerm | GroupTableItem) => { + const message = logPatternAnalysisUrlError + ? logPatternAnalysisUrlError + : viewInLogPatternAnalysisMessage; + + const clickHandler = async () => { + const openInLogPatternAnalysisUrl = await generateLogPatternAnalysisUrl(tableItem); + if (typeof openInLogPatternAnalysisUrl === 'string') { + await application.navigateToUrl(openInLogPatternAnalysisUrl); + } + }; + + const isDisabled = logPatternAnalysisUrlError !== undefined; + + return ( + + ); + }, + }; +}; diff --git a/x-pack/plugins/aiops/public/hooks/use_data.ts b/x-pack/plugins/aiops/public/hooks/use_data.ts index c390582ccae1a..62f4c596cc60e 100644 --- a/x-pack/plugins/aiops/public/hooks/use_data.ts +++ b/x-pack/plugins/aiops/public/hooks/use_data.ts @@ -18,7 +18,7 @@ import { mlTimefilterRefresh$, useTimefilter } from '@kbn/ml-date-picker'; import { PLUGIN_ID } from '../../common'; import type { DocumentStatsSearchStrategyParams } from '../get_document_stats'; -import type { AiOpsIndexBasedAppState } from '../components/explain_log_rate_spikes/explain_log_rate_spikes_app_state'; +import type { AiOpsIndexBasedAppState } from '../application/utils/url_state'; import { getEsQueryFromSavedSearch } from '../application/utils/search_utils'; import type { GroupTableItem } from '../components/spike_analysis_table/types'; diff --git a/x-pack/plugins/aiops/tsconfig.json b/x-pack/plugins/aiops/tsconfig.json index 89c236ba25c99..6c9aafffa26d0 100644 --- a/x-pack/plugins/aiops/tsconfig.json +++ b/x-pack/plugins/aiops/tsconfig.json @@ -50,6 +50,7 @@ "@kbn/ml-route-utils", "@kbn/unified-field-list-plugin", "@kbn/ml-random-sampler-utils", + "@kbn/utility-types", "@kbn/ml-error-utils", ], "exclude": [ diff --git a/x-pack/test/functional/apps/aiops/explain_log_rate_spikes.ts b/x-pack/test/functional/apps/aiops/explain_log_rate_spikes.ts index 54b1baae454bd..7731eea1d9d7a 100644 --- a/x-pack/test/functional/apps/aiops/explain_log_rate_spikes.ts +++ b/x-pack/test/functional/apps/aiops/explain_log_rate_spikes.ts @@ -13,8 +13,8 @@ import type { FtrProviderContext } from '../../ftr_provider_context'; import { isTestDataExpectedWithSampleProbability, type TestData } from './types'; import { explainLogRateSpikesTestData } from './test_data'; -export default function ({ getPageObject, getService }: FtrProviderContext) { - const headerPage = getPageObject('header'); +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const PageObjects = getPageObjects(['common', 'console', 'header', 'home', 'security']); const elasticChart = getService('elasticChart'); const aiops = getService('aiops'); @@ -58,7 +58,7 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { await aiops.explainLogRateSpikesPage.assertSamplingProbabilityMissing(); } - await headerPage.waitUntilLoadingHasFinished(); + await PageObjects.header.waitUntilLoadingHasFinished(); await ml.testExecution.logTestStep( `${testData.suiteTitle} displays elements in the doc count panel correctly` @@ -78,77 +78,78 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { await aiops.explainLogRateSpikesPage.clickDocumentCountChart(testData.chartClickCoordinates); await aiops.explainLogRateSpikesPage.assertAnalysisSectionExists(); - await ml.testExecution.logTestStep('displays the no results found prompt'); - await aiops.explainLogRateSpikesPage.assertNoResultsFoundEmptyPromptExists(); + if (testData.brushDeviationTargetTimestamp) { + await ml.testExecution.logTestStep('displays the no results found prompt'); + await aiops.explainLogRateSpikesPage.assertNoResultsFoundEmptyPromptExists(); - await ml.testExecution.logTestStep('adjusts the brushes to get analysis results'); - await aiops.explainLogRateSpikesPage.assertRerunAnalysisButtonExists(false); + await ml.testExecution.logTestStep('adjusts the brushes to get analysis results'); + await aiops.explainLogRateSpikesPage.assertRerunAnalysisButtonExists(false); - // Get the current width of the deviation brush for later comparison. - const brushSelectionWidthBefore = await aiops.explainLogRateSpikesPage.getBrushSelectionWidth( - 'aiopsBrushDeviation' - ); - - // Get the px values for the timestamp we want to move the brush to. - const { targetPx, intervalPx } = await aiops.explainLogRateSpikesPage.getPxForTimestamp( - testData.brushDeviationTargetTimestamp - ); - - // Adjust the right brush handle - await aiops.explainLogRateSpikesPage.adjustBrushHandler( - 'aiopsBrushDeviation', - 'handle--e', - targetPx + intervalPx * testData.brushIntervalFactor - ); - - // Adjust the left brush handle - await aiops.explainLogRateSpikesPage.adjustBrushHandler( - 'aiopsBrushDeviation', - 'handle--w', - targetPx - intervalPx * (testData.brushIntervalFactor - 1) - ); + // Get the current width of the deviation brush for later comparison. + const brushSelectionWidthBefore = + await aiops.explainLogRateSpikesPage.getBrushSelectionWidth('aiopsBrushDeviation'); - if (testData.brushBaselineTargetTimestamp) { // Get the px values for the timestamp we want to move the brush to. - const { targetPx: targetBaselinePx } = - await aiops.explainLogRateSpikesPage.getPxForTimestamp( - testData.brushBaselineTargetTimestamp - ); + const { targetPx, intervalPx } = await aiops.explainLogRateSpikesPage.getPxForTimestamp( + testData.brushDeviationTargetTimestamp + ); // Adjust the right brush handle await aiops.explainLogRateSpikesPage.adjustBrushHandler( - 'aiopsBrushBaseline', + 'aiopsBrushDeviation', 'handle--e', - targetBaselinePx + intervalPx * testData.brushIntervalFactor + targetPx + intervalPx * testData.brushIntervalFactor ); // Adjust the left brush handle await aiops.explainLogRateSpikesPage.adjustBrushHandler( - 'aiopsBrushBaseline', + 'aiopsBrushDeviation', 'handle--w', - targetBaselinePx - intervalPx * (testData.brushIntervalFactor - 1) + targetPx - intervalPx * (testData.brushIntervalFactor - 1) ); - } - // Get the new brush selection width for later comparison. - const brushSelectionWidthAfter = await aiops.explainLogRateSpikesPage.getBrushSelectionWidth( - 'aiopsBrushDeviation' - ); + if (testData.brushBaselineTargetTimestamp) { + // Get the px values for the timestamp we want to move the brush to. + const { targetPx: targetBaselinePx } = + await aiops.explainLogRateSpikesPage.getPxForTimestamp( + testData.brushBaselineTargetTimestamp + ); + + // Adjust the right brush handle + await aiops.explainLogRateSpikesPage.adjustBrushHandler( + 'aiopsBrushBaseline', + 'handle--e', + targetBaselinePx + intervalPx * testData.brushIntervalFactor + ); - // Assert the adjusted brush: The selection width should have changed and - // we test if the selection is smaller than two bucket intervals. - // Finally, the adjusted brush should trigger - // a warning on the "Rerun analysis" button. - expect(brushSelectionWidthBefore).not.to.be(brushSelectionWidthAfter); - expect(brushSelectionWidthAfter).not.to.be.greaterThan( - intervalPx * 2 * testData.brushIntervalFactor - ); + // Adjust the left brush handle + await aiops.explainLogRateSpikesPage.adjustBrushHandler( + 'aiopsBrushBaseline', + 'handle--w', + targetBaselinePx - intervalPx * (testData.brushIntervalFactor - 1) + ); + } + + // Get the new brush selection width for later comparison. + const brushSelectionWidthAfter = + await aiops.explainLogRateSpikesPage.getBrushSelectionWidth('aiopsBrushDeviation'); + + // Assert the adjusted brush: The selection width should have changed and + // we test if the selection is smaller than two bucket intervals. + // Finally, the adjusted brush should trigger + // a warning on the "Rerun analysis" button. + expect(brushSelectionWidthBefore).not.to.be(brushSelectionWidthAfter); + expect(brushSelectionWidthAfter).not.to.be.greaterThan( + intervalPx * 2 * testData.brushIntervalFactor + ); - await aiops.explainLogRateSpikesPage.assertRerunAnalysisButtonExists(true); + await aiops.explainLogRateSpikesPage.assertRerunAnalysisButtonExists(true); - await ml.testExecution.logTestStep('rerun the analysis with adjusted settings'); + await ml.testExecution.logTestStep('rerun the analysis with adjusted settings'); + + await aiops.explainLogRateSpikesPage.clickRerunAnalysisButton(true); + } - await aiops.explainLogRateSpikesPage.clickRerunAnalysisButton(true); await aiops.explainLogRateSpikesPage.assertProgressTitle('Progress: 100% — Done.'); // The group switch should be disabled by default @@ -178,14 +179,14 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { ); } - // Assert the field selector that allows to costumize grouping + await ml.testExecution.logTestStep('open the field filter'); await aiops.explainLogRateSpikesPage.assertFieldFilterPopoverButtonExists(false); await aiops.explainLogRateSpikesPage.clickFieldFilterPopoverButton(true); await aiops.explainLogRateSpikesPage.assertFieldSelectorFieldNameList( testData.expected.fieldSelectorPopover ); - // Filter fields + await ml.testExecution.logTestStep('filter fields'); await aiops.explainLogRateSpikesPage.setFieldSelectorSearch(testData.fieldSelectorSearch); await aiops.explainLogRateSpikesPage.assertFieldSelectorFieldNameList([ testData.fieldSelectorSearch, @@ -196,6 +197,7 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { ); if (testData.fieldSelectorApplyAvailable) { + await ml.testExecution.logTestStep('regroup results'); await aiops.explainLogRateSpikesPage.clickFieldFilterApplyButton(); if (!isTestDataExpectedWithSampleProbability(testData.expected)) { @@ -206,6 +208,28 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { ); } } + + if (testData.action !== undefined) { + await ml.testExecution.logTestStep('check all table row actions are present'); + await aiops.explainLogRateSpikesAnalysisGroupsTable.assertRowActions( + testData.action.tableRowId + ); + + await ml.testExecution.logTestStep('click log pattern analysis action'); + await aiops.explainLogRateSpikesAnalysisGroupsTable.clickRowAction( + testData.action.tableRowId, + testData.action.type + ); + + await ml.testExecution.logTestStep('check log pattern analysis page loaded correctly'); + await aiops.logPatternAnalysisPageProvider.assertLogCategorizationPageExists(); + await aiops.logPatternAnalysisPageProvider.assertTotalDocumentCount( + testData.action.expected.totalDocCount + ); + await aiops.logPatternAnalysisPageProvider.assertQueryInput( + testData.action.expected.queryBar + ); + } }); } @@ -223,13 +247,27 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { await ml.testResources.setKibanaTimeZoneToUTC(); - await ml.securityUI.loginAsMlPowerUser(); + if (testData.dataGenerator === 'kibana_sample_data_logs') { + await PageObjects.security.login('elastic', 'changeme', { + expectSuccess: true, + }); + + await PageObjects.common.navigateToUrl('home', '/tutorial_directory/sampleData', { + useActualUrl: true, + }); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.home.addSampleDataSet('logs'); + await PageObjects.header.waitUntilLoadingHasFinished(); + } else { + await ml.securityUI.loginAsMlPowerUser(); + } }); after(async () => { await elasticChart.setNewChartUiDebugFlag(false); - await ml.testResources.deleteIndexPatternByTitle(testData.sourceIndexOrSavedSearch); - + if (testData.dataGenerator !== 'kibana_sample_data_logs') { + await ml.testResources.deleteIndexPatternByTitle(testData.sourceIndexOrSavedSearch); + } await aiops.explainLogRateSpikesDataGenerator.removeGeneratedData(testData.dataGenerator); }); diff --git a/x-pack/test/functional/apps/aiops/test_data.ts b/x-pack/test/functional/apps/aiops/test_data.ts index b6d3293aeba81..95e21fbfc7800 100644 --- a/x-pack/test/functional/apps/aiops/test_data.ts +++ b/x-pack/test/functional/apps/aiops/test_data.ts @@ -7,6 +7,111 @@ import type { TestData } from './types'; +export const kibanaLogsDataViewTestData: TestData = { + suiteTitle: 'kibana sample data logs', + dataGenerator: 'kibana_sample_data_logs', + isSavedSearch: false, + sourceIndexOrSavedSearch: 'kibana_sample_data_logs', + brushIntervalFactor: 1, + chartClickCoordinates: [235, 0], + fieldSelectorSearch: 'referer', + fieldSelectorApplyAvailable: true, + action: { + type: 'LogPatternAnalysis', + tableRowId: '488337254', + expected: { + queryBar: + 'clientip:30.156.16.164 AND host.keyword:elastic-elastic-elastic.org AND ip:30.156.16.163 AND response.keyword:404 AND machine.os.keyword:win xp AND geo.dest:IN AND geo.srcdest:US\\:IN', + totalDocCount: '100', + }, + }, + expected: { + totalDocCountFormatted: '14,074', + analysisGroupsTable: [ + { + group: + '* clientip: 30.156.16.164* host.keyword: elastic-elastic-elastic.org* ip: 30.156.16.163* referer: http://www.elastic-elastic-elastic.com/success/timothy-l-kopra* response.keyword: 404Showing 5 out of 8 items. 8 items unique to this group.', + docCount: '100', + }, + ], + filteredAnalysisGroupsTable: [ + { + group: + '* clientip: 30.156.16.164* host.keyword: elastic-elastic-elastic.org* ip: 30.156.16.163* response.keyword: 404* machine.os.keyword: win xpShowing 5 out of 7 items. 7 items unique to this group.', + docCount: '100', + }, + ], + analysisTable: [ + { + fieldName: 'clientip', + fieldValue: '30.156.16.164', + logRate: 'Chart type:bar chart', + pValue: '3.10e-13', + impact: 'High', + }, + { + fieldName: 'geo.dest', + fieldValue: 'IN', + logRate: 'Chart type:bar chart', + pValue: '0.000716', + impact: 'Medium', + }, + { + fieldName: 'geo.srcdest', + fieldValue: 'US:IN', + logRate: 'Chart type:bar chart', + pValue: '0.000716', + impact: 'Medium', + }, + { + fieldName: 'host.keyword', + fieldValue: 'elastic-elastic-elastic.org', + logRate: 'Chart type:bar chart', + pValue: '7.14e-9', + impact: 'High', + }, + { + fieldName: 'ip', + fieldValue: '30.156.16.163', + logRate: 'Chart type:bar chart', + pValue: '3.28e-13', + impact: 'High', + }, + { + fieldName: 'machine.os.keyword', + fieldValue: 'win xp', + logRate: 'Chart type:bar chart', + pValue: '0.0000997', + impact: 'Medium', + }, + { + fieldName: 'referer', + fieldValue: 'http://www.elastic-elastic-elastic.com/success/timothy-l-kopra', + logRate: 'Chart type:bar chart', + pValue: '4.74e-13', + impact: 'High', + }, + { + fieldName: 'response.keyword', + fieldValue: '404', + logRate: 'Chart type:bar chart', + pValue: '0.00000604', + impact: 'Medium', + }, + ], + fieldSelectorPopover: [ + 'clientip', + 'geo.dest', + 'geo.srcdest', + 'host.keyword', + 'ip', + 'machine.os.keyword', + 'referer', + 'response.keyword', + ], + }, +}; + export const farequoteDataViewTestData: TestData = { suiteTitle: 'farequote with spike', dataGenerator: 'farequote_with_spike', @@ -122,6 +227,7 @@ export const artificialLogDataViewTestData: TestData = { }; export const explainLogRateSpikesTestData: TestData[] = [ + kibanaLogsDataViewTestData, farequoteDataViewTestData, farequoteDataViewTestDataWithQuery, artificialLogDataViewTestData, diff --git a/x-pack/test/functional/apps/aiops/types.ts b/x-pack/test/functional/apps/aiops/types.ts index 01733a8e1a2af..2093d4d961363 100644 --- a/x-pack/test/functional/apps/aiops/types.ts +++ b/x-pack/test/functional/apps/aiops/types.ts @@ -7,6 +7,15 @@ import { isPopulatedObject } from '@kbn/ml-is-populated-object'; +interface TestDataTableActionLogPatternAnalysis { + type: 'LogPatternAnalysis'; + tableRowId: string; + expected: { + queryBar: string; + totalDocCount: string; + }; +} + interface TestDataExpectedWithSampleProbability { totalDocCountFormatted: string; sampleProbabilityFormatted: string; @@ -40,11 +49,12 @@ export interface TestData { sourceIndexOrSavedSearch: string; rowsPerPage?: 10 | 25 | 50; brushBaselineTargetTimestamp?: number; - brushDeviationTargetTimestamp: number; + brushDeviationTargetTimestamp?: number; brushIntervalFactor: number; chartClickCoordinates: [number, number]; fieldSelectorSearch: string; fieldSelectorApplyAvailable: boolean; query?: string; + action?: TestDataTableActionLogPatternAnalysis; expected: TestDataExpectedWithSampleProbability | TestDataExpectedWithoutSampleProbability; } diff --git a/x-pack/test/functional/services/aiops/explain_log_rate_spikes_analysis_groups_table.ts b/x-pack/test/functional/services/aiops/explain_log_rate_spikes_analysis_groups_table.ts index 18cadebbf9afd..b533c50677944 100644 --- a/x-pack/test/functional/services/aiops/explain_log_rate_spikes_analysis_groups_table.ts +++ b/x-pack/test/functional/services/aiops/explain_log_rate_spikes_analysis_groups_table.ts @@ -5,12 +5,17 @@ * 2.0. */ +import expect from '@kbn/expect'; + import { FtrProviderContext } from '../../ftr_provider_context'; export function ExplainLogRateSpikesAnalysisGroupsTableProvider({ getService, }: FtrProviderContext) { + const find = getService('find'); const testSubjects = getService('testSubjects'); + const retry = getService('retry'); + const browser = getService('browser'); return new (class AnalysisTable { public async assertSpikeAnalysisTableExists() { @@ -55,5 +60,50 @@ export function ExplainLogRateSpikesAnalysisGroupsTableProvider({ return rows; } + + public rowSelector(rowId: string, subSelector?: string) { + const row = `~aiopsSpikeAnalysisGroupsTable > ~row-${rowId}`; + return !subSelector ? row : `${row} > ${subSelector}`; + } + + public async ensureActionsMenuOpen(rowId: string) { + await retry.tryForTime(30 * 1000, async () => { + await this.ensureActionsMenuClosed(); + + if (!(await find.existsByCssSelector('.euiContextMenuPanel', 1000))) { + await testSubjects.click(this.rowSelector(rowId, 'euiCollapsedItemActionsButton')); + expect(await find.existsByCssSelector('.euiContextMenuPanel', 1000)).to.eql( + true, + 'Actions popover should exist' + ); + } + }); + } + + public async ensureActionsMenuClosed() { + await retry.tryForTime(30 * 1000, async () => { + await browser.pressKeys(browser.keys.ESCAPE); + expect(await find.existsByCssSelector('.euiContextMenuPanel', 1000)).to.eql( + false, + 'Actions popover should not exist' + ); + }); + } + + public async assertRowActions(rowId: string) { + await this.ensureActionsMenuOpen(rowId); + + await testSubjects.existOrFail('aiopsTableActionButtonCopyToClipboard enabled'); + await testSubjects.existOrFail('aiopsTableActionButtonDiscover enabled'); + await testSubjects.existOrFail('aiopsTableActionButtonLogPatternAnalysis enabled'); + + await this.ensureActionsMenuClosed(); + } + + public async clickRowAction(rowId: string, action: string) { + await this.ensureActionsMenuOpen(rowId); + await testSubjects.click(`aiopsTableActionButton${action} enabled`); + await testSubjects.missingOrFail(`aiopsTableActionButton${action} enabled`); + } })(); } diff --git a/x-pack/test/functional/services/aiops/explain_log_rate_spikes_data_generator.ts b/x-pack/test/functional/services/aiops/explain_log_rate_spikes_data_generator.ts index 1a80ac679f29b..228d47bbc746f 100644 --- a/x-pack/test/functional/services/aiops/explain_log_rate_spikes_data_generator.ts +++ b/x-pack/test/functional/services/aiops/explain_log_rate_spikes_data_generator.ts @@ -122,6 +122,10 @@ export function ExplainLogRateSpikesDataGeneratorProvider({ getService }: FtrPro return new (class DataGenerator { public async generateData(dataGenerator: string) { switch (dataGenerator) { + case 'kibana_sample_data_logs': + // will be added via UI + break; + case 'farequote_with_spike': await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/farequote'); @@ -191,6 +195,10 @@ export function ExplainLogRateSpikesDataGeneratorProvider({ getService }: FtrPro public async removeGeneratedData(dataGenerator: string) { switch (dataGenerator) { + case 'kibana_sample_data_logs': + // do not remove + break; + case 'farequote_with_spike': await esArchiver.unload('x-pack/test/functional/es_archives/ml/farequote'); break; diff --git a/x-pack/test/functional/services/aiops/explain_log_rate_spikes_page.ts b/x-pack/test/functional/services/aiops/explain_log_rate_spikes_page.ts index 3da9ed7c760b7..736437a1d3976 100644 --- a/x-pack/test/functional/services/aiops/explain_log_rate_spikes_page.ts +++ b/x-pack/test/functional/services/aiops/explain_log_rate_spikes_page.ts @@ -229,9 +229,11 @@ export function ExplainLogRateSpikesPageProvider({ }, async assertProgressTitle(expectedProgressTitle: string) { - await testSubjects.existOrFail('aiopProgressTitle'); - const currentProgressTitle = await testSubjects.getVisibleText('aiopProgressTitle'); - expect(currentProgressTitle).to.be(expectedProgressTitle); + await retry.tryForTime(30 * 1000, async () => { + await testSubjects.existOrFail('aiopProgressTitle'); + const currentProgressTitle = await testSubjects.getVisibleText('aiopProgressTitle'); + expect(currentProgressTitle).to.be(expectedProgressTitle); + }); }, async navigateToIndexPatternSelection() { diff --git a/x-pack/test/functional/services/aiops/index.ts b/x-pack/test/functional/services/aiops/index.ts index 4816d37bcff04..8c208f182f3bd 100644 --- a/x-pack/test/functional/services/aiops/index.ts +++ b/x-pack/test/functional/services/aiops/index.ts @@ -11,6 +11,7 @@ import { ExplainLogRateSpikesPageProvider } from './explain_log_rate_spikes_page import { ExplainLogRateSpikesAnalysisTableProvider } from './explain_log_rate_spikes_analysis_table'; import { ExplainLogRateSpikesAnalysisGroupsTableProvider } from './explain_log_rate_spikes_analysis_groups_table'; import { ExplainLogRateSpikesDataGeneratorProvider } from './explain_log_rate_spikes_data_generator'; +import { LogPatternAnalysisPageProvider } from './log_pattern_analysis_page'; export function AiopsProvider(context: FtrProviderContext) { const explainLogRateSpikesPage = ExplainLogRateSpikesPageProvider(context); @@ -18,11 +19,13 @@ export function AiopsProvider(context: FtrProviderContext) { const explainLogRateSpikesAnalysisGroupsTable = ExplainLogRateSpikesAnalysisGroupsTableProvider(context); const explainLogRateSpikesDataGenerator = ExplainLogRateSpikesDataGeneratorProvider(context); + const logPatternAnalysisPageProvider = LogPatternAnalysisPageProvider(context); return { explainLogRateSpikesPage, explainLogRateSpikesAnalysisTable, explainLogRateSpikesAnalysisGroupsTable, explainLogRateSpikesDataGenerator, + logPatternAnalysisPageProvider, }; } diff --git a/x-pack/test/functional/services/aiops/log_pattern_analysis_page.ts b/x-pack/test/functional/services/aiops/log_pattern_analysis_page.ts new file mode 100644 index 0000000000000..37872b8d7c051 --- /dev/null +++ b/x-pack/test/functional/services/aiops/log_pattern_analysis_page.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 type { FtrProviderContext } from '../../ftr_provider_context'; + +export function LogPatternAnalysisPageProvider({ getService, getPageObject }: FtrProviderContext) { + const retry = getService('retry'); + const testSubjects = getService('testSubjects'); + + return { + async assertLogCategorizationPageExists() { + await retry.tryForTime(30 * 1000, async () => { + await testSubjects.existOrFail('aiopsLogCategorizationPage'); + }); + }, + + async assertQueryInput(expectedQueryString: string) { + const aiopsQueryInput = await testSubjects.find('aiopsQueryInput'); + const actualQueryString = await aiopsQueryInput.getVisibleText(); + expect(actualQueryString).to.eql( + expectedQueryString, + `Expected query bar text to be '${expectedQueryString}' (got '${actualQueryString}')` + ); + }, + + async assertTotalDocumentCount(expectedFormattedTotalDocCount: string) { + await retry.tryForTime(5000, async () => { + const docCount = await testSubjects.getVisibleText('aiopsTotalDocCount'); + expect(docCount).to.eql( + expectedFormattedTotalDocCount, + `Expected total document count to be '${expectedFormattedTotalDocCount}' (got '${docCount}')` + ); + }); + }, + }; +} From 06545277d7dfc379d69c2e68879fc409fa5b71fd Mon Sep 17 00:00:00 2001 From: Carlos Crespo Date: Mon, 24 Apr 2023 15:02:44 -0300 Subject: [PATCH 05/52] [Infrastructure UI] Replace Snapshot API with InfraMetrics API in Hosts View (#155531) closes [#154443](https://github.com/elastic/kibana/issues/154443) ## Summary This PR replaces the usage of the Snapshot API in favor of the new `metrics/infra` endpoint and also includes a new control in the Search Bar to allow users to select how many hosts they want the API to return. https://user-images.githubusercontent.com/2767137/233728658-bccc7258-6955-47fb-8f7b-85ef6ec5d0f9.mov Because the KPIs now needs to show an "Average (of X hosts)", they will only start fetching the data once the table has been loaded. The hosts count KPI tile was not converted to Lens, because the page needs to know the total number of hosts. ### Possible follow-up Since now everything depends on the table to be loaded, I have been experimenting with batched requests to the new API. The idea is to fetch at least the host names as soon as possible. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../http_api/infra/get_infra_metrics.ts | 6 +- .../infra/public/hooks/use_lens_attributes.ts | 8 +- .../hosts/components/chart/lens_wrapper.tsx | 23 +++- .../components/chart/metric_chart_wrapper.tsx | 70 ++-------- .../hosts/components/hosts_container.tsx | 2 +- .../hosts/components/kpis/hosts_tile.tsx | 44 ++++-- .../hosts/components/kpis/kpi_grid.tsx | 62 +++------ .../metrics/hosts/components/kpis/tile.tsx | 64 +++++++-- .../{ => search_bar}/controls_content.tsx | 22 ++- .../components/search_bar/limit_options.tsx | 81 +++++++++++ .../search_bar/unified_search_bar.tsx | 126 +++++++++++++++++ .../components/tabs/logs/logs_tab_content.tsx | 7 +- .../components/tabs/metrics/metric_chart.tsx | 4 +- .../components/tabs/metrics/metrics_grid.tsx | 36 ++--- .../hosts/components/unified_search_bar.tsx | 98 ------------- .../public/pages/metrics/hosts/constants.ts | 6 +- .../metrics/hosts/hooks/use_alerts_query.ts | 4 +- .../metrics/hosts/hooks/use_data_view.ts | 1 + .../metrics/hosts/hooks/use_host_count.ts | 129 ++++++++++++++++++ .../hosts/hooks/use_hosts_table.test.ts | 31 +++-- .../metrics/hosts/hooks/use_hosts_table.tsx | 63 +++++---- .../metrics/hosts/hooks/use_hosts_view.ts | 91 ++++++------ .../metrics/hosts/hooks/use_unified_search.ts | 30 ++-- .../hooks/use_unified_search_url_state.ts | 18 ++- .../infra/public/pages/metrics/hosts/types.ts | 4 +- .../server/routes/infra/lib/constants.ts | 3 + x-pack/plugins/infra/tsconfig.json | 2 +- .../translations/translations/fr-FR.json | 10 +- .../translations/translations/ja-JP.json | 10 +- .../translations/translations/zh-CN.json | 10 +- .../api_integration/apis/metrics_ui/infra.ts | 4 + 31 files changed, 673 insertions(+), 396 deletions(-) rename x-pack/plugins/infra/public/pages/metrics/hosts/components/{ => search_bar}/controls_content.tsx (79%) create mode 100644 x-pack/plugins/infra/public/pages/metrics/hosts/components/search_bar/limit_options.tsx create mode 100644 x-pack/plugins/infra/public/pages/metrics/hosts/components/search_bar/unified_search_bar.tsx delete mode 100644 x-pack/plugins/infra/public/pages/metrics/hosts/components/unified_search_bar.tsx create mode 100644 x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_host_count.ts diff --git a/x-pack/plugins/infra/common/http_api/infra/get_infra_metrics.ts b/x-pack/plugins/infra/common/http_api/infra/get_infra_metrics.ts index bcfeeafcee06f..80e5e501169d6 100644 --- a/x-pack/plugins/infra/common/http_api/infra/get_infra_metrics.ts +++ b/x-pack/plugins/infra/common/http_api/infra/get_infra_metrics.ts @@ -23,8 +23,9 @@ export const RangeRT = rt.type({ }); export const InfraAssetMetadataTypeRT = rt.keyof({ - 'host.os.name': null, 'cloud.provider': null, + 'host.ip': null, + 'host.os.name': null, }); export const InfraAssetMetricsRT = rt.type({ @@ -35,7 +36,7 @@ export const InfraAssetMetricsRT = rt.type({ export const InfraAssetMetadataRT = rt.type({ // keep the actual field name from the index mappings name: InfraAssetMetadataTypeRT, - value: rt.union([rt.string, rt.number, rt.null]), + value: rt.union([rt.string, rt.null]), }); export const GetInfraMetricsRequestBodyPayloadRT = rt.intersection([ @@ -64,6 +65,7 @@ export const GetInfraMetricsResponsePayloadRT = rt.type({ export type InfraAssetMetrics = rt.TypeOf; export type InfraAssetMetadata = rt.TypeOf; +export type InfraAssetMetadataType = rt.TypeOf; export type InfraAssetMetricType = rt.TypeOf; export type InfraAssetMetricsItem = rt.TypeOf; diff --git a/x-pack/plugins/infra/public/hooks/use_lens_attributes.ts b/x-pack/plugins/infra/public/hooks/use_lens_attributes.ts index c9ce48c909f9a..6250d20750e29 100644 --- a/x-pack/plugins/infra/public/hooks/use_lens_attributes.ts +++ b/x-pack/plugins/infra/public/hooks/use_lens_attributes.ts @@ -74,11 +74,7 @@ export const useLensAttributes = ({ return visualizationAttributes; }, [dataView, formulaAPI, options, type, visualizationType]); - const injectFilters = (data: { - timeRange: TimeRange; - filters: Filter[]; - query: Query; - }): LensAttributes | null => { + const injectFilters = (data: { filters: Filter[]; query: Query }): LensAttributes | null => { if (!attributes) { return null; } @@ -121,7 +117,7 @@ export const useLensAttributes = ({ return true; }, async execute(_context: ActionExecutionContext): Promise { - const injectedAttributes = injectFilters({ timeRange, filters, query }); + const injectedAttributes = injectFilters({ filters, query }); if (injectedAttributes) { navigateToPrefilledEditor( { diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/chart/lens_wrapper.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/chart/lens_wrapper.tsx index 9a2472949f54c..34e536aaf37d2 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/chart/lens_wrapper.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/chart/lens_wrapper.tsx @@ -15,7 +15,7 @@ import { useIntersectedOnce } from '../../../../../hooks/use_intersection_once'; import { LensAttributes } from '../../../../../common/visualizations'; import { ChartLoader } from './chart_loader'; -export interface Props { +export interface LensWrapperProps { id: string; attributes: LensAttributes | null; dateRange: TimeRange; @@ -42,11 +42,12 @@ export const LensWrapper = ({ lastReloadRequestTime, loading = false, hasTitle = false, -}: Props) => { +}: LensWrapperProps) => { const intersectionRef = useRef(null); const [loadedOnce, setLoadedOnce] = useState(false); const [state, setState] = useState({ + attributes, lastReloadRequestTime, query, filters, @@ -65,15 +66,23 @@ export const LensWrapper = ({ useEffect(() => { if ((intersection?.intersectionRatio ?? 0) === 1) { setState({ + attributes, lastReloadRequestTime, query, - dateRange, filters, + dateRange, }); } - }, [dateRange, filters, intersection?.intersectionRatio, lastReloadRequestTime, query]); + }, [ + attributes, + dateRange, + filters, + intersection?.intersectionRatio, + lastReloadRequestTime, + query, + ]); - const isReady = attributes && intersectedOnce; + const isReady = state.attributes && intersectedOnce; return (
@@ -83,11 +92,11 @@ export const LensWrapper = ({ style={style} hasTitle={hasTitle} > - {isReady && ( + {state.attributes && ( ; - -type AcceptedType = SnapshotMetricType | 'hostsCount'; - -export interface ChartBaseProps - extends Pick< - MetricWTrend, - 'title' | 'color' | 'extra' | 'subtitle' | 'trendA11yDescription' | 'trendA11yTitle' - > { - type: AcceptedType; - toolTip: string; - metricType: MetricType; - ['data-test-subj']?: string; -} - -interface Props extends ChartBaseProps { +export interface Props extends Pick { id: string; - nodes: SnapshotNode[]; loading: boolean; - overrideValue?: number; + value: number; + toolTip: string; + ['data-test-subj']?: string; } const MIN_HEIGHT = 150; @@ -49,23 +25,13 @@ export const MetricChartWrapper = ({ extra, id, loading, - metricType, - nodes, - overrideValue, + value, subtitle, title, toolTip, - trendA11yDescription, - trendA11yTitle, - type, ...props }: Props) => { const loadedOnce = useRef(false); - const metrics = useMemo(() => (nodes ?? [])[0]?.metrics ?? [], [nodes]); - const metricsTimeseries = useMemo( - () => (metrics ?? []).find((m) => m.name === type)?.timeseries, - [metrics, type] - ); useEffect(() => { if (!loadedOnce.current && !loading) { @@ -76,29 +42,13 @@ export const MetricChartWrapper = ({ }; }, [loading]); - const metricsValue = useMemo(() => { - if (overrideValue) { - return overrideValue; - } - return (metrics ?? []).find((m) => m.name === type)?.[metricType] ?? 0; - }, [metricType, metrics, overrideValue, type]); - const metricsData: MetricWNumber = { title, subtitle, color, extra, - value: metricsValue, - valueFormatter: (d: number) => - type === 'hostsCount' ? d.toString() : createInventoryMetricFormatter({ type })(d), - ...(!!metricsTimeseries - ? { - trend: metricsTimeseries.rows.map((row) => ({ x: row.timestamp, y: row.metric_0 ?? 0 })), - trendShape: MetricTrendShape.Area, - trendA11yTitle, - trendA11yDescription, - } - : {}), + value, + valueFormatter: (d: number) => d.toString(), }; return ( diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_container.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_container.tsx index 0c965feca8e9e..d42944857af34 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_container.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_container.tsx @@ -10,7 +10,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { InfraLoadingPanel } from '../../../../components/loading'; import { useMetricsDataViewContext } from '../hooks/use_data_view'; -import { UnifiedSearchBar } from './unified_search_bar'; +import { UnifiedSearchBar } from './search_bar/unified_search_bar'; import { HostsTable } from './hosts_table'; import { KPIGrid } from './kpis/kpi_grid'; import { Tabs } from './tabs/tabs'; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpis/hosts_tile.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpis/hosts_tile.tsx index 396c0bd72ad71..14a617682bf25 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpis/hosts_tile.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpis/hosts_tile.tsx @@ -4,22 +4,46 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { i18n } from '@kbn/i18n'; import React from 'react'; +import { useHostCountContext } from '../../hooks/use_host_count'; +import { useUnifiedSearchContext } from '../../hooks/use_unified_search'; -import { useHostsViewContext } from '../../hooks/use_hosts_view'; -import { type ChartBaseProps, MetricChartWrapper } from '../chart/metric_chart_wrapper'; +import { type Props, MetricChartWrapper } from '../chart/metric_chart_wrapper'; -export const HostsTile = ({ type, ...props }: ChartBaseProps) => { - const { hostNodes, loading } = useHostsViewContext(); +const HOSTS_CHART: Omit = { + id: `metric-hostCount`, + color: '#6DCCB1', + title: i18n.translate('xpack.infra.hostsViewPage.metricTrend.hostCount.title', { + defaultMessage: 'Hosts', + }), + toolTip: i18n.translate('xpack.infra.hostsViewPage.metricTrend.hostCount.tooltip', { + defaultMessage: 'The number of hosts returned by your current search criteria.', + }), + ['data-test-subj']: 'hostsView-metricsTrend-hosts', +}; + +export const HostsTile = () => { + const { data: hostCountData, isRequestRunning: hostCountLoading } = useHostCountContext(); + const { searchCriteria } = useUnifiedSearchContext(); + + const getSubtitle = () => { + return searchCriteria.limit < (hostCountData?.count.value ?? 0) + ? i18n.translate('xpack.infra.hostsViewPage.metricTrend.subtitle.hostCount.limit', { + defaultMessage: 'Limited to {limit}', + values: { + limit: searchCriteria.limit, + }, + }) + : undefined; + }; return ( ); }; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpis/kpi_grid.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpis/kpi_grid.tsx index 2dbd0c4324eca..c3f751d26befb 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpis/kpi_grid.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpis/kpi_grid.tsx @@ -9,10 +9,10 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { KPIChartProps, Tile } from './tile'; +import { HostCountProvider } from '../../hooks/use_host_count'; import { HostsTile } from './hosts_tile'; -import { ChartBaseProps } from '../chart/metric_chart_wrapper'; -const KPI_CHARTS: KPIChartProps[] = [ +const KPI_CHARTS: Array> = [ { type: 'cpu', trendLine: true, @@ -20,9 +20,6 @@ const KPI_CHARTS: KPIChartProps[] = [ title: i18n.translate('xpack.infra.hostsViewPage.metricTrend.cpu.title', { defaultMessage: 'CPU usage', }), - subtitle: i18n.translate('xpack.infra.hostsViewPage.metricTrend.cpu.subtitle', { - defaultMessage: 'Average', - }), toolTip: i18n.translate('xpack.infra.hostsViewPage.metricTrend.cpu.tooltip', { defaultMessage: 'Average of percentage of CPU time spent in states other than Idle and IOWait, normalized 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.', @@ -35,9 +32,6 @@ const KPI_CHARTS: KPIChartProps[] = [ title: i18n.translate('xpack.infra.hostsViewPage.metricTrend.memory.title', { defaultMessage: 'Memory usage', }), - subtitle: i18n.translate('xpack.infra.hostsViewPage.metricTrend.memory.subtitle', { - defaultMessage: 'Average', - }), toolTip: i18n.translate('xpack.infra.hostsViewPage.metricTrend.memory.tooltip', { defaultMessage: "Average of percentage of main memory usage excluding page cache. This includes resident memory for all processes plus memory used by the kernel structures and code apart the page cache. A high level indicates a situation of memory saturation for a host. 100% means the main memory is entirely filled with memory that can't be reclaimed, except by swapping out.", @@ -50,9 +44,6 @@ const KPI_CHARTS: KPIChartProps[] = [ title: i18n.translate('xpack.infra.hostsViewPage.metricTrend.rx.title', { defaultMessage: 'Network inbound (RX)', }), - subtitle: i18n.translate('xpack.infra.hostsViewPage.metricTrend.rx.subtitle', { - defaultMessage: 'Average', - }), toolTip: i18n.translate('xpack.infra.hostsViewPage.metricTrend.rx.tooltip', { defaultMessage: 'Number of bytes which have been received per second on the public interfaces of the hosts.', @@ -65,9 +56,6 @@ const KPI_CHARTS: KPIChartProps[] = [ title: i18n.translate('xpack.infra.hostsViewPage.metricTrend.tx.title', { defaultMessage: 'Network outbound (TX)', }), - subtitle: i18n.translate('xpack.infra.hostsViewPage.metricTrend.tx.subtitle', { - defaultMessage: 'Average', - }), toolTip: i18n.translate('xpack.infra.hostsViewPage.metricTrend.tx.tooltip', { defaultMessage: 'Number of bytes which have been received per second on the public interfaces of the hosts.', @@ -75,38 +63,24 @@ const KPI_CHARTS: KPIChartProps[] = [ }, ]; -const HOSTS_CHART: ChartBaseProps = { - type: 'hostsCount', - color: '#6DCCB1', - metricType: 'value', - title: i18n.translate('xpack.infra.hostsViewPage.metricTrend.hostCount.title', { - defaultMessage: 'Hosts', - }), - trendA11yTitle: i18n.translate('xpack.infra.hostsViewPage.metricTrend.hostCount.a11y.title', { - defaultMessage: 'Hosts count.', - }), - toolTip: i18n.translate('xpack.infra.hostsViewPage.metricTrend.hostCount.tooltip', { - defaultMessage: 'The number of hosts returned by your current search criteria.', - }), - ['data-test-subj']: 'hostsView-metricsTrend-hosts', -}; - export const KPIGrid = () => { return ( - - - - - {KPI_CHARTS.map(({ ...chartProp }) => ( - - + + + + - ))} - + {KPI_CHARTS.map(({ ...chartProp }) => ( + + + + ))} + + ); }; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpis/tile.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpis/tile.tsx index a95f18b4a10ee..89eebeefd240e 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpis/tile.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpis/tile.tsx @@ -4,9 +4,9 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React from 'react'; +import React, { useMemo } from 'react'; -import { Action } from '@kbn/ui-actions-plugin/public'; +import { i18n } from '@kbn/i18n'; import { BrushTriggerEvent } from '@kbn/charts-plugin/public'; import { EuiIcon, @@ -24,6 +24,9 @@ import { useUnifiedSearchContext } from '../../hooks/use_unified_search'; import { HostsLensMetricChartFormulas } from '../../../../../common/visualizations'; import { useHostsViewContext } from '../../hooks/use_hosts_view'; import { LensWrapper } from '../chart/lens_wrapper'; +import { createHostsFilter } from '../../utils'; +import { useHostCountContext } from '../../hooks/use_host_count'; +import { useAfterLoadedState } from '../../hooks/use_after_loaded_state'; export interface KPIChartProps { title: string; @@ -38,7 +41,6 @@ const MIN_HEIGHT = 150; export const Tile = ({ title, - subtitle, type, backgroundColor, toolTip, @@ -46,14 +48,28 @@ export const Tile = ({ }: KPIChartProps) => { const { searchCriteria, onSubmit } = useUnifiedSearchContext(); const { dataView } = useMetricsDataViewContext(); - const { baseRequest } = useHostsViewContext(); + const { requestTs, hostNodes, loading: hostsLoading } = useHostsViewContext(); + const { data: hostCountData, isRequestRunning: hostCountLoading } = useHostCountContext(); + + const getSubtitle = () => { + return searchCriteria.limit < (hostCountData?.count.value ?? 0) + ? i18n.translate('xpack.infra.hostsViewPage.metricTrend.subtitle.average.limit', { + defaultMessage: 'Average (of {limit} hosts)', + values: { + limit: searchCriteria.limit, + }, + }) + : i18n.translate('xpack.infra.hostsViewPage.metricTrend.subtitle.average', { + defaultMessage: 'Average', + }); + }; const { attributes, getExtraActions, error } = useLensAttributes({ type, dataView, options: { title, - subtitle, + subtitle: getSubtitle(), backgroundColor, showTrendLine: trendLine, showTitle: false, @@ -61,15 +77,24 @@ export const Tile = ({ visualizationType: 'metricChart', }); - const filters = [...searchCriteria.filters, ...searchCriteria.panelFilters]; + const hostsFilterQuery = useMemo(() => { + return createHostsFilter( + hostNodes.map((p) => p.name), + dataView + ); + }, [hostNodes, dataView]); + + const filters = useMemo( + () => [...searchCriteria.filters, ...searchCriteria.panelFilters, ...[hostsFilterQuery]], + [hostsFilterQuery, searchCriteria.filters, searchCriteria.panelFilters] + ); + const extraActionOptions = getExtraActions({ timeRange: searchCriteria.dateRange, filters, query: searchCriteria.query, }); - const extraActions: Action[] = [extraActionOptions.openInLens]; - const handleBrushEnd = ({ range }: BrushTriggerEvent['data']) => { const [min, max] = range; onSubmit({ @@ -81,6 +106,14 @@ export const Tile = ({ }); }; + const loading = hostsLoading || !attributes || hostCountLoading; + const { afterLoadedState } = useAfterLoadedState(loading, { + attributes, + lastReloadRequestTime: requestTs, + ...searchCriteria, + filters, + }); + return ( )} @@ -134,7 +168,7 @@ export const Tile = ({ const EuiPanelStyled = styled(EuiPanel)` .echMetric { - border-radius: ${(p) => p.theme.eui.euiBorderRadius}; + border-radius: ${({ theme }) => theme.eui.euiBorderRadius}; pointer-events: none; } `; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/controls_content.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/search_bar/controls_content.tsx similarity index 79% rename from x-pack/plugins/infra/public/pages/metrics/hosts/components/controls_content.tsx rename to x-pack/plugins/infra/public/pages/metrics/hosts/components/search_bar/controls_content.tsx index a3e82b9901422..e2bd7d0c74dae 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/controls_content.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/search_bar/controls_content.tsx @@ -12,15 +12,16 @@ import { type ControlGroupInput, } from '@kbn/controls-plugin/public'; import { ViewMode } from '@kbn/embeddable-plugin/public'; -import type { Filter, Query, TimeRange } from '@kbn/es-query'; +import { compareFilters, COMPARE_ALL_OPTIONS, Filter, Query, TimeRange } from '@kbn/es-query'; import { DataView } from '@kbn/data-views-plugin/public'; -import { Subscription } from 'rxjs'; -import { useControlPanels } from '../hooks/use_control_panels_url_state'; +import { skipWhile, Subscription } from 'rxjs'; +import { useControlPanels } from '../../hooks/use_control_panels_url_state'; interface Props { dataView: DataView | undefined; timeRange: TimeRange; filters: Filter[]; + selectedOptions: Filter[]; query: Query; onFiltersChange: (filters: Filter[]) => void; } @@ -29,6 +30,7 @@ export const ControlsContent: React.FC = ({ dataView, filters, query, + selectedOptions, timeRange, onFiltersChange, }) => { @@ -55,15 +57,21 @@ export const ControlsContent: React.FC = ({ const loadCompleteHandler = useCallback( (controlGroup: ControlGroupAPI) => { if (!controlGroup) return; - inputSubscription.current = controlGroup.onFiltersPublished$.subscribe((newFilters) => { - onFiltersChange(newFilters); - }); + inputSubscription.current = controlGroup.onFiltersPublished$ + .pipe( + skipWhile((newFilters) => + compareFilters(selectedOptions, newFilters, COMPARE_ALL_OPTIONS) + ) + ) + .subscribe((newFilters) => { + onFiltersChange(newFilters); + }); filterSubscription.current = controlGroup .getInput$() .subscribe(({ panels }) => setControlPanels(panels)); }, - [onFiltersChange, setControlPanels] + [onFiltersChange, setControlPanels, selectedOptions] ); useEffect(() => { diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/search_bar/limit_options.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/search_bar/limit_options.tsx new file mode 100644 index 0000000000000..1d8ce4f9c9e87 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/search_bar/limit_options.tsx @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { + EuiButtonGroup, + EuiButtonGroupOptionProps, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiToolTip, + EuiText, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { HOST_LIMIT_OPTIONS } from '../../constants'; +import { HostLimitOptions } from '../../types'; + +interface Props { + limit: HostLimitOptions; + onChange: (limit: number) => void; +} + +export const LimitOptions = ({ limit, onChange }: Props) => { + return ( + + + + + + + + + + + + + + + onChange(value)} + /> + + + ); +}; + +const buildId = (option: number) => `hostLimit_${option}`; +const options: EuiButtonGroupOptionProps[] = HOST_LIMIT_OPTIONS.map((option) => ({ + id: buildId(option), + label: `${option}`, + value: option, + 'data-test-subj': `hostsViewLimitSelection${option}button`, +})); diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/search_bar/unified_search_bar.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/search_bar/unified_search_bar.tsx new file mode 100644 index 0000000000000..ef515cc018839 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/search_bar/unified_search_bar.tsx @@ -0,0 +1,126 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useMemo } from 'react'; +import { compareFilters, COMPARE_ALL_OPTIONS, type Filter } from '@kbn/es-query'; +import { i18n } from '@kbn/i18n'; +import { + EuiFlexGrid, + useEuiTheme, + EuiHorizontalRule, + EuiFlexGroup, + EuiFlexItem, +} from '@elastic/eui'; +import { css } from '@emotion/react'; +import { METRICS_APP_DATA_TEST_SUBJ } from '../../../../../apps/metrics_app'; +import { useKibanaContextForPlugin } from '../../../../../hooks/use_kibana'; +import { useUnifiedSearchContext } from '../../hooks/use_unified_search'; +import { ControlsContent } from './controls_content'; +import { useMetricsDataViewContext } from '../../hooks/use_data_view'; +import { HostsSearchPayload } from '../../hooks/use_unified_search_url_state'; +import { LimitOptions } from './limit_options'; +import { HostLimitOptions } from '../../types'; + +export const UnifiedSearchBar = () => { + const { + services: { unifiedSearch, application }, + } = useKibanaContextForPlugin(); + const { dataView } = useMetricsDataViewContext(); + const { searchCriteria, onSubmit } = useUnifiedSearchContext(); + + const { SearchBar } = unifiedSearch.ui; + + const onLimitChange = (limit: number) => { + onSubmit({ limit }); + }; + + const onPanelFiltersChange = (panelFilters: Filter[]) => { + if (!compareFilters(searchCriteria.panelFilters, panelFilters, COMPARE_ALL_OPTIONS)) { + onSubmit({ panelFilters }); + } + }; + + const handleRefresh = (payload: HostsSearchPayload, isUpdate?: boolean) => { + // This makes sure `onQueryChange` is only called when the submit button is clicked + if (isUpdate === false) { + onSubmit(payload); + } + }; + + return ( + + + + 0.5)', + })} + onQuerySubmit={handleRefresh} + showSaveQuery={Boolean(application?.capabilities?.visualize?.saveQuery)} + showDatePicker + showFilterBar + showQueryInput + showQueryMenu + useDefaultBehaviors + /> + + + + + + + + + + + + + + + ); +}; + +const StickyContainer = (props: { children: React.ReactNode }) => { + const { euiTheme } = useEuiTheme(); + + const top = useMemo(() => { + const wrapper = document.querySelector(`[data-test-subj="${METRICS_APP_DATA_TEST_SUBJ}"]`); + if (!wrapper) { + return `calc(${euiTheme.size.xxxl} * 2)`; + } + + return `${wrapper.getBoundingClientRect().top}px`; + }, [euiTheme]); + + return ( + + ); +}; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/logs/logs_tab_content.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/logs/logs_tab_content.tsx index d5cc0b0f021d7..6813dee1caa10 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/logs/logs_tab_content.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/logs/logs_tab_content.tsx @@ -9,7 +9,6 @@ import React, { useMemo } from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { InfraLoadingPanel } from '../../../../../../components/loading'; -import { SnapshotNode } from '../../../../../../../common/http_api'; import { LogStream } from '../../../../../../components/log_stream'; import { useHostsViewContext } from '../../../hooks/use_hosts_view'; import { useUnifiedSearchContext } from '../../../hooks/use_unified_search'; @@ -30,7 +29,7 @@ export const LogsTabContent = () => { ); const logsLinkToStreamQuery = useMemo(() => { - const hostsFilterQueryParam = createHostsFilterQueryParam(hostNodes); + const hostsFilterQueryParam = createHostsFilterQueryParam(hostNodes.map((p) => p.name)); if (filterQuery.query && hostsFilterQueryParam) { return `${filterQuery.query} and ${hostsFilterQueryParam}`; @@ -83,12 +82,12 @@ export const LogsTabContent = () => { ); }; -const createHostsFilterQueryParam = (hostNodes: SnapshotNode[]): string => { +const createHostsFilterQueryParam = (hostNodes: string[]): string => { if (!hostNodes.length) { return ''; } - const joinedHosts = hostNodes.map((p) => p.name).join(' or '); + const joinedHosts = hostNodes.join(' or '); const hostsQueryParam = `host.name:(${joinedHosts})`; return hostsQueryParam; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/metrics/metric_chart.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/metrics/metric_chart.tsx index 28d07b94d9437..f81228957107a 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/metrics/metric_chart.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/metrics/metric_chart.tsx @@ -40,13 +40,13 @@ export const MetricChart = ({ title, type, breakdownSize }: MetricChartProps) => const { euiTheme } = useEuiTheme(); const { searchCriteria, onSubmit } = useUnifiedSearchContext(); const { dataView } = useMetricsDataViewContext(); - const { baseRequest, loading } = useHostsViewContext(); + const { requestTs, loading } = useHostsViewContext(); const { currentPage } = useHostsTableContext(); // prevents updates on requestTs and serchCriteria states from relaoding the chart // we want it to reload only once the table has finished loading const { afterLoadedState } = useAfterLoadedState(loading, { - lastReloadRequestTime: baseRequest.requestTs, + lastReloadRequestTime: requestTs, ...searchCriteria, }); diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/metrics/metrics_grid.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/metrics/metrics_grid.tsx index e307dde0d09e5..7f3dac7a3af16 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/metrics/metrics_grid.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/metrics/metrics_grid.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { EuiFlexGrid, EuiFlexItem, EuiFlexGroup, EuiText, EuiI18n } from '@elastic/eui'; +import { EuiFlexGrid, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { MetricChart, MetricChartProps } from './metric_chart'; @@ -64,32 +64,12 @@ const CHARTS_IN_ORDER: Array & { fullRo export const MetricsGrid = React.memo(() => { return ( - - - - - - {DEFAULT_BREAKDOWN_SIZE}, - attribute: name, - }} - /> - - - - - - - {CHARTS_IN_ORDER.map(({ fullRow, ...chartProp }) => ( - - - - ))} - - - + + {CHARTS_IN_ORDER.map(({ fullRow, ...chartProp }) => ( + + + + ))} + ); }); diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/unified_search_bar.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/unified_search_bar.tsx deleted file mode 100644 index 168f825a9d2d1..0000000000000 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/unified_search_bar.tsx +++ /dev/null @@ -1,98 +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 { compareFilters, COMPARE_ALL_OPTIONS, type Filter } from '@kbn/es-query'; -import { i18n } from '@kbn/i18n'; -import { EuiFlexGrid, useEuiTheme } from '@elastic/eui'; -import { css } from '@emotion/react'; -import { EuiHorizontalRule } from '@elastic/eui'; -import { METRICS_APP_DATA_TEST_SUBJ } from '../../../../apps/metrics_app'; -import { useKibanaContextForPlugin } from '../../../../hooks/use_kibana'; -import { useUnifiedSearchContext } from '../hooks/use_unified_search'; -import { ControlsContent } from './controls_content'; -import { useMetricsDataViewContext } from '../hooks/use_data_view'; -import { HostsSearchPayload } from '../hooks/use_unified_search_url_state'; - -export const UnifiedSearchBar = () => { - const { - services: { unifiedSearch, application }, - } = useKibanaContextForPlugin(); - const { dataView } = useMetricsDataViewContext(); - const { searchCriteria, onSubmit } = useUnifiedSearchContext(); - - const { SearchBar } = unifiedSearch.ui; - - const onPanelFiltersChange = (panelFilters: Filter[]) => { - if (!compareFilters(searchCriteria.panelFilters, panelFilters, COMPARE_ALL_OPTIONS)) { - onSubmit({ panelFilters }); - } - }; - - const handleRefresh = (payload: HostsSearchPayload, isUpdate?: boolean) => { - // This makes sure `onQueryChange` is only called when the submit button is clicked - if (isUpdate === false) { - onSubmit(payload); - } - }; - - return ( - - 0.5)', - })} - onQuerySubmit={handleRefresh} - showSaveQuery={Boolean(application?.capabilities?.visualize?.saveQuery)} - showDatePicker - showFilterBar - showQueryInput - showQueryMenu - useDefaultBehaviors - /> - - - - ); -}; - -const StickyContainer = (props: { children: React.ReactNode }) => { - const { euiTheme } = useEuiTheme(); - - const top = useMemo(() => { - const wrapper = document.querySelector(`[data-test-subj="${METRICS_APP_DATA_TEST_SUBJ}"]`); - if (!wrapper) { - return `calc(${euiTheme.size.xxxl} * 2)`; - } - - return `${wrapper.getBoundingClientRect().top}px`; - }, [euiTheme]); - - return ( - - ); -}; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/constants.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/constants.ts index b854120a86887..69cfc446d0095 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/constants.ts +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/constants.ts @@ -7,13 +7,15 @@ import { i18n } from '@kbn/i18n'; import { ALERT_STATUS, ALERT_STATUS_ACTIVE, ALERT_STATUS_RECOVERED } from '@kbn/rule-data-utils'; -import { AlertStatusFilter } from './types'; +import { AlertStatusFilter, HostLimitOptions } from './types'; export const ALERT_STATUS_ALL = 'all'; export const TIMESTAMP_FIELD = '@timestamp'; export const DATA_VIEW_PREFIX = 'infra_metrics'; +export const DEFAULT_HOST_LIMIT: HostLimitOptions = 100; export const DEFAULT_PAGE_SIZE = 10; +export const LOCAL_STORAGE_HOST_LIMIT_KEY = 'hostsView:hostLimitSelection'; export const LOCAL_STORAGE_PAGE_SIZE_KEY = 'hostsView:pageSizeSelection'; export const ALL_ALERTS: AlertStatusFilter = { @@ -55,3 +57,5 @@ export const ALERT_STATUS_QUERY = { [ACTIVE_ALERTS.status]: ACTIVE_ALERTS.query, [RECOVERED_ALERTS.status]: RECOVERED_ALERTS.query, }; + +export const HOST_LIMIT_OPTIONS = [10, 20, 50, 100, 500] as const; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_alerts_query.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_alerts_query.ts index 7a895591d68c7..200bff521d86a 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_alerts_query.ts +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_alerts_query.ts @@ -9,7 +9,7 @@ import createContainer from 'constate'; import { getTime } from '@kbn/data-plugin/common'; import { ALERT_TIME_RANGE } from '@kbn/rule-data-utils'; import { BoolQuery, buildEsQuery, Filter } from '@kbn/es-query'; -import { SnapshotNode } from '../../../../../common/http_api'; +import { InfraAssetMetricsItem } from '../../../../../common/http_api'; import { useUnifiedSearchContext } from './use_unified_search'; import { HostsState } from './use_unified_search_url_state'; import { useHostsViewContext } from './use_hosts_view'; @@ -63,7 +63,7 @@ const createAlertsEsQuery = ({ status, }: { dateRange: HostsState['dateRange']; - hostNodes: SnapshotNode[]; + hostNodes: InfraAssetMetricsItem[]; status?: AlertStatus; }): AlertsEsQuery => { const alertStatusFilter = createAlertStatusFilter(status); diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_data_view.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_data_view.ts index 94e3a963075be..83fedf4292937 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_data_view.ts +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_data_view.ts @@ -72,6 +72,7 @@ export const useDataView = ({ metricAlias }: { metricAlias: string }) => { }, [hasError, notifications, metricAlias]); return { + metricAlias, dataView, loading, hasError, diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_host_count.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_host_count.ts new file mode 100644 index 0000000000000..5575c46e621f1 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_host_count.ts @@ -0,0 +1,129 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as rt from 'io-ts'; +import { ES_SEARCH_STRATEGY, IKibanaSearchResponse } from '@kbn/data-plugin/common'; +import { useCallback, useEffect } from 'react'; +import { catchError, map, Observable, of, startWith } from 'rxjs'; +import createContainer from 'constate'; +import type { QueryDslQueryContainer, SearchResponse } from '@elastic/elasticsearch/lib/api/types'; +import { decodeOrThrow } from '../../../../../common/runtime_types'; +import { useDataSearch, useLatestPartialDataSearchResponse } from '../../../../utils/data_search'; +import { useMetricsDataViewContext } from './use_data_view'; +import { useUnifiedSearchContext } from './use_unified_search'; + +export const useHostCount = () => { + const { dataView, metricAlias } = useMetricsDataViewContext(); + const { buildQuery, getParsedDateRange } = useUnifiedSearchContext(); + + const { search: fetchHostCount, requests$ } = useDataSearch({ + getRequest: useCallback(() => { + const query = buildQuery(); + const dateRange = getParsedDateRange(); + + const filters: QueryDslQueryContainer = { + bool: { + ...query.bool, + filter: [ + ...query.bool.filter, + { + exists: { + field: 'host.name', + }, + }, + { + range: { + [dataView?.timeFieldName ?? '@timestamp']: { + gte: dateRange.from, + lte: dateRange.to, + format: 'strict_date_optional_time', + }, + }, + }, + ], + }, + }; + + return { + request: { + params: { + allow_no_indices: true, + ignore_unavailable: true, + index: metricAlias, + size: 0, + track_total_hits: false, + body: { + query: filters, + aggs: { + count: { + cardinality: { + field: 'host.name', + }, + }, + }, + }, + }, + }, + options: { strategy: ES_SEARCH_STRATEGY }, + }; + }, [buildQuery, dataView, getParsedDateRange, metricAlias]), + parseResponses: normalizeDataSearchResponse, + }); + + const { isRequestRunning, isResponsePartial, latestResponseData, latestResponseErrors } = + useLatestPartialDataSearchResponse(requests$); + + useEffect(() => { + fetchHostCount(); + }, [fetchHostCount]); + + return { + errors: latestResponseErrors, + isRequestRunning, + isResponsePartial, + data: latestResponseData ?? null, + }; +}; + +export const HostCount = createContainer(useHostCount); +export const [HostCountProvider, useHostCountContext] = HostCount; + +const INITIAL_STATE = { + data: null, + errors: [], + isPartial: true, + isRunning: true, + loaded: 0, + total: undefined, +}; +const normalizeDataSearchResponse = ( + response$: Observable>>> +) => + response$.pipe( + map((response) => ({ + data: decodeOrThrow(HostCountResponseRT)(response.rawResponse.aggregations), + errors: [], + isPartial: response.isPartial ?? false, + isRunning: response.isRunning ?? false, + loaded: response.loaded, + total: response.total, + })), + startWith(INITIAL_STATE), + catchError((error) => + of({ + ...INITIAL_STATE, + errors: [error.message ?? error], + isRunning: false, + }) + ) + ); + +const HostCountResponseRT = rt.type({ + count: rt.type({ + value: rt.number, + }), +}); diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.test.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.test.ts index a921a0daeb011..5619a788b19a7 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.test.ts +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.test.ts @@ -7,7 +7,7 @@ import { useHostsTable } from './use_hosts_table'; import { renderHook } from '@testing-library/react-hooks'; -import { SnapshotNode } from '../../../../../common/http_api'; +import { InfraAssetMetricsItem } from '../../../../../common/http_api'; import * as useUnifiedSearchHooks from './use_unified_search'; import * as useHostsViewHooks from './use_hosts_view'; @@ -22,20 +22,20 @@ const mockUseHostsViewContext = useHostsViewHooks.useHostsViewContext as jest.Mo typeof useHostsViewHooks.useHostsViewContext >; -const mockHostNode: SnapshotNode[] = [ +const mockHostNode: InfraAssetMetricsItem[] = [ { metrics: [ { name: 'rx', - avg: 252456.92916666667, + value: 252456.92916666667, }, { name: 'tx', - avg: 252758.425, + value: 252758.425, }, { name: 'memory', - avg: 0.94525, + value: 0.94525, }, { name: 'cpu', @@ -43,25 +43,28 @@ const mockHostNode: SnapshotNode[] = [ }, { name: 'memoryTotal', - avg: 34359.738368, + value: 34359.738368, }, ], - path: [{ value: 'host-0', label: 'host-0', os: null, cloudProvider: 'aws' }], + metadata: [ + { name: 'host.os.name', value: null }, + { name: 'cloud.provider', value: 'aws' }, + ], name: 'host-0', }, { metrics: [ { name: 'rx', - avg: 95.86339715321859, + value: 95.86339715321859, }, { name: 'tx', - avg: 110.38566859563191, + value: 110.38566859563191, }, { name: 'memory', - avg: 0.5400000214576721, + value: 0.5400000214576721, }, { name: 'cpu', @@ -69,12 +72,12 @@ const mockHostNode: SnapshotNode[] = [ }, { name: 'memoryTotal', - avg: 9.194304, + value: 9.194304, }, ], - path: [ - { value: 'host-1', label: 'host-1' }, - { value: 'host-1', label: 'host-1', ip: '243.86.94.22', os: 'macOS' }, + metadata: [ + { name: 'host.os.name', value: 'macOS' }, + { name: 'host.ip', value: '243.86.94.22' }, ], name: 'host-1', }, diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx index 2d2d6c9d7f8e4..7350f402c57ec 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx @@ -15,10 +15,10 @@ import { isNumber } from 'lodash/fp'; import { useKibanaContextForPlugin } from '../../../../hooks/use_kibana'; import { createInventoryMetricFormatter } from '../../inventory_view/lib/create_inventory_metric_formatter'; import { HostsTableEntryTitle } from '../components/hosts_table_entry_title'; -import type { - SnapshotNode, - SnapshotNodeMetric, - SnapshotMetricInput, +import { + InfraAssetMetadataType, + InfraAssetMetricsItem, + InfraAssetMetricType, } from '../../../../../common/http_api'; import { useHostFlyoutOpen } from './use_host_flyout_open_url_state'; import { Sorting, useHostsTableProperties } from './use_hosts_table_url_state'; @@ -29,42 +29,55 @@ import { useUnifiedSearchContext } from './use_unified_search'; * Columns and items types */ export type CloudProvider = 'gcp' | 'aws' | 'azure' | 'unknownProvider'; +type HostMetrics = Record; -type HostMetric = 'cpu' | 'diskLatency' | 'rx' | 'tx' | 'memory' | 'memoryTotal'; - -type HostMetrics = Record; - -export interface HostNodeRow extends HostMetrics { +interface HostMetadata { os?: string | null; ip?: string | null; servicesOnHost?: number | null; title: { name: string; cloudProvider?: CloudProvider | null }; - name: string; id: string; } +export type HostNodeRow = HostMetadata & + HostMetrics & { + name: string; + }; /** * Helper functions */ -const formatMetric = (type: SnapshotMetricInput['type'], value: number | undefined | null) => { +const formatMetric = (type: InfraAssetMetricType, value: number | undefined | null) => { return value || value === 0 ? createInventoryMetricFormatter({ type })(value) : 'N/A'; }; -const buildItemsList = (nodes: SnapshotNode[]) => { - return nodes.map(({ metrics, path, name }) => ({ - id: `${name}-${path.at(-1)?.os ?? '-'}`, - name, - os: path.at(-1)?.os ?? '-', - ip: path.at(-1)?.ip ?? '', - title: { +const buildItemsList = (nodes: InfraAssetMetricsItem[]): HostNodeRow[] => { + return nodes.map(({ metrics, metadata, name }) => { + const metadataKeyValue = metadata.reduce( + (acc, curr) => ({ + ...acc, + [curr.name]: curr.value, + }), + {} as Record + ); + + return { name, - cloudProvider: path.at(-1)?.cloudProvider ?? null, - }, - ...metrics.reduce((data, metric) => { - data[metric.name as HostMetric] = metric.avg ?? metric.value; - return data; - }, {} as HostMetrics), - })) as HostNodeRow[]; + id: `${name}-${metadataKeyValue['host.os.name'] ?? '-'}`, + title: { + name, + cloudProvider: (metadataKeyValue['cloud.provider'] as CloudProvider) ?? null, + }, + os: metadataKeyValue['host.os.name'] ?? '-', + ip: metadataKeyValue['host.ip'] ?? '', + ...metrics.reduce( + (acc, curr) => ({ + ...acc, + [curr.name]: curr.value ?? 0, + }), + {} as HostMetrics + ), + }; + }); }; const isTitleColumn = (cell: any): cell is HostNodeRow['title'] => { diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_view.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_view.ts index d0df961dc7ef9..f84acf5931ea1 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_view.ts +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_view.ts @@ -12,16 +12,21 @@ * 2.0. */ -import { useMemo } from 'react'; +import { useEffect, useMemo, useRef } from 'react'; import createContainer from 'constate'; import { BoolQuery } from '@kbn/es-query'; -import { SnapshotMetricType } from '../../../../../common/inventory_models/types'; +import useAsyncFn from 'react-use/lib/useAsyncFn'; +import { useKibanaContextForPlugin } from '../../../../hooks/use_kibana'; import { useSourceContext } from '../../../../containers/metrics_source'; -import { useSnapshot, type UseSnapshotRequest } from '../../inventory_view/hooks/use_snaphot'; import { useUnifiedSearchContext } from './use_unified_search'; -import { StringDateRangeTimestamp } from './use_unified_search_url_state'; +import { + GetInfraMetricsRequestBodyPayload, + GetInfraMetricsResponsePayload, + InfraAssetMetricType, +} from '../../../../../common/http_api'; +import { StringDateRange } from './use_unified_search_url_state'; -const HOST_TABLE_METRICS: Array<{ type: SnapshotMetricType }> = [ +const HOST_TABLE_METRICS: Array<{ type: InfraAssetMetricType }> = [ { type: 'rx' }, { type: 'tx' }, { type: 'memory' }, @@ -30,40 +35,52 @@ const HOST_TABLE_METRICS: Array<{ type: SnapshotMetricType }> = [ { type: 'memoryTotal' }, ]; +const BASE_INFRA_METRICS_PATH = '/api/metrics/infra'; + export const useHostsView = () => { const { sourceId } = useSourceContext(); - const { buildQuery, getDateRangeAsTimestamp } = useUnifiedSearchContext(); + const { + services: { http }, + } = useKibanaContextForPlugin(); + const { buildQuery, getParsedDateRange, searchCriteria } = useUnifiedSearchContext(); + const abortCtrlRef = useRef(new AbortController()); const baseRequest = useMemo( () => - createSnapshotRequest({ - dateRange: getDateRangeAsTimestamp(), + createInfraMetricsRequest({ + dateRange: getParsedDateRange(), esQuery: buildQuery(), sourceId, + limit: searchCriteria.limit, }), - [buildQuery, getDateRangeAsTimestamp, sourceId] + [buildQuery, getParsedDateRange, sourceId, searchCriteria.limit] ); - // Snapshot endpoint internally uses the indices stored in source.configuration.metricAlias. - // For the Unified Search, we create a data view, which for now will be built off of source.configuration.metricAlias too - // if we introduce data view selection, we'll have to change this hook and the endpoint to accept a new parameter for the indices - const { - loading, - error, - nodes: hostNodes, - } = useSnapshot( - { - ...baseRequest, - metrics: HOST_TABLE_METRICS, + const [state, refetch] = useAsyncFn( + () => { + abortCtrlRef.current.abort(); + abortCtrlRef.current = new AbortController(); + + return http.post(`${BASE_INFRA_METRICS_PATH}`, { + signal: abortCtrlRef.current.signal, + body: JSON.stringify(baseRequest), + }); }, - { abortable: true } + [baseRequest, http], + { loading: true } ); + useEffect(() => { + refetch(); + }, [refetch]); + + const { value, error, loading } = state; + return { - baseRequest, + requestTs: baseRequest.requestTs, loading, error, - hostNodes, + hostNodes: value?.nodes ?? [], }; }; @@ -73,30 +90,26 @@ export const [HostsViewProvider, useHostsViewContext] = HostsView; /** * Helpers */ -const createSnapshotRequest = ({ + +const createInfraMetricsRequest = ({ esQuery, sourceId, dateRange, + limit, }: { esQuery: { bool: BoolQuery }; sourceId: string; - dateRange: StringDateRangeTimestamp; -}): UseSnapshotRequest => ({ - filterQuery: JSON.stringify(esQuery), - metrics: [], - groupBy: [], - nodeType: 'host', - sourceId, - currentTime: dateRange.to, - includeTimeseries: false, - sendRequestImmediately: true, - timerange: { - interval: '1m', + dateRange: StringDateRange; + limit: number; +}): GetInfraMetricsRequestBodyPayload & { requestTs: number } => ({ + type: 'host', + query: esQuery, + range: { from: dateRange.from, to: dateRange.to, - ignoreLookback: true, }, - // The user might want to click on the submit button without changing the filters - // This makes sure all child components will re-render. + metrics: HOST_TABLE_METRICS, + limit, + sourceId, requestTs: Date.now(), }); diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_unified_search.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_unified_search.ts index 950bd9cf3c94e..e242f58054c6c 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_unified_search.ts +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_unified_search.ts @@ -23,14 +23,14 @@ import { } from './use_unified_search_url_state'; const buildQuerySubmittedPayload = ( - hostState: HostsState & { dateRangeTimestamp: StringDateRangeTimestamp } + hostState: HostsState & { parsedDateRange: StringDateRangeTimestamp } ) => { - const { panelFilters, filters, dateRangeTimestamp, query: queryObj } = hostState; + const { panelFilters, filters, parsedDateRange, query: queryObj } = hostState; return { control_filters: panelFilters.map((filter) => JSON.stringify(filter)), filters: filters.map((filter) => JSON.stringify(filter)), - interval: telemetryTimeRangeFormatter(dateRangeTimestamp.to - dateRangeTimestamp.from), + interval: telemetryTimeRangeFormatter(parsedDateRange.to - parsedDateRange.from), query: queryObj.query, }; }; @@ -41,8 +41,8 @@ const getDefaultTimestamps = () => { const now = Date.now(); return { - from: now - DEFAULT_FROM_IN_MILLISECONDS, - to: now, + from: new Date(now - DEFAULT_FROM_IN_MILLISECONDS).toISOString(), + to: new Date(now).toISOString(), }; }; @@ -63,16 +63,25 @@ export const useUnifiedSearch = () => { const onSubmit = (params?: HostsSearchPayload) => setSearch(params ?? {}); - const getDateRangeAsTimestamp = useCallback(() => { + const getParsedDateRange = useCallback(() => { const defaults = getDefaultTimestamps(); - const from = DateMath.parse(searchCriteria.dateRange.from)?.valueOf() ?? defaults.from; + const from = DateMath.parse(searchCriteria.dateRange.from)?.toISOString() ?? defaults.from; const to = - DateMath.parse(searchCriteria.dateRange.to, { roundUp: true })?.valueOf() ?? defaults.to; + DateMath.parse(searchCriteria.dateRange.to, { roundUp: true })?.toISOString() ?? defaults.to; return { from, to }; }, [searchCriteria.dateRange]); + const getDateRangeAsTimestamp = useCallback(() => { + const parsedDate = getParsedDateRange(); + + const from = new Date(parsedDate.from).getTime(); + const to = new Date(parsedDate.to).getTime(); + + return { from, to }; + }, [getParsedDateRange]); + const buildQuery = useCallback(() => { return buildEsQuery(dataView, searchCriteria.query, [ ...searchCriteria.filters, @@ -116,15 +125,16 @@ export const useUnifiedSearch = () => { // Track telemetry event on query/filter/date changes useEffect(() => { - const dateRangeTimestamp = getDateRangeAsTimestamp(); + const parsedDateRange = getDateRangeAsTimestamp(); telemetry.reportHostsViewQuerySubmitted( - buildQuerySubmittedPayload({ ...searchCriteria, dateRangeTimestamp }) + buildQuerySubmittedPayload({ ...searchCriteria, parsedDateRange }) ); }, [getDateRangeAsTimestamp, searchCriteria, telemetry]); return { buildQuery, onSubmit, + getParsedDateRange, getDateRangeAsTimestamp, searchCriteria, }; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_unified_search_url_state.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_unified_search_url_state.ts index 861f3c26472e8..bae9f2ed3f713 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_unified_search_url_state.ts +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_unified_search_url_state.ts @@ -13,11 +13,13 @@ import { fold } from 'fp-ts/lib/Either'; import { constant, identity } from 'fp-ts/lib/function'; import { enumeration } from '@kbn/securitysolution-io-ts-types'; import { FilterStateStore } from '@kbn/es-query'; +import useLocalStorage from 'react-use/lib/useLocalStorage'; import { useUrlState } from '../../../../utils/use_url_state'; import { useKibanaTimefilterTime, useSyncKibanaTimeFilterTime, } from '../../../../hooks/use_kibana_timefilter_time'; +import { DEFAULT_HOST_LIMIT, LOCAL_STORAGE_HOST_LIMIT_KEY } from '../constants'; const DEFAULT_QUERY = { language: 'kuery', @@ -32,6 +34,7 @@ const INITIAL_HOSTS_STATE: HostsState = { filters: [], panelFilters: [], dateRange: INITIAL_DATE_RANGE, + limit: DEFAULT_HOST_LIMIT, }; const reducer = (prevState: HostsState, params: HostsSearchPayload) => { @@ -45,9 +48,17 @@ const reducer = (prevState: HostsState, params: HostsSearchPayload) => { export const useHostsUrlState = (): [HostsState, HostsStateUpdater] => { const [getTime] = useKibanaTimefilterTime(INITIAL_DATE_RANGE); + const [localStorageHostLimit, setLocalStorageHostLimit] = useLocalStorage( + LOCAL_STORAGE_HOST_LIMIT_KEY, + INITIAL_HOSTS_STATE.limit + ); const [urlState, setUrlState] = useUrlState({ - defaultState: { ...INITIAL_HOSTS_STATE, dateRange: getTime() }, + defaultState: { + ...INITIAL_HOSTS_STATE, + dateRange: getTime(), + limit: localStorageHostLimit ?? INITIAL_HOSTS_STATE.limit, + }, decodeUrlState, encodeUrlState, urlStateKey: '_a', @@ -57,6 +68,9 @@ export const useHostsUrlState = (): [HostsState, HostsStateUpdater] => { const [search, setSearch] = useReducer(reducer, urlState); if (!deepEqual(search, urlState)) { setUrlState(search); + if (localStorageHostLimit !== search.limit) { + setLocalStorageHostLimit(search.limit); + } } useSyncKibanaTimeFilterTime(INITIAL_DATE_RANGE, urlState.dateRange, (dateRange) => @@ -110,6 +124,7 @@ const HostsStateRT = rt.type({ panelFilters: HostsFiltersRT, query: HostsQueryStateRT, dateRange: StringDateRangeRT, + limit: rt.number, }); export type HostsState = rt.TypeOf; @@ -118,6 +133,7 @@ export type HostsSearchPayload = Partial; export type HostsStateUpdater = (params: HostsSearchPayload) => void; +export type StringDateRange = rt.TypeOf; export interface StringDateRangeTimestamp { from: number; to: number; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/types.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/types.ts index 6b948fb0da6c9..080b47f54d4da 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/types.ts +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/types.ts @@ -7,7 +7,7 @@ import { Filter } from '@kbn/es-query'; import { ALERT_STATUS_ACTIVE, ALERT_STATUS_RECOVERED } from '@kbn/rule-data-utils'; -import { ALERT_STATUS_ALL } from './constants'; +import { ALERT_STATUS_ALL, HOST_LIMIT_OPTIONS } from './constants'; export type AlertStatus = | typeof ALERT_STATUS_ACTIVE @@ -19,3 +19,5 @@ export interface AlertStatusFilter { query?: Filter['query']; label: string; } + +export type HostLimitOptions = typeof HOST_LIMIT_OPTIONS[number]; diff --git a/x-pack/plugins/infra/server/routes/infra/lib/constants.ts b/x-pack/plugins/infra/server/routes/infra/lib/constants.ts index bc9131d1d52fc..f39eaafdd039e 100644 --- a/x-pack/plugins/infra/server/routes/infra/lib/constants.ts +++ b/x-pack/plugins/infra/server/routes/infra/lib/constants.ts @@ -24,6 +24,9 @@ export const METADATA_AGGREGATION: Record Date: Mon, 24 Apr 2023 20:15:18 +0200 Subject: [PATCH 06/52] Fleet: allow Universal Profiling symbolizer permissions on indices (#155642) ## Summary For the introduction of the Universal Profiling symbolizer in Cloud, Fleet needs an update. The reason for Universal Profiling symbolizer to be different from other packages running via Fleet is that: 1. it ingests data into indicesm not only data-streams 2. it uses a non-conventional naming scheme for indices --- x-pack/plugins/fleet/common/constants/epm.ts | 1 + ...kage_policies_to_agent_permissions.test.ts | 92 +++++++++++++++++++ .../package_policies_to_agent_permissions.ts | 33 +++++++ 3 files changed, 126 insertions(+) diff --git a/x-pack/plugins/fleet/common/constants/epm.ts b/x-pack/plugins/fleet/common/constants/epm.ts index a1d73b452cf72..2635dbc05399f 100644 --- a/x-pack/plugins/fleet/common/constants/epm.ts +++ b/x-pack/plugins/fleet/common/constants/epm.ts @@ -16,6 +16,7 @@ export const FLEET_ENDPOINT_PACKAGE = 'endpoint'; export const FLEET_APM_PACKAGE = 'apm'; export const FLEET_SYNTHETICS_PACKAGE = 'synthetics'; export const FLEET_KUBERNETES_PACKAGE = 'kubernetes'; +export const FLEET_UNIVERSAL_PROFILING_SYMBOLIZER_PACKAGE = 'profiler_symbolizer'; export const FLEET_CLOUD_SECURITY_POSTURE_PACKAGE = 'cloud_security_posture'; export const FLEET_CLOUD_SECURITY_POSTURE_KSPM_POLICY_TEMPLATE = 'kspm'; diff --git a/x-pack/plugins/fleet/server/services/agent_policies/package_policies_to_agent_permissions.test.ts b/x-pack/plugins/fleet/server/services/agent_policies/package_policies_to_agent_permissions.test.ts index 1f5ea87c6d6f9..f093b20eaaeb4 100644 --- a/x-pack/plugins/fleet/server/services/agent_policies/package_policies_to_agent_permissions.test.ts +++ b/x-pack/plugins/fleet/server/services/agent_policies/package_policies_to_agent_permissions.test.ts @@ -13,6 +13,7 @@ import type { DataStreamMeta } from './package_policies_to_agent_permissions'; import { getDataStreamPrivileges, storedPackagePoliciesToAgentPermissions, + UNIVERSAL_PROFILING_PERMISSIONS, } from './package_policies_to_agent_permissions'; const packageInfoCache = new Map(); @@ -137,6 +138,56 @@ packageInfoCache.set('osquery_manager-0.3.0', { }, }, }); +packageInfoCache.set('profiler_symbolizer-8.8.0-preview', { + format_version: '2.7.0', + name: 'profiler_symbolizer', + title: 'Universal Profiling Symbolizer', + version: '8.8.0-preview', + license: 'basic', + description: + ' Fleet-wide, whole-system, continuous profiling with zero instrumentation. Symbolize native frames.', + type: 'integration', + release: 'beta', + categories: ['monitoring', 'elastic_stack'], + icons: [ + { + src: '/img/logo_profiling_symbolizer.svg', + title: 'logo symbolizer', + size: '32x32', + type: 'image/svg+xml', + }, + ], + owner: { github: 'elastic/profiling' }, + data_streams: [], + latestVersion: '8.8.0-preview', + notice: undefined, + status: 'not_installed', + assets: { + kibana: { + csp_rule_template: [], + dashboard: [], + visualization: [], + search: [], + index_pattern: [], + map: [], + lens: [], + security_rule: [], + ml_module: [], + tag: [], + osquery_pack_asset: [], + osquery_saved_query: [], + }, + elasticsearch: { + component_template: [], + ingest_pipeline: [], + ilm_policy: [], + transform: [], + index_template: [], + data_stream_ilm_policy: [], + ml_model: [], + }, + }, +}); describe('storedPackagePoliciesToAgentPermissions()', () => { it('Returns `undefined` if there are no package policies', async () => { @@ -363,6 +414,47 @@ describe('storedPackagePoliciesToAgentPermissions()', () => { }, }); }); + + it('Returns the Universal Profiling permissions for profiler_symbolizer package', async () => { + const packagePolicies: PackagePolicy[] = [ + { + id: 'package-policy-uuid-test-123', + name: 'test-policy', + namespace: '', + enabled: true, + package: { name: 'profiler_symbolizer', version: '8.8.0-preview', title: 'Test Package' }, + inputs: [ + { + type: 'pf-elastic-symbolizer', + enabled: true, + streams: [], + }, + ], + created_at: '', + updated_at: '', + created_by: '', + updated_by: '', + revision: 1, + policy_id: '', + }, + ]; + + const permissions = await storedPackagePoliciesToAgentPermissions( + packageInfoCache, + packagePolicies + ); + + expect(permissions).toMatchObject({ + 'package-policy-uuid-test-123': { + indices: [ + { + names: ['profiling-*'], + privileges: UNIVERSAL_PROFILING_PERMISSIONS, + }, + ], + }, + }); + }); }); describe('getDataStreamPrivileges()', () => { diff --git a/x-pack/plugins/fleet/server/services/agent_policies/package_policies_to_agent_permissions.ts b/x-pack/plugins/fleet/server/services/agent_policies/package_policies_to_agent_permissions.ts index 02c44024421ce..f8cd73901e0d7 100644 --- a/x-pack/plugins/fleet/server/services/agent_policies/package_policies_to_agent_permissions.ts +++ b/x-pack/plugins/fleet/server/services/agent_policies/package_policies_to_agent_permissions.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { FLEET_UNIVERSAL_PROFILING_SYMBOLIZER_PACKAGE } from '../../../common/constants'; + import { getNormalizedDataStreams } from '../../../common/services'; import type { @@ -19,6 +21,16 @@ import { pkgToPkgKey } from '../epm/registry'; export const DEFAULT_CLUSTER_PERMISSIONS = ['monitor']; +export const UNIVERSAL_PROFILING_PERMISSIONS = [ + 'auto_configure', + 'read', + 'create_doc', + 'create', + 'write', + 'index', + 'view_index_metadata', +]; + export async function storedPackagePoliciesToAgentPermissions( packageInfoCache: Map, packagePolicies?: PackagePolicy[] @@ -42,6 +54,12 @@ export async function storedPackagePoliciesToAgentPermissions( const pkg = packageInfoCache.get(pkgToPkgKey(packagePolicy.package))!; + // Special handling for Universal Profiling packages, as it does not use data streams _only_, + // but also indices that do not adhere to the convention. + if (pkg.name === FLEET_UNIVERSAL_PROFILING_SYMBOLIZER_PACKAGE) { + return Promise.resolve(universalProfilingPermissions(packagePolicy.id)); + } + const dataStreams = getNormalizedDataStreams(pkg); if (!dataStreams || dataStreams.length === 0) { return [packagePolicy.name, undefined]; @@ -175,3 +193,18 @@ export function getDataStreamPrivileges(dataStream: DataStreamMeta, namespace: s privileges, }; } + +async function universalProfilingPermissions(packagePolicyId: string): Promise<[string, any]> { + const profilingIndexPattern = 'profiling-*'; + return [ + packagePolicyId, + { + indices: [ + { + names: [profilingIndexPattern], + privileges: UNIVERSAL_PROFILING_PERMISSIONS, + }, + ], + }, + ]; +} From 953437f05d26ee42ff70eb128e6646617bf7ba62 Mon Sep 17 00:00:00 2001 From: Sander Philipse <94373878+sphilipse@users.noreply.github.com> Date: Mon, 24 Apr 2023 20:21:39 +0200 Subject: [PATCH 07/52] [Enterprise Search] Fix wording on content settings page (#155383) ## Summary This updates the content on the content settings page. Screenshot 2023-04-20 at 14 02 02 --- packages/kbn-doc-links/src/get_doc_links.ts | 1 + packages/kbn-doc-links/src/types.ts | 1 + .../components/settings/settings.tsx | 37 ++++++++++--------- .../components/settings/settings_panel.tsx | 4 +- .../shared/doc_links/doc_links.ts | 3 ++ .../translations/translations/fr-FR.json | 2 - .../translations/translations/ja-JP.json | 2 - .../translations/translations/zh-CN.json | 2 - 8 files changed, 27 insertions(+), 25 deletions(-) diff --git a/packages/kbn-doc-links/src/get_doc_links.ts b/packages/kbn-doc-links/src/get_doc_links.ts index d9fbd1dfc8554..dc75ebee9a5eb 100644 --- a/packages/kbn-doc-links/src/get_doc_links.ts +++ b/packages/kbn-doc-links/src/get_doc_links.ts @@ -153,6 +153,7 @@ export const getDocLinks = ({ kibanaBranch }: GetDocLinkOptions): DocLinks => { licenseManagement: `${ENTERPRISE_SEARCH_DOCS}license-management.html`, machineLearningStart: `${ENTERPRISE_SEARCH_DOCS}machine-learning-start.html`, mailService: `${ENTERPRISE_SEARCH_DOCS}mailer-configuration.html`, + mlDocumentEnrichment: `${ENTERPRISE_SEARCH_DOCS}document-enrichment.html`, start: `${ENTERPRISE_SEARCH_DOCS}start.html`, syncRules: `${ENTERPRISE_SEARCH_DOCS}sync-rules.html`, troubleshootSetup: `${ENTERPRISE_SEARCH_DOCS}troubleshoot-setup.html`, diff --git a/packages/kbn-doc-links/src/types.ts b/packages/kbn-doc-links/src/types.ts index 92dc64e916644..efe5e95f238d0 100644 --- a/packages/kbn-doc-links/src/types.ts +++ b/packages/kbn-doc-links/src/types.ts @@ -138,6 +138,7 @@ export interface DocLinks { readonly licenseManagement: string; readonly machineLearningStart: string; readonly mailService: string; + readonly mlDocumentEnrichment: string; readonly start: string; readonly syncRules: string; readonly troubleshootSetup: string; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/settings/settings.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/settings/settings.tsx index a1bbe6c39956f..840010d3aa219 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/settings/settings.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/settings/settings.tsx @@ -12,6 +12,8 @@ import { useActions, useValues } from 'kea'; import { EuiButton, EuiLink, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + import { docLinks } from '../../../shared/doc_links'; import { EnterpriseSearchContentPageTemplate } from '../layout/page_template'; @@ -36,6 +38,21 @@ export const Settings: React.FC = () => { }), ]} pageHeader={{ + description: ( + + {i18n.translate('xpack.enterpriseSearch.content.settings.ingestLink', { + defaultMessage: 'ingest pipelines', + })} + + ), + }} + /> + ), pageTitle: i18n.translate('xpack.enterpriseSearch.content.settings.headerTitle', { defaultMessage: 'Content Settings', }), @@ -69,19 +86,12 @@ export const Settings: React.FC = () => { 'xpack.enterpriseSearch.content.settings.contentExtraction.description', { defaultMessage: - 'Allow all ingestion mechanisms on your Enterprise Search deployment to extract searchable content from binary files, like PDFs and Word documents. This setting applies to all new Elasticsearch indices created by an Enterprise Search ingestion mechanism.', + 'Extract searchable content from binary files, like PDFs and Word documents.', } )} label={i18n.translate('xpack.enterpriseSearch.content.settings.contactExtraction.label', { defaultMessage: 'Content extraction', })} - link={ - - {i18n.translate('xpack.enterpriseSearch.content.settings.contactExtraction.link', { - defaultMessage: 'Learn more about content extraction', - })} - - } onChange={() => setPipeline({ ...pipelineState, @@ -105,13 +115,6 @@ export const Settings: React.FC = () => { label={i18n.translate('xpack.enterpriseSearch.content.settings.whitespaceReduction.label', { defaultMessage: 'Whitespace reduction', })} - link={ - - {i18n.translate('xpack.enterpriseSearch.content.settings.whitespaceReduction.link', { - defaultMessage: 'Learn more about whitespace reduction', - })} - - } onChange={() => setPipeline({ ...pipelineState, @@ -139,9 +142,9 @@ export const Settings: React.FC = () => { defaultMessage: 'ML Inference', })} link={ - + {i18n.translate('xpack.enterpriseSearch.content.settings.mlInference.link', { - defaultMessage: 'Learn more about content extraction', + defaultMessage: 'Learn more about document enrichment with ML', })} } diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/settings/settings_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/settings/settings_panel.tsx index fc0f3cca3e06c..673861d80e6ef 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/settings/settings_panel.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/settings/settings_panel.tsx @@ -21,7 +21,7 @@ import { i18n } from '@kbn/i18n'; interface SettingsPanelProps { description: string; label: string; - link: React.ReactNode; + link?: React.ReactNode; onChange: (event: EuiSwitchEvent) => void; title: string; value: boolean; @@ -61,7 +61,7 @@ export const SettingsPanel: React.FC = ({ - {link} + {link && {link}} diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts b/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts index 938aaa88d1bdf..cb6663eebf6ab 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts @@ -92,6 +92,7 @@ class DocLinks { public languageClients: string; public licenseManagement: string; public machineLearningStart: string; + public mlDocumentEnrichment: string; public pluginsIngestAttachment: string; public queryDsl: string; public searchUIAppSearch: string; @@ -219,6 +220,7 @@ class DocLinks { this.languageClients = ''; this.licenseManagement = ''; this.machineLearningStart = ''; + this.mlDocumentEnrichment = ''; this.pluginsIngestAttachment = ''; this.queryDsl = ''; this.searchUIAppSearch = ''; @@ -340,6 +342,7 @@ class DocLinks { this.languageClients = docLinks.links.enterpriseSearch.languageClients; this.licenseManagement = docLinks.links.enterpriseSearch.licenseManagement; this.machineLearningStart = docLinks.links.enterpriseSearch.machineLearningStart; + this.mlDocumentEnrichment = docLinks.links.enterpriseSearch.mlDocumentEnrichment; this.pluginsIngestAttachment = docLinks.links.plugins.ingestAttachment; this.queryDsl = docLinks.links.query.queryDsl; this.searchUIAppSearch = docLinks.links.searchUI.appSearch; diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index d09b84345903e..f6610dba543b2 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -12711,7 +12711,6 @@ "xpack.enterpriseSearch.content.searchIndices.syncStatus.columnTitle": "Statut", "xpack.enterpriseSearch.content.settings.breadcrumb": "Paramètres", "xpack.enterpriseSearch.content.settings.contactExtraction.label": "Extraction du contenu", - "xpack.enterpriseSearch.content.settings.contactExtraction.link": "En savoir plus sur l'extraction de contenu", "xpack.enterpriseSearch.content.settings.contentExtraction.description": "Autoriser tous les mécanismes d'ingestion de votre déploiement Enterprise Search à extraire le contenu interrogeable des fichiers binaires tels que les documents PDF et Word. Ce paramètre s'applique à tous les nouveaux index Elasticsearch créés par un mécanisme d'ingestion Enterprise Search.", "xpack.enterpriseSearch.content.settings.contentExtraction.descriptionTwo": "Vous pouvez également activer ou désactiver cette fonctionnalité pour un index spécifique sur la page de configuration de l'index.", "xpack.enterpriseSearch.content.settings.contentExtraction.title": "Extraction de contenu de l'ensemble du déploiement", @@ -12725,7 +12724,6 @@ "xpack.enterpriseSearch.content.settings.whitespaceReduction.deploymentHeaderTitle": "Réduction d'espaces sur l'ensemble du déploiement", "xpack.enterpriseSearch.content.settings.whiteSpaceReduction.description": "La réduction d'espaces supprimera le contenu de texte intégral des espaces par défaut.", "xpack.enterpriseSearch.content.settings.whitespaceReduction.label": "Réduction d'espaces", - "xpack.enterpriseSearch.content.settings.whitespaceReduction.link": "En savoir plus sur la réduction d'espaces", "xpack.enterpriseSearch.content.shared.result.header.metadata.deleteDocument": "Supprimer le document", "xpack.enterpriseSearch.content.shared.result.header.metadata.title": "Métadonnées du document", "xpack.enterpriseSearch.content.sources.basicRulesTable.includeEverythingMessage": "Inclure tout le reste à partir de cette source", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 0fc77aa25087d..91baf79cfaaa0 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -12710,7 +12710,6 @@ "xpack.enterpriseSearch.content.searchIndices.syncStatus.columnTitle": "ステータス", "xpack.enterpriseSearch.content.settings.breadcrumb": "設定", "xpack.enterpriseSearch.content.settings.contactExtraction.label": "コンテンツ抽出", - "xpack.enterpriseSearch.content.settings.contactExtraction.link": "コンテンツ抽出の詳細", "xpack.enterpriseSearch.content.settings.contentExtraction.description": "エンタープライズ サーチデプロイですべてのインジェストメソッドで、PDFやWordドキュメントなどのバイナリファイルから検索可能なコンテンツを抽出できます。この設定は、エンタープライズ サーチインジェストメカニズムで作成されたすべての新しいElasticsearchインデックスに適用されます。", "xpack.enterpriseSearch.content.settings.contentExtraction.descriptionTwo": "インデックスの構成ページで、特定のインデックスに対して、この機能を有効化または無効化することもできます。", "xpack.enterpriseSearch.content.settings.contentExtraction.title": "デプロイレベルのコンテンツ抽出", @@ -12724,7 +12723,6 @@ "xpack.enterpriseSearch.content.settings.whitespaceReduction.deploymentHeaderTitle": "デプロイレベルの空白削除", "xpack.enterpriseSearch.content.settings.whiteSpaceReduction.description": "空白削除では、デフォルトで全文コンテンツから空白を削除します。", "xpack.enterpriseSearch.content.settings.whitespaceReduction.label": "空白削除", - "xpack.enterpriseSearch.content.settings.whitespaceReduction.link": "空白削除の詳細", "xpack.enterpriseSearch.content.shared.result.header.metadata.deleteDocument": "ドキュメントを削除", "xpack.enterpriseSearch.content.shared.result.header.metadata.title": "ドキュメントメタデータ", "xpack.enterpriseSearch.content.sources.basicRulesTable.includeEverythingMessage": "このソースの他のすべての項目を含める", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 1ce46a2242307..ed0f6a5c2fedb 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -12711,7 +12711,6 @@ "xpack.enterpriseSearch.content.searchIndices.syncStatus.columnTitle": "状态", "xpack.enterpriseSearch.content.settings.breadcrumb": "设置", "xpack.enterpriseSearch.content.settings.contactExtraction.label": "内容提取", - "xpack.enterpriseSearch.content.settings.contactExtraction.link": "详细了解内容提取", "xpack.enterpriseSearch.content.settings.contentExtraction.description": "允许您的 Enterprise Search 部署上的所有采集机制从 PDF 和 Word 文档等二进制文件中提取可搜索内容。此设置适用于由 Enterprise Search 采集机制创建的所有新 Elasticsearch 索引。", "xpack.enterpriseSearch.content.settings.contentExtraction.descriptionTwo": "您还可以在索引的配置页面针对特定索引启用或禁用此功能。", "xpack.enterpriseSearch.content.settings.contentExtraction.title": "部署广泛内容提取", @@ -12725,7 +12724,6 @@ "xpack.enterpriseSearch.content.settings.whitespaceReduction.deploymentHeaderTitle": "部署广泛的空白缩减", "xpack.enterpriseSearch.content.settings.whiteSpaceReduction.description": "默认情况下,空白缩减将清除空白的全文本内容。", "xpack.enterpriseSearch.content.settings.whitespaceReduction.label": "空白缩减", - "xpack.enterpriseSearch.content.settings.whitespaceReduction.link": "详细了解空白缩减", "xpack.enterpriseSearch.content.shared.result.header.metadata.deleteDocument": "删除文档", "xpack.enterpriseSearch.content.shared.result.header.metadata.title": "文档元数据", "xpack.enterpriseSearch.content.sources.basicRulesTable.includeEverythingMessage": "包括来自此源的所有其他内容", From 4382e1cf3227e966995c2486043d34f1b875d7d9 Mon Sep 17 00:00:00 2001 From: Patrick Mueller Date: Mon, 24 Apr 2023 15:14:30 -0400 Subject: [PATCH 08/52] [ResponseOps] adds mustache lambdas and array.asJSON (#150572) partially resolves some issues in https://github.com/elastic/kibana/issues/84217 Adds Mustache lambdas for alerting actions to format dates with `{{#FormatDate}}`, evaluate math expressions with `{{#EvalMath}}`, and provide easier JSON formatting with `{{#ParseHjson}}` and a new `asJSON` property added to arrays. --- .../server/lib/mustache_lambdas.test.ts | 201 +++++++++ .../actions/server/lib/mustache_lambdas.ts | 114 +++++ .../server/lib/mustache_renderer.test.ts | 11 + .../actions/server/lib/mustache_renderer.ts | 9 +- x-pack/plugins/actions/tsconfig.json | 3 +- .../server/slack_simulation.ts | 2 +- .../server/webhook_simulation.ts | 4 +- .../plugins/alerts/server/alert_types.ts | 1 + .../alerting/group4/mustache_templates.ts | 421 ++++++++---------- 9 files changed, 534 insertions(+), 232 deletions(-) create mode 100644 x-pack/plugins/actions/server/lib/mustache_lambdas.test.ts create mode 100644 x-pack/plugins/actions/server/lib/mustache_lambdas.ts diff --git a/x-pack/plugins/actions/server/lib/mustache_lambdas.test.ts b/x-pack/plugins/actions/server/lib/mustache_lambdas.test.ts new file mode 100644 index 0000000000000..6f67c4dd39ea8 --- /dev/null +++ b/x-pack/plugins/actions/server/lib/mustache_lambdas.test.ts @@ -0,0 +1,201 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 dedent from 'dedent'; + +import { renderMustacheString } from './mustache_renderer'; + +describe('mustache lambdas', () => { + describe('FormatDate', () => { + it('date with defaults is successful', () => { + const timeStamp = '2022-11-29T15:52:44Z'; + const template = dedent` + {{#FormatDate}} {{timeStamp}} {{/FormatDate}} + `.trim(); + + expect(renderMustacheString(template, { timeStamp }, 'none')).toEqual('2022-11-29 03:52pm'); + }); + + it('date with a time zone is successful', () => { + const timeStamp = '2022-11-29T15:52:44Z'; + const template = dedent` + {{#FormatDate}} {{timeStamp}} ; America/New_York {{/FormatDate}} + `.trim(); + + expect(renderMustacheString(template, { timeStamp }, 'none')).toEqual('2022-11-29 10:52am'); + }); + + it('date with a format is successful', () => { + const timeStamp = '2022-11-29T15:52:44Z'; + const template = dedent` + {{#FormatDate}} {{timeStamp}} ;; dddd MMM Do YYYY HH:mm:ss.SSS {{/FormatDate}} + `.trim(); + + expect(renderMustacheString(template, { timeStamp }, 'none')).toEqual( + 'Tuesday Nov 29th 2022 15:52:44.000' + ); + }); + + it('date with a format and timezone is successful', () => { + const timeStamp = '2022-11-29T15:52:44Z'; + const template = dedent` + {{#FormatDate}} {{timeStamp}};America/New_York;dddd MMM Do YYYY HH:mm:ss.SSS {{/FormatDate}} + `.trim(); + + expect(renderMustacheString(template, { timeStamp }, 'none').trim()).toEqual( + 'Tuesday Nov 29th 2022 10:52:44.000' + ); + }); + + it('empty date produces error', () => { + const timeStamp = ''; + const template = dedent` + {{#FormatDate}} {{/FormatDate}} + `.trim(); + + expect(renderMustacheString(template, { timeStamp }, 'none').trim()).toEqual( + 'error rendering mustache template "{{#FormatDate}} {{/FormatDate}}": date is empty' + ); + }); + + it('invalid date produces error', () => { + const timeStamp = 'this is not a d4t3'; + const template = dedent` + {{#FormatDate}}{{timeStamp}}{{/FormatDate}} + `.trim(); + + expect(renderMustacheString(template, { timeStamp }, 'none').trim()).toEqual( + 'error rendering mustache template "{{#FormatDate}}{{timeStamp}}{{/FormatDate}}": invalid date "this is not a d4t3"' + ); + }); + + it('invalid timezone produces error', () => { + const timeStamp = '2023-04-10T23:52:39'; + const template = dedent` + {{#FormatDate}}{{timeStamp}};NotATime Zone!{{/FormatDate}} + `.trim(); + + expect(renderMustacheString(template, { timeStamp }, 'none').trim()).toEqual( + 'error rendering mustache template "{{#FormatDate}}{{timeStamp}};NotATime Zone!{{/FormatDate}}": unknown timeZone value "NotATime Zone!"' + ); + }); + + it('invalid format produces error', () => { + const timeStamp = '2023-04-10T23:52:39'; + const template = dedent` + {{#FormatDate}}{{timeStamp}};;garbage{{/FormatDate}} + `.trim(); + + // not clear how to force an error, it pretty much does something with + // ANY string + expect(renderMustacheString(template, { timeStamp }, 'none').trim()).toEqual( + 'gamrbamg2' // a => am/pm (so am here); e => day of week + ); + }); + }); + + describe('EvalMath', () => { + it('math is successful', () => { + const vars = { + context: { + a: { b: 1 }, + c: { d: 2 }, + }, + }; + const template = dedent` + {{#EvalMath}} 1 + 0 {{/EvalMath}} + {{#EvalMath}} 1 + context.a.b {{/EvalMath}} + {{#context}} + {{#EvalMath}} 1 + c.d {{/EvalMath}} + {{/context}} + `.trim(); + + const result = renderMustacheString(template, vars, 'none'); + expect(result).toEqual(`1\n2\n3\n`); + }); + + it('invalid expression produces error', () => { + const vars = { + context: { + a: { b: 1 }, + c: { d: 2 }, + }, + }; + const template = dedent` + {{#EvalMath}} ) 1 ++++ 0 ( {{/EvalMath}} + `.trim(); + + const result = renderMustacheString(template, vars, 'none'); + expect(result).toEqual( + `error rendering mustache template "{{#EvalMath}} ) 1 ++++ 0 ( {{/EvalMath}}": error evaluating tinymath expression ") 1 ++++ 0 (": Failed to parse expression. Expected "(", function, literal, or whitespace but ")" found.` + ); + }); + }); + + describe('ParseHJson', () => { + it('valid Hjson is successful', () => { + const vars = { + context: { + a: { b: 1 }, + c: { d: 2 }, + }, + }; + const hjson = ` + { + # specify rate in requests/second (because comments are helpful!) + rate: 1000 + + a: {{context.a}} + a_b: {{context.a.b}} + c: {{context.c}} + c_d: {{context.c.d}} + + # list items can be separated by lines, or commas, and trailing + # commas permitted + list: [ + 1 2 + 3 + 4,5,6, + ] + }`; + const template = dedent` + {{#ParseHjson}} ${hjson} {{/ParseHjson}} + `.trim(); + + const result = renderMustacheString(template, vars, 'none'); + expect(JSON.parse(result)).toMatchInlineSnapshot(` + Object { + "a": Object { + "b": 1, + }, + "a_b": 1, + "c": Object { + "d": 2, + }, + "c_d": 2, + "list": Array [ + "1 2", + 3, + 4, + 5, + 6, + ], + "rate": 1000, + } + `); + }); + + it('renders an error message on parse errors', () => { + const template = dedent` + {{#ParseHjson}} [1,2,3,,] {{/ParseHjson}} + `.trim(); + + const result = renderMustacheString(template, {}, 'none'); + expect(result).toMatch(/^error rendering mustache template .*/); + }); + }); +}); diff --git a/x-pack/plugins/actions/server/lib/mustache_lambdas.ts b/x-pack/plugins/actions/server/lib/mustache_lambdas.ts new file mode 100644 index 0000000000000..62ba5621e0e1e --- /dev/null +++ b/x-pack/plugins/actions/server/lib/mustache_lambdas.ts @@ -0,0 +1,114 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as tinymath from '@kbn/tinymath'; +import { parse as hjsonParse } from 'hjson'; + +import moment from 'moment-timezone'; + +type Variables = Record; + +const DefaultDateTimeZone = 'UTC'; +const DefaultDateFormat = 'YYYY-MM-DD hh:mma'; + +export function getMustacheLambdas(): Variables { + return getLambdas(); +} + +const TimeZoneSet = new Set(moment.tz.names()); + +type RenderFn = (text: string) => string; + +function getLambdas() { + return { + EvalMath: () => + // mustache invokes lamdas with `this` set to the current "view" (variables) + function (this: Variables, text: string, render: RenderFn) { + return evalMath(this, render(text.trim())); + }, + ParseHjson: () => + function (text: string, render: RenderFn) { + return parseHjson(render(text.trim())); + }, + FormatDate: () => + function (text: string, render: RenderFn) { + const dateString = render(text.trim()).trim(); + return formatDate(dateString); + }, + }; +} + +function evalMath(vars: Variables, o: unknown): string { + const expr = `${o}`; + try { + const result = tinymath.evaluate(expr, vars); + return `${result}`; + } catch (err) { + throw new Error(`error evaluating tinymath expression "${expr}": ${err.message}`); + } +} + +function parseHjson(o: unknown): string { + const hjsonObject = `${o}`; + let object: unknown; + + try { + object = hjsonParse(hjsonObject); + } catch (err) { + throw new Error(`error parsing Hjson "${hjsonObject}": ${err.message}`); + } + + return JSON.stringify(object); +} + +function formatDate(dateString: unknown): string { + const { date, timeZone, format } = splitDateString(`${dateString}`); + + if (date === '') { + throw new Error(`date is empty`); + } + + if (isNaN(new Date(date).valueOf())) { + throw new Error(`invalid date "${date}"`); + } + + let mDate: moment.Moment; + try { + mDate = moment(date); + if (!mDate.isValid()) { + throw new Error(`date is invalid`); + } + } catch (err) { + throw new Error(`error evaluating moment date "${date}": ${err.message}`); + } + + if (!TimeZoneSet.has(timeZone)) { + throw new Error(`unknown timeZone value "${timeZone}"`); + } + + try { + mDate.tz(timeZone); + } catch (err) { + throw new Error(`error evaluating moment timeZone "${timeZone}": ${err.message}`); + } + + try { + return mDate.format(format); + } catch (err) { + throw new Error(`error evaluating moment format "${format}": ${err.message}`); + } +} + +function splitDateString(dateString: string) { + const parts = dateString.split(';', 3).map((s) => s.trim()); + const [date = '', timeZone = '', format = ''] = parts; + return { + date, + timeZone: timeZone || DefaultDateTimeZone, + format: format || DefaultDateFormat, + }; +} diff --git a/x-pack/plugins/actions/server/lib/mustache_renderer.test.ts b/x-pack/plugins/actions/server/lib/mustache_renderer.test.ts index 964a793d8f81c..3a02ce0d1a983 100644 --- a/x-pack/plugins/actions/server/lib/mustache_renderer.test.ts +++ b/x-pack/plugins/actions/server/lib/mustache_renderer.test.ts @@ -58,6 +58,12 @@ describe('mustache_renderer', () => { expect(renderMustacheString('{{f.g}}', variables, escape)).toBe('3'); expect(renderMustacheString('{{f.h}}', variables, escape)).toBe(''); expect(renderMustacheString('{{i}}', variables, escape)).toBe('42,43,44'); + + if (escape === 'markdown') { + expect(renderMustacheString('{{i.asJSON}}', variables, escape)).toBe('\\[42,43,44\\]'); + } else { + expect(renderMustacheString('{{i.asJSON}}', variables, escape)).toBe('[42,43,44]'); + } }); } @@ -339,6 +345,11 @@ describe('mustache_renderer', () => { const expected = '1 - {"c":2,"d":[3,4]} -- 5,{"f":6,"g":7}'; expect(renderMustacheString('{{a}} - {{b}} -- {{e}}', deepVariables, 'none')).toEqual(expected); + + expect(renderMustacheString('{{e}}', deepVariables, 'none')).toEqual('5,{"f":6,"g":7}'); + expect(renderMustacheString('{{e.asJSON}}', deepVariables, 'none')).toEqual( + '[5,{"f":6,"g":7}]' + ); }); describe('converting dot variables', () => { diff --git a/x-pack/plugins/actions/server/lib/mustache_renderer.ts b/x-pack/plugins/actions/server/lib/mustache_renderer.ts index fc4381fa0c9c3..37713167e9a34 100644 --- a/x-pack/plugins/actions/server/lib/mustache_renderer.ts +++ b/x-pack/plugins/actions/server/lib/mustache_renderer.ts @@ -7,8 +7,10 @@ import Mustache from 'mustache'; import { isString, isPlainObject, cloneDeepWith, merge } from 'lodash'; +import { getMustacheLambdas } from './mustache_lambdas'; export type Escape = 'markdown' | 'slack' | 'json' | 'none'; + type Variables = Record; // return a rendered mustache template with no escape given the specified variables and escape @@ -25,11 +27,13 @@ export function renderMustacheStringNoEscape(string: string, variables: Variable // return a rendered mustache template given the specified variables and escape export function renderMustacheString(string: string, variables: Variables, escape: Escape): string { const augmentedVariables = augmentObjectVariables(variables); + const lambdas = getMustacheLambdas(); + const previousMustacheEscape = Mustache.escape; Mustache.escape = getEscape(escape); try { - return Mustache.render(`${string}`, augmentedVariables); + return Mustache.render(`${string}`, { ...lambdas, ...augmentedVariables }); } catch (err) { // log error; the mustache code does not currently leak variables return `error rendering mustache template "${string}": ${err.message}`; @@ -98,6 +102,9 @@ function addToStringDeep(object: unknown): void { // walk arrays, but don't add a toString() as mustache already does something if (Array.isArray(object)) { + // instead, add an asJSON() + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (object as any).asJSON = () => JSON.stringify(object); object.forEach((element) => addToStringDeep(element)); return; } diff --git a/x-pack/plugins/actions/tsconfig.json b/x-pack/plugins/actions/tsconfig.json index 77ef11e88bfe3..8c253cb644fee 100644 --- a/x-pack/plugins/actions/tsconfig.json +++ b/x-pack/plugins/actions/tsconfig.json @@ -33,7 +33,8 @@ "@kbn/logging-mocks", "@kbn/core-elasticsearch-client-server-mocks", "@kbn/safer-lodash-set", - "@kbn/core-http-server-mocks" + "@kbn/core-http-server-mocks", + "@kbn/tinymath", ], "exclude": [ "target/**/*", diff --git a/x-pack/test/alerting_api_integration/common/plugins/actions_simulators/server/slack_simulation.ts b/x-pack/test/alerting_api_integration/common/plugins/actions_simulators/server/slack_simulation.ts index f1a67c568b67f..eee078591a3a7 100644 --- a/x-pack/test/alerting_api_integration/common/plugins/actions_simulators/server/slack_simulation.ts +++ b/x-pack/test/alerting_api_integration/common/plugins/actions_simulators/server/slack_simulation.ts @@ -35,7 +35,7 @@ export async function initPlugin() { } // store a message that was posted to be remembered - const match = text.match(/^message (.*)$/); + const match = text.match(/^message ([\S\s]*)$/); if (match) { messages.push(match[1]); response.statusCode = 200; diff --git a/x-pack/test/alerting_api_integration/common/plugins/actions_simulators/server/webhook_simulation.ts b/x-pack/test/alerting_api_integration/common/plugins/actions_simulators/server/webhook_simulation.ts index f3f0aa8f6469b..baa6ee80a8e53 100644 --- a/x-pack/test/alerting_api_integration/common/plugins/actions_simulators/server/webhook_simulation.ts +++ b/x-pack/test/alerting_api_integration/common/plugins/actions_simulators/server/webhook_simulation.ts @@ -79,7 +79,7 @@ function createServerCallback() { } // store a payload that was posted to be remembered - const match = data.match(/^payload (.*)$/); + const match = data.match(/^payload ([\S\s]*)$/); if (match) { payloads.push(match[1]); response.statusCode = 200; @@ -89,6 +89,8 @@ function createServerCallback() { response.statusCode = 400; response.end(`unexpected body ${data}`); + // eslint-disable-next-line no-console + console.log(`webhook simulator received unexpected body: ${data}`); return; }); } else { diff --git a/x-pack/test/alerting_api_integration/common/plugins/alerts/server/alert_types.ts b/x-pack/test/alerting_api_integration/common/plugins/alerts/server/alert_types.ts index 60e7e82966864..6d716b5d3c235 100644 --- a/x-pack/test/alerting_api_integration/common/plugins/alerts/server/alert_types.ts +++ b/x-pack/test/alerting_api_integration/common/plugins/alerts/server/alert_types.ts @@ -47,6 +47,7 @@ export const DeepContextVariables = { arrayI: [44, 45], nullJ: null, undefinedK: undefined, + dateL: '2023-04-20T04:13:17.858Z', }; function getAlwaysFiringAlertType() { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/mustache_templates.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/mustache_templates.ts index 1eb7d99b93bda..764dd728b1347 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/mustache_templates.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/mustache_templates.ts @@ -14,13 +14,16 @@ import http from 'http'; import getPort from 'get-port'; -import { URL, format as formatUrl } from 'url'; import axios from 'axios'; import expect from '@kbn/expect'; import { getWebhookServer, getSlackServer } from '@kbn/actions-simulators-plugin/server/plugin'; import { Spaces } from '../../../scenarios'; -import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../../common/lib'; +import { + getUrlPrefix, + getTestRuleData as getCoreTestRuleData, + ObjectRemover, +} from '../../../../common/lib'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export @@ -32,8 +35,10 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon const objectRemover = new ObjectRemover(supertest); let webhookSimulatorURL: string = ''; let webhookServer: http.Server; + let webhookConnector: any; let slackSimulatorURL: string = ''; let slackServer: http.Server; + let slackConnector: any; before(async () => { let availablePort: number; @@ -42,6 +47,7 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon availablePort = await getPort({ port: 9000 }); webhookServer.listen(availablePort); webhookSimulatorURL = `http://localhost:${availablePort}`; + webhookConnector = await createWebhookConnector(webhookSimulatorURL); slackServer = await getSlackServer(); availablePort = await getPort({ port: getPort.makeRange(9000, 9100) }); @@ -49,6 +55,7 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon slackServer.listen(availablePort); } slackSimulatorURL = `http://localhost:${availablePort}`; + slackConnector = await createSlackConnector(slackSimulatorURL); }); after(async () => { @@ -57,219 +64,177 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon slackServer.close(); }); - it('should handle escapes in webhook', async () => { - const url = formatUrl(new URL(webhookSimulatorURL), { auth: false }); - const actionResponse = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) - .set('kbn-xsrf', 'test') - .send({ - name: 'testing mustache escapes for webhook', - connector_type_id: '.webhook', - secrets: {}, - config: { - headers: { - 'Content-Type': 'text/plain', - }, - url, + describe('escaping', () => { + it('should handle escapes in webhook', async () => { + // from x-pack/test/alerting_api_integration/common/plugins/alerts/server/alert_types.ts, + // const EscapableStrings + const template = '{{context.escapableDoubleQuote}} -- {{context.escapableLineFeed}}'; + const rule = await createRule({ + id: webhookConnector.id, + group: 'default', + params: { + body: `payload {{alertId}} - ${template}`, + }, + }); + const body = await retry.try(async () => waitForActionBody(webhookSimulatorURL, rule.id)); + expect(body).to.be(`\\"double quote\\" -- line\\nfeed`); + }); + + it('should handle escapes in slack', async () => { + // from x-pack/test/alerting_api_integration/common/plugins/alerts/server/alert_types.ts, + // const EscapableStrings + const template = + '{{context.escapableBacktic}} -- {{context.escapableBold}} -- {{context.escapableBackticBold}} -- {{context.escapableHtml}}'; + + const rule = await createRule({ + id: slackConnector.id, + group: 'default', + params: { + message: `message {{alertId}} - ${template}`, }, }); - expect(actionResponse.status).to.eql(200); - const createdAction = actionResponse.body; - objectRemover.add(Spaces.space1.id, createdAction.id, 'connector', 'actions'); - - // from x-pack/test/alerting_api_integration/common/plugins/alerts/server/alert_types.ts, - // const EscapableStrings - const varsTemplate = '{{context.escapableDoubleQuote}} -- {{context.escapableLineFeed}}'; - const alertResponse = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) - .set('kbn-xsrf', 'foo') - .send( - getTestRuleData({ - name: 'testing variable escapes for webhook', - rule_type_id: 'test.patternFiring', - params: { - pattern: { instance: [true] }, - }, - actions: [ - { - id: createdAction.id, - group: 'default', - params: { - body: `payload {{alertId}} - ${varsTemplate}`, - }, - }, - ], - }) + const body = await retry.try(async () => waitForActionBody(slackSimulatorURL, rule.id)); + expect(body).to.be("back'tic -- `*bold*` -- `'*bold*'` -- <&>"); + }); + + it('should handle context variable object expansion', async () => { + // from x-pack/test/alerting_api_integration/common/plugins/alerts/server/alert_types.ts, + // const DeepContextVariables + const template = '{{context.deep}}'; + const rule = await createRule({ + id: slackConnector.id, + group: 'default', + params: { + message: `message {{alertId}} - ${template}`, + }, + }); + const body = await retry.try(async () => waitForActionBody(slackSimulatorURL, rule.id)); + expect(body).to.be( + '{"objectA":{"stringB":"B","arrayC":[{"stringD":"D1","numberE":42},{"stringD":"D2","numberE":43}],"objectF":{"stringG":"G","nullG":null}},"stringH":"H","arrayI":[44,45],"nullJ":null,"dateL":"2023-04-20T04:13:17.858Z"}' ); - expect(alertResponse.status).to.eql(200); - const createdAlert = alertResponse.body; - objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); - - const body = await retry.try(async () => - waitForActionBody(webhookSimulatorURL, createdAlert.id) - ); - expect(body).to.be(`\\"double quote\\" -- line\\nfeed`); - }); - - it('should handle escapes in slack', async () => { - const actionResponse = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) - .set('kbn-xsrf', 'test') - .send({ - name: "testing backtic'd mustache escapes for slack", - connector_type_id: '.slack', - secrets: { - webhookUrl: slackSimulatorURL, + }); + + it('should render kibanaBaseUrl as empty string since not configured', async () => { + const template = 'kibanaBaseUrl: "{{kibanaBaseUrl}}"'; + const rule = await createRule({ + id: slackConnector.id, + group: 'default', + params: { + message: `message {{alertId}} - ${template}`, }, }); - expect(actionResponse.status).to.eql(200); - const createdAction = actionResponse.body; - objectRemover.add(Spaces.space1.id, createdAction.id, 'connector', 'actions'); - // from x-pack/test/alerting_api_integration/common/plugins/alerts/server/alert_types.ts, - // const EscapableStrings - const varsTemplate = - '{{context.escapableBacktic}} -- {{context.escapableBold}} -- {{context.escapableBackticBold}} -- {{context.escapableHtml}}'; + const body = await retry.try(async () => waitForActionBody(slackSimulatorURL, rule.id)); + expect(body).to.be('kibanaBaseUrl: ""'); + }); - const alertResponse = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) - .set('kbn-xsrf', 'foo') - .send( - getTestRuleData({ - name: 'testing variable escapes for slack', - rule_type_id: 'test.patternFiring', - params: { - pattern: { instance: [true] }, - }, - actions: [ - { - id: createdAction.id, - group: 'default', - params: { - message: `message {{alertId}} - ${varsTemplate}`, - }, - }, - ], - }) - ); - expect(alertResponse.status).to.eql(200); - const createdAlert = alertResponse.body; - objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); - - const body = await retry.try(async () => - waitForActionBody(slackSimulatorURL, createdAlert.id) - ); - expect(body).to.be("back'tic -- `*bold*` -- `'*bold*'` -- <&>"); - }); - - it('should handle context variable object expansion', async () => { - const actionResponse = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) - .set('kbn-xsrf', 'test') - .send({ - name: 'testing context variable expansion', - connector_type_id: '.slack', - secrets: { - webhookUrl: slackSimulatorURL, + it('should render action variables in rule action', async () => { + const rule = await createRule({ + id: webhookConnector.id, + group: 'default', + params: { + body: `payload {{rule.id}} - old id variable: {{alertId}}, new id variable: {{rule.id}}, old name variable: {{alertName}}, new name variable: {{rule.name}}`, }, }); - expect(actionResponse.status).to.eql(200); - const createdAction = actionResponse.body; - objectRemover.add(Spaces.space1.id, createdAction.id, 'connector', 'actions'); - - // from x-pack/test/alerting_api_integration/common/plugins/alerts/server/alert_types.ts, - // const DeepContextVariables - const varsTemplate = '{{context.deep}}'; - const alertResponse = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) - .set('kbn-xsrf', 'foo') - .send( - getTestRuleData({ - name: 'testing context variable expansion', - rule_type_id: 'test.patternFiring', - params: { - pattern: { instance: [true, true] }, - }, - actions: [ - { - id: createdAction.id, - group: 'default', - params: { - message: `message {{alertId}} - ${varsTemplate}`, - }, - }, - ], - }) + const body = await retry.try(async () => waitForActionBody(webhookSimulatorURL, rule.id)); + expect(body).to.be( + `old id variable: ${rule.id}, new id variable: ${rule.id}, old name variable: ${rule.name}, new name variable: ${rule.name}` ); - expect(alertResponse.status).to.eql(200); - const createdAlert = alertResponse.body; - objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); - - const body = await retry.try(async () => - waitForActionBody(slackSimulatorURL, createdAlert.id) - ); - expect(body).to.be( - '{"objectA":{"stringB":"B","arrayC":[{"stringD":"D1","numberE":42},{"stringD":"D2","numberE":43}],"objectF":{"stringG":"G","nullG":null}},"stringH":"H","arrayI":[44,45],"nullJ":null}' - ); + }); }); - it('should render kibanaBaseUrl as empty string since not configured', async () => { - const actionResponse = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) - .set('kbn-xsrf', 'test') - .send({ - name: 'testing context variable expansion', - connector_type_id: '.slack', - secrets: { - webhookUrl: slackSimulatorURL, + describe('lambdas', () => { + it('should handle ParseHjson', async () => { + const template = `{{#ParseHjson}} { + ruleId: {{rule.id}} + ruleName: {{rule.name}} + } {{/ParseHjson}}`; + const rule = await createRule({ + id: webhookConnector.id, + group: 'default', + params: { + body: `payload {{alertId}} - ${template}`, }, }); - expect(actionResponse.status).to.eql(200); - const createdAction = actionResponse.body; - objectRemover.add(Spaces.space1.id, createdAction.id, 'connector', 'actions'); - - const varsTemplate = 'kibanaBaseUrl: "{{kibanaBaseUrl}}"'; + const body = await retry.try(async () => waitForActionBody(webhookSimulatorURL, rule.id)); + expect(body).to.be(`{"ruleId":"${rule.id}","ruleName":"testing mustache templates"}`); + }); + + it('should handle asJSON', async () => { + // from x-pack/test/alerting_api_integration/common/plugins/alerts/server/alert_types.ts, + // const DeepContextVariables + const template = `{{#context.deep.objectA}} + {{{arrayC}}} {{{arrayC.asJSON}}} + {{/context.deep.objectA}} + `; + const rule = await createRule({ + id: webhookConnector.id, + group: 'default', + params: { + body: `payload {{alertId}} - ${template}`, + }, + }); + const body = await retry.try(async () => waitForActionBody(webhookSimulatorURL, rule.id)); + const expected1 = `{"stringD":"D1","numberE":42},{"stringD":"D2","numberE":43}`; + const expected2 = `[{"stringD":"D1","numberE":42},{"stringD":"D2","numberE":43}]`; + expect(body.trim()).to.be(`${expected1} ${expected2}`); + }); + + it('should handle EvalMath', async () => { + // from x-pack/test/alerting_api_integration/common/plugins/alerts/server/alert_types.ts, + // const DeepContextVariables + const template = `{{#context.deep}}avg({{arrayI.0}}, {{arrayI.1}})/100 => {{#EvalMath}} + round((arrayI[0] + arrayI[1]) / 2 / 100, 2) + {{/EvalMath}}{{/context.deep}}`; + const rule = await createRule({ + id: slackConnector.id, + group: 'default', + params: { + message: `message {{alertId}} - ${template}`, + }, + }); + const body = await retry.try(async () => waitForActionBody(slackSimulatorURL, rule.id)); + expect(body).to.be(`avg(44, 45)/100 => 0.45`); + }); + + it('should handle FormatDate', async () => { + // from x-pack/test/alerting_api_integration/common/plugins/alerts/server/alert_types.ts, + // const DeepContextVariables + const template = `{{#context.deep}}{{#FormatDate}} + {{{dateL}}} ; America/New_York; dddd MMM Do YYYY HH:mm:ss + {{/FormatDate}}{{/context.deep}}`; + const rule = await createRule({ + id: slackConnector.id, + group: 'default', + params: { + message: `message {{alertId}} - ${template}`, + }, + }); + const body = await retry.try(async () => waitForActionBody(slackSimulatorURL, rule.id)); + expect(body.trim()).to.be(`Thursday Apr 20th 2023 00:13:17`); + }); + }); - const alertResponse = await supertest + async function createRule(action: any) { + const ruleResponse = await supertest .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') - .send( - getTestRuleData({ - name: 'testing context variable kibanaBaseUrl', - rule_type_id: 'test.patternFiring', - params: { - pattern: { instance: [true, true] }, - }, - actions: [ - { - id: createdAction.id, - group: 'default', - params: { - message: `message {{alertId}} - ${varsTemplate}`, - }, - }, - ], - }) - ); - expect(alertResponse.status).to.eql(200); - const createdAlert = alertResponse.body; - objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); - - const body = await retry.try(async () => - waitForActionBody(slackSimulatorURL, createdAlert.id) - ); - expect(body).to.be('kibanaBaseUrl: ""'); - }); + .send(getTestRuleData({ actions: [action] })); + expect(ruleResponse.status).to.eql(200); + const rule = ruleResponse.body; + objectRemover.add(Spaces.space1.id, rule.id, 'rule', 'alerting'); + + return rule; + } - it('should render action variables in rule action', async () => { - const url = formatUrl(new URL(webhookSimulatorURL), { auth: false }); - const actionResponse = await supertest + async function createWebhookConnector(url: string) { + const createResponse = await supertest .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) .set('kbn-xsrf', 'test') .send({ - name: 'testing action variable rendering', + name: 'testing mustache for webhook', connector_type_id: '.webhook', secrets: {}, config: { @@ -279,42 +244,30 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon url, }, }); - expect(actionResponse.status).to.eql(200); - const createdAction = actionResponse.body; - objectRemover.add(Spaces.space1.id, createdAction.id, 'connector', 'actions'); + expect(createResponse.status).to.eql(200); + const connector = createResponse.body; + objectRemover.add(Spaces.space1.id, connector.id, 'connector', 'actions'); - const alertResponse = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) - .set('kbn-xsrf', 'foo') - .send( - getTestRuleData({ - name: 'testing variable escapes for webhook', - rule_type_id: 'test.patternFiring', - params: { - pattern: { instance: [true] }, - }, - actions: [ - { - id: createdAction.id, - group: 'default', - params: { - body: `payload {{rule.id}} - old id variable: {{alertId}}, new id variable: {{rule.id}}, old name variable: {{alertName}}, new name variable: {{rule.name}}`, - }, - }, - ], - }) - ); - expect(alertResponse.status).to.eql(200); - const createdAlert = alertResponse.body; - objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); - - const body = await retry.try(async () => - waitForActionBody(webhookSimulatorURL, createdAlert.id) - ); - expect(body).to.be( - `old id variable: ${createdAlert.id}, new id variable: ${createdAlert.id}, old name variable: ${createdAlert.name}, new name variable: ${createdAlert.name}` - ); - }); + return connector; + } + + async function createSlackConnector(url: string) { + const createResponse = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) + .set('kbn-xsrf', 'test') + .send({ + name: 'testing mustache for slack', + connector_type_id: '.slack', + secrets: { + webhookUrl: url, + }, + }); + expect(createResponse.status).to.eql(200); + const connector = createResponse.body; + objectRemover.add(Spaces.space1.id, connector.id, 'connector', 'actions'); + + return connector; + } }); async function waitForActionBody(url: string, id: string): Promise { @@ -322,7 +275,7 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon expect(response.status).to.eql(200); for (const datum of response.data) { - const match = datum.match(/^(.*) - (.*)$/); + const match = datum.match(/^(.*) - ([\S\s]*)$/); if (match == null) continue; if (match[1] === id) return match[2]; @@ -331,3 +284,15 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon throw new Error(`no action body posted yet for id ${id}`); } } + +function getTestRuleData(overrides: any) { + const defaults = { + name: 'testing mustache templates', + rule_type_id: 'test.patternFiring', + params: { + pattern: { instance: [true] }, + }, + }; + + return getCoreTestRuleData({ ...overrides, ...defaults }); +} From e46cb1ab8a98af3cbad8c6affcd3477cea9abc9d Mon Sep 17 00:00:00 2001 From: Rodney Norris Date: Mon, 24 Apr 2023 14:16:43 -0500 Subject: [PATCH 09/52] [Enterprise Search][Search Applications] introduce content page (#155632) ## Summary Introduced a Content page for Search Application that combined the Indices and Schema pages into a single page with tabs ### Screenshots image image image ### 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 - [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) --- .../engine/engine_connect/engine_connect.tsx | 4 +- .../components/engine/engine_indices.tsx | 206 +++++++--------- .../components/engine/engine_schema.tsx | 227 ++++++++---------- .../components/engine/engine_view.tsx | 12 +- .../engine/search_application_content.tsx | 156 ++++++++++++ .../enterprise_search_content/routes.ts | 8 +- .../applications/shared/layout/nav.test.tsx | 11 +- .../public/applications/shared/layout/nav.tsx | 19 +- .../translations/translations/fr-FR.json | 4 - .../translations/translations/ja-JP.json | 4 - .../translations/translations/zh-CN.json | 4 - 11 files changed, 372 insertions(+), 283 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/search_application_content.tsx diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/engine_connect.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/engine_connect.tsx index 2dd55304b6035..a6c29285f4f66 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/engine_connect.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/engine_connect.tsx @@ -15,7 +15,7 @@ import { i18n } from '@kbn/i18n'; import { generateEncodedPath } from '../../../../shared/encode_path_params'; import { KibanaLogic } from '../../../../shared/kibana'; import { - SEARCH_APPLICATION_CONNECT_PATH, + SEARCH_APPLICATION_CONTENT_PATH, EngineViewTabs, SearchApplicationConnectTabs, } from '../../../routes'; @@ -55,7 +55,7 @@ export const EngineConnect: React.FC = () => { }>(); const onTabClick = (tab: SearchApplicationConnectTabs) => () => { KibanaLogic.values.navigateToUrl( - generateEncodedPath(SEARCH_APPLICATION_CONNECT_PATH, { + generateEncodedPath(SEARCH_APPLICATION_CONTENT_PATH, { engineName, connectTabId: tab, }) diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_indices.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_indices.tsx index 4bcd3fcf4f215..285846f7ccb1c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_indices.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_indices.tsx @@ -11,7 +11,6 @@ import { useActions, useValues } from 'kea'; import { EuiBasicTableColumn, - EuiButton, EuiCallOut, EuiConfirmModal, EuiIcon, @@ -32,24 +31,15 @@ import { KibanaLogic } from '../../../shared/kibana'; import { EuiLinkTo } from '../../../shared/react_router_helpers'; import { TelemetryLogic } from '../../../shared/telemetry/telemetry_logic'; -import { SEARCH_INDEX_PATH, EngineViewTabs } from '../../routes'; +import { SEARCH_INDEX_PATH } from '../../routes'; -import { EnterpriseSearchEnginesPageTemplate } from '../layout/engines_page_template'; - -import { AddIndicesFlyout } from './add_indices_flyout'; import { EngineIndicesLogic } from './engine_indices_logic'; -const pageTitle = i18n.translate('xpack.enterpriseSearch.content.engine.indices.pageTitle', { - defaultMessage: 'Indices', -}); - export const EngineIndices: React.FC = () => { const subduedBackground = useEuiBackgroundColor('subdued'); const { sendEnterpriseSearchTelemetry } = useActions(TelemetryLogic); - const { engineData, engineName, isLoadingEngine, addIndicesFlyoutOpen } = - useValues(EngineIndicesLogic); - const { removeIndexFromEngine, openAddIndicesFlyout, closeAddIndicesFlyout } = - useActions(EngineIndicesLogic); + const { engineData } = useValues(EngineIndicesLogic); + const { removeIndexFromEngine } = useActions(EngineIndicesLogic); const { navigateToUrl } = useValues(KibanaLogic); const [removeIndexConfirm, setConfirmRemoveIndex] = useState(null); @@ -177,116 +167,92 @@ export const EngineIndices: React.FC = () => { }, ]; return ( - + {hasUnknownIndices && ( + <> + - {i18n.translate('xpack.enterpriseSearch.content.engine.indices.addNewIndicesButton', { - defaultMessage: 'Add new indices', - })} - , - ], - }} - engineName={engineName} - > - <> - {hasUnknownIndices && ( - <> - + {i18n.translate( + 'xpack.enterpriseSearch.content.engine.indices.unknownIndicesCallout.description', + { + defaultMessage: + 'Some data might be unreachable from this search application. Check for any pending operations or errors on affected indices, or remove indices that should no longer be used by this search application.', + } )} - > -

- {i18n.translate( - 'xpack.enterpriseSearch.content.engine.indices.unknownIndicesCallout.description', - { - defaultMessage: - 'Some data might be unreachable from this search application. Check for any pending operations or errors on affected indices, or remove those that should no longer be used by this search application.', - } - )} -

-
- - - )} - { - if (index.health === 'unknown') { - return { style: { backgroundColor: subduedBackground } }; - } +

+
+ + + )} + { + if (index.health === 'unknown') { + return { style: { backgroundColor: subduedBackground } }; + } - return {}; - }} - search={{ - box: { - incremental: true, - placeholder: i18n.translate( - 'xpack.enterpriseSearch.content.engine.indices.searchPlaceholder', - { defaultMessage: 'Filter indices' } - ), - schema: true, - }, + return {}; + }} + search={{ + box: { + incremental: true, + placeholder: i18n.translate( + 'xpack.enterpriseSearch.content.engine.indices.searchPlaceholder', + { defaultMessage: 'Filter indices' } + ), + schema: true, + }, + }} + pagination + sorting + /> + {removeIndexConfirm !== null && ( + setConfirmRemoveIndex(null)} + onConfirm={() => { + removeIndexFromEngine(removeIndexConfirm); + setConfirmRemoveIndex(null); + sendEnterpriseSearchTelemetry({ + action: 'clicked', + metric: 'entSearchContent-engines-indices-removeIndexConfirm', + }); }} - pagination - sorting - /> - {removeIndexConfirm !== null && ( - setConfirmRemoveIndex(null)} - onConfirm={() => { - removeIndexFromEngine(removeIndexConfirm); - setConfirmRemoveIndex(null); - sendEnterpriseSearchTelemetry({ - action: 'clicked', - metric: 'entSearchContent-engines-indices-removeIndexConfirm', - }); - }} - title={i18n.translate( - 'xpack.enterpriseSearch.content.engine.indices.removeIndexConfirm.title', - { defaultMessage: 'Remove this index from the Search Application' } - )} - buttonColor="danger" - cancelButtonText={CANCEL_BUTTON_LABEL} - confirmButtonText={i18n.translate( - 'xpack.enterpriseSearch.content.engine.indices.removeIndexConfirm.text', - { - defaultMessage: 'Yes, Remove This Index', - } - )} - defaultFocusedButton="confirm" - maxWidth - > - -

- {i18n.translate( - 'xpack.enterpriseSearch.content.engine.indices.removeIndexConfirm.description', - { - defaultMessage: - "This won't delete the index. You may add it back to this search application at a later time.", - } - )} -

-
-
- )} - {addIndicesFlyoutOpen && } - -
+ title={i18n.translate( + 'xpack.enterpriseSearch.content.engine.indices.removeIndexConfirm.title', + { defaultMessage: 'Remove this index from the search application' } + )} + buttonColor="danger" + cancelButtonText={CANCEL_BUTTON_LABEL} + confirmButtonText={i18n.translate( + 'xpack.enterpriseSearch.content.engine.indices.removeIndexConfirm.text', + { + defaultMessage: 'Yes, Remove This Index', + } + )} + defaultFocusedButton="confirm" + maxWidth + > + +

+ {i18n.translate( + 'xpack.enterpriseSearch.content.engine.indices.removeIndexConfirm.description', + { + defaultMessage: + "This won't delete the index. You may add it back to this search application at a later time.", + } + )} +

+
+ + )} + ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_schema.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_schema.tsx index 4ceba9cccb142..fd10624188ff0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_schema.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_schema.tsx @@ -42,8 +42,7 @@ import { docLinks } from '../../../shared/doc_links'; import { generateEncodedPath } from '../../../shared/encode_path_params'; import { KibanaLogic } from '../../../shared/kibana'; import { EuiLinkTo } from '../../../shared/react_router_helpers'; -import { EngineViewTabs, SEARCH_INDEX_TAB_PATH } from '../../routes'; -import { EnterpriseSearchEnginesPageTemplate } from '../layout/engines_page_template'; +import { SEARCH_INDEX_TAB_PATH } from '../../routes'; import { EngineIndicesLogic } from './engine_indices_logic'; @@ -153,10 +152,6 @@ const SchemaFieldDetails: React.FC<{ schemaField: SchemaField }> = ({ schemaFiel ); }; -const pageTitle = i18n.translate('xpack.enterpriseSearch.content.engine.schema.pageTitle', { - defaultMessage: 'Schema', -}); - export const EngineSchema: React.FC = () => { const { engineName } = useValues(EngineIndicesLogic); const [onlyShowConflicts, setOnlyShowConflicts] = useState(false); @@ -348,125 +343,115 @@ export const EngineSchema: React.FC = () => { ); return ( - - <> - - - - - - {i18n.translate('xpack.enterpriseSearch.content.engine.schema.filters.label', { - defaultMessage: 'Filter By', - })} - - - setIsFilterByPopoverOpen(false)} - panelPaddingSize="none" - anchorPosition="downCenter" + <> + + + + + + {i18n.translate('xpack.enterpriseSearch.content.engine.schema.filters.label', { + defaultMessage: 'Filter By', + })} + + + setIsFilterByPopoverOpen(false)} + panelPaddingSize="none" + anchorPosition="downCenter" + > + setSelectedEsFieldTypes(options)} > - ( +
+ {search} + {list} +
+ )} +
+ + + setSelectedEsFieldTypes(esFieldTypes)} + > + {i18n.translate( + 'xpack.enterpriseSearch.content.engine.schema.filters.clearAll', { - defaultMessage: 'Filter list ', + defaultMessage: 'Clear all ', } - ), - }} - options={selectedEsFieldTypes} - onChange={(options) => setSelectedEsFieldTypes(options)} - > - {(list, search) => ( -
- {search} - {list} -
- )} -
- - - setSelectedEsFieldTypes(esFieldTypes)} - > - {i18n.translate( - 'xpack.enterpriseSearch.content.engine.schema.filters.clearAll', - { - defaultMessage: 'Clear all ', - } - )} - - - -
-
-
+ )} + +
+ +
+
- - - {totalConflictsHiddenByTypeFilters > 0 && ( - - } - color="danger" - iconType="iInCircle" - > -

- {i18n.translate( - 'xpack.enterpriseSearch.content.engine.schema.filters.conflict.callout.subTitle', - { - defaultMessage: - 'In order to see all field conflicts you must clear your field filters', - } - )} -

- setSelectedEsFieldTypes(esFieldTypes)}> - {i18n.translate( - 'xpack.enterpriseSearch.content.engine.schema.filters.conflict.callout.clearFilters', - { - defaultMessage: 'Clear filters ', - } - )} - -
- )}
- -
+ + + {totalConflictsHiddenByTypeFilters > 0 && ( + + } + color="danger" + iconType="iInCircle" + > +

+ {i18n.translate( + 'xpack.enterpriseSearch.content.engine.schema.filters.conflict.callout.subTitle', + { + defaultMessage: + 'In order to see all field conflicts you must clear your field filters', + } + )} +

+ setSelectedEsFieldTypes(esFieldTypes)}> + {i18n.translate( + 'xpack.enterpriseSearch.content.engine.schema.filters.conflict.callout.clearFilters', + { + defaultMessage: 'Clear filters ', + } + )} + +
+ )} + + ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_view.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_view.tsx index 35e56f825d33a..6bbfe9b1de967 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_view.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_view.tsx @@ -17,9 +17,11 @@ import { Status } from '../../../../../common/types/api'; import { KibanaLogic } from '../../../shared/kibana'; import { ENGINE_PATH, + SEARCH_APPLICATION_CONTENT_PATH, SEARCH_APPLICATION_CONNECT_PATH, EngineViewTabs, SearchApplicationConnectTabs, + SearchApplicationContentTabs, } from '../../routes'; import { DeleteEngineModal } from '../engines/delete_engine_modal'; @@ -27,11 +29,10 @@ import { EnterpriseSearchEnginesPageTemplate } from '../layout/engines_page_temp import { EngineConnect } from './engine_connect/engine_connect'; import { EngineError } from './engine_error'; -import { EngineIndices } from './engine_indices'; -import { EngineSchema } from './engine_schema'; import { EngineSearchPreview } from './engine_search_preview/engine_search_preview'; import { EngineViewLogic } from './engine_view_logic'; import { EngineHeaderDocsAction } from './header_docs_action'; +import { SearchApplicationContent } from './search_application_content'; export const EngineView: React.FC = () => { const { fetchEngine, closeDeleteEngineModal } = useActions(EngineViewLogic); @@ -74,8 +75,11 @@ export const EngineView: React.FC = () => { path={`${ENGINE_PATH}/${EngineViewTabs.PREVIEW}`} component={EngineSearchPreview} /> - - + + { + switch (tabId) { + case SearchApplicationContentTabs.INDICES: + return INDICES_TAB_TITLE; + case SearchApplicationContentTabs.SCHEMA: + return SCHEMA_TAB_TITLE; + default: + return tabId; + } +}; + +const ContentTabs: string[] = Object.values(SearchApplicationContentTabs); + +export const SearchApplicationContent = () => { + const { engineName, isLoadingEngine } = useValues(EngineViewLogic); + const { addIndicesFlyoutOpen } = useValues(EngineIndicesLogic); + const { closeAddIndicesFlyout, openAddIndicesFlyout } = useActions(EngineIndicesLogic); + const { contentTabId = SearchApplicationContentTabs.INDICES } = useParams<{ + contentTabId?: string; + }>(); + + if (!ContentTabs.includes(contentTabId)) { + return ( + + + + ); + } + + const onTabClick = (tab: SearchApplicationContentTabs) => () => { + KibanaLogic.values.navigateToUrl( + generateEncodedPath(SEARCH_APPLICATION_CONTENT_PATH, { + contentTabId: tab, + engineName, + }) + ); + }; + + return ( + + KibanaLogic.values.navigateToUrl( + generateEncodedPath(ENGINE_PATH, { + engineName, + }) + ), + text: ( + <> + {engineName} + + ), + }, + ], + pageTitle, + rightSideItems: [ + + {i18n.translate('xpack.enterpriseSearch.content.engine.indices.addNewIndicesButton', { + defaultMessage: 'Add new indices', + })} + , + ], + tabs: [ + { + isSelected: contentTabId === SearchApplicationContentTabs.INDICES, + label: INDICES_TAB_TITLE, + onClick: onTabClick(SearchApplicationContentTabs.INDICES), + }, + { + isSelected: contentTabId === SearchApplicationContentTabs.SCHEMA, + label: SCHEMA_TAB_TITLE, + onClick: onTabClick(SearchApplicationContentTabs.SCHEMA), + }, + ], + }} + engineName={engineName} + > + {contentTabId === SearchApplicationContentTabs.INDICES && } + {contentTabId === SearchApplicationContentTabs.SCHEMA && } + {addIndicesFlyoutOpen && } + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/routes.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/routes.ts index ea5672222014f..7b5bc7bf286f5 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/routes.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/routes.ts @@ -29,8 +29,7 @@ export const ENGINES_PATH = `${ROOT_PATH}engines`; export enum EngineViewTabs { PREVIEW = 'preview', - INDICES = 'indices', - SCHEMA = 'schema', + CONTENT = 'content', CONNECT = 'connect', } export const ENGINE_CREATION_PATH = `${ENGINES_PATH}/new`; @@ -40,5 +39,10 @@ export const SEARCH_APPLICATION_CONNECT_PATH = `${ENGINE_PATH}/${EngineViewTabs. export enum SearchApplicationConnectTabs { API = 'api', } +export const SEARCH_APPLICATION_CONTENT_PATH = `${ENGINE_PATH}/${EngineViewTabs.CONTENT}/:contentTabId`; +export enum SearchApplicationContentTabs { + INDICES = 'indices', + SCHEMA = 'schema', +} export const ML_MANAGE_TRAINED_MODELS_PATH = '/app/ml/trained_models'; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx index 11458565f781a..dcb343a2beec4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx @@ -290,14 +290,9 @@ describe('useEnterpriseSearchEngineNav', () => { name: 'Preview', }, { - href: `/app/enterprise_search/content/engines/${engineName}/indices`, - id: 'enterpriseSearchEngineIndices', - name: 'Indices', - }, - { - href: `/app/enterprise_search/content/engines/${engineName}/schema`, - id: 'enterpriseSearchEngineSchema', - name: 'Schema', + href: `/app/enterprise_search/content/engines/${engineName}/content`, + id: 'enterpriseSearchApplicationsContent', + name: 'Content', }, { href: `/app/enterprise_search/content/engines/${engineName}/connect`, diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx index f06e32b9e30c8..7fbc354ff725f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx @@ -204,23 +204,14 @@ export const useEnterpriseSearchEngineNav = (engineName?: string, isEmptyState?: }), }, { - id: 'enterpriseSearchEngineIndices', - name: i18n.translate('xpack.enterpriseSearch.nav.engine.indicesTitle', { - defaultMessage: 'Indices', + id: 'enterpriseSearchApplicationsContent', + name: i18n.translate('xpack.enterpriseSearch.nav.engine.contentTitle', { + defaultMessage: 'Content', }), ...generateNavLink({ shouldNotCreateHref: true, - to: `${enginePath}/${EngineViewTabs.INDICES}`, - }), - }, - { - id: 'enterpriseSearchEngineSchema', - name: i18n.translate('xpack.enterpriseSearch.nav.engine.schemaTitle', { - defaultMessage: 'Schema', - }), - ...generateNavLink({ - shouldNotCreateHref: true, - to: `${enginePath}/${EngineViewTabs.SCHEMA}`, + shouldShowActiveForSubroutes: true, + to: `${enginePath}/${EngineViewTabs.CONTENT}`, }), }, { diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index f6610dba543b2..f7cf6a952a796 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -12229,7 +12229,6 @@ "xpack.enterpriseSearch.content.engine.indices.docsCount.columnTitle": "Nombre de documents", "xpack.enterpriseSearch.content.engine.indices.health.columnTitle": "Intégrité des index", "xpack.enterpriseSearch.content.engine.indices.name.columnTitle": "Nom de l'index", - "xpack.enterpriseSearch.content.engine.indices.pageTitle": "Index", "xpack.enterpriseSearch.content.engine.indices.removeIndexConfirm.description": "L'index ne sera pas supprimé. Vous pourrez l'ajouter de nouveau à ce moteur ultérieurement.", "xpack.enterpriseSearch.content.engine.indices.removeIndexConfirm.text": "Oui, retirer cet index", "xpack.enterpriseSearch.content.engine.indices.removeIndexConfirm.title": "Retirer cet index du moteur", @@ -12237,7 +12236,6 @@ "xpack.enterpriseSearch.content.engine.indicesSelect.docsLabel": "Documents :", "xpack.enterpriseSearch.content.engine.schema.field_name.columnTitle": "Nom du champ", "xpack.enterpriseSearch.content.engine.schema.field_type.columnTitle": "Type du champ", - "xpack.enterpriseSearch.content.engine.schema.pageTitle": "Schéma", "xpack.enterpriseSearch.content.engineList.deleteEngineModal.confirmButton.title": "Oui, supprimer ce moteur ", "xpack.enterpriseSearch.content.engineList.deleteEngineModal.delete.description": "La suppression de votre moteur ne pourra pas être annulée. Vos index ne seront pas affectés. ", "xpack.enterpriseSearch.content.engineList.deleteEngineModal.title": "Supprimer définitivement ce moteur ?", @@ -13091,8 +13089,6 @@ "xpack.enterpriseSearch.nav.contentSettingsTitle": "Paramètres", "xpack.enterpriseSearch.nav.contentTitle": "Contenu", "xpack.enterpriseSearch.nav.elasticsearchTitle": "Elasticsearch", - "xpack.enterpriseSearch.nav.engine.indicesTitle": "Index", - "xpack.enterpriseSearch.nav.engine.schemaTitle": "Schéma", "xpack.enterpriseSearch.nav.enterpriseSearchOverviewTitle": "Aperçu", "xpack.enterpriseSearch.nav.searchExperiencesTitle": "Expériences de recherche", "xpack.enterpriseSearch.nav.searchIndicesTitle": "Index", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 91baf79cfaaa0..2e5fdf22d200b 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -12228,7 +12228,6 @@ "xpack.enterpriseSearch.content.engine.indices.docsCount.columnTitle": "ドキュメント数", "xpack.enterpriseSearch.content.engine.indices.health.columnTitle": "インデックス正常性", "xpack.enterpriseSearch.content.engine.indices.name.columnTitle": "インデックス名", - "xpack.enterpriseSearch.content.engine.indices.pageTitle": "インデックス", "xpack.enterpriseSearch.content.engine.indices.removeIndexConfirm.description": "インデックスは削除されません。後からこのエンジンに追加することができます。", "xpack.enterpriseSearch.content.engine.indices.removeIndexConfirm.text": "はい。このインデックスを削除します", "xpack.enterpriseSearch.content.engine.indices.removeIndexConfirm.title": "このインデックスをエンジンから削除", @@ -12236,7 +12235,6 @@ "xpack.enterpriseSearch.content.engine.indicesSelect.docsLabel": "ドキュメント:", "xpack.enterpriseSearch.content.engine.schema.field_name.columnTitle": "フィールド名", "xpack.enterpriseSearch.content.engine.schema.field_type.columnTitle": "フィールド型", - "xpack.enterpriseSearch.content.engine.schema.pageTitle": "スキーマ", "xpack.enterpriseSearch.content.engineList.deleteEngineModal.confirmButton.title": "はい。このエンジンを削除 ", "xpack.enterpriseSearch.content.engineList.deleteEngineModal.delete.description": "エンジンを削除すると、元に戻せません。インデックスには影響しません。", "xpack.enterpriseSearch.content.engineList.deleteEngineModal.title": "このエンジンを完全に削除しますか?", @@ -13090,8 +13088,6 @@ "xpack.enterpriseSearch.nav.contentSettingsTitle": "設定", "xpack.enterpriseSearch.nav.contentTitle": "コンテンツ", "xpack.enterpriseSearch.nav.elasticsearchTitle": "Elasticsearch", - "xpack.enterpriseSearch.nav.engine.indicesTitle": "インデックス", - "xpack.enterpriseSearch.nav.engine.schemaTitle": "スキーマ", "xpack.enterpriseSearch.nav.enterpriseSearchOverviewTitle": "概要", "xpack.enterpriseSearch.nav.searchExperiencesTitle": "検索エクスペリエンス", "xpack.enterpriseSearch.nav.searchIndicesTitle": "インデックス", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index ed0f6a5c2fedb..7199ea360f5e5 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -12229,7 +12229,6 @@ "xpack.enterpriseSearch.content.engine.indices.docsCount.columnTitle": "文档计数", "xpack.enterpriseSearch.content.engine.indices.health.columnTitle": "索引运行状况", "xpack.enterpriseSearch.content.engine.indices.name.columnTitle": "索引名称", - "xpack.enterpriseSearch.content.engine.indices.pageTitle": "索引", "xpack.enterpriseSearch.content.engine.indices.removeIndexConfirm.description": "这不会删除该索引。您可以在稍后将其重新添加到此引擎。", "xpack.enterpriseSearch.content.engine.indices.removeIndexConfirm.text": "是,移除此索引", "xpack.enterpriseSearch.content.engine.indices.removeIndexConfirm.title": "从引擎中移除此索引", @@ -12237,7 +12236,6 @@ "xpack.enterpriseSearch.content.engine.indicesSelect.docsLabel": "文档:", "xpack.enterpriseSearch.content.engine.schema.field_name.columnTitle": "字段名称", "xpack.enterpriseSearch.content.engine.schema.field_type.columnTitle": "字段类型", - "xpack.enterpriseSearch.content.engine.schema.pageTitle": "架构", "xpack.enterpriseSearch.content.engineList.deleteEngineModal.confirmButton.title": "是,删除此引擎 ", "xpack.enterpriseSearch.content.engineList.deleteEngineModal.delete.description": "删除引擎是不可逆操作。您的索引不会受到影响。", "xpack.enterpriseSearch.content.engineList.deleteEngineModal.title": "永久删除此引擎?", @@ -13091,8 +13089,6 @@ "xpack.enterpriseSearch.nav.contentSettingsTitle": "设置", "xpack.enterpriseSearch.nav.contentTitle": "内容", "xpack.enterpriseSearch.nav.elasticsearchTitle": "Elasticsearch", - "xpack.enterpriseSearch.nav.engine.indicesTitle": "索引", - "xpack.enterpriseSearch.nav.engine.schemaTitle": "架构", "xpack.enterpriseSearch.nav.enterpriseSearchOverviewTitle": "概览", "xpack.enterpriseSearch.nav.searchExperiencesTitle": "搜索体验", "xpack.enterpriseSearch.nav.searchIndicesTitle": "索引", From 32de23bdb348bea70b31c7746632d20b6193bc77 Mon Sep 17 00:00:00 2001 From: Catherine Liu Date: Mon, 24 Apr 2023 12:40:48 -0700 Subject: [PATCH 10/52] [Dashboard] Scroll to new panel (#152056) ## Summary Closes #97064. This scrolls to a newly added panel on a dashboard instead of remaining at the top. The user can see the new panel without having to manually scroll to the bottom. ~This also scrolls to the maximized panel when you minimize instead of just throwing you back to the top of the dashboard.~ Note: Scrolling on minimize will be addressed in a future PR. This scrolling behavior also seems to work with portable dashboards embedded in another apps, but it may require additional work on the consumer to call `scrollToPanel` in the appropriate callbacks when adding panels. #### Scrolls to newly added panel and shows a success border animation ![Apr-18-2023 07-40-41](https://user-images.githubusercontent.com/1697105/232812491-5bf3ee3a-c81d-4dd3-8b04-67978da3b9a8.gif) #### Scrolls to panel on return from editor ![Apr-18-2023 07-56-35](https://user-images.githubusercontent.com/1697105/232817401-6cfd7085-91b6-4f05-be1c-e47f6cc3edab.gif) #### Scrolls to panel clone ![Apr-18-2023 07-54-43](https://user-images.githubusercontent.com/1697105/232816928-2b473778-76e1-4781-8e51-f9e46ab74b9b.gif) #### Scrolling in portable dashboards example ![Apr-18-2023 08-13-14](https://user-images.githubusercontent.com/1697105/232822632-ffcbd9ad-9cad-4185-931c-a68fbf7e0fbe.gif) ### Checklist Delete any items that are not applicable to this PR. - [ ] 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) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [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 - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] 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)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] 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)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### 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> Co-authored-by: Hannah Mudge --- .../controls_example/public/edit_example.tsx | 2 +- .../editor/open_add_data_control_flyout.tsx | 36 +++++++++++++----- .../dashboard_actions/clone_panel_action.tsx | 1 + .../dashboard_actions/expand_panel_action.tsx | 4 ++ .../replace_panel_flyout.tsx | 2 + .../add_data_control_button.tsx | 8 +++- .../add_time_slider_control_button.tsx | 7 +++- .../top_nav/dashboard_editing_toolbar.tsx | 2 + .../component/grid/_dashboard_grid.scss | 10 +++-- .../component/grid/dashboard_grid.tsx | 13 ++++++- .../component/grid/dashboard_grid_item.tsx | 16 +++++++- .../component/panel/_dashboard_panel.scss | 25 +++++++++++++ .../panel/dashboard_panel_placement.ts | 1 + .../embeddable/api/add_panel_from_library.ts | 4 ++ .../embeddable/api/panel_management.ts | 11 ++++-- .../embeddable/create/create_dashboard.ts | 22 +++++++---- .../embeddable/dashboard_container.tsx | 37 +++++++++++++++++++ .../state/dashboard_container_reducers.ts | 8 ++++ .../public/dashboard_container/types.ts | 2 + .../add_panel/add_panel_flyout.tsx | 6 ++- .../add_panel/open_add_panel_flyout.tsx | 3 ++ 21 files changed, 188 insertions(+), 32 deletions(-) diff --git a/examples/controls_example/public/edit_example.tsx b/examples/controls_example/public/edit_example.tsx index f6297befa615c..148867337fedd 100644 --- a/examples/controls_example/public/edit_example.tsx +++ b/examples/controls_example/public/edit_example.tsx @@ -133,7 +133,7 @@ export const EditExample = () => { iconType="plusInCircle" isDisabled={controlGroupAPI === undefined} onClick={() => { - controlGroupAPI!.openAddDataControlFlyout(controlInputTransform); + controlGroupAPI!.openAddDataControlFlyout({ controlInputTransform }); }} > Add control diff --git a/src/plugins/controls/public/control_group/editor/open_add_data_control_flyout.tsx b/src/plugins/controls/public/control_group/editor/open_add_data_control_flyout.tsx index bf60193432488..695eaa42e064d 100644 --- a/src/plugins/controls/public/control_group/editor/open_add_data_control_flyout.tsx +++ b/src/plugins/controls/public/control_group/editor/open_add_data_control_flyout.tsx @@ -8,6 +8,7 @@ import React from 'react'; import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { isErrorEmbeddable } from '@kbn/embeddable-plugin/public'; import { ControlGroupContainer, @@ -32,8 +33,12 @@ import { DataControlInput, OPTIONS_LIST_CONTROL, RANGE_SLIDER_CONTROL } from '.. export function openAddDataControlFlyout( this: ControlGroupContainer, - controlInputTransform?: ControlInputTransform + options?: { + controlInputTransform?: ControlInputTransform; + onSave?: (id: string) => void; + } ) { + const { controlInputTransform, onSave } = options || {}; const { overlays: { openFlyout, openConfirm }, controls: { getControlFactory }, @@ -71,7 +76,7 @@ export function openAddDataControlFlyout( updateTitle={(newTitle) => (controlInput.title = newTitle)} updateWidth={(defaultControlWidth) => this.updateInput({ defaultControlWidth })} updateGrow={(defaultControlGrow: boolean) => this.updateInput({ defaultControlGrow })} - onSave={(type) => { + onSave={async (type) => { this.closeAllFlyouts(); if (!type) { return; @@ -86,17 +91,28 @@ export function openAddDataControlFlyout( controlInput = controlInputTransform({ ...controlInput }, type); } - if (type === OPTIONS_LIST_CONTROL) { - this.addOptionsListControl(controlInput as AddOptionsListControlProps); - return; - } + let newControl; - if (type === RANGE_SLIDER_CONTROL) { - this.addRangeSliderControl(controlInput as AddRangeSliderControlProps); - return; + switch (type) { + case OPTIONS_LIST_CONTROL: + newControl = await this.addOptionsListControl( + controlInput as AddOptionsListControlProps + ); + break; + case RANGE_SLIDER_CONTROL: + newControl = await this.addRangeSliderControl( + controlInput as AddRangeSliderControlProps + ); + break; + default: + newControl = await this.addDataControlFromField( + controlInput as AddDataControlProps + ); } - this.addDataControlFromField(controlInput as AddDataControlProps); + if (onSave && !isErrorEmbeddable(newControl)) { + onSave(newControl.id); + } }} onCancel={onCancel} onTypeEditorChange={(partialInput) => diff --git a/src/plugins/dashboard/public/dashboard_actions/clone_panel_action.tsx b/src/plugins/dashboard/public/dashboard_actions/clone_panel_action.tsx index 482553e2f002f..228db7138fd54 100644 --- a/src/plugins/dashboard/public/dashboard_actions/clone_panel_action.tsx +++ b/src/plugins/dashboard/public/dashboard_actions/clone_panel_action.tsx @@ -95,6 +95,7 @@ export class ClonePanelAction implements Action { height: panelToClone.gridData.h, currentPanels: dashboard.getInput().panels, placeBesideId: panelToClone.explicitInput.id, + scrollToPanel: true, } as IPanelPlacementBesideArgs ); } diff --git a/src/plugins/dashboard/public/dashboard_actions/expand_panel_action.tsx b/src/plugins/dashboard/public/dashboard_actions/expand_panel_action.tsx index 0d3dd592dcc34..4e98a6dd31024 100644 --- a/src/plugins/dashboard/public/dashboard_actions/expand_panel_action.tsx +++ b/src/plugins/dashboard/public/dashboard_actions/expand_panel_action.tsx @@ -64,5 +64,9 @@ export class ExpandPanelAction implements Action { } const newValue = isExpanded(embeddable) ? undefined : embeddable.id; (embeddable.parent as DashboardContainer).setExpandedPanelId(newValue); + + if (!newValue) { + (embeddable.parent as DashboardContainer).setScrollToPanelId(embeddable.id); + } } } diff --git a/src/plugins/dashboard/public/dashboard_actions/replace_panel_flyout.tsx b/src/plugins/dashboard/public/dashboard_actions/replace_panel_flyout.tsx index 4a2ac8f41d6a6..14067f0b6aa68 100644 --- a/src/plugins/dashboard/public/dashboard_actions/replace_panel_flyout.tsx +++ b/src/plugins/dashboard/public/dashboard_actions/replace_panel_flyout.tsx @@ -21,6 +21,7 @@ import { Toast } from '@kbn/core/public'; import { DashboardPanelState } from '../../common'; import { pluginServices } from '../services/plugin_services'; import { dashboardReplacePanelActionStrings } from './_dashboard_actions_strings'; +import { DashboardContainer } from '../dashboard_container'; interface Props { container: IContainer; @@ -82,6 +83,7 @@ export class ReplacePanelFlyout extends React.Component { }, }); + (container as DashboardContainer).setHighlightPanelId(id); this.showToast(name); this.props.onClose(); }; diff --git a/src/plugins/dashboard/public/dashboard_app/top_nav/controls_toolbar_button/add_data_control_button.tsx b/src/plugins/dashboard/public/dashboard_app/top_nav/controls_toolbar_button/add_data_control_button.tsx index 6cef7e858b165..e7c7daa2bcc27 100644 --- a/src/plugins/dashboard/public/dashboard_app/top_nav/controls_toolbar_button/add_data_control_button.tsx +++ b/src/plugins/dashboard/public/dashboard_app/top_nav/controls_toolbar_button/add_data_control_button.tsx @@ -10,6 +10,7 @@ import React from 'react'; import { EuiContextMenuItem } from '@elastic/eui'; import { ControlGroupContainer } from '@kbn/controls-plugin/public'; import { getAddControlButtonTitle } from '../../_dashboard_app_strings'; +import { useDashboardAPI } from '../../dashboard_app'; interface Props { closePopover: () => void; @@ -17,6 +18,11 @@ interface Props { } export const AddDataControlButton = ({ closePopover, controlGroup, ...rest }: Props) => { + const dashboard = useDashboardAPI(); + const onSave = () => { + dashboard.scrollToTop(); + }; + return ( { - controlGroup.openAddDataControlFlyout(); + controlGroup.openAddDataControlFlyout({ onSave }); closePopover(); }} > diff --git a/src/plugins/dashboard/public/dashboard_app/top_nav/controls_toolbar_button/add_time_slider_control_button.tsx b/src/plugins/dashboard/public/dashboard_app/top_nav/controls_toolbar_button/add_time_slider_control_button.tsx index 8283144e1c155..cbd514be8ba13 100644 --- a/src/plugins/dashboard/public/dashboard_app/top_nav/controls_toolbar_button/add_time_slider_control_button.tsx +++ b/src/plugins/dashboard/public/dashboard_app/top_nav/controls_toolbar_button/add_time_slider_control_button.tsx @@ -13,6 +13,7 @@ import { getAddTimeSliderControlButtonTitle, getOnlyOneTimeSliderControlMsg, } from '../../_dashboard_app_strings'; +import { useDashboardAPI } from '../../dashboard_app'; interface Props { closePopover: () => void; @@ -21,6 +22,7 @@ interface Props { export const AddTimeSliderControlButton = ({ closePopover, controlGroup, ...rest }: Props) => { const [hasTimeSliderControl, setHasTimeSliderControl] = useState(false); + const dashboard = useDashboardAPI(); useEffect(() => { const subscription = controlGroup.getInput$().subscribe(() => { @@ -42,8 +44,9 @@ export const AddTimeSliderControlButton = ({ closePopover, controlGroup, ...rest { - controlGroup.addTimeSliderControl(); + onClick={async () => { + await controlGroup.addTimeSliderControl(); + dashboard.scrollToTop(); closePopover(); }} data-test-subj="controls-create-timeslider-button" diff --git a/src/plugins/dashboard/public/dashboard_app/top_nav/dashboard_editing_toolbar.tsx b/src/plugins/dashboard/public/dashboard_app/top_nav/dashboard_editing_toolbar.tsx index 03b609ae99736..708af176d785d 100644 --- a/src/plugins/dashboard/public/dashboard_app/top_nav/dashboard_editing_toolbar.tsx +++ b/src/plugins/dashboard/public/dashboard_app/top_nav/dashboard_editing_toolbar.tsx @@ -110,6 +110,8 @@ export function DashboardEditingToolbar() { const newEmbeddable = await dashboard.addNewEmbeddable(embeddableFactory.type, explicitInput); if (newEmbeddable) { + dashboard.setScrollToPanelId(newEmbeddable.id); + dashboard.setHighlightPanelId(newEmbeddable.id); toasts.addSuccess({ title: dashboardReplacePanelActionStrings.getSuccessMessage(newEmbeddable.getTitle()), 'data-test-subj': 'addEmbeddableToDashboardSuccess', diff --git a/src/plugins/dashboard/public/dashboard_container/component/grid/_dashboard_grid.scss b/src/plugins/dashboard/public/dashboard_container/component/grid/_dashboard_grid.scss index 7e9529a90be8b..cc96c816ce8b7 100644 --- a/src/plugins/dashboard/public/dashboard_container/component/grid/_dashboard_grid.scss +++ b/src/plugins/dashboard/public/dashboard_container/component/grid/_dashboard_grid.scss @@ -36,10 +36,13 @@ } /** - * When a single panel is expanded, all the other panels are hidden in the grid. + * When a single panel is expanded, all the other panels moved offscreen. + * Shifting the rendered panels offscreen prevents a quick flash when redrawing the panels on minimize */ .dshDashboardGrid__item--hidden { - display: none; + position: absolute; + top: -9999px; + left: -9999px; } /** @@ -53,11 +56,12 @@ * 1. We need to mark this as important because react grid layout sets the width and height of the panels inline. */ .dshDashboardGrid__item--expanded { + position: absolute; height: 100% !important; /* 1 */ width: 100% !important; /* 1 */ top: 0 !important; /* 1 */ left: 0 !important; /* 1 */ - transform: translate(0, 0) !important; /* 1 */ + transform: none !important; padding: $euiSizeS; // Altered panel styles can be found in ../panel diff --git a/src/plugins/dashboard/public/dashboard_container/component/grid/dashboard_grid.tsx b/src/plugins/dashboard/public/dashboard_container/component/grid/dashboard_grid.tsx index b840fcd408977..0055e24685b89 100644 --- a/src/plugins/dashboard/public/dashboard_container/component/grid/dashboard_grid.tsx +++ b/src/plugins/dashboard/public/dashboard_container/component/grid/dashboard_grid.tsx @@ -12,7 +12,7 @@ import 'react-grid-layout/css/styles.css'; import { pick } from 'lodash'; import classNames from 'classnames'; import { useEffectOnce } from 'react-use/lib'; -import React, { useState, useMemo, useCallback } from 'react'; +import React, { useState, useMemo, useCallback, useEffect } from 'react'; import { Layout, Responsive as ResponsiveReactGridLayout } from 'react-grid-layout'; import { ViewMode } from '@kbn/embeddable-plugin/public'; @@ -38,6 +38,15 @@ export const DashboardGrid = ({ viewportWidth }: { viewportWidth: number }) => { setTimeout(() => setAnimatePanelTransforms(true), 500); }); + useEffect(() => { + if (expandedPanelId) { + setAnimatePanelTransforms(false); + } else { + // delaying enabling CSS transforms to the next tick prevents a panel slide animation on minimize + setTimeout(() => setAnimatePanelTransforms(true), 0); + } + }, [expandedPanelId]); + const { onPanelStatusChange } = useDashboardPerformanceTracker({ panelCount: Object.keys(panels).length, }); @@ -98,7 +107,7 @@ export const DashboardGrid = ({ viewportWidth }: { viewportWidth: number }) => { 'dshLayout-withoutMargins': !useMargins, 'dshLayout--viewing': viewMode === ViewMode.VIEW, 'dshLayout--editing': viewMode !== ViewMode.VIEW, - 'dshLayout--noAnimation': !animatePanelTransforms, + 'dshLayout--noAnimation': !animatePanelTransforms || expandedPanelId, 'dshLayout-isMaximizedPanel': expandedPanelId !== undefined, }); diff --git a/src/plugins/dashboard/public/dashboard_container/component/grid/dashboard_grid_item.tsx b/src/plugins/dashboard/public/dashboard_container/component/grid/dashboard_grid_item.tsx index 45aa70fd50feb..39ff6ebc48418 100644 --- a/src/plugins/dashboard/public/dashboard_container/component/grid/dashboard_grid_item.tsx +++ b/src/plugins/dashboard/public/dashboard_container/component/grid/dashboard_grid_item.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React, { useState, useRef, useEffect } from 'react'; +import React, { useState, useRef, useEffect, useLayoutEffect } from 'react'; import { EuiLoadingChart } from '@elastic/eui'; import classNames from 'classnames'; @@ -56,6 +56,8 @@ const Item = React.forwardRef( embeddable: { EmbeddablePanel: PanelComponent }, } = pluginServices.getServices(); const container = useDashboardContainer(); + const scrollToPanelId = container.select((state) => state.componentState.scrollToPanelId); + const highlightPanelId = container.select((state) => state.componentState.highlightPanelId); const expandPanel = expandedPanelId !== undefined && expandedPanelId === id; const hidePanel = expandedPanelId !== undefined && expandedPanelId !== id; @@ -66,11 +68,23 @@ const Item = React.forwardRef( printViewport__vis: container.getInput().viewMode === ViewMode.PRINT, }); + useLayoutEffect(() => { + if (typeof ref !== 'function' && ref?.current) { + if (scrollToPanelId === id) { + container.scrollToPanel(ref.current); + } + if (highlightPanelId === id) { + container.highlightPanel(ref.current); + } + } + }, [id, container, scrollToPanelId, highlightPanelId, ref]); + return (
diff --git a/src/plugins/dashboard/public/dashboard_container/component/panel/_dashboard_panel.scss b/src/plugins/dashboard/public/dashboard_container/component/panel/_dashboard_panel.scss index f04e5e29d960b..f8715220ddf37 100644 --- a/src/plugins/dashboard/public/dashboard_container/component/panel/_dashboard_panel.scss +++ b/src/plugins/dashboard/public/dashboard_container/component/panel/_dashboard_panel.scss @@ -11,6 +11,10 @@ box-shadow: none; border-radius: 0; } + + .dshDashboardGrid__item--highlighted { + border-radius: 0; + } } // Remove border color unless in editing mode @@ -25,3 +29,24 @@ cursor: default; } } + +@keyframes highlightOutline { + 0% { + outline: solid $euiSizeXS transparentize($euiColorSuccess, 1); + } + 25% { + outline: solid $euiSizeXS transparentize($euiColorSuccess, .5); + } + 100% { + outline: solid $euiSizeXS transparentize($euiColorSuccess, 1); + } +} + +.dshDashboardGrid__item--highlighted { + border-radius: $euiSizeXS; + animation-name: highlightOutline; + animation-duration: 4s; + animation-timing-function: ease-out; + // keeps outline from getting cut off by other panels without margins + z-index: 999 !important; +} diff --git a/src/plugins/dashboard/public/dashboard_container/component/panel/dashboard_panel_placement.ts b/src/plugins/dashboard/public/dashboard_container/component/panel/dashboard_panel_placement.ts index 77b51874319ba..e570e1eadd6ca 100644 --- a/src/plugins/dashboard/public/dashboard_container/component/panel/dashboard_panel_placement.ts +++ b/src/plugins/dashboard/public/dashboard_container/component/panel/dashboard_panel_placement.ts @@ -24,6 +24,7 @@ export interface IPanelPlacementArgs { width: number; height: number; currentPanels: { [key: string]: DashboardPanelState }; + scrollToPanel?: boolean; } export interface IPanelPlacementBesideArgs extends IPanelPlacementArgs { diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/api/add_panel_from_library.ts b/src/plugins/dashboard/public/dashboard_container/embeddable/api/add_panel_from_library.ts index ef4f4dc7ea5c9..c708937e3d56e 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/api/add_panel_from_library.ts +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/api/add_panel_from_library.ts @@ -41,6 +41,10 @@ export function addFromLibrary(this: DashboardContainer) { notifications, overlays, theme, + onAddPanel: (id: string) => { + this.setScrollToPanelId(id); + this.setHighlightPanelId(id); + }, }) ); } diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/api/panel_management.ts b/src/plugins/dashboard/public/dashboard_container/embeddable/api/panel_management.ts index cb2ce9af37bcd..7b02001a93c6c 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/api/panel_management.ts +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/api/panel_management.ts @@ -128,7 +128,12 @@ export function showPlaceholderUntil newStateComplete) - .then((newPanelState: Partial) => - this.replacePanel(placeholderPanelState, newPanelState) - ); + .then(async (newPanelState: Partial) => { + const panelId = await this.replacePanel(placeholderPanelState, newPanelState); + + if (placementArgs?.scrollToPanel) { + this.setScrollToPanelId(panelId); + this.setHighlightPanelId(panelId); + } + }); } diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts index 7609a4f3eb95f..f0a20e832e431 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts @@ -181,12 +181,13 @@ export const createDashboard = async ( const incomingEmbeddable = creationOptions?.incomingEmbeddable; if (incomingEmbeddable) { initialInput.viewMode = ViewMode.EDIT; // view mode must always be edit to recieve an embeddable. - if ( + + const panelExists = incomingEmbeddable.embeddableId && - Boolean(initialInput.panels[incomingEmbeddable.embeddableId]) - ) { + Boolean(initialInput.panels[incomingEmbeddable.embeddableId]); + if (panelExists) { // this embeddable already exists, we will update the explicit input. - const panelToUpdate = initialInput.panels[incomingEmbeddable.embeddableId]; + const panelToUpdate = initialInput.panels[incomingEmbeddable.embeddableId as string]; const sameType = panelToUpdate.type === incomingEmbeddable.type; panelToUpdate.type = incomingEmbeddable.type; @@ -195,17 +196,22 @@ export const createDashboard = async ( ...(sameType ? panelToUpdate.explicitInput : {}), ...incomingEmbeddable.input, - id: incomingEmbeddable.embeddableId, + id: incomingEmbeddable.embeddableId as string, // maintain hide panel titles setting. hidePanelTitles: panelToUpdate.explicitInput.hidePanelTitles, }; } else { // otherwise this incoming embeddable is brand new and can be added via the default method after the dashboard container is created. - untilDashboardReady().then((container) => - container.addNewEmbeddable(incomingEmbeddable.type, incomingEmbeddable.input) - ); + untilDashboardReady().then(async (container) => { + container.addNewEmbeddable(incomingEmbeddable.type, incomingEmbeddable.input); + }); } + + untilDashboardReady().then(async (container) => { + container.setScrollToPanelId(incomingEmbeddable.embeddableId); + container.setHighlightPanelId(incomingEmbeddable.embeddableId); + }); } // -------------------------------------------------------------------------------------- diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx b/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx index 5b7a589afa950..d5a5385e779b3 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx @@ -398,4 +398,41 @@ export class DashboardContainer extends Container { + this.dispatch.setScrollToPanelId(id); + }; + + public scrollToPanel = async (panelRef: HTMLDivElement) => { + const id = this.getState().componentState.scrollToPanelId; + if (!id) return; + + this.untilEmbeddableLoaded(id).then(() => { + this.setScrollToPanelId(undefined); + panelRef.scrollIntoView({ block: 'center' }); + }); + }; + + public scrollToTop = () => { + window.scroll(0, 0); + }; + + public setHighlightPanelId = (id: string | undefined) => { + this.dispatch.setHighlightPanelId(id); + }; + + public highlightPanel = (panelRef: HTMLDivElement) => { + const id = this.getState().componentState.highlightPanelId; + + if (id && panelRef) { + this.untilEmbeddableLoaded(id).then(() => { + panelRef.classList.add('dshDashboardGrid__item--highlighted'); + // Removes the class after the highlight animation finishes + setTimeout(() => { + panelRef.classList.remove('dshDashboardGrid__item--highlighted'); + }, 5000); + }); + } + this.setHighlightPanelId(undefined); + }; } diff --git a/src/plugins/dashboard/public/dashboard_container/state/dashboard_container_reducers.ts b/src/plugins/dashboard/public/dashboard_container/state/dashboard_container_reducers.ts index 70bf3a7d65989..86a58bb72f639 100644 --- a/src/plugins/dashboard/public/dashboard_container/state/dashboard_container_reducers.ts +++ b/src/plugins/dashboard/public/dashboard_container/state/dashboard_container_reducers.ts @@ -209,4 +209,12 @@ export const dashboardContainerReducers = { setHasOverlays: (state: DashboardReduxState, action: PayloadAction) => { state.componentState.hasOverlays = action.payload; }, + + setScrollToPanelId: (state: DashboardReduxState, action: PayloadAction) => { + state.componentState.scrollToPanelId = action.payload; + }, + + setHighlightPanelId: (state: DashboardReduxState, action: PayloadAction) => { + state.componentState.highlightPanelId = action.payload; + }, }; diff --git a/src/plugins/dashboard/public/dashboard_container/types.ts b/src/plugins/dashboard/public/dashboard_container/types.ts index 6e8ff1f5c98a0..544317d9f6bcc 100644 --- a/src/plugins/dashboard/public/dashboard_container/types.ts +++ b/src/plugins/dashboard/public/dashboard_container/types.ts @@ -33,6 +33,8 @@ export interface DashboardPublicState { fullScreenMode?: boolean; savedQueryId?: string; lastSavedId?: string; + scrollToPanelId?: string; + highlightPanelId?: string; } export interface DashboardRenderPerformanceStats { diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx index dcaa3880678ab..ea7c150bf38b8 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx @@ -30,6 +30,7 @@ interface Props { SavedObjectFinder: React.ComponentType; showCreateNewMenu?: boolean; reportUiCounter?: UsageCollectionStart['reportUiCounter']; + onAddPanel?: (id: string) => void; } interface State { @@ -101,7 +102,7 @@ export class AddPanelFlyout extends React.Component { throw new EmbeddableFactoryNotFoundError(savedObjectType); } - this.props.container.addNewEmbeddable( + const embeddable = await this.props.container.addNewEmbeddable( factoryForSavedObjectType.type, { savedObjectId } ); @@ -109,6 +110,9 @@ export class AddPanelFlyout extends React.Component { this.doTelemetryForAddEvent(this.props.container.type, factoryForSavedObjectType, so); this.showToast(name); + if (this.props.onAddPanel) { + this.props.onAddPanel(embeddable.id); + } }; private doTelemetryForAddEvent( diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/open_add_panel_flyout.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/open_add_panel_flyout.tsx index 4cc5a7ccb6e11..eb2722dcf9869 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/open_add_panel_flyout.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/open_add_panel_flyout.tsx @@ -24,6 +24,7 @@ export function openAddPanelFlyout(options: { showCreateNewMenu?: boolean; reportUiCounter?: UsageCollectionStart['reportUiCounter']; theme: ThemeServiceStart; + onAddPanel?: (id: string) => void; }): OverlayRef { const { embeddable, @@ -35,11 +36,13 @@ export function openAddPanelFlyout(options: { showCreateNewMenu, reportUiCounter, theme, + onAddPanel, } = options; const flyoutSession = overlays.openFlyout( toMountPoint( { if (flyoutSession) { flyoutSession.close(); From 275c36031428d7ec2881e0c57154d1b3a96ade5f Mon Sep 17 00:00:00 2001 From: Dominique Clarke Date: Mon, 24 Apr 2023 16:19:59 -0400 Subject: [PATCH 11/52] [Synthetics] enable auto re-generation of monitor management api when read permissions are missing (#155203) Resolves https://github.com/elastic/kibana/issues/151695 Auto regenerates the synthetics api key when it does not include `synthetics-*` read permissions. Also ensures key are regenerated when deleted via stack management. A user without permissions to enable monitor management will see this callout when monitor management is disabled for either reason ![Synthetics-Overview-Synthetics-Kibana (1)](https://user-images.githubusercontent.com/11356435/232926046-ea39115b-acc7-40a7-8ec1-de77a20daf53.png) ## Testing lack of read permissions This PR is hard to test. I did so by adjusting the code to force the creation of an api key without read permissions. Here's how I did it: 1. connect to a clean ES instance by creating a new oblt cluster or running `yarn es snapshot 2. Remove read permissions for the api key https://github.com/elastic/kibana/pull/155203/files#diff-e38e55402aedfdb1a8a17bdd557364cd3649e1590b5e92fb44ed639f03ba880dR30 3. Remove read permission check here https://github.com/elastic/kibana/pull/155203/files#diff-e38e55402aedfdb1a8a17bdd557364cd3649e1590b5e92fb44ed639f03ba880dR60 4. Navigate to Synthetics app and create your first monitor 5. Navigate to Stack Management -> Api Keys. Click on he api key to inspect it's privileges. You should not see `read` permissions. 6. Remove the changes listed in step 2 and 3 and make sure the branch is back in sync with this PR 7. Navigate to the Synthetics app again. 9. Navigate to stack management -> api keys. Ensure there is only one synthetics monitor management api key. Click on he api key to inspect it's privileges. You should now see `read` permissions. 10. Delete this api key 11. Navigate back to the Synthetics app 12. Navigate back to stack management -> api keys. Notice tha api key has been regenerated --- .../management/disabled_callout.tsx | 40 +-- .../management/invalid_api_key_callout.tsx | 80 ----- .../monitors_page/management/labels.ts | 6 +- .../synthetics_enablement.tsx | 40 +-- .../monitors_page/overview/overview_page.tsx | 2 +- .../apps/synthetics/hooks/use_enablement.ts | 11 +- .../state/synthetics_enablement/actions.ts | 14 - .../state/synthetics_enablement/api.ts | 10 +- .../state/synthetics_enablement/effects.ts | 27 +- .../state/synthetics_enablement/index.ts | 39 -- .../lib/saved_objects/service_api_key.ts | 2 +- .../plugins/synthetics/server/routes/index.ts | 2 - .../routes/synthetics_service/enablement.ts | 56 +-- .../synthetics_service/get_api_key.test.ts | 63 +++- .../server/synthetics_service/get_api_key.ts | 26 +- .../translations/translations/fr-FR.json | 5 - .../translations/translations/ja-JP.json | 5 - .../translations/translations/zh-CN.json | 5 - .../apis/synthetics/add_monitor_project.ts | 2 +- .../apis/synthetics/edit_monitor.ts | 2 +- .../apis/synthetics/get_monitor.ts | 2 +- .../apis/synthetics/get_monitor_overview.ts | 2 +- .../apis/synthetics/synthetics_enablement.ts | 334 ++++++++++++++---- 23 files changed, 395 insertions(+), 380 deletions(-) delete mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/invalid_api_key_callout.tsx diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/disabled_callout.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/disabled_callout.tsx index 0a75b9a44499b..885f44eaa5ae1 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/disabled_callout.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/disabled_callout.tsx @@ -6,41 +6,24 @@ */ import React from 'react'; -import { EuiButton, EuiCallOut, EuiLink } from '@elastic/eui'; -import { InvalidApiKeyCalloutCallout } from './invalid_api_key_callout'; +import { EuiCallOut, EuiLink, EuiSpacer } from '@elastic/eui'; import * as labels from './labels'; import { useEnablement } from '../../../hooks'; export const DisabledCallout = ({ total }: { total: number }) => { - const { enablement, enableSynthetics, invalidApiKeyError, loading } = useEnablement(); + const { enablement, invalidApiKeyError, loading } = useEnablement(); const showDisableCallout = !enablement.isEnabled && total > 0; - const showInvalidApiKeyError = invalidApiKeyError && total > 0; + const showInvalidApiKeyCallout = invalidApiKeyError && total > 0; - if (showInvalidApiKeyError) { - return ; - } - - if (!showDisableCallout) { + if (!showDisableCallout && !showInvalidApiKeyCallout) { return null; } - return ( - -

{labels.CALLOUT_MANAGEMENT_DESCRIPTION}

- {enablement.canEnable || loading ? ( - { - enableSynthetics(); - }} - isLoading={loading} - > - {labels.SYNTHETICS_ENABLE_LABEL} - - ) : ( + return !enablement.canEnable && !loading ? ( + <> + +

{labels.CALLOUT_MANAGEMENT_DESCRIPTION}

{labels.CALLOUT_MANAGEMENT_CONTACT_ADMIN}{' '} { {labels.LEARN_MORE_LABEL}

- )} -
- ); +
+ + + ) : null; }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/invalid_api_key_callout.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/invalid_api_key_callout.tsx deleted file mode 100644 index 70816a69c2188..0000000000000 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/invalid_api_key_callout.tsx +++ /dev/null @@ -1,80 +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 { EuiButton, EuiCallOut, EuiLink, EuiSpacer } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { useEnablement } from '../../../hooks'; - -export const InvalidApiKeyCalloutCallout = () => { - const { enablement, enableSynthetics, loading } = useEnablement(); - - return ( - <> - -

{CALLOUT_MANAGEMENT_DESCRIPTION}

- {enablement.canEnable || loading ? ( - { - enableSynthetics(); - }} - isLoading={loading} - > - {SYNTHETICS_ENABLE_LABEL} - - ) : ( -

- {CALLOUT_MANAGEMENT_CONTACT_ADMIN}{' '} - - {LEARN_MORE_LABEL} - -

- )} -
- - - ); -}; - -const LEARN_MORE_LABEL = i18n.translate( - 'xpack.synthetics.monitorManagement.manageMonitorLoadingLabel.callout.invalidKey', - { - defaultMessage: 'Learn more', - } -); - -const API_KEY_MISSING = i18n.translate('xpack.synthetics.monitorManagement.callout.apiKeyMissing', { - defaultMessage: 'Monitor Management is currently disabled because of missing API key', -}); - -const CALLOUT_MANAGEMENT_CONTACT_ADMIN = i18n.translate( - 'xpack.synthetics.monitorManagement.callout.disabledCallout.invalidKey', - { - defaultMessage: 'Contact your administrator to enable Monitor Management.', - } -); - -const CALLOUT_MANAGEMENT_DESCRIPTION = i18n.translate( - 'xpack.synthetics.monitorManagement.callout.description.invalidKey', - { - defaultMessage: `Monitor Management is currently disabled. To run your monitors in one of Elastic's global managed testing locations, you need to re-enable monitor management.`, - } -); - -const SYNTHETICS_ENABLE_LABEL = i18n.translate( - 'xpack.synthetics.monitorManagement.syntheticsEnableLabel.invalidKey', - { - defaultMessage: 'Enable monitor management', - } -); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/labels.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/labels.ts index aca280e74fcb2..ff297267dcb62 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/labels.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/labels.ts @@ -24,14 +24,14 @@ export const LEARN_MORE_LABEL = i18n.translate( export const CALLOUT_MANAGEMENT_DISABLED = i18n.translate( 'xpack.synthetics.monitorManagement.callout.disabled', { - defaultMessage: 'Monitor Management is disabled', + defaultMessage: 'Monitor Management is currently disabled', } ); export const CALLOUT_MANAGEMENT_CONTACT_ADMIN = i18n.translate( 'xpack.synthetics.monitorManagement.callout.disabled.adminContact', { - defaultMessage: 'Please contact your administrator to enable Monitor Management.', + defaultMessage: 'Monitor Management will be enabled when an admin visits the Synthetics app.', } ); @@ -39,7 +39,7 @@ export const CALLOUT_MANAGEMENT_DESCRIPTION = i18n.translate( 'xpack.synthetics.monitorManagement.callout.description.disabled', { defaultMessage: - 'Monitor Management is currently disabled. To run your monitors on Elastic managed Synthetics service, enable Monitor Management. Your existing monitors are paused.', + "Monitor Management requires a valid API key to run your monitors on Elastic's global managed testing locations. If you already had enabled Monitor Management previously, the API key may no longer be valid.", } ); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/synthetics_enablement/synthetics_enablement.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/synthetics_enablement/synthetics_enablement.tsx index d6b927bbc43b3..e4fcee12f65a5 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/synthetics_enablement/synthetics_enablement.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/synthetics_enablement/synthetics_enablement.tsx @@ -6,16 +6,16 @@ */ import React, { useState, useEffect, useRef } from 'react'; -import { EuiEmptyPrompt, EuiButton, EuiTitle, EuiLink } from '@elastic/eui'; +import { EuiEmptyPrompt, EuiTitle, EuiLink } from '@elastic/eui'; import { useEnablement } from '../../../../hooks/use_enablement'; import { kibanaService } from '../../../../../../utils/kibana_service'; import * as labels from './labels'; export const EnablementEmptyState = () => { - const { error, enablement, enableSynthetics, loading } = useEnablement(); + const { error, enablement, loading } = useEnablement(); const [shouldFocusEnablementButton, setShouldFocusEnablementButton] = useState(false); const [isEnabling, setIsEnabling] = useState(false); - const { isEnabled, canEnable } = enablement; + const { isEnabled } = enablement; const isEnabledRef = useRef(isEnabled); const buttonRef = useRef(null); @@ -44,11 +44,6 @@ export const EnablementEmptyState = () => { } }, [isEnabled, isEnabling, error]); - const handleEnableSynthetics = () => { - enableSynthetics(); - setIsEnabling(true); - }; - useEffect(() => { if (shouldFocusEnablementButton) { buttonRef.current?.focus(); @@ -57,33 +52,8 @@ export const EnablementEmptyState = () => { return !isEnabled && !loading ? ( - {canEnable - ? labels.MONITOR_MANAGEMENT_ENABLEMENT_LABEL - : labels.SYNTHETICS_APP_DISABLED_LABEL} - - } - body={ -

- {canEnable - ? labels.MONITOR_MANAGEMENT_ENABLEMENT_MESSAGE - : labels.MONITOR_MANAGEMENT_DISABLED_MESSAGE} -

- } - actions={ - canEnable ? ( - - {labels.MONITOR_MANAGEMENT_ENABLEMENT_BTN_LABEL} - - ) : null - } + title={

{labels.SYNTHETICS_APP_DISABLED_LABEL}

} + body={

{labels.MONITOR_MANAGEMENT_DISABLED_MESSAGE}

} footer={ <> diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview_page.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview_page.tsx index a98f78249adbe..2e4eb6ca03a31 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview_page.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview_page.tsx @@ -101,8 +101,8 @@ export const OverviewPage: React.FC = () => { return ( <> - + diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_enablement.ts b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_enablement.ts index 394da8aefc086..fe726d0cbe3d2 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_enablement.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_enablement.ts @@ -5,14 +5,9 @@ * 2.0. */ -import { useEffect, useCallback } from 'react'; +import { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { - getSyntheticsEnablement, - enableSynthetics, - disableSynthetics, - selectSyntheticsEnablement, -} from '../state'; +import { getSyntheticsEnablement, selectSyntheticsEnablement } from '../state'; export function useEnablement() { const dispatch = useDispatch(); @@ -35,7 +30,5 @@ export function useEnablement() { invalidApiKeyError: enablement ? !Boolean(enablement?.isValidApiKey) : false, error, loading, - enableSynthetics: useCallback(() => dispatch(enableSynthetics()), [dispatch]), - disableSynthetics: useCallback(() => dispatch(disableSynthetics()), [dispatch]), }; } diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/actions.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/actions.ts index 7369ce0917e5a..78c0d9484149e 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/actions.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/actions.ts @@ -16,17 +16,3 @@ export const getSyntheticsEnablementSuccess = createAction( '[SYNTHETICS_ENABLEMENT] GET FAILURE' ); - -export const disableSynthetics = createAction('[SYNTHETICS_ENABLEMENT] DISABLE'); -export const disableSyntheticsSuccess = createAction<{}>('[SYNTHETICS_ENABLEMENT] DISABLE SUCCESS'); -export const disableSyntheticsFailure = createAction( - '[SYNTHETICS_ENABLEMENT] DISABLE FAILURE' -); - -export const enableSynthetics = createAction('[SYNTHETICS_ENABLEMENT] ENABLE'); -export const enableSyntheticsSuccess = createAction( - '[SYNTHETICS_ENABLEMENT] ENABLE SUCCESS' -); -export const enableSyntheticsFailure = createAction( - '[SYNTHETICS_ENABLEMENT] ENABLE FAILURE' -); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/api.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/api.ts index 62b48676e3965..2e009cc0b89d2 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/api.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/api.ts @@ -14,17 +14,9 @@ import { apiService } from '../../../../utils/api_service'; export const fetchGetSyntheticsEnablement = async (): Promise => { - return await apiService.get( + return await apiService.put( API_URLS.SYNTHETICS_ENABLEMENT, undefined, MonitorManagementEnablementResultCodec ); }; - -export const fetchDisableSynthetics = async (): Promise<{}> => { - return await apiService.delete(API_URLS.SYNTHETICS_ENABLEMENT); -}; - -export const fetchEnableSynthetics = async (): Promise => { - return await apiService.post(API_URLS.SYNTHETICS_ENABLEMENT); -}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/effects.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/effects.ts index d3134c60f8fd3..14c912b07ce99 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/effects.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/effects.ts @@ -5,20 +5,15 @@ * 2.0. */ -import { takeLatest, takeLeading } from 'redux-saga/effects'; +import { takeLeading } from 'redux-saga/effects'; +import { i18n } from '@kbn/i18n'; import { getSyntheticsEnablement, getSyntheticsEnablementSuccess, getSyntheticsEnablementFailure, - disableSynthetics, - disableSyntheticsSuccess, - disableSyntheticsFailure, - enableSynthetics, - enableSyntheticsSuccess, - enableSyntheticsFailure, } from './actions'; -import { fetchGetSyntheticsEnablement, fetchDisableSynthetics, fetchEnableSynthetics } from './api'; import { fetchEffectFactory } from '../utils/fetch_effect'; +import { fetchGetSyntheticsEnablement } from './api'; export function* fetchSyntheticsEnablementEffect() { yield takeLeading( @@ -26,15 +21,13 @@ export function* fetchSyntheticsEnablementEffect() { fetchEffectFactory( fetchGetSyntheticsEnablement, getSyntheticsEnablementSuccess, - getSyntheticsEnablementFailure + getSyntheticsEnablementFailure, + undefined, + failureMessage ) ); - yield takeLatest( - disableSynthetics, - fetchEffectFactory(fetchDisableSynthetics, disableSyntheticsSuccess, disableSyntheticsFailure) - ); - yield takeLatest( - enableSynthetics, - fetchEffectFactory(fetchEnableSynthetics, enableSyntheticsSuccess, enableSyntheticsFailure) - ); } + +const failureMessage = i18n.translate('xpack.synthetics.settings.enablement.fail', { + defaultMessage: 'Failed to enable Monitor Management', +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/index.ts index 62cbce9bfe05b..26bf2b50b8325 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/index.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/index.ts @@ -9,12 +9,6 @@ import { createReducer } from '@reduxjs/toolkit'; import { getSyntheticsEnablement, getSyntheticsEnablementSuccess, - disableSynthetics, - disableSyntheticsSuccess, - disableSyntheticsFailure, - enableSynthetics, - enableSyntheticsSuccess, - enableSyntheticsFailure, getSyntheticsEnablementFailure, } from './actions'; import { MonitorManagementEnablementResult } from '../../../../../common/runtime_types'; @@ -45,39 +39,6 @@ export const syntheticsEnablementReducer = createReducer(initialState, (builder) .addCase(getSyntheticsEnablementFailure, (state, action) => { state.loading = false; state.error = action.payload; - }) - - .addCase(disableSynthetics, (state) => { - state.loading = true; - }) - .addCase(disableSyntheticsSuccess, (state, action) => { - state.loading = false; - state.error = null; - state.enablement = { - canEnable: state.enablement?.canEnable ?? false, - areApiKeysEnabled: state.enablement?.areApiKeysEnabled ?? false, - canManageApiKeys: state.enablement?.canManageApiKeys ?? false, - isEnabled: false, - isValidApiKey: true, - }; - }) - .addCase(disableSyntheticsFailure, (state, action) => { - state.loading = false; - state.error = action.payload; - }) - - .addCase(enableSynthetics, (state) => { - state.loading = true; - state.enablement = null; - }) - .addCase(enableSyntheticsSuccess, (state, action) => { - state.loading = false; - state.error = null; - state.enablement = action.payload; - }) - .addCase(enableSyntheticsFailure, (state, action) => { - state.loading = false; - state.error = action.payload; }); }); diff --git a/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/service_api_key.ts b/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/service_api_key.ts index adab53c9d4268..3c62f99f7e67b 100644 --- a/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/service_api_key.ts +++ b/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/service_api_key.ts @@ -72,7 +72,7 @@ const getSyntheticsServiceAPIKey = async (server: UptimeServerSetup) => { } }; -const setSyntheticsServiceApiKey = async ( +export const setSyntheticsServiceApiKey = async ( soClient: SavedObjectsClientContract, apiKey: SyntheticsServiceApiKey ) => { diff --git a/x-pack/plugins/synthetics/server/routes/index.ts b/x-pack/plugins/synthetics/server/routes/index.ts index 9e2038d05962a..836143d55f014 100644 --- a/x-pack/plugins/synthetics/server/routes/index.ts +++ b/x-pack/plugins/synthetics/server/routes/index.ts @@ -20,7 +20,6 @@ import { getServiceLocationsRoute } from './synthetics_service/get_service_locat import { deleteSyntheticsMonitorRoute } from './monitor_cruds/delete_monitor'; import { disableSyntheticsRoute, - enableSyntheticsRoute, getSyntheticsEnablementRoute, } from './synthetics_service/enablement'; import { @@ -61,7 +60,6 @@ export const syntheticsAppRestApiRoutes: SyntheticsRestApiRouteFactory[] = [ deleteSyntheticsMonitorProjectRoute, disableSyntheticsRoute, editSyntheticsMonitorRoute, - enableSyntheticsRoute, getServiceLocationsRoute, getSyntheticsMonitorRoute, getSyntheticsProjectMonitorsRoute, diff --git a/x-pack/plugins/synthetics/server/routes/synthetics_service/enablement.ts b/x-pack/plugins/synthetics/server/routes/synthetics_service/enablement.ts index c4561f3ee9e00..87a10dbee9a8e 100644 --- a/x-pack/plugins/synthetics/server/routes/synthetics_service/enablement.ts +++ b/x-pack/plugins/synthetics/server/routes/synthetics_service/enablement.ts @@ -5,18 +5,12 @@ * 2.0. */ import { syntheticsServiceAPIKeySavedObject } from '../../legacy_uptime/lib/saved_objects/service_api_key'; -import { - SyntheticsRestApiRouteFactory, - UMRestApiRouteFactory, -} from '../../legacy_uptime/routes/types'; +import { SyntheticsRestApiRouteFactory } from '../../legacy_uptime/routes/types'; import { API_URLS } from '../../../common/constants'; -import { - generateAndSaveServiceAPIKey, - SyntheticsForbiddenError, -} from '../../synthetics_service/get_api_key'; +import { generateAndSaveServiceAPIKey } from '../../synthetics_service/get_api_key'; -export const getSyntheticsEnablementRoute: UMRestApiRouteFactory = (libs) => ({ - method: 'GET', +export const getSyntheticsEnablementRoute: SyntheticsRestApiRouteFactory = (libs) => ({ + method: 'PUT', path: API_URLS.SYNTHETICS_ENABLEMENT, validate: {}, handler: async ({ savedObjectsClient, request, server }): Promise => { @@ -25,7 +19,18 @@ export const getSyntheticsEnablementRoute: UMRestApiRouteFactory = (libs) => ({ server, }); const { canEnable, isEnabled } = result; - if (canEnable && !isEnabled && server.config.service?.manifestUrl) { + const { security } = server; + const { apiKey, isValid } = await libs.requests.getAPIKeyForSyntheticsService({ + server, + }); + if (apiKey && !isValid) { + await syntheticsServiceAPIKeySavedObject.delete(savedObjectsClient); + await security.authc.apiKeys?.invalidateAsInternalUser({ + ids: [apiKey?.id || ''], + }); + } + const regenerationRequired = !isEnabled || !isValid; + if (canEnable && regenerationRequired && server.config.service?.manifestUrl) { await generateAndSaveServiceAPIKey({ request, authSavedObjectsClient: savedObjectsClient, @@ -68,7 +73,7 @@ export const disableSyntheticsRoute: SyntheticsRestApiRouteFactory = (libs) => ( server, }); await syntheticsServiceAPIKeySavedObject.delete(savedObjectsClient); - await security.authc.apiKeys?.invalidate(request, { ids: [apiKey?.id || ''] }); + await security.authc.apiKeys?.invalidateAsInternalUser({ ids: [apiKey?.id || ''] }); return response.ok({}); } catch (e) { server.logger.error(e); @@ -76,30 +81,3 @@ export const disableSyntheticsRoute: SyntheticsRestApiRouteFactory = (libs) => ( } }, }); - -export const enableSyntheticsRoute: UMRestApiRouteFactory = (libs) => ({ - method: 'POST', - path: API_URLS.SYNTHETICS_ENABLEMENT, - validate: {}, - handler: async ({ request, response, server, savedObjectsClient }): Promise => { - const { logger } = server; - try { - await generateAndSaveServiceAPIKey({ - request, - authSavedObjectsClient: savedObjectsClient, - server, - }); - return response.ok({ - body: await libs.requests.getSyntheticsEnablement({ - server, - }), - }); - } catch (e) { - logger.error(e); - if (e instanceof SyntheticsForbiddenError) { - return response.forbidden(); - } - throw e; - } - }, -}); diff --git a/x-pack/plugins/synthetics/server/synthetics_service/get_api_key.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/get_api_key.test.ts index 4b15f4da43515..ca4a18e88d5d9 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/get_api_key.test.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/get_api_key.test.ts @@ -25,16 +25,6 @@ describe('getAPIKeyTest', function () { const logger = loggerMock.create(); - jest.spyOn(authUtils, 'checkHasPrivileges').mockResolvedValue({ - index: { - [syntheticsIndex]: { - auto_configure: true, - create_doc: true, - view_index_metadata: true, - }, - }, - } as any); - const server = { logger, security, @@ -52,6 +42,20 @@ describe('getAPIKeyTest', function () { encoded: '@#$%^&', }); + beforeEach(() => { + jest.clearAllMocks(); + jest.spyOn(authUtils, 'checkHasPrivileges').mockResolvedValue({ + index: { + [syntheticsIndex]: { + auto_configure: true, + create_doc: true, + view_index_metadata: true, + read: true, + }, + }, + } as any); + }); + it('should return existing api key', async () => { const getObject = jest .fn() @@ -79,4 +83,43 @@ describe('getAPIKeyTest', function () { 'ba997842-b0cf-4429-aa9d-578d9bf0d391' ); }); + + it('invalidates api keys with missing read permissions', async () => { + jest.spyOn(authUtils, 'checkHasPrivileges').mockResolvedValue({ + index: { + [syntheticsIndex]: { + auto_configure: true, + create_doc: true, + view_index_metadata: true, + read: false, + }, + }, + } as any); + + const getObject = jest + .fn() + .mockReturnValue({ attributes: { apiKey: 'qwerty', id: 'test', name: 'service-api-key' } }); + + encryptedSavedObjects.getClient = jest.fn().mockReturnValue({ + getDecryptedAsInternalUser: getObject, + }); + const apiKey = await getAPIKeyForSyntheticsService({ + server, + }); + + expect(apiKey).toEqual({ + apiKey: { apiKey: 'qwerty', id: 'test', name: 'service-api-key' }, + isValid: false, + }); + + expect(encryptedSavedObjects.getClient).toHaveBeenCalledTimes(1); + expect(getObject).toHaveBeenCalledTimes(1); + expect(encryptedSavedObjects.getClient).toHaveBeenCalledWith({ + includedHiddenTypes: [syntheticsServiceApiKey.name], + }); + expect(getObject).toHaveBeenCalledWith( + 'uptime-synthetics-api-key', + 'ba997842-b0cf-4429-aa9d-578d9bf0d391' + ); + }); }); diff --git a/x-pack/plugins/synthetics/server/synthetics_service/get_api_key.ts b/x-pack/plugins/synthetics/server/synthetics_service/get_api_key.ts index 0bc4f656901f4..79af4d6cfc718 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/get_api_key.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/get_api_key.ts @@ -56,7 +56,8 @@ export const getAPIKeyForSyntheticsService = async ({ const hasPermissions = indexPermissions.auto_configure && indexPermissions.create_doc && - indexPermissions.view_index_metadata; + indexPermissions.view_index_metadata && + indexPermissions.read; if (!hasPermissions) { return { isValid: false, apiKey }; @@ -92,6 +93,7 @@ export const generateAPIKey = async ({ } if (uptimePrivileges) { + /* Exposed to the user. Must create directly with the user */ return security.authc.apiKeys?.create(request, { name: 'synthetics-api-key (required for project monitors)', kibana_role_descriptors: { @@ -122,7 +124,8 @@ export const generateAPIKey = async ({ throw new SyntheticsForbiddenError(); } - return security.authc.apiKeys?.create(request, { + /* Not exposed to the user. May grant as internal user */ + return security.authc.apiKeys?.grantAsInternalUser(request, { name: 'synthetics-api-key (required for monitor management)', role_descriptors: { synthetics_writer: serviceApiKeyPrivileges, @@ -160,23 +163,24 @@ export const generateAndSaveServiceAPIKey = async ({ export const getSyntheticsEnablement = async ({ server }: { server: UptimeServerSetup }) => { const { security, config } = server; + const [apiKey, hasPrivileges, areApiKeysEnabled] = await Promise.all([ + getAPIKeyForSyntheticsService({ server }), + hasEnablePermissions(server), + security.authc.apiKeys.areAPIKeysEnabled(), + ]); + + const { canEnable, canManageApiKeys } = hasPrivileges; + if (!config.service?.manifestUrl) { return { canEnable: true, - canManageApiKeys: true, + canManageApiKeys, isEnabled: true, isValidApiKey: true, areApiKeysEnabled: true, }; } - const [apiKey, hasPrivileges, areApiKeysEnabled] = await Promise.all([ - getAPIKeyForSyntheticsService({ server }), - hasEnablePermissions(server), - security.authc.apiKeys.areAPIKeysEnabled(), - ]); - - const { canEnable, canManageApiKeys } = hasPrivileges; return { canEnable, canManageApiKeys, @@ -217,7 +221,7 @@ const hasEnablePermissions = async ({ uptimeEsClient }: UptimeServerSetup) => { return { canManageApiKeys, - canEnable: canManageApiKeys && hasClusterPermissions && hasIndexPermissions, + canEnable: hasClusterPermissions && hasIndexPermissions, }; }; diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index f7cf6a952a796..fda996fca0a65 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -34922,12 +34922,9 @@ "xpack.synthetics.monitorManagement.apiKey.label": "Clé d'API", "xpack.synthetics.monitorManagement.apiKeyWarning.label": "Cette clé d’API ne sera affichée qu'une seule fois. Veuillez en conserver une copie pour vos propres dossiers.", "xpack.synthetics.monitorManagement.areYouSure": "Voulez-vous vraiment supprimer cet emplacement ?", - "xpack.synthetics.monitorManagement.callout.apiKeyMissing": "La Gestion des moniteurs est actuellement désactivée en raison d'une clé d'API manquante", "xpack.synthetics.monitorManagement.callout.description.disabled": "La Gestion des moniteurs est actuellement désactivée. Pour exécuter vos moniteurs sur le service Synthetics géré par Elastic, activez la Gestion des moniteurs. Vos moniteurs existants ont été suspendus.", - "xpack.synthetics.monitorManagement.callout.description.invalidKey": "La Gestion des moniteurs est actuellement désactivée. Pour exécuter vos moniteurs dans l'un des emplacements de tests gérés globaux d'Elastic, vous devez ré-activer la Gestion des moniteurs.", "xpack.synthetics.monitorManagement.callout.disabled": "La Gestion des moniteurs est désactivée", "xpack.synthetics.monitorManagement.callout.disabled.adminContact": "Veuillez contacter votre administrateur pour activer la Gestion des moniteurs.", - "xpack.synthetics.monitorManagement.callout.disabledCallout.invalidKey": "Contactez votre administrateur pour activer la Gestion des moniteurs.", "xpack.synthetics.monitorManagement.cancelLabel": "Annuler", "xpack.synthetics.monitorManagement.cannotSaveIntegration": "Vous n'êtes pas autorisé à mettre à jour les intégrations. Des autorisations d'écriture pour les intégrations sont requises.", "xpack.synthetics.monitorManagement.closeButtonLabel": "Fermer", @@ -34976,7 +34973,6 @@ "xpack.synthetics.monitorManagement.locationName": "Nom de l’emplacement", "xpack.synthetics.monitorManagement.locationsLabel": "Emplacements", "xpack.synthetics.monitorManagement.manageMonitorLoadingLabel": "Chargement de la liste Gestion des moniteurs", - "xpack.synthetics.monitorManagement.manageMonitorLoadingLabel.callout.invalidKey": "En savoir plus", "xpack.synthetics.monitorManagement.manageMonitorLoadingLabel.callout.learnMore": "En savoir plus.", "xpack.synthetics.monitorManagement.monitorAddedSuccessMessage": "Moniteur ajouté avec succès.", "xpack.synthetics.monitorManagement.monitorEditedSuccessMessage": "Moniteur mis à jour.", @@ -35017,7 +35013,6 @@ "xpack.synthetics.monitorManagement.steps": "Étapes", "xpack.synthetics.monitorManagement.summary.heading": "Résumé", "xpack.synthetics.monitorManagement.syntheticsDisabledSuccess": "Gestion des moniteurs désactivée avec succès.", - "xpack.synthetics.monitorManagement.syntheticsEnableLabel.invalidKey": "Activer la Gestion des moniteurs", "xpack.synthetics.monitorManagement.syntheticsEnableLabel.management": "Activer la Gestion des moniteurs", "xpack.synthetics.monitorManagement.syntheticsEnableSuccess": "Gestion des moniteurs activée avec succès.", "xpack.synthetics.monitorManagement.testResult": "Résultat du test", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 2e5fdf22d200b..11ccd44a6eee7 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -34901,12 +34901,9 @@ "xpack.synthetics.monitorManagement.apiKey.label": "API キー", "xpack.synthetics.monitorManagement.apiKeyWarning.label": "このAPIキーは1回だけ表示されます。自分の記録用にコピーして保管してください。", "xpack.synthetics.monitorManagement.areYouSure": "この場所を削除しますか?", - "xpack.synthetics.monitorManagement.callout.apiKeyMissing": "現在、APIキーがないため、モニター管理は無効です", "xpack.synthetics.monitorManagement.callout.description.disabled": "モニター管理は現在無効です。Elasticで管理されたSyntheticsサービスでモニターを実行するには、モニター管理を有効にします。既存のモニターが一時停止しています。", - "xpack.synthetics.monitorManagement.callout.description.invalidKey": "モニター管理は現在無効です。Elasticのグローバル管理されたテストロケーションのいずれかでモニターを実行するには、モニター管理を再有効化する必要があります。", "xpack.synthetics.monitorManagement.callout.disabled": "モニター管理が無効です", "xpack.synthetics.monitorManagement.callout.disabled.adminContact": "モニター管理を有効にするには、管理者に連絡してください。", - "xpack.synthetics.monitorManagement.callout.disabledCallout.invalidKey": "モニター管理を有効にするには、管理者に連絡してください。", "xpack.synthetics.monitorManagement.cancelLabel": "キャンセル", "xpack.synthetics.monitorManagement.cannotSaveIntegration": "統合を更新する権限がありません。統合書き込み権限が必要です。", "xpack.synthetics.monitorManagement.closeButtonLabel": "閉じる", @@ -34955,7 +34952,6 @@ "xpack.synthetics.monitorManagement.locationName": "場所名", "xpack.synthetics.monitorManagement.locationsLabel": "場所", "xpack.synthetics.monitorManagement.manageMonitorLoadingLabel": "モニター管理を読み込んでいます", - "xpack.synthetics.monitorManagement.manageMonitorLoadingLabel.callout.invalidKey": "詳細", "xpack.synthetics.monitorManagement.manageMonitorLoadingLabel.callout.learnMore": "詳細情報", "xpack.synthetics.monitorManagement.monitorAddedSuccessMessage": "モニターが正常に追加されました。", "xpack.synthetics.monitorManagement.monitorEditedSuccessMessage": "モニターは正常に更新されました。", @@ -34996,7 +34992,6 @@ "xpack.synthetics.monitorManagement.steps": "ステップ", "xpack.synthetics.monitorManagement.summary.heading": "まとめ", "xpack.synthetics.monitorManagement.syntheticsDisabledSuccess": "モニター管理は正常に無効にされました。", - "xpack.synthetics.monitorManagement.syntheticsEnableLabel.invalidKey": "モニター管理を有効にする", "xpack.synthetics.monitorManagement.syntheticsEnableLabel.management": "モニター管理を有効にする", "xpack.synthetics.monitorManagement.syntheticsEnableSuccess": "モニター管理は正常に有効にされました。", "xpack.synthetics.monitorManagement.testResult": "テスト結果", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 7199ea360f5e5..4eae043b7b884 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -34917,12 +34917,9 @@ "xpack.synthetics.monitorManagement.apiKey.label": "API 密钥", "xpack.synthetics.monitorManagement.apiKeyWarning.label": "此 API 密钥仅显示一次。请保留副本作为您自己的记录。", "xpack.synthetics.monitorManagement.areYouSure": "是否确定要删除此位置?", - "xpack.synthetics.monitorManagement.callout.apiKeyMissing": "由于缺少 API 密钥,监测管理当前已禁用", "xpack.synthetics.monitorManagement.callout.description.disabled": "监测管理当前处于禁用状态。要在 Elastic 托管 Synthetics 服务上运行监测,请启用监测管理。现有监测已暂停。", - "xpack.synthetics.monitorManagement.callout.description.invalidKey": "监测管理当前处于禁用状态。要在 Elastic 的全球托管测试位置之一运行监测,您需要重新启用监测管理。", "xpack.synthetics.monitorManagement.callout.disabled": "已禁用监测管理", "xpack.synthetics.monitorManagement.callout.disabled.adminContact": "请联系管理员启用监测管理。", - "xpack.synthetics.monitorManagement.callout.disabledCallout.invalidKey": "请联系管理员启用监测管理。", "xpack.synthetics.monitorManagement.cancelLabel": "取消", "xpack.synthetics.monitorManagement.cannotSaveIntegration": "您无权更新集成。需要集成写入权限。", "xpack.synthetics.monitorManagement.closeButtonLabel": "关闭", @@ -34971,7 +34968,6 @@ "xpack.synthetics.monitorManagement.locationName": "位置名称", "xpack.synthetics.monitorManagement.locationsLabel": "位置", "xpack.synthetics.monitorManagement.manageMonitorLoadingLabel": "正在加载监测管理", - "xpack.synthetics.monitorManagement.manageMonitorLoadingLabel.callout.invalidKey": "了解详情", "xpack.synthetics.monitorManagement.manageMonitorLoadingLabel.callout.learnMore": "了解详情。", "xpack.synthetics.monitorManagement.monitorAddedSuccessMessage": "已成功添加监测。", "xpack.synthetics.monitorManagement.monitorEditedSuccessMessage": "已成功更新监测。", @@ -35012,7 +35008,6 @@ "xpack.synthetics.monitorManagement.steps": "步长", "xpack.synthetics.monitorManagement.summary.heading": "摘要", "xpack.synthetics.monitorManagement.syntheticsDisabledSuccess": "已成功禁用监测管理。", - "xpack.synthetics.monitorManagement.syntheticsEnableLabel.invalidKey": "启用监测管理", "xpack.synthetics.monitorManagement.syntheticsEnableLabel.management": "启用监测管理", "xpack.synthetics.monitorManagement.syntheticsEnableSuccess": "已成功启用监测管理。", "xpack.synthetics.monitorManagement.testResult": "测试结果", diff --git a/x-pack/test/api_integration/apis/synthetics/add_monitor_project.ts b/x-pack/test/api_integration/apis/synthetics/add_monitor_project.ts index b1bc58abe7113..e6a8ae14cda1a 100644 --- a/x-pack/test/api_integration/apis/synthetics/add_monitor_project.ts +++ b/x-pack/test/api_integration/apis/synthetics/add_monitor_project.ts @@ -74,7 +74,7 @@ export default function ({ getService }: FtrProviderContext) { }; before(async () => { - await supertest.post(API_URLS.SYNTHETICS_ENABLEMENT).set('kbn-xsrf', 'true').expect(200); + await supertest.put(API_URLS.SYNTHETICS_ENABLEMENT).set('kbn-xsrf', 'true').expect(200); await supertest.post('/api/fleet/setup').set('kbn-xsrf', 'true').send().expect(200); await supertest .post('/api/fleet/epm/packages/synthetics/0.11.4') diff --git a/x-pack/test/api_integration/apis/synthetics/edit_monitor.ts b/x-pack/test/api_integration/apis/synthetics/edit_monitor.ts index f20a2cdf61a45..bf4447a1b5969 100644 --- a/x-pack/test/api_integration/apis/synthetics/edit_monitor.ts +++ b/x-pack/test/api_integration/apis/synthetics/edit_monitor.ts @@ -43,7 +43,7 @@ export default function ({ getService }: FtrProviderContext) { before(async () => { _httpMonitorJson = getFixtureJson('http_monitor'); await supertest.post('/api/fleet/setup').set('kbn-xsrf', 'true').send().expect(200); - await supertest.post(API_URLS.SYNTHETICS_ENABLEMENT).set('kbn-xsrf', 'true').expect(200); + await supertest.put(API_URLS.SYNTHETICS_ENABLEMENT).set('kbn-xsrf', 'true').expect(200); const testPolicyName = 'Fleet test server policy' + Date.now(); const apiResponse = await testPrivateLocations.addFleetPolicy(testPolicyName); diff --git a/x-pack/test/api_integration/apis/synthetics/get_monitor.ts b/x-pack/test/api_integration/apis/synthetics/get_monitor.ts index 5394ca64545e6..00772c5550ac1 100644 --- a/x-pack/test/api_integration/apis/synthetics/get_monitor.ts +++ b/x-pack/test/api_integration/apis/synthetics/get_monitor.ts @@ -32,7 +32,7 @@ export default function ({ getService }: FtrProviderContext) { }; before(async () => { - await supertest.post(API_URLS.SYNTHETICS_ENABLEMENT).set('kbn-xsrf', 'true').expect(200); + await supertest.put(API_URLS.SYNTHETICS_ENABLEMENT).set('kbn-xsrf', 'true').expect(200); _monitors = [ getFixtureJson('icmp_monitor'), diff --git a/x-pack/test/api_integration/apis/synthetics/get_monitor_overview.ts b/x-pack/test/api_integration/apis/synthetics/get_monitor_overview.ts index 25aad0704cddd..625dbdac61608 100644 --- a/x-pack/test/api_integration/apis/synthetics/get_monitor_overview.ts +++ b/x-pack/test/api_integration/apis/synthetics/get_monitor_overview.ts @@ -55,7 +55,7 @@ export default function ({ getService }: FtrProviderContext) { }; before(async () => { - await supertest.post(API_URLS.SYNTHETICS_ENABLEMENT).set('kbn-xsrf', 'true').expect(200); + await supertest.put(API_URLS.SYNTHETICS_ENABLEMENT).set('kbn-xsrf', 'true').expect(200); await kibanaServer.spaces.create({ id: SPACE_ID, name: SPACE_NAME }); await security.role.create(roleName, { kibana: [ diff --git a/x-pack/test/api_integration/apis/synthetics/synthetics_enablement.ts b/x-pack/test/api_integration/apis/synthetics/synthetics_enablement.ts index d031a6c505c8f..bf68da4c148f5 100644 --- a/x-pack/test/api_integration/apis/synthetics/synthetics_enablement.ts +++ b/x-pack/test/api_integration/apis/synthetics/synthetics_enablement.ts @@ -6,24 +6,57 @@ */ import { API_URLS } from '@kbn/synthetics-plugin/common/constants'; +import { + syntheticsApiKeyID, + syntheticsApiKeyObjectType, +} from '@kbn/synthetics-plugin/server/legacy_uptime/lib/saved_objects/service_api_key'; import { serviceApiKeyPrivileges } from '@kbn/synthetics-plugin/server/synthetics_service/get_api_key'; import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { + const correctPrivileges = { + applications: [], + cluster: ['monitor', 'read_ilm', 'read_pipeline'], + indices: [ + { + allow_restricted_indices: false, + names: ['synthetics-*'], + privileges: ['view_index_metadata', 'create_doc', 'auto_configure', 'read'], + }, + ], + metadata: {}, + run_as: [], + transient_metadata: { + enabled: true, + }, + }; + describe('SyntheticsEnablement', () => { const supertestWithAuth = getService('supertest'); const supertest = getService('supertestWithoutAuth'); const security = getService('security'); const kibanaServer = getService('kibanaServer'); - before(async () => { - await supertestWithAuth.delete(API_URLS.SYNTHETICS_ENABLEMENT).set('kbn-xsrf', 'true'); - }); - - describe('[GET] - /internal/uptime/service/enablement', () => { - ['manage_security', 'manage_own_api_key', 'manage_api_key'].forEach((privilege) => { - it(`returns response for an admin with privilege ${privilege}`, async () => { + const esSupertest = getService('esSupertest'); + + const getApiKeys = async () => { + const { body } = await esSupertest.get(`/_security/api_key`).query({ with_limited_by: true }); + const apiKeys = body.api_keys || []; + return apiKeys.filter( + (apiKey: any) => apiKey.name.includes('synthetics-api-key') && apiKey.invalidated === false + ); + }; + + describe('[PUT] /internal/uptime/service/enablement', () => { + beforeEach(async () => { + const apiKeys = await getApiKeys(); + if (apiKeys.length) { + await supertestWithAuth.delete(API_URLS.SYNTHETICS_ENABLEMENT).set('kbn-xsrf', 'true'); + } + }); + ['manage_security', 'manage_api_key', 'manage_own_api_key'].forEach((privilege) => { + it(`returns response when user can manage api keys`, async () => { const username = 'admin'; const roleName = `synthetics_admin-${privilege}`; const password = `${username}-password`; @@ -38,7 +71,7 @@ export default function ({ getService }: FtrProviderContext) { }, ], elasticsearch: { - cluster: [privilege, ...serviceApiKeyPrivileges.cluster], + cluster: [privilege], indices: serviceApiKeyPrivileges.indices, }, }); @@ -50,7 +83,7 @@ export default function ({ getService }: FtrProviderContext) { }); const apiResponse = await supertest - .get(API_URLS.SYNTHETICS_ENABLEMENT) + .put(API_URLS.SYNTHETICS_ENABLEMENT) .auth(username, password) .set('kbn-xsrf', 'true') .expect(200); @@ -58,17 +91,10 @@ export default function ({ getService }: FtrProviderContext) { expect(apiResponse.body).eql({ areApiKeysEnabled: true, canManageApiKeys: true, - canEnable: true, - isEnabled: true, - isValidApiKey: true, + canEnable: false, + isEnabled: false, + isValidApiKey: false, }); - if (privilege !== 'manage_own_api_key') { - await supertest - .delete(API_URLS.SYNTHETICS_ENABLEMENT) - .auth(username, password) - .set('kbn-xsrf', 'true') - .expect(200); - } } finally { await security.user.delete(username); await security.role.delete(roleName); @@ -76,9 +102,9 @@ export default function ({ getService }: FtrProviderContext) { }); }); - it('returns response for an uptime all user without admin privileges', async () => { - const username = 'uptime'; - const roleName = 'uptime_user'; + it(`returns response for an admin with privilege`, async () => { + const username = 'admin'; + const roleName = `synthetics_admin`; const password = `${username}-password`; try { await security.role.create(roleName, { @@ -90,7 +116,10 @@ export default function ({ getService }: FtrProviderContext) { spaces: ['*'], }, ], - elasticsearch: {}, + elasticsearch: { + cluster: serviceApiKeyPrivileges.cluster, + indices: serviceApiKeyPrivileges.indices, + }, }); await security.user.create(username, { @@ -100,7 +129,7 @@ export default function ({ getService }: FtrProviderContext) { }); const apiResponse = await supertest - .get(API_URLS.SYNTHETICS_ENABLEMENT) + .put(API_URLS.SYNTHETICS_ENABLEMENT) .auth(username, password) .set('kbn-xsrf', 'true') .expect(200); @@ -108,19 +137,20 @@ export default function ({ getService }: FtrProviderContext) { expect(apiResponse.body).eql({ areApiKeysEnabled: true, canManageApiKeys: false, - canEnable: false, - isEnabled: false, - isValidApiKey: false, + canEnable: true, + isEnabled: true, + isValidApiKey: true, }); + const validApiKeys = await getApiKeys(); + expect(validApiKeys.length).eql(1); + expect(validApiKeys[0].role_descriptors.synthetics_writer).eql(correctPrivileges); } finally { - await security.role.delete(roleName); await security.user.delete(username); + await security.role.delete(roleName); } }); - }); - describe('[POST] - /internal/uptime/service/enablement', () => { - it('with an admin', async () => { + it(`does not create excess api keys`, async () => { const username = 'admin'; const roleName = `synthetics_admin`; const password = `${username}-password`; @@ -135,7 +165,7 @@ export default function ({ getService }: FtrProviderContext) { }, ], elasticsearch: { - cluster: ['manage_security', ...serviceApiKeyPrivileges.cluster], + cluster: serviceApiKeyPrivileges.cluster, indices: serviceApiKeyPrivileges.indices, }, }); @@ -146,38 +176,213 @@ export default function ({ getService }: FtrProviderContext) { full_name: 'a kibana user', }); - await supertest - .post(API_URLS.SYNTHETICS_ENABLEMENT) + const apiResponse = await supertest + .put(API_URLS.SYNTHETICS_ENABLEMENT) + .auth(username, password) + .set('kbn-xsrf', 'true') + .expect(200); + + expect(apiResponse.body).eql({ + areApiKeysEnabled: true, + canManageApiKeys: false, + canEnable: true, + isEnabled: true, + isValidApiKey: true, + }); + + const validApiKeys = await getApiKeys(); + expect(validApiKeys.length).eql(1); + expect(validApiKeys[0].role_descriptors.synthetics_writer).eql(correctPrivileges); + + // call api a second time + const apiResponse2 = await supertest + .put(API_URLS.SYNTHETICS_ENABLEMENT) .auth(username, password) .set('kbn-xsrf', 'true') .expect(200); + + expect(apiResponse2.body).eql({ + areApiKeysEnabled: true, + canManageApiKeys: false, + canEnable: true, + isEnabled: true, + isValidApiKey: true, + }); + + const validApiKeys2 = await getApiKeys(); + expect(validApiKeys2.length).eql(1); + expect(validApiKeys2[0].role_descriptors.synthetics_writer).eql(correctPrivileges); + } finally { + await security.user.delete(username); + await security.role.delete(roleName); + } + }); + + it(`auto re-enables the api key when created with invalid permissions and invalidates old api key`, async () => { + const username = 'admin'; + const roleName = `synthetics_admin`; + const password = `${username}-password`; + try { + // create api key with incorrect permissions + const apiKeyResult = await esSupertest + .post(`/_security/api_key`) + .send({ + name: 'synthetics-api-key', + expiration: '1d', + role_descriptors: { + 'role-a': { + cluster: serviceApiKeyPrivileges.cluster, + indices: [ + { + names: ['synthetics-*'], + privileges: ['view_index_metadata', 'create_doc', 'auto_configure'], + }, + ], + }, + }, + }) + .expect(200); + kibanaServer.savedObjects.create({ + id: syntheticsApiKeyID, + type: syntheticsApiKeyObjectType, + overwrite: true, + attributes: { + id: apiKeyResult.body.id, + name: 'synthetics-api-key (required for monitor management)', + apiKey: apiKeyResult.body.api_key, + }, + }); + + const validApiKeys = await getApiKeys(); + expect(validApiKeys.length).eql(1); + expect(validApiKeys[0].role_descriptors.synthetics_writer).not.eql(correctPrivileges); + + await security.role.create(roleName, { + kibana: [ + { + feature: { + uptime: ['all'], + }, + spaces: ['*'], + }, + ], + elasticsearch: { + cluster: serviceApiKeyPrivileges.cluster, + indices: serviceApiKeyPrivileges.indices, + }, + }); + + await security.user.create(username, { + password, + roles: [roleName], + full_name: 'a kibana user', + }); + const apiResponse = await supertest - .get(API_URLS.SYNTHETICS_ENABLEMENT) + .put(API_URLS.SYNTHETICS_ENABLEMENT) .auth(username, password) .set('kbn-xsrf', 'true') .expect(200); expect(apiResponse.body).eql({ areApiKeysEnabled: true, - canManageApiKeys: true, + canManageApiKeys: false, canEnable: true, isEnabled: true, isValidApiKey: true, }); + + const validApiKeys2 = await getApiKeys(); + expect(validApiKeys2.length).eql(1); + expect(validApiKeys2[0].role_descriptors.synthetics_writer).eql(correctPrivileges); } finally { - await supertest - .delete(API_URLS.SYNTHETICS_ENABLEMENT) + await security.user.delete(username); + await security.role.delete(roleName); + } + }); + + it(`auto re-enables api key when invalidated`, async () => { + const username = 'admin'; + const roleName = `synthetics_admin`; + const password = `${username}-password`; + try { + await security.role.create(roleName, { + kibana: [ + { + feature: { + uptime: ['all'], + }, + spaces: ['*'], + }, + ], + elasticsearch: { + cluster: serviceApiKeyPrivileges.cluster, + indices: serviceApiKeyPrivileges.indices, + }, + }); + + await security.user.create(username, { + password, + roles: [roleName], + full_name: 'a kibana user', + }); + + const apiResponse = await supertest + .put(API_URLS.SYNTHETICS_ENABLEMENT) + .auth(username, password) + .set('kbn-xsrf', 'true') + .expect(200); + + expect(apiResponse.body).eql({ + areApiKeysEnabled: true, + canManageApiKeys: false, + canEnable: true, + isEnabled: true, + isValidApiKey: true, + }); + + const validApiKeys = await getApiKeys(); + expect(validApiKeys.length).eql(1); + expect(validApiKeys[0].role_descriptors.synthetics_writer).eql(correctPrivileges); + + // delete api key + await esSupertest + .delete(`/_security/api_key`) + .send({ + ids: [validApiKeys[0].id], + }) + .expect(200); + + const validApiKeysAferDeletion = await getApiKeys(); + expect(validApiKeysAferDeletion.length).eql(0); + + // call api a second time + const apiResponse2 = await supertest + .put(API_URLS.SYNTHETICS_ENABLEMENT) .auth(username, password) .set('kbn-xsrf', 'true') .expect(200); + + expect(apiResponse2.body).eql({ + areApiKeysEnabled: true, + canManageApiKeys: false, + canEnable: true, + isEnabled: true, + isValidApiKey: true, + }); + + const validApiKeys2 = await getApiKeys(); + expect(validApiKeys2.length).eql(1); + expect(validApiKeys2[0].role_descriptors.synthetics_writer).eql(correctPrivileges); + } finally { await security.user.delete(username); await security.role.delete(roleName); } }); - it('with an uptime user', async () => { + it('returns response for an uptime all user without admin privileges', async () => { const username = 'uptime'; - const roleName = `uptime_user`; + const roleName = 'uptime_user'; const password = `${username}-password`; try { await security.role.create(roleName, { @@ -198,16 +403,12 @@ export default function ({ getService }: FtrProviderContext) { full_name: 'a kibana user', }); - await supertest - .post(API_URLS.SYNTHETICS_ENABLEMENT) - .auth(username, password) - .set('kbn-xsrf', 'true') - .expect(403); const apiResponse = await supertest - .get(API_URLS.SYNTHETICS_ENABLEMENT) + .put(API_URLS.SYNTHETICS_ENABLEMENT) .auth(username, password) .set('kbn-xsrf', 'true') .expect(200); + expect(apiResponse.body).eql({ areApiKeysEnabled: true, canManageApiKeys: false, @@ -216,13 +417,19 @@ export default function ({ getService }: FtrProviderContext) { isValidApiKey: false, }); } finally { - await security.user.delete(username); await security.role.delete(roleName); + await security.user.delete(username); } }); }); - describe('[DELETE] - /internal/uptime/service/enablement', () => { + describe('[DELETE] /internal/uptime/service/enablement', () => { + beforeEach(async () => { + const apiKeys = await getApiKeys(); + if (apiKeys.length) { + await supertestWithAuth.delete(API_URLS.SYNTHETICS_ENABLEMENT).set('kbn-xsrf', 'true'); + } + }); it('with an admin', async () => { const username = 'admin'; const roleName = `synthetics_admin`; @@ -238,7 +445,7 @@ export default function ({ getService }: FtrProviderContext) { }, ], elasticsearch: { - cluster: ['manage_security', ...serviceApiKeyPrivileges.cluster], + cluster: serviceApiKeyPrivileges.cluster, indices: serviceApiKeyPrivileges.indices, }, }); @@ -250,7 +457,7 @@ export default function ({ getService }: FtrProviderContext) { }); await supertest - .post(API_URLS.SYNTHETICS_ENABLEMENT) + .put(API_URLS.SYNTHETICS_ENABLEMENT) .auth(username, password) .set('kbn-xsrf', 'true') .expect(200); @@ -261,14 +468,14 @@ export default function ({ getService }: FtrProviderContext) { .expect(200); expect(delResponse.body).eql({}); const apiResponse = await supertest - .get(API_URLS.SYNTHETICS_ENABLEMENT) + .put(API_URLS.SYNTHETICS_ENABLEMENT) .auth(username, password) .set('kbn-xsrf', 'true') .expect(200); expect(apiResponse.body).eql({ areApiKeysEnabled: true, - canManageApiKeys: true, + canManageApiKeys: false, canEnable: true, isEnabled: true, isValidApiKey: true, @@ -303,7 +510,7 @@ export default function ({ getService }: FtrProviderContext) { }); await supertestWithAuth - .post(API_URLS.SYNTHETICS_ENABLEMENT) + .put(API_URLS.SYNTHETICS_ENABLEMENT) .set('kbn-xsrf', 'true') .expect(200); await supertest @@ -312,7 +519,7 @@ export default function ({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'true') .expect(403); const apiResponse = await supertest - .get(API_URLS.SYNTHETICS_ENABLEMENT) + .put(API_URLS.SYNTHETICS_ENABLEMENT) .auth(username, password) .set('kbn-xsrf', 'true') .expect(200); @@ -351,7 +558,7 @@ export default function ({ getService }: FtrProviderContext) { }, ], elasticsearch: { - cluster: ['manage_security', ...serviceApiKeyPrivileges.cluster], + cluster: serviceApiKeyPrivileges.cluster, indices: serviceApiKeyPrivileges.indices, }, }); @@ -364,21 +571,21 @@ export default function ({ getService }: FtrProviderContext) { // can enable synthetics in default space when enabled in a non default space const apiResponseGet = await supertest - .get(`/s/${SPACE_ID}${API_URLS.SYNTHETICS_ENABLEMENT}`) + .put(`/s/${SPACE_ID}${API_URLS.SYNTHETICS_ENABLEMENT}`) .auth(username, password) .set('kbn-xsrf', 'true') .expect(200); expect(apiResponseGet.body).eql({ areApiKeysEnabled: true, - canManageApiKeys: true, + canManageApiKeys: false, canEnable: true, isEnabled: true, isValidApiKey: true, }); await supertest - .post(`/s/${SPACE_ID}${API_URLS.SYNTHETICS_ENABLEMENT}`) + .put(`/s/${SPACE_ID}${API_URLS.SYNTHETICS_ENABLEMENT}`) .auth(username, password) .set('kbn-xsrf', 'true') .expect(200); @@ -388,14 +595,14 @@ export default function ({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'true') .expect(200); const apiResponse = await supertest - .get(API_URLS.SYNTHETICS_ENABLEMENT) + .put(API_URLS.SYNTHETICS_ENABLEMENT) .auth(username, password) .set('kbn-xsrf', 'true') .expect(200); expect(apiResponse.body).eql({ areApiKeysEnabled: true, - canManageApiKeys: true, + canManageApiKeys: false, canEnable: true, isEnabled: true, isValidApiKey: true, @@ -403,7 +610,7 @@ export default function ({ getService }: FtrProviderContext) { // can disable synthetics in non default space when enabled in default space await supertest - .post(API_URLS.SYNTHETICS_ENABLEMENT) + .put(API_URLS.SYNTHETICS_ENABLEMENT) .auth(username, password) .set('kbn-xsrf', 'true') .expect(200); @@ -413,14 +620,14 @@ export default function ({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'true') .expect(200); const apiResponse2 = await supertest - .get(API_URLS.SYNTHETICS_ENABLEMENT) + .put(API_URLS.SYNTHETICS_ENABLEMENT) .auth(username, password) .set('kbn-xsrf', 'true') .expect(200); expect(apiResponse2.body).eql({ areApiKeysEnabled: true, - canManageApiKeys: true, + canManageApiKeys: false, canEnable: true, isEnabled: true, isValidApiKey: true, @@ -428,6 +635,7 @@ export default function ({ getService }: FtrProviderContext) { } finally { await security.user.delete(username); await security.role.delete(roleName); + await kibanaServer.spaces.delete(SPACE_ID); } }); }); From b9551e2cf0281509781b4bc0b71a845d03e8600c Mon Sep 17 00:00:00 2001 From: Pete Hampton Date: Mon, 24 Apr 2023 23:05:00 +0100 Subject: [PATCH 12/52] Sec Telemetry: Add Kubernetes and misc fields to filterlist (#152129) ## Summary Adds Kubernetes and other fields to the telemetry allowlist. ### 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 ### 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: Terrance DeJesus <99630311+terrancedejesus@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Isai <59296946+imays11@users.noreply.github.com> --- .../lib/telemetry/filterlists/index.test.ts | 21 +++++ .../filterlists/prebuilt_rules_alerts.ts | 87 +++++++++++++++++ .../server/lib/telemetry/helpers.test.ts | 94 +++++++++++++++++++ .../server/lib/telemetry/helpers.ts | 47 +++++++++- .../telemetry/tasks/prebuilt_rule_alerts.ts | 9 +- .../server/lib/telemetry/types.ts | 13 +++ 6 files changed, 268 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/filterlists/index.test.ts b/x-pack/plugins/security_solution/server/lib/telemetry/filterlists/index.test.ts index 9e5c383e825d1..631b67cd49601 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/filterlists/index.test.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/filterlists/index.test.ts @@ -26,6 +26,9 @@ describe('Security Telemetry filters', () => { 'event.provider': true, 'event.type': true, 'powershell.file.script_block_text': true, + 'kubernetes.pod.uid': true, + 'kubernetes.pod.name': true, + 'kubernetes.pod.ip': true, package_version: true, }; @@ -177,5 +180,23 @@ describe('Security Telemetry filters', () => { package_version: '3.4.1', }); }); + + it('copies over kubernetes fields', () => { + const event = { + not_event: 'much data, much wow', + 'event.id': '36857486973080746231799376445175633955031786243637182487', + 'event.ingested': 'May 17, 2022 @ 00:22:07.000', + 'kubernetes.pod.uid': '059a3767-7492-4fb5-92d4-93f458ddab44', + 'kubernetes.pod.name': 'kube-dns-6f4fd4zzz-7z7xj', + 'kubernetes.pod.ip': '10-245-0-5', + }; + expect(copyAllowlistedFields(allowlist, event)).toStrictEqual({ + 'event.id': '36857486973080746231799376445175633955031786243637182487', + 'event.ingested': 'May 17, 2022 @ 00:22:07.000', + 'kubernetes.pod.uid': '059a3767-7492-4fb5-92d4-93f458ddab44', + 'kubernetes.pod.name': 'kube-dns-6f4fd4zzz-7z7xj', + 'kubernetes.pod.ip': '10-245-0-5', + }); + }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/filterlists/prebuilt_rules_alerts.ts b/x-pack/plugins/security_solution/server/lib/telemetry/filterlists/prebuilt_rules_alerts.ts index 225206cca4b0d..42235cae66574 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/filterlists/prebuilt_rules_alerts.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/filterlists/prebuilt_rules_alerts.ts @@ -215,6 +215,9 @@ export const prebuiltRuleAllowlistFields: AllowlistFields = { target_resources: true, }, }, + properties: { + category: true, + }, signinlogs: { properties: { app_display_name: true, @@ -253,6 +256,85 @@ export const prebuiltRuleAllowlistFields: AllowlistFields = { setting: { name: true, }, + application: { + name: true, + }, + old_value: true, + role: { + name: true, + }, + }, + event: { + type: true, + }, + }, + // kubernetes + kubernetes: { + audit: { + annotations: true, + verb: true, + user: { + groups: true, + }, + impersonatedUser: { + groups: true, + }, + objectRef: { + name: true, + namespace: true, + resource: true, + subresource: true, + }, + requestObject: { + spec: { + containers: { + image: true, + securityContext: { + allowPrivilegeEscalation: true, + capabilities: { + add: true, + }, + privileged: true, + procMount: true, + runAsGroup: true, + runAsUser: true, + }, + }, + hostIPC: true, + hostNetwork: true, + hostPID: true, + securityContext: { + runAsGroup: true, + runAsUser: true, + }, + serviceAccountName: true, + type: true, + volumes: { + hostPath: { + path: true, + }, + }, + }, + }, + requestURI: true, + responseObject: { + roleRef: { + kind: true, + resourceName: true, + }, + rules: true, + spec: { + containers: { + securityContext: { + allowPrivilegeEscalation: true, + }, + }, + }, + }, + responseStatus: { + code: true, + }, + userAgent: true, }, }, // office 360 @@ -275,6 +357,11 @@ export const prebuiltRuleAllowlistFields: AllowlistFields = { Enabled: true, ForwardAsAttachmentTo: true, ForwardTo: true, + ModifiedProperties: { + Role_DisplayName: { + NewValue: true, + }, + }, RedirectTo: true, }, }, diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/helpers.test.ts b/x-pack/plugins/security_solution/server/lib/telemetry/helpers.test.ts index 4e3db8c657e04..e01eb21cbe68c 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/helpers.test.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/helpers.test.ts @@ -25,6 +25,7 @@ import { tlog, setIsElasticCloudDeployment, createTaskMetric, + processK8sUsernames, } from './helpers'; import type { ESClusterInfo, ESLicense, ExceptionListItem } from './types'; import type { PolicyConfig, PolicyData } from '../../../common/endpoint/types'; @@ -963,6 +964,7 @@ describe.skip('test create task metrics', () => { passed: true, }); }); + test('can succeed when error given', async () => { const stubTaskName = 'test'; const stubPassed = false; @@ -982,3 +984,95 @@ describe.skip('test create task metrics', () => { }); }); }); + +describe('Pii is removed from a kubernetes prebuilt rule alert', () => { + test('a document without the sensitive values is ignored', async () => { + const clusterUuid = '7c5f1d31-ce87-4090-8dbf-decaac0261ca'; + const testDocument = { + kubernetes: { + audit: {}, + pod: { + uid: 'test', + name: 'test', + ip: 'test', + labels: 'test', + annotations: 'test', + }, + }, + powershell: { + command_line: 'test', + module: 'test', + module_loaded: 'test', + module_version: 'test', + process_name: 'test', + }, + }; + + const ignoredDocument = processK8sUsernames(clusterUuid, testDocument); + expect(ignoredDocument).toEqual(testDocument); + }); + + test('kubernetes system usernames are not sanitized from a document', async () => { + const clusterUuid = '7c5f1d31-ce87-4090-8dbf-decaac0261ca'; + const testDocument = { + kubernetes: { + pod: { + uid: 'test', + name: 'test', + ip: 'test', + labels: 'test', + annotations: 'test', + }, + audit: { + user: { + username: 'system:serviceaccount:default:default', + groups: [ + 'system:serviceaccounts', + 'system:serviceaccounts:default', + 'system:authenticated', + ], + }, + impersonated_user: { + username: 'system:serviceaccount:default:default', + groups: [ + 'system:serviceaccounts', + 'system:serviceaccounts:default', + 'system:authenticated', + ], + }, + }, + }, + }; + + const sanitizedDocument = processK8sUsernames(clusterUuid, testDocument); + expect(sanitizedDocument).toEqual(testDocument); + }); + + test('kubernetes system usernames are sanitized from a document when not system users', async () => { + const clusterUuid = '7c5f1d31-ce87-4090-8dbf-decaac0261ca'; + const testDocument = { + kubernetes: { + pod: { + uid: 'test', + name: 'test', + ip: 'test', + labels: 'test', + annotations: 'test', + }, + audit: { + user: { + username: 'user1', + groups: ['group1', 'group2', 'group3'], + }, + impersonated_user: { + username: 'impersonatedUser1', + groups: ['group4', 'group5', 'group6'], + }, + }, + }, + }; + + const sanitizedDocument = processK8sUsernames(clusterUuid, testDocument); + expect(sanitizedDocument).toEqual(testDocument); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts b/x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts index f03621899c800..f5d6bc41ee349 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts @@ -8,8 +8,9 @@ import moment from 'moment'; import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; import type { PackagePolicy } from '@kbn/fleet-plugin/common/types/models/package_policy'; -import { merge } from 'lodash'; +import { merge, set } from 'lodash'; import type { Logger } from '@kbn/core/server'; +import { sha256 } from 'js-sha256'; import { copyAllowlistedFields, filterList } from './filterlists'; import type { PolicyConfig, PolicyData } from '../../../common/endpoint/types'; import type { @@ -300,3 +301,47 @@ export const createTaskMetric = ( error_message: errorMessage, }; }; + +function obfuscateString(clusterId: string, toHash: string): string { + const valueToObfuscate = toHash + clusterId; + return sha256.create().update(valueToObfuscate).hex(); +} + +function isAllowlistK8sUsername(username: string) { + return ( + username === 'edit' || + username === 'view' || + username === 'admin' || + username === 'elastic-agent' || + username === 'cluster-admin' || + username.startsWith('system') + ); +} + +export const processK8sUsernames = (clusterId: string, event: TelemetryEvent): TelemetryEvent => { + // if there is no kubernetes key, return the event as is + if (event.kubernetes === undefined && event.kubernetes === null) { + return event; + } + + const username = event?.kubernetes?.audit?.user?.username; + const impersonatedUser = event?.kubernetes?.audit?.impersonated_user?.username; + + if (username !== undefined && username !== null && !isAllowlistK8sUsername(username)) { + set(event, 'kubernetes.audit.user.username', obfuscateString(clusterId, username)); + } + + if ( + impersonatedUser !== undefined && + impersonatedUser !== null && + !isAllowlistK8sUsername(impersonatedUser) + ) { + set( + event, + 'kubernetes.audit.impersonated_user.username', + obfuscateString(clusterId, impersonatedUser) + ); + } + + return event; +}; diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/prebuilt_rule_alerts.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/prebuilt_rule_alerts.ts index a7fab953dad38..0fdc6cf32a69c 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/prebuilt_rule_alerts.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/prebuilt_rule_alerts.ts @@ -11,7 +11,7 @@ import type { ITelemetryReceiver } from '../receiver'; import type { ESClusterInfo, ESLicense, TelemetryEvent } from '../types'; import type { TaskExecutionPeriod } from '../task'; import { TELEMETRY_CHANNEL_DETECTION_ALERTS, TASK_METRICS_CHANNEL } from '../constants'; -import { batchTelemetryRecords, tlog, createTaskMetric } from '../helpers'; +import { batchTelemetryRecords, createTaskMetric, processK8sUsernames, tlog } from '../helpers'; import { copyAllowlistedFields, filterList } from '../filterlists'; export function createTelemetryPrebuiltRuleAlertsTaskConfig(maxTelemetryBatch: number) { @@ -70,7 +70,12 @@ export function createTelemetryPrebuiltRuleAlertsTaskConfig(maxTelemetryBatch: n copyAllowlistedFields(filterList.prebuiltRulesAlerts, event) ); - const enrichedAlerts = processedAlerts.map( + const sanitizedAlerts = processedAlerts.map( + (event: TelemetryEvent): TelemetryEvent => + processK8sUsernames(clusterInfo?.cluster_uuid, event) + ); + + const enrichedAlerts = sanitizedAlerts.map( (event: TelemetryEvent): TelemetryEvent => ({ ...event, licence_id: licenseInfo?.uid, diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/types.ts b/x-pack/plugins/security_solution/server/lib/telemetry/types.ts index ba61f6b85aaab..df3b571714b29 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/types.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/types.ts @@ -64,6 +64,19 @@ export interface TelemetryEvent { id?: string; kind?: string; }; + kubernetes?: { + audit?: { + user?: { + username?: string; + groups?: string[]; + }; + impersonated_user?: { + username?: string; + groups?: string[]; + }; + pod?: SearchTypes; + }; + }; } // EP Policy Response From d5f12ac22fd505843990593ee5954e89202224e0 Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Mon, 24 Apr 2023 16:20:20 -0600 Subject: [PATCH 13/52] [ML] Data Frame Analytics custom URLs: adds ability to set custom time range in urls (#155337) ## Summary Related meta issue: https://github.com/elastic/kibana/issues/150375 This PR adds a custom time range picker to the custom urls UI for Data Frame Analytics jobs. When not selected, the timerange will default to the global timerange - this is the same behavior as before. image image image ### Checklist Delete any items that are not applicable to this PR. - [ ] 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) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [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 - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] 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)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] 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)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../custom_time_range_picker.tsx | 156 ++++++++++++++++++ .../custom_urls/custom_url_editor/editor.tsx | 107 +++++++----- .../custom_urls/custom_url_editor/utils.ts | 28 +++- .../components/custom_urls/custom_urls.tsx | 4 + 4 files changed, 249 insertions(+), 46 deletions(-) create mode 100644 x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/custom_time_range_picker.tsx diff --git a/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/custom_time_range_picker.tsx b/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/custom_time_range_picker.tsx new file mode 100644 index 0000000000000..620aabd1c842b --- /dev/null +++ b/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/custom_time_range_picker.tsx @@ -0,0 +1,156 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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, { FC, useMemo, useState } from 'react'; +import moment, { type Moment } from 'moment'; +import { + EuiDatePicker, + EuiDatePickerRange, + EuiFlexItem, + EuiFlexGroup, + EuiFormRow, + EuiIconTip, + EuiSpacer, + EuiSwitch, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { i18n } from '@kbn/i18n'; +import { useMlKibana } from '../../../contexts/kibana'; + +interface CustomUrlTimeRangePickerProps { + onCustomTimeRangeChange: (customTimeRange?: { start: Moment; end: Moment }) => void; + customTimeRange?: { start: Moment; end: Moment }; +} + +/* + * React component for the form for adding a custom time range. + */ +export const CustomTimeRangePicker: FC = ({ + onCustomTimeRangeChange, + customTimeRange, +}) => { + const [showCustomTimeRangeSelector, setShowCustomTimeRangeSelector] = useState(false); + const { + services: { + data: { + query: { + timefilter: { timefilter }, + }, + }, + }, + } = useMlKibana(); + + const onCustomTimeRangeSwitchChange = (checked: boolean) => { + if (checked === false) { + // Clear the custom time range so it isn't persisted + onCustomTimeRangeChange(undefined); + } + setShowCustomTimeRangeSelector(checked); + }; + + // If the custom time range is not set, default to the timefilter settings + const currentTimeRange = useMemo( + () => + customTimeRange ?? { + start: moment(timefilter.getAbsoluteTime().from), + end: moment(timefilter.getAbsoluteTime().to), + }, + [customTimeRange, timefilter] + ); + + const handleStartChange = (date: moment.Moment) => { + onCustomTimeRangeChange({ ...currentTimeRange, start: date }); + }; + const handleEndChange = (date: moment.Moment) => { + onCustomTimeRangeChange({ ...currentTimeRange, end: date }); + }; + + const { start, end } = currentTimeRange; + + return ( + <> + + + + + + + + } + > + + } + checked={showCustomTimeRangeSelector} + onChange={(e) => onCustomTimeRangeSwitchChange(e.target.checked)} + compressed + /> + + + + {showCustomTimeRangeSelector ? ( + <> + + + } + > + end} + startDateControl={ + + } + endDateControl={ + + } + /> + + + ) : null} + + ); +}; diff --git a/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/editor.tsx b/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/editor.tsx index 315c60fab6a6f..523f59c32f224 100644 --- a/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/editor.tsx +++ b/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/editor.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import React, { ChangeEvent, useMemo, useState, useRef, useEffect, FC } from 'react'; +import React, { ChangeEvent, useState, useRef, useEffect, FC } from 'react'; +import { type Moment } from 'moment'; import { EuiComboBox, @@ -29,10 +30,11 @@ import { DataView } from '@kbn/data-views-plugin/public'; import { CustomUrlSettings, isValidCustomUrlSettingsTimeRange } from './utils'; import { isValidLabel } from '../../../util/custom_url_utils'; import { type DataFrameAnalyticsConfig } from '../../../../../common/types/data_frame_analytics'; -import { Job, isAnomalyDetectionJob } from '../../../../../common/types/anomaly_detection_jobs'; +import { type Job } from '../../../../../common/types/anomaly_detection_jobs'; import { TIME_RANGE_TYPE, TimeRangeType, URL_TYPE } from './constants'; import { UrlConfig } from '../../../../../common/types/custom_urls'; +import { CustomTimeRangePicker } from './custom_time_range_picker'; import { useMlKibana } from '../../../contexts/kibana'; import { getDropDownOptions } from './get_dropdown_options'; @@ -66,6 +68,7 @@ interface CustomUrlEditorProps { dashboards: Array<{ id: string; title: string }>; dataViewListItems: DataViewListItem[]; showTimeRangeSelector?: boolean; + showCustomTimeRangeSelector: boolean; job: Job | DataFrameAnalyticsConfig; } @@ -78,10 +81,12 @@ export const CustomUrlEditor: FC = ({ savedCustomUrls, dashboards, dataViewListItems, + showTimeRangeSelector, + showCustomTimeRangeSelector, job, }) => { const [queryEntityFieldNames, setQueryEntityFieldNames] = useState([]); - const isAnomalyJob = useMemo(() => isAnomalyDetectionJob(job), [job]); + const [hasTimefield, setHasTimefield] = useState(false); const { services: { @@ -101,6 +106,9 @@ export const CustomUrlEditor: FC = ({ } catch (e) { dataViewToUse = undefined; } + if (dataViewToUse && dataViewToUse.timeFieldName) { + setHasTimefield(true); + } const dropDownOptions = await getDropDownOptions(isFirst.current, job, dataViewToUse); setQueryEntityFieldNames(dropDownOptions); @@ -132,6 +140,13 @@ export const CustomUrlEditor: FC = ({ }); }; + const onCustomTimeRangeChange = (timeRange?: { start: Moment; end: Moment }) => { + setEditCustomUrl({ + ...customUrl, + customTimeRange: timeRange, + }); + }; + const onDashboardChange = (e: ChangeEvent) => { const kibanaSettings = customUrl.kibanaSettings; setEditCustomUrl({ @@ -345,58 +360,66 @@ export const CustomUrlEditor: FC = ({ /> )} + {type === URL_TYPE.KIBANA_DASHBOARD || + (type === URL_TYPE.KIBANA_DISCOVER && showCustomTimeRangeSelector && hasTimefield) ? ( + + ) : null} - {(type === URL_TYPE.KIBANA_DASHBOARD || type === URL_TYPE.KIBANA_DISCOVER) && isAnomalyJob && ( - <> - - - - - } - className="url-time-range" - display="rowCompressed" - > - - - - {timeRange.type === TIME_RANGE_TYPE.INTERVAL && ( - + {(type === URL_TYPE.KIBANA_DASHBOARD || type === URL_TYPE.KIBANA_DISCOVER) && + showTimeRangeSelector && ( + <> + + + } className="url-time-range" - error={invalidIntervalError} - isInvalid={isInvalidTimeRange} display="rowCompressed" > - - )} - - - )} + {timeRange.type === TIME_RANGE_TYPE.INTERVAL && ( + + + } + className="url-time-range" + error={invalidIntervalError} + isInvalid={isInvalidTimeRange} + display="rowCompressed" + > + + + + )} + + + )} {type === URL_TYPE.OTHER && ( { // Get the complete list of attributes for the selected dashboard (query, filters). const { dashboardId, queryFieldNames } = settings.kibanaSettings ?? {}; @@ -253,11 +269,13 @@ async function buildDashboardUrlFromSettings(settings: CustomUrlSettings): Promi const dashboard = getDashboard(); + const { from, to } = getUrlRangeFromSettings(settings); + const location = await dashboard?.locator?.getLocation({ dashboardId, timeRange: { - from: '$earliest$', - to: '$latest$', + from, + to, mode: 'absolute', }, filters, @@ -299,10 +317,12 @@ function buildDiscoverUrlFromSettings(settings: CustomUrlSettings) { // Add time settings to the global state URL parameter with $earliest$ and // $latest$ tokens which get substituted for times around the time of the // anomaly on which the URL will be run against. + const { from, to } = getUrlRangeFromSettings(settings); + const _g = rison.encode({ time: { - from: '$earliest$', - to: '$latest$', + from, + to, mode: 'absolute', }, }); diff --git a/x-pack/plugins/ml/public/application/components/custom_urls/custom_urls.tsx b/x-pack/plugins/ml/public/application/components/custom_urls/custom_urls.tsx index 9d3db04fa40de..4f9ad5245cf91 100644 --- a/x-pack/plugins/ml/public/application/components/custom_urls/custom_urls.tsx +++ b/x-pack/plugins/ml/public/application/components/custom_urls/custom_urls.tsx @@ -42,6 +42,8 @@ import { import { openCustomUrlWindow } from '../../util/custom_url_utils'; import { UrlConfig } from '../../../../common/types/custom_urls'; import type { CustomUrlsWrapperProps } from './custom_urls_wrapper'; +import { isAnomalyDetectionJob } from '../../../../common/types/anomaly_detection_jobs'; +import { isDataFrameAnalyticsConfigs } from '../../../../common/types/data_frame_analytics'; const MAX_NUMBER_DASHBOARDS = 1000; @@ -206,6 +208,8 @@ class CustomUrlsUI extends Component { const editMode = this.props.editMode ?? 'inline'; const editor = ( Date: Tue, 25 Apr 2023 01:12:32 +0200 Subject: [PATCH 14/52] [serverless-docker] Allow using SERVERLESS env var (#155670) --- .../docker_generator/resources/base/bin/kibana-docker | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker index b08d26b8c657d..3123aa7f391df 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker +++ b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker @@ -414,6 +414,7 @@ kibana_vars=( xpack.task_manager.event_loop_delay.monitor xpack.task_manager.event_loop_delay.warn_threshold xpack.uptime.index + serverless ) longopts='' From 675ed0eee2eb582321bfb9b379783c973a490bd2 Mon Sep 17 00:00:00 2001 From: Nikita Indik Date: Tue, 25 Apr 2023 01:22:56 +0200 Subject: [PATCH 15/52] [Security Solution] Add active maintenance window callout to the Rules Management page (#155386) **Addresses:** https://github.com/elastic/kibana/issues/155099 **Documentation issue:** https://github.com/elastic/security-docs/issues/3181 ## Summary Adds a Maintenance Window callout to the Rules Management page. This callout is only displayed when a maintenance window is running. Screenshot 2023-04-21 at 13 24 11 ### Checklist - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [x] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) issue created: https://github.com/elastic/security-docs/issues/3181 - [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] 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) ### For maintainers - [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) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Georgii Gorbachev --- x-pack/plugins/alerting/common/index.ts | 6 + .../alerting/common/maintenance_window.ts | 5 + .../active_maintenance_windows.ts | 7 +- .../archive_maintenance_window.ts | 7 +- .../create_maintenance_window.ts | 14 +- .../delete_maintenance_window.ts | 7 +- .../find_maintenance_windows.ts | 7 +- .../finish_maintenance_window.ts | 7 +- .../get_maintenance_window.ts | 7 +- .../update_maintenance_window.ts | 7 +- .../detection_rules/maintenance_window.cy.ts | 58 ++++++++ .../security_solution/cypress/tsconfig.json | 3 +- .../maintenance_window_callout/api.ts | 18 +++ .../maintenance_window_callout.test.tsx | 136 ++++++++++++++++++ .../maintenance_window_callout.tsx | 27 ++++ .../translations.ts | 36 +++++ .../use_fetch_active_maintenance_windows.ts | 27 ++++ .../pages/rule_management/index.tsx | 3 + 18 files changed, 359 insertions(+), 23 deletions(-) create mode 100644 x-pack/plugins/security_solution/cypress/e2e/detection_rules/maintenance_window.cy.ts create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/maintenance_window_callout/api.ts create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/maintenance_window_callout/maintenance_window_callout.test.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/maintenance_window_callout/maintenance_window_callout.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/maintenance_window_callout/translations.ts create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/maintenance_window_callout/use_fetch_active_maintenance_windows.ts diff --git a/x-pack/plugins/alerting/common/index.ts b/x-pack/plugins/alerting/common/index.ts index 8b4c04d15cfc4..1fa0806effdef 100644 --- a/x-pack/plugins/alerting/common/index.ts +++ b/x-pack/plugins/alerting/common/index.ts @@ -58,6 +58,12 @@ export const LEGACY_BASE_ALERT_API_PATH = '/api/alerts'; export const BASE_ALERTING_API_PATH = '/api/alerting'; export const INTERNAL_BASE_ALERTING_API_PATH = '/internal/alerting'; export const INTERNAL_ALERTING_API_FIND_RULES_PATH = `${INTERNAL_BASE_ALERTING_API_PATH}/rules/_find`; + +export const INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH = + `${INTERNAL_BASE_ALERTING_API_PATH}/rules/maintenance_window` as const; +export const INTERNAL_ALERTING_API_GET_ACTIVE_MAINTENANCE_WINDOWS_PATH = + `${INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH}/_active` as const; + export const ALERTS_FEATURE_ID = 'alerts'; export const MONITORING_HISTORY_LIMIT = 200; export const ENABLE_MAINTENANCE_WINDOWS = false; diff --git a/x-pack/plugins/alerting/common/maintenance_window.ts b/x-pack/plugins/alerting/common/maintenance_window.ts index 0392d0cdb3667..e41140f8fc918 100644 --- a/x-pack/plugins/alerting/common/maintenance_window.ts +++ b/x-pack/plugins/alerting/common/maintenance_window.ts @@ -46,6 +46,11 @@ export type MaintenanceWindow = MaintenanceWindowSOAttributes & { id: string; }; +export type MaintenanceWindowCreateBody = Omit< + MaintenanceWindowSOProperties, + 'events' | 'expirationDate' | 'enabled' | 'archived' +>; + export interface MaintenanceWindowClientContext { getModificationMetadata: () => Promise; savedObjectsClient: SavedObjectsClientContract; diff --git a/x-pack/plugins/alerting/server/routes/maintenance_window/active_maintenance_windows.ts b/x-pack/plugins/alerting/server/routes/maintenance_window/active_maintenance_windows.ts index 706630edfbd4a..8bd3f7d3e0b49 100644 --- a/x-pack/plugins/alerting/server/routes/maintenance_window/active_maintenance_windows.ts +++ b/x-pack/plugins/alerting/server/routes/maintenance_window/active_maintenance_windows.ts @@ -8,7 +8,10 @@ import { IRouter } from '@kbn/core/server'; import { ILicenseState } from '../../lib'; import { verifyAccessAndContext, rewriteMaintenanceWindowRes } from '../lib'; -import { AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH } from '../../types'; +import { + AlertingRequestHandlerContext, + INTERNAL_ALERTING_API_GET_ACTIVE_MAINTENANCE_WINDOWS_PATH, +} from '../../types'; import { MAINTENANCE_WINDOW_API_PRIVILEGES } from '../../../common'; export const activeMaintenanceWindowsRoute = ( @@ -17,7 +20,7 @@ export const activeMaintenanceWindowsRoute = ( ) => { router.get( { - path: `${INTERNAL_BASE_ALERTING_API_PATH}/rules/maintenance_window/_active`, + path: INTERNAL_ALERTING_API_GET_ACTIVE_MAINTENANCE_WINDOWS_PATH, validate: {}, options: { tags: [`access:${MAINTENANCE_WINDOW_API_PRIVILEGES.READ_MAINTENANCE_WINDOW}`], diff --git a/x-pack/plugins/alerting/server/routes/maintenance_window/archive_maintenance_window.ts b/x-pack/plugins/alerting/server/routes/maintenance_window/archive_maintenance_window.ts index 123f374f79b05..e46bc07463e2f 100644 --- a/x-pack/plugins/alerting/server/routes/maintenance_window/archive_maintenance_window.ts +++ b/x-pack/plugins/alerting/server/routes/maintenance_window/archive_maintenance_window.ts @@ -9,7 +9,10 @@ import { IRouter } from '@kbn/core/server'; import { schema } from '@kbn/config-schema'; import { ILicenseState } from '../../lib'; import { verifyAccessAndContext, rewritePartialMaintenanceBodyRes } from '../lib'; -import { AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH } from '../../types'; +import { + AlertingRequestHandlerContext, + INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH, +} from '../../types'; import { MAINTENANCE_WINDOW_API_PRIVILEGES } from '../../../common'; const paramSchema = schema.object({ @@ -26,7 +29,7 @@ export const archiveMaintenanceWindowRoute = ( ) => { router.post( { - path: `${INTERNAL_BASE_ALERTING_API_PATH}/rules/maintenance_window/{id}/_archive`, + path: `${INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH}/{id}/_archive`, validate: { params: paramSchema, body: bodySchema, diff --git a/x-pack/plugins/alerting/server/routes/maintenance_window/create_maintenance_window.ts b/x-pack/plugins/alerting/server/routes/maintenance_window/create_maintenance_window.ts index a74147d15890c..d26f8494e1061 100644 --- a/x-pack/plugins/alerting/server/routes/maintenance_window/create_maintenance_window.ts +++ b/x-pack/plugins/alerting/server/routes/maintenance_window/create_maintenance_window.ts @@ -14,8 +14,11 @@ import { RewriteRequestCase, rewriteMaintenanceWindowRes, } from '../lib'; -import { AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH } from '../../types'; -import { MaintenanceWindowSOProperties, MAINTENANCE_WINDOW_API_PRIVILEGES } from '../../../common'; +import { + AlertingRequestHandlerContext, + INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH, +} from '../../types'; +import { MaintenanceWindowCreateBody, MAINTENANCE_WINDOW_API_PRIVILEGES } from '../../../common'; const bodySchema = schema.object({ title: schema.string(), @@ -23,11 +26,6 @@ const bodySchema = schema.object({ r_rule: rRuleSchema, }); -type MaintenanceWindowCreateBody = Omit< - MaintenanceWindowSOProperties, - 'events' | 'expirationDate' | 'enabled' | 'archived' ->; - export const rewriteQueryReq: RewriteRequestCase = ({ r_rule: rRule, ...rest @@ -42,7 +40,7 @@ export const createMaintenanceWindowRoute = ( ) => { router.post( { - path: `${INTERNAL_BASE_ALERTING_API_PATH}/rules/maintenance_window`, + path: INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH, validate: { body: bodySchema, }, diff --git a/x-pack/plugins/alerting/server/routes/maintenance_window/delete_maintenance_window.ts b/x-pack/plugins/alerting/server/routes/maintenance_window/delete_maintenance_window.ts index 2415dbe74b53d..c9ea00ef170de 100644 --- a/x-pack/plugins/alerting/server/routes/maintenance_window/delete_maintenance_window.ts +++ b/x-pack/plugins/alerting/server/routes/maintenance_window/delete_maintenance_window.ts @@ -9,7 +9,10 @@ import { IRouter } from '@kbn/core/server'; import { schema } from '@kbn/config-schema'; import { ILicenseState } from '../../lib'; import { verifyAccessAndContext } from '../lib'; -import { AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH } from '../../types'; +import { + AlertingRequestHandlerContext, + INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH, +} from '../../types'; import { MAINTENANCE_WINDOW_API_PRIVILEGES } from '../../../common'; const paramSchema = schema.object({ @@ -22,7 +25,7 @@ export const deleteMaintenanceWindowRoute = ( ) => { router.delete( { - path: `${INTERNAL_BASE_ALERTING_API_PATH}/rules/maintenance_window/{id}`, + path: `${INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH}/{id}`, validate: { params: paramSchema, }, diff --git a/x-pack/plugins/alerting/server/routes/maintenance_window/find_maintenance_windows.ts b/x-pack/plugins/alerting/server/routes/maintenance_window/find_maintenance_windows.ts index b581a011630a9..e9262b3e51079 100644 --- a/x-pack/plugins/alerting/server/routes/maintenance_window/find_maintenance_windows.ts +++ b/x-pack/plugins/alerting/server/routes/maintenance_window/find_maintenance_windows.ts @@ -8,7 +8,10 @@ import { IRouter } from '@kbn/core/server'; import { ILicenseState } from '../../lib'; import { verifyAccessAndContext, rewriteMaintenanceWindowRes } from '../lib'; -import { AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH } from '../../types'; +import { + AlertingRequestHandlerContext, + INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH, +} from '../../types'; import { MAINTENANCE_WINDOW_API_PRIVILEGES } from '../../../common'; export const findMaintenanceWindowsRoute = ( @@ -17,7 +20,7 @@ export const findMaintenanceWindowsRoute = ( ) => { router.get( { - path: `${INTERNAL_BASE_ALERTING_API_PATH}/rules/maintenance_window/_find`, + path: `${INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH}/_find`, validate: {}, options: { tags: [`access:${MAINTENANCE_WINDOW_API_PRIVILEGES.READ_MAINTENANCE_WINDOW}`], diff --git a/x-pack/plugins/alerting/server/routes/maintenance_window/finish_maintenance_window.ts b/x-pack/plugins/alerting/server/routes/maintenance_window/finish_maintenance_window.ts index 2cd5ff9ba0994..0cb663043d57a 100644 --- a/x-pack/plugins/alerting/server/routes/maintenance_window/finish_maintenance_window.ts +++ b/x-pack/plugins/alerting/server/routes/maintenance_window/finish_maintenance_window.ts @@ -9,7 +9,10 @@ import { IRouter } from '@kbn/core/server'; import { schema } from '@kbn/config-schema'; import { ILicenseState } from '../../lib'; import { verifyAccessAndContext, rewritePartialMaintenanceBodyRes } from '../lib'; -import { AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH } from '../../types'; +import { + AlertingRequestHandlerContext, + INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH, +} from '../../types'; import { MAINTENANCE_WINDOW_API_PRIVILEGES } from '../../../common'; const paramSchema = schema.object({ @@ -22,7 +25,7 @@ export const finishMaintenanceWindowRoute = ( ) => { router.post( { - path: `${INTERNAL_BASE_ALERTING_API_PATH}/rules/maintenance_window/{id}/_finish`, + path: `${INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH}/{id}/_finish`, validate: { params: paramSchema, }, diff --git a/x-pack/plugins/alerting/server/routes/maintenance_window/get_maintenance_window.ts b/x-pack/plugins/alerting/server/routes/maintenance_window/get_maintenance_window.ts index dc01beeef148a..b92281373817d 100644 --- a/x-pack/plugins/alerting/server/routes/maintenance_window/get_maintenance_window.ts +++ b/x-pack/plugins/alerting/server/routes/maintenance_window/get_maintenance_window.ts @@ -9,7 +9,10 @@ import { IRouter } from '@kbn/core/server'; import { schema } from '@kbn/config-schema'; import { ILicenseState } from '../../lib'; import { verifyAccessAndContext, rewriteMaintenanceWindowRes } from '../lib'; -import { AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH } from '../../types'; +import { + AlertingRequestHandlerContext, + INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH, +} from '../../types'; import { MAINTENANCE_WINDOW_API_PRIVILEGES } from '../../../common'; const paramSchema = schema.object({ @@ -22,7 +25,7 @@ export const getMaintenanceWindowRoute = ( ) => { router.get( { - path: `${INTERNAL_BASE_ALERTING_API_PATH}/rules/maintenance_window/{id}`, + path: `${INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH}/{id}`, validate: { params: paramSchema, }, diff --git a/x-pack/plugins/alerting/server/routes/maintenance_window/update_maintenance_window.ts b/x-pack/plugins/alerting/server/routes/maintenance_window/update_maintenance_window.ts index 7778b4d621359..5e63624587152 100644 --- a/x-pack/plugins/alerting/server/routes/maintenance_window/update_maintenance_window.ts +++ b/x-pack/plugins/alerting/server/routes/maintenance_window/update_maintenance_window.ts @@ -14,7 +14,10 @@ import { RewriteRequestCase, rewritePartialMaintenanceBodyRes, } from '../lib'; -import { AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH } from '../../types'; +import { + AlertingRequestHandlerContext, + INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH, +} from '../../types'; import { MaintenanceWindowSOProperties, MAINTENANCE_WINDOW_API_PRIVILEGES } from '../../../common'; const paramSchema = schema.object({ @@ -49,7 +52,7 @@ export const updateMaintenanceWindowRoute = ( ) => { router.post( { - path: `${INTERNAL_BASE_ALERTING_API_PATH}/rules/maintenance_window/{id}`, + path: `${INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH}/{id}`, validate: { body: bodySchema, params: paramSchema, diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/maintenance_window.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/maintenance_window.cy.ts new file mode 100644 index 0000000000000..af534d325ebca --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/maintenance_window.cy.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 { INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH } from '@kbn/alerting-plugin/common'; +import type { MaintenanceWindowCreateBody } from '@kbn/alerting-plugin/common'; +import type { AsApiContract } from '@kbn/alerting-plugin/server/routes/lib'; +import { cleanKibana } from '../../tasks/common'; +import { login, visit } from '../../tasks/login'; +import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../urls/navigation'; + +describe('Maintenance window callout on Rule Management page', () => { + let maintenanceWindowId = ''; + + before(() => { + cleanKibana(); + login(); + + const body: AsApiContract = { + title: 'My maintenance window', + duration: 60000, // 1 minute + r_rule: { + dtstart: new Date().toISOString(), + tzid: 'Europe/Amsterdam', + freq: 0, + count: 1, + }, + }; + + // Create a test maintenance window + cy.request({ + method: 'POST', + url: INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH, + headers: { 'kbn-xsrf': 'cypress-creds' }, + body, + }).then((response) => { + maintenanceWindowId = response.body.id; + }); + }); + + after(() => { + // Delete a test maintenance window + cy.request({ + method: 'DELETE', + url: `${INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH}/${maintenanceWindowId}`, + headers: { 'kbn-xsrf': 'cypress-creds' }, + }); + }); + + it('Displays the callout when there are running maintenance windows', () => { + visit(DETECTIONS_RULE_MANAGEMENT_URL); + + cy.contains('A maintenance window is currently running'); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/tsconfig.json b/x-pack/plugins/security_solution/cypress/tsconfig.json index 4af63c6d1b406..4f7ce1b811f70 100644 --- a/x-pack/plugins/security_solution/cypress/tsconfig.json +++ b/x-pack/plugins/security_solution/cypress/tsconfig.json @@ -28,6 +28,7 @@ "force": true }, "@kbn/rison", - "@kbn/datemath" + "@kbn/datemath", + "@kbn/alerting-plugin" ] } diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/maintenance_window_callout/api.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/maintenance_window_callout/api.ts new file mode 100644 index 0000000000000..9d3c28429df5b --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/maintenance_window_callout/api.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 type { MaintenanceWindow } from '@kbn/alerting-plugin/common/maintenance_window'; +import { INTERNAL_ALERTING_API_GET_ACTIVE_MAINTENANCE_WINDOWS_PATH } from '@kbn/alerting-plugin/common'; +import { KibanaServices } from '../../../../common/lib/kibana'; + +export const fetchActiveMaintenanceWindows = async ( + signal?: AbortSignal +): Promise => + KibanaServices.get().http.fetch(INTERNAL_ALERTING_API_GET_ACTIVE_MAINTENANCE_WINDOWS_PATH, { + method: 'GET', + signal, + }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/maintenance_window_callout/maintenance_window_callout.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/maintenance_window_callout/maintenance_window_callout.test.tsx new file mode 100644 index 0000000000000..20a8e0d1f2f94 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/maintenance_window_callout/maintenance_window_callout.test.tsx @@ -0,0 +1,136 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { render, waitFor, cleanup } from '@testing-library/react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { MaintenanceWindowStatus } from '@kbn/alerting-plugin/common'; +import type { MaintenanceWindow } from '@kbn/alerting-plugin/common'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; +import { useAppToastsMock } from '../../../../common/hooks/use_app_toasts.mock'; +import { MaintenanceWindowCallout } from './maintenance_window_callout'; +import { TestProviders } from '../../../../common/mock'; +import { fetchActiveMaintenanceWindows } from './api'; + +jest.mock('../../../../common/hooks/use_app_toasts'); + +jest.mock('./api', () => ({ + fetchActiveMaintenanceWindows: jest.fn(() => Promise.resolve([])), +})); + +const RUNNING_MAINTENANCE_WINDOW_1: Partial = { + title: 'Maintenance window 1', + id: '63057284-ac31-42ba-fe22-adfe9732e5ae', + status: MaintenanceWindowStatus.Running, + events: [{ gte: '2023-04-20T16:27:30.753Z', lte: '2023-04-20T16:57:30.753Z' }], +}; + +const RUNNING_MAINTENANCE_WINDOW_2: Partial = { + title: 'Maintenance window 2', + id: '45894340-df98-11ed-ac81-bfcb4982b4fd', + status: MaintenanceWindowStatus.Running, + events: [{ gte: '2023-04-20T16:47:42.871Z', lte: '2023-04-20T17:11:32.192Z' }], +}; + +const UPCOMING_MAINTENANCE_WINDOW: Partial = { + title: 'Upcoming maintenance window', + id: '5eafe070-e030-11ed-ac81-bfcb4982b4fd', + status: MaintenanceWindowStatus.Upcoming, + events: [ + { gte: '2023-04-21T10:36:14.028Z', lte: '2023-04-21T10:37:00.000Z' }, + { gte: '2023-04-28T10:36:14.028Z', lte: '2023-04-28T10:37:00.000Z' }, + ], +}; + +describe('MaintenanceWindowCallout', () => { + let appToastsMock: jest.Mocked>; + + beforeEach(() => { + jest.resetAllMocks(); + + appToastsMock = useAppToastsMock.create(); + (useAppToasts as jest.Mock).mockReturnValue(appToastsMock); + }); + + afterEach(() => { + cleanup(); + jest.restoreAllMocks(); + }); + + it('should be visible if currently there is at least one "running" maintenance window', async () => { + (fetchActiveMaintenanceWindows as jest.Mock).mockResolvedValue([RUNNING_MAINTENANCE_WINDOW_1]); + + const { findByText } = render(, { wrapper: TestProviders }); + + expect(await findByText('A maintenance window is currently running')).toBeInTheDocument(); + }); + + it('should be visible if currently there are multiple "running" maintenance windows', async () => { + (fetchActiveMaintenanceWindows as jest.Mock).mockResolvedValue([ + RUNNING_MAINTENANCE_WINDOW_1, + RUNNING_MAINTENANCE_WINDOW_2, + ]); + + const { findAllByText } = render(, { wrapper: TestProviders }); + + expect(await findAllByText('A maintenance window is currently running')).toHaveLength(1); + }); + + it('should NOT be visible if currently there are no active (running or upcoming) maintenance windows', async () => { + (fetchActiveMaintenanceWindows as jest.Mock).mockResolvedValue([]); + + const { container } = render(, { wrapper: TestProviders }); + + expect(container).toBeEmptyDOMElement(); + }); + + it('should NOT be visible if currently there are no "running" maintenance windows', async () => { + (fetchActiveMaintenanceWindows as jest.Mock).mockResolvedValue([UPCOMING_MAINTENANCE_WINDOW]); + + const { container } = render(, { wrapper: TestProviders }); + + expect(container).toBeEmptyDOMElement(); + }); + + it('should see an error toast if there was an error while fetching maintenance windows', async () => { + const createReactQueryWrapper = () => { + const queryClient = new QueryClient({ + defaultOptions: { + queries: { + // Turn retries off, otherwise we won't be able to test errors + retry: false, + }, + }, + logger: { + // Turn network error logging off, so we don't log the failed request to the console + error: () => {}, + // eslint-disable-next-line no-console + log: console.log, + // eslint-disable-next-line no-console + warn: console.warn, + }, + }); + const wrapper: React.FC = ({ children }) => ( + {children} + ); + return wrapper; + }; + + const mockError = new Error('Network error'); + (fetchActiveMaintenanceWindows as jest.Mock).mockRejectedValue(mockError); + + render(, { wrapper: createReactQueryWrapper() }); + + await waitFor(() => { + expect(appToastsMock.addError).toHaveBeenCalledTimes(1); + expect(appToastsMock.addError).toHaveBeenCalledWith(mockError, { + title: 'Failed to check if any maintenance window is currently running', + toastMessage: "Notification actions won't run while a maintenance window is running.", + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/maintenance_window_callout/maintenance_window_callout.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/maintenance_window_callout/maintenance_window_callout.tsx new file mode 100644 index 0000000000000..878347dc37c98 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/maintenance_window_callout/maintenance_window_callout.tsx @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiCallOut } from '@elastic/eui'; +import { MaintenanceWindowStatus } from '@kbn/alerting-plugin/common'; +import { useFetchActiveMaintenanceWindows } from './use_fetch_active_maintenance_windows'; +import * as i18n from './translations'; + +export function MaintenanceWindowCallout(): JSX.Element | null { + const { data } = useFetchActiveMaintenanceWindows(); + const activeMaintenanceWindows = data || []; + + if (activeMaintenanceWindows.some(({ status }) => status === MaintenanceWindowStatus.Running)) { + return ( + + {i18n.MAINTENANCE_WINDOW_RUNNING_DESCRIPTION} + + ); + } + + return null; +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/maintenance_window_callout/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/maintenance_window_callout/translations.ts new file mode 100644 index 0000000000000..21071aee618a1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/maintenance_window_callout/translations.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const MAINTENANCE_WINDOW_RUNNING = i18n.translate( + 'xpack.securitySolution.detectionEngine.ruleManagementUi.maintenanceWindowCallout.maintenanceWindowActive', + { + defaultMessage: 'A maintenance window is currently running', + } +); + +export const MAINTENANCE_WINDOW_RUNNING_DESCRIPTION = i18n.translate( + 'xpack.securitySolution.detectionEngine.ruleManagementUi.maintenanceWindowCallout.maintenanceWindowActiveDescription', + { + defaultMessage: "Notification actions won't run while a maintenance window is running.", + } +); + +export const FETCH_ERROR = i18n.translate( + 'xpack.securitySolution.detectionEngine.ruleManagementUi.maintenanceWindowCallout.fetchError', + { + defaultMessage: 'Failed to check if any maintenance window is currently running', + } +); + +export const FETCH_ERROR_DESCRIPTION = i18n.translate( + 'xpack.securitySolution.detectionEngine.ruleManagementUi.maintenanceWindowCallout.fetchErrorDescription', + { + defaultMessage: "Notification actions won't run while a maintenance window is running.", + } +); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/maintenance_window_callout/use_fetch_active_maintenance_windows.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/maintenance_window_callout/use_fetch_active_maintenance_windows.ts new file mode 100644 index 0000000000000..3603cafbda935 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/maintenance_window_callout/use_fetch_active_maintenance_windows.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useQuery } from '@tanstack/react-query'; +import { INTERNAL_ALERTING_API_GET_ACTIVE_MAINTENANCE_WINDOWS_PATH } from '@kbn/alerting-plugin/common'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; +import * as i18n from './translations'; +import { fetchActiveMaintenanceWindows } from './api'; + +export const useFetchActiveMaintenanceWindows = () => { + const { addError } = useAppToasts(); + + return useQuery( + ['GET', INTERNAL_ALERTING_API_GET_ACTIVE_MAINTENANCE_WINDOWS_PATH], + ({ signal }) => fetchActiveMaintenanceWindows(signal), + { + refetchInterval: 60000, + onError: (error) => { + addError(error, { title: i18n.FETCH_ERROR, toastMessage: i18n.FETCH_ERROR_DESCRIPTION }); + }, + } + ); +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/rule_management/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/rule_management/index.tsx index 95e0857874423..03231f2030eb2 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/rule_management/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/rule_management/index.tsx @@ -42,6 +42,8 @@ import { RulesTableContextProvider } from '../../components/rules_table/rules_ta import * as i18n from '../../../../detections/pages/detection_engine/rules/translations'; import { useInvalidateFetchRuleManagementFiltersQuery } from '../../../rule_management/api/hooks/use_fetch_rule_management_filters_query'; +import { MaintenanceWindowCallout } from '../../components/maintenance_window_callout/maintenance_window_callout'; + const RulesPageComponent: React.FC = () => { const [isImportModalVisible, showImportModal, hideImportModal] = useBoolState(); const [isValueListFlyoutVisible, showValueListFlyout, hideValueListFlyout] = useBoolState(); @@ -158,6 +160,7 @@ const RulesPageComponent: React.FC = () => { prePackagedTimelineStatus === 'timelineNeedUpdate') && ( )} + From 402085882773e450582e238bb04ede62807a5404 Mon Sep 17 00:00:00 2001 From: Catherine Liu Date: Mon, 24 Apr 2023 18:54:41 -0700 Subject: [PATCH 16/52] [Shared UX] Clean up toolbar styles (#155667) ## Summary Closes #155668. This PR fixes a few minor visual buttons with the `Toolbar` component styles. #### Before Screenshot 2023-04-24 at 2 41 16 PM #### After Screenshot 2023-04-24 at 2 39 09 PM Changes: - apply same 8px gap between all toolbar buttons - remove shadow from icon button group buttons causing the double border between buttons - fix border radius on button group to match other toolbar buttons ### Checklist Delete any items that are not applicable to this PR. - [ ] 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) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [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 - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] 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)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] 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)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### 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) --- .../__snapshots__/icon_button_group.test.tsx.snap | 4 ++-- .../buttons/icon_button_group/icon_button_group.styles.ts | 8 ++++++++ .../src/buttons/icon_button_group/icon_button_group.tsx | 1 + .../src/toolbar/__snapshots__/toolbar.test.tsx.snap | 2 +- packages/shared-ux/button_toolbar/src/toolbar/toolbar.tsx | 2 +- 5 files changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/shared-ux/button_toolbar/src/buttons/icon_button_group/__snapshots__/icon_button_group.test.tsx.snap b/packages/shared-ux/button_toolbar/src/buttons/icon_button_group/__snapshots__/icon_button_group.test.tsx.snap index 3780fa1bcddd6..c472f58ec3e2f 100644 --- a/packages/shared-ux/button_toolbar/src/buttons/icon_button_group/__snapshots__/icon_button_group.test.tsx.snap +++ b/packages/shared-ux/button_toolbar/src/buttons/icon_button_group/__snapshots__/icon_button_group.test.tsx.snap @@ -2,7 +2,7 @@ exports[` is rendered 1`] = `
is rendered 1`] = `
diff --git a/packages/shared-ux/button_toolbar/src/toolbar/toolbar.tsx b/packages/shared-ux/button_toolbar/src/toolbar/toolbar.tsx index eef0ce05eed6e..f5b5c2bce0cd0 100644 --- a/packages/shared-ux/button_toolbar/src/toolbar/toolbar.tsx +++ b/packages/shared-ux/button_toolbar/src/toolbar/toolbar.tsx @@ -59,7 +59,7 @@ export const Toolbar = ({ children }: Props) => { {primaryButton} - + {iconButtonGroup ? {iconButtonGroup} : null} {extra} From 273eec0f64dceab0ac2589bb31264d7533f6e942 Mon Sep 17 00:00:00 2001 From: Matthew Kime Date: Mon, 24 Apr 2023 20:58:22 -0500 Subject: [PATCH 17/52] [content management / maps] Create abstract types for saved object usage with content management api (#154985) ## Summary Abstract types for using Saved Objects with the content management api. This should significantly reduce the amount of code to use additional saved object types. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .github/CODEOWNERS | 1 + package.json | 1 + .../kbn-content-management-utils/README.md | 58 ++++ .../kbn-content-management-utils/index.ts | 9 + .../jest.config.js | 13 + .../kbn-content-management-utils/kibana.jsonc | 5 + .../kbn-content-management-utils/package.json | 6 + .../tsconfig.json | 22 ++ .../kbn-content-management-utils/types.ts | 283 ++++++++++++++++++ tsconfig.base.json | 2 + .../maps/common/content_management/index.ts | 4 +- .../common/content_management/v1/index.ts | 4 +- .../common/content_management/v1/types.ts | 104 ++----- .../public/content_management/maps_client.ts | 11 +- .../server/content_management/maps_storage.ts | 16 +- x-pack/plugins/maps/tsconfig.json | 1 + yarn.lock | 4 + 17 files changed, 452 insertions(+), 92 deletions(-) create mode 100644 packages/kbn-content-management-utils/README.md create mode 100644 packages/kbn-content-management-utils/index.ts create mode 100644 packages/kbn-content-management-utils/jest.config.js create mode 100644 packages/kbn-content-management-utils/kibana.jsonc create mode 100644 packages/kbn-content-management-utils/package.json create mode 100644 packages/kbn-content-management-utils/tsconfig.json create mode 100644 packages/kbn-content-management-utils/types.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 63cf2327516fa..2da78ed653a0d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -88,6 +88,7 @@ packages/content-management/content_editor @elastic/appex-sharedux examples/content_management_examples @elastic/appex-sharedux src/plugins/content_management @elastic/appex-sharedux packages/content-management/table_list @elastic/appex-sharedux +packages/kbn-content-management-utils @elastic/kibana-data-discovery examples/controls_example @elastic/kibana-presentation src/plugins/controls @elastic/kibana-presentation src/core @elastic/kibana-core diff --git a/package.json b/package.json index 56fdffa498b05..06bec180ca93b 100644 --- a/package.json +++ b/package.json @@ -187,6 +187,7 @@ "@kbn/content-management-examples-plugin": "link:examples/content_management_examples", "@kbn/content-management-plugin": "link:src/plugins/content_management", "@kbn/content-management-table-list": "link:packages/content-management/table_list", + "@kbn/content-management-utils": "link:packages/kbn-content-management-utils", "@kbn/controls-example-plugin": "link:examples/controls_example", "@kbn/controls-plugin": "link:src/plugins/controls", "@kbn/core": "link:src/core", diff --git a/packages/kbn-content-management-utils/README.md b/packages/kbn-content-management-utils/README.md new file mode 100644 index 0000000000000..32c28b9fc4807 --- /dev/null +++ b/packages/kbn-content-management-utils/README.md @@ -0,0 +1,58 @@ +# Content management utils + +Utilities to ease the implementation of the Content Management API with Saved Objects. + +```ts +import type { + ContentManagementCrudTypes, + CreateOptions, + SearchOptions, + UpdateOptions, +} from '@kbn/content-management-utils'; +import { MapContentType } from '../types'; + +export type MapCrudTypes = ContentManagementCrudTypes; + +/* eslint-disable-next-line @typescript-eslint/consistent-type-definitions */ +export type MapAttributes = { + title: string; + description?: string; + mapStateJSON?: string; + layerListJSON?: string; + uiStateJSON?: string; +}; + +export type MapItem = MapCrudTypes['Item']; +export type PartialMapItem = MapCrudTypes['PartialItem']; + +// ----------- GET -------------- + +export type MapGetIn = MapCrudTypes['GetIn']; +export type MapGetOut = MapCrudTypes['GetOut']; + +// ----------- CREATE -------------- + +export type MapCreateIn = MapCrudTypes['CreateIn']; +export type MapCreateOut = MapCrudTypes['CreateOut']; +export type MapCreateOptions = CreateOptions; + +// ----------- UPDATE -------------- + +export type MapUpdateIn = MapCrudTypes['UpdateIn']; +export type MapUpdateOut = MapCrudTypes['UpdateOut']; +export type MapUpdateOptions = UpdateOptions; + +// ----------- DELETE -------------- + +export type MapDeleteIn = MapCrudTypes['DeleteIn']; +export type MapDeleteOut = MapCrudTypes['DeleteOut']; + +// ----------- SEARCH -------------- + +export type MapSearchIn = MapCrudTypes['SearchIn']; +export type MapSearchOut = MapCrudTypes['SearchOut']; +export type MapSearchOptions = SearchOptions; + +``` + + diff --git a/packages/kbn-content-management-utils/index.ts b/packages/kbn-content-management-utils/index.ts new file mode 100644 index 0000000000000..12594660136d8 --- /dev/null +++ b/packages/kbn-content-management-utils/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 * from './types'; diff --git a/packages/kbn-content-management-utils/jest.config.js b/packages/kbn-content-management-utils/jest.config.js new file mode 100644 index 0000000000000..b1e7646521e26 --- /dev/null +++ b/packages/kbn-content-management-utils/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-content-management-utils'], +}; diff --git a/packages/kbn-content-management-utils/kibana.jsonc b/packages/kbn-content-management-utils/kibana.jsonc new file mode 100644 index 0000000000000..06779896a47c4 --- /dev/null +++ b/packages/kbn-content-management-utils/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/content-management-utils", + "owner": "@elastic/kibana-data-discovery" +} diff --git a/packages/kbn-content-management-utils/package.json b/packages/kbn-content-management-utils/package.json new file mode 100644 index 0000000000000..8b59a10c3d429 --- /dev/null +++ b/packages/kbn-content-management-utils/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/content-management-utils", + "private": true, + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0" +} \ No newline at end of file diff --git a/packages/kbn-content-management-utils/tsconfig.json b/packages/kbn-content-management-utils/tsconfig.json new file mode 100644 index 0000000000000..7de04c3c13451 --- /dev/null +++ b/packages/kbn-content-management-utils/tsconfig.json @@ -0,0 +1,22 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node", + "react" + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx", + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/content-management-plugin", + "@kbn/core-saved-objects-api-server", + ] +} diff --git a/packages/kbn-content-management-utils/types.ts b/packages/kbn-content-management-utils/types.ts new file mode 100644 index 0000000000000..1f15c69947f63 --- /dev/null +++ b/packages/kbn-content-management-utils/types.ts @@ -0,0 +1,283 @@ +/* + * Copyright 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 { + GetIn, + GetResult, + CreateIn, + CreateResult, + SearchIn, + SearchResult, + UpdateIn, + UpdateResult, + DeleteIn, + DeleteResult, +} from '@kbn/content-management-plugin/common'; + +import type { + SortOrder, + AggregationsAggregationContainer, + SortResults, +} from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; + +import { + MutatingOperationRefreshSetting, + SavedObjectsPitParams, + SavedObjectsFindOptionsReference, +} from '@kbn/core-saved-objects-api-server'; + +type KueryNode = any; + +export interface Reference { + type: string; + id: string; + name: string; +} + +/** Saved Object create options - Pick and Omit to customize */ +export interface SavedObjectCreateOptions { + /** (not recommended) Specify an id for the document */ + id?: string; + /** Overwrite existing documents (defaults to false) */ + overwrite?: boolean; + /** + * An opaque version number which changes on each successful write operation. + * Can be used in conjunction with `overwrite` for implementing optimistic concurrency control. + **/ + version?: string; + /** Array of referenced saved objects. */ + references?: Reference[]; + /** The Elasticsearch Refresh setting for this operation */ + refresh?: boolean; + /** + * Optional initial namespaces for the object to be created in. If this is defined, it will supersede the namespace ID that is in + * {@link SavedObjectsCreateOptions}. + * + * * For shareable object types (registered with `namespaceType: 'multiple'`): this option can be used to specify one or more spaces, + * including the "All spaces" identifier (`'*'`). + * * For isolated object types (registered with `namespaceType: 'single'` or `namespaceType: 'multiple-isolated'`): this option can only + * be used to specify a single space, and the "All spaces" identifier (`'*'`) is not allowed. + * * For global object types (registered with `namespaceType: 'agnostic'`): this option cannot be used. + */ + initialNamespaces?: string[]; +} + +/** Saved Object search options - Pick and Omit to customize */ +export interface SavedObjectSearchOptions { + /** the page of results to return */ + page?: number; + /** the number of objects per page */ + perPage?: number; + /** which field to sort by */ + sortField?: string; + /** sort order, ascending or descending */ + sortOrder?: SortOrder; + /** + * An array of fields to include in the results + * @example + * SavedObjects.find({type: 'dashboard', fields: ['attributes.name', 'attributes.location']}) + */ + fields?: string[]; + /** Search documents using the Elasticsearch Simple Query String syntax. See Elasticsearch Simple Query String `query` argument for more information */ + search?: string; + /** The fields to perform the parsed query against. See Elasticsearch Simple Query String `fields` argument for more information */ + searchFields?: string[]; + /** + * Use the sort values from the previous page to retrieve the next page of results. + */ + searchAfter?: SortResults; + /** + * The fields to perform the parsed query against. Unlike the `searchFields` argument, these are expected to be root fields and will not + * be modified. If used in conjunction with `searchFields`, both are concatenated together. + */ + rootSearchFields?: string[]; + /** + * Search for documents having a reference to the specified objects. + * Use `hasReferenceOperator` to specify the operator to use when searching for multiple references. + */ + hasReference?: SavedObjectsFindOptionsReference | SavedObjectsFindOptionsReference[]; + /** + * The operator to use when searching by multiple references using the `hasReference` option. Defaults to `OR` + */ + hasReferenceOperator?: 'AND' | 'OR'; + /** + * Search for documents *not* having a reference to the specified objects. + * Use `hasNoReferenceOperator` to specify the operator to use when searching for multiple references. + */ + hasNoReference?: SavedObjectsFindOptionsReference | SavedObjectsFindOptionsReference[]; + /** + * The operator to use when searching by multiple references using the `hasNoReference` option. Defaults to `OR` + */ + hasNoReferenceOperator?: 'AND' | 'OR'; + /** + * The search operator to use with the provided filter. Defaults to `OR` + */ + defaultSearchOperator?: 'AND' | 'OR'; + /** filter string for the search query */ + filter?: string | KueryNode; + /** + * A record of aggregations to perform. + * The API currently only supports a limited set of metrics and bucket aggregation types. + * Additional aggregation types can be contributed to Core. + * + * @example + * Aggregating on SO attribute field + * ```ts + * const aggs = { latest_version: { max: { field: 'dashboard.attributes.version' } } }; + * return client.find({ type: 'dashboard', aggs }) + * ``` + * + * @example + * Aggregating on SO root field + * ```ts + * const aggs = { latest_update: { max: { field: 'dashboard.updated_at' } } }; + * return client.find({ type: 'dashboard', aggs }) + * ``` + * + * @alpha + */ + aggs?: Record; + /** array of namespaces to search */ + namespaces?: string[]; + /** + * Search against a specific Point In Time (PIT) that you've opened with {@link SavedObjectsClient.openPointInTimeForType}. + */ + pit?: SavedObjectsPitParams; +} + +/** Saved Object update options - Pick and Omit to customize */ +export interface SavedObjectUpdateOptions { + /** Array of referenced saved objects. */ + references?: Reference[]; + version?: string; + /** The Elasticsearch Refresh setting for this operation */ + refresh?: MutatingOperationRefreshSetting; + /** If specified, will be used to perform an upsert if the object doesn't exist */ + upsert?: Attributes; + /** + * The Elasticsearch `retry_on_conflict` setting for this operation. + * Defaults to `0` when `version` is provided, `3` otherwise. + */ + retryOnConflict?: number; +} + +/** Return value for Saved Object get, T is item returned */ +export type GetResultSO = GetResult< + T, + { + outcome: 'exactMatch' | 'aliasMatch' | 'conflict'; + aliasTargetId?: string; + aliasPurpose?: 'savedObjectConversion' | 'savedObjectImport'; + } +>; + +/** + * Saved object with metadata + */ +export interface SOWithMetadata { + id: string; + type: string; + version?: string; + createdAt?: string; + updatedAt?: string; + error?: { + error: string; + message: string; + statusCode: number; + metadata?: Record; + }; + attributes: Attributes; + references: Reference[]; + namespaces?: string[]; + originId?: string; +} + +type PartialItem = Omit< + SOWithMetadata, + 'attributes' | 'references' +> & { + attributes: Partial; + references: Reference[] | undefined; +}; + +/** + * Types used by content management storage + * @argument ContentType - content management type. assumed to be the same as saved object type + * @argument Attributes - attributes of the saved object + */ +export interface ContentManagementCrudTypes< + ContentType extends string, + Attributes extends object, + CreateOptions extends object, + UpdateOptions extends object, + SearchOptions extends object +> { + /** + * Complete saved object + */ + Item: SOWithMetadata; + /** + * Partial saved object, used as output for update + */ + PartialItem: PartialItem; + /** + * Create options + */ + CreateOptions: CreateOptions; + /** + * Update options + */ + UpdateOptions: UpdateOptions; + /** + * Search options + */ + SearchOptions: SearchOptions; + /** + * Get item params + */ + GetIn: GetIn; + /** + * Get item result + */ + GetOut: GetResultSO>; + /** + * Create item params + */ + CreateIn: CreateIn; + /** + * Create item result + */ + CreateOut: CreateResult>; + + /** + * Search item params + */ + SearchIn: SearchIn; + /** + * Search item result + */ + SearchOut: SearchResult>; + + /** + * Update item params + */ + UpdateIn: UpdateIn; + /** + * Update item result + */ + UpdateOut: UpdateResult>; + + /** + * Delete item params + */ + DeleteIn: DeleteIn; + /** + * Delete item result + */ + DeleteOut: DeleteResult; +} diff --git a/tsconfig.base.json b/tsconfig.base.json index e96e5439e9c2c..929f3e791ad1e 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -170,6 +170,8 @@ "@kbn/content-management-plugin/*": ["src/plugins/content_management/*"], "@kbn/content-management-table-list": ["packages/content-management/table_list"], "@kbn/content-management-table-list/*": ["packages/content-management/table_list/*"], + "@kbn/content-management-utils": ["packages/kbn-content-management-utils"], + "@kbn/content-management-utils/*": ["packages/kbn-content-management-utils/*"], "@kbn/controls-example-plugin": ["examples/controls_example"], "@kbn/controls-example-plugin/*": ["examples/controls_example/*"], "@kbn/controls-plugin": ["src/plugins/controls"], diff --git a/x-pack/plugins/maps/common/content_management/index.ts b/x-pack/plugins/maps/common/content_management/index.ts index daafc194a37fc..f389e3fea36e7 100644 --- a/x-pack/plugins/maps/common/content_management/index.ts +++ b/x-pack/plugins/maps/common/content_management/index.ts @@ -17,10 +17,10 @@ export type { MapGetOut, MapCreateIn, MapCreateOut, - CreateOptions, + MapCreateOptions, MapUpdateIn, MapUpdateOut, - UpdateOptions, + MapUpdateOptions, MapDeleteIn, MapDeleteOut, MapSearchIn, diff --git a/x-pack/plugins/maps/common/content_management/v1/index.ts b/x-pack/plugins/maps/common/content_management/v1/index.ts index 272e0e1eb5f2e..535a15e5a23a6 100644 --- a/x-pack/plugins/maps/common/content_management/v1/index.ts +++ b/x-pack/plugins/maps/common/content_management/v1/index.ts @@ -13,10 +13,10 @@ export type { MapGetOut, MapCreateIn, MapCreateOut, - CreateOptions, + MapCreateOptions, MapUpdateIn, MapUpdateOut, - UpdateOptions, + MapUpdateOptions, MapDeleteIn, MapDeleteOut, MapSearchIn, diff --git a/x-pack/plugins/maps/common/content_management/v1/types.ts b/x-pack/plugins/maps/common/content_management/v1/types.ts index d5a7351226c23..c19713e641659 100644 --- a/x-pack/plugins/maps/common/content_management/v1/types.ts +++ b/x-pack/plugins/maps/common/content_management/v1/types.ts @@ -6,24 +6,22 @@ */ import type { - GetIn, - GetResult, - CreateIn, - CreateResult, - SearchIn, - SearchResult, - UpdateIn, - UpdateResult, - DeleteIn, - DeleteResult, -} from '@kbn/content-management-plugin/common'; + ContentManagementCrudTypes, + SavedObjectCreateOptions, + SavedObjectUpdateOptions, +} from '@kbn/content-management-utils'; import { MapContentType } from '../types'; -interface Reference { - type: string; - id: string; - name: string; -} +export type MapCrudTypes = ContentManagementCrudTypes< + MapContentType, + MapAttributes, + Pick, + Pick, + { + /** Flag to indicate to only search the text on the "title" field */ + onlyTitle?: boolean; + } +>; /* eslint-disable-next-line @typescript-eslint/consistent-type-definitions */ export type MapAttributes = { @@ -34,77 +32,33 @@ export type MapAttributes = { uiStateJSON?: string; }; -export interface MapItem { - id: string; - type: string; - version?: string; - createdAt?: string; - updatedAt?: string; - error?: { - error: string; - message: string; - statusCode: number; - metadata?: Record; - }; - attributes: MapAttributes; - references: Reference[]; - namespaces?: string[]; - originId?: string; -} - -export type PartialMapItem = Omit & { - attributes: Partial; - references: Reference[] | undefined; -}; +export type MapItem = MapCrudTypes['Item']; +export type PartialMapItem = MapCrudTypes['PartialItem']; // ----------- GET -------------- -export type MapGetIn = GetIn; - -export type MapGetOut = GetResult< - MapItem, - { - outcome: 'exactMatch' | 'aliasMatch' | 'conflict'; - aliasTargetId?: string; - aliasPurpose?: 'savedObjectConversion' | 'savedObjectImport'; - } ->; +export type MapGetIn = MapCrudTypes['GetIn']; +export type MapGetOut = MapCrudTypes['GetOut']; // ----------- CREATE -------------- -export interface CreateOptions { - /** Array of referenced saved objects. */ - references?: Reference[]; -} - -export type MapCreateIn = CreateIn; - -export type MapCreateOut = CreateResult; +export type MapCreateIn = MapCrudTypes['CreateIn']; +export type MapCreateOut = MapCrudTypes['CreateOut']; +export type MapCreateOptions = MapCrudTypes['CreateOptions']; // ----------- UPDATE -------------- -export interface UpdateOptions { - /** Array of referenced saved objects. */ - references?: Reference[]; -} - -export type MapUpdateIn = UpdateIn; - -export type MapUpdateOut = UpdateResult; +export type MapUpdateIn = MapCrudTypes['UpdateIn']; +export type MapUpdateOut = MapCrudTypes['UpdateOut']; +export type MapUpdateOptions = MapCrudTypes['UpdateOptions']; // ----------- DELETE -------------- -export type MapDeleteIn = DeleteIn; - -export type MapDeleteOut = DeleteResult; +export type MapDeleteIn = MapCrudTypes['DeleteIn']; +export type MapDeleteOut = MapCrudTypes['DeleteOut']; // ----------- SEARCH -------------- -export interface MapSearchOptions { - /** Flag to indicate to only search the text on the "title" field */ - onlyTitle?: boolean; -} - -export type MapSearchIn = SearchIn; - -export type MapSearchOut = SearchResult; +export type MapSearchIn = MapCrudTypes['SearchIn']; +export type MapSearchOut = MapCrudTypes['SearchOut']; +export type MapSearchOptions = MapCrudTypes['SearchOptions']; diff --git a/x-pack/plugins/maps/public/content_management/maps_client.ts b/x-pack/plugins/maps/public/content_management/maps_client.ts index 932765899da22..065d44fdc0681 100644 --- a/x-pack/plugins/maps/public/content_management/maps_client.ts +++ b/x-pack/plugins/maps/public/content_management/maps_client.ts @@ -19,18 +19,19 @@ import type { MapSearchOut, MapSearchOptions, } from '../../common/content_management'; +import { CONTENT_ID as contentTypeId } from '../../common/content_management'; import { getContentManagement } from '../kibana_services'; const get = async (id: string) => { return getContentManagement().client.get({ - contentTypeId: 'map', + contentTypeId, id, }); }; const create = async ({ data, options }: Omit) => { const res = await getContentManagement().client.create({ - contentTypeId: 'map', + contentTypeId, data, options, }); @@ -39,7 +40,7 @@ const create = async ({ data, options }: Omit) => const update = async ({ id, data, options }: Omit) => { const res = await getContentManagement().client.update({ - contentTypeId: 'map', + contentTypeId, id, data, options, @@ -49,14 +50,14 @@ const update = async ({ id, data, options }: Omit) const deleteMap = async (id: string) => { await getContentManagement().client.delete({ - contentTypeId: 'map', + contentTypeId, id, }); }; const search = async (query: SearchQuery = {}, options?: MapSearchOptions) => { return getContentManagement().client.search({ - contentTypeId: 'map', + contentTypeId, query, options, }); diff --git a/x-pack/plugins/maps/server/content_management/maps_storage.ts b/x-pack/plugins/maps/server/content_management/maps_storage.ts index 5a031c675a32c..3063980a561f3 100644 --- a/x-pack/plugins/maps/server/content_management/maps_storage.ts +++ b/x-pack/plugins/maps/server/content_management/maps_storage.ts @@ -28,10 +28,10 @@ import type { MapGetOut, MapCreateIn, MapCreateOut, - CreateOptions, + MapCreateOptions, MapUpdateIn, MapUpdateOut, - UpdateOptions, + MapUpdateOptions, MapDeleteOut, MapSearchOptions, MapSearchOut, @@ -140,7 +140,7 @@ export class MapsStorage async create( ctx: StorageContext, data: MapCreateIn['data'], - options: CreateOptions + options: MapCreateOptions ): Promise { const { utils: { getTransforms }, @@ -157,8 +157,8 @@ export class MapsStorage } const { value: optionsToLatest, error: optionsError } = transforms.create.in.options.up< - CreateOptions, - CreateOptions + MapCreateOptions, + MapCreateOptions >(options); if (optionsError) { throw Boom.badRequest(`Invalid options. ${optionsError.message}`); @@ -191,7 +191,7 @@ export class MapsStorage ctx: StorageContext, id: string, data: MapUpdateIn['data'], - options: UpdateOptions + options: MapUpdateOptions ): Promise { const { utils: { getTransforms }, @@ -208,8 +208,8 @@ export class MapsStorage } const { value: optionsToLatest, error: optionsError } = transforms.update.in.options.up< - CreateOptions, - CreateOptions + MapCreateOptions, + MapCreateOptions >(options); if (optionsError) { throw Boom.badRequest(`Invalid options. ${optionsError.message}`); diff --git a/x-pack/plugins/maps/tsconfig.json b/x-pack/plugins/maps/tsconfig.json index 9e501ba66559c..398139fe00d34 100644 --- a/x-pack/plugins/maps/tsconfig.json +++ b/x-pack/plugins/maps/tsconfig.json @@ -67,6 +67,7 @@ "@kbn/core-saved-objects-api-server", "@kbn/object-versioning", "@kbn/field-types", + "@kbn/content-management-utils", ], "exclude": [ "target/**/*", diff --git a/yarn.lock b/yarn.lock index d120721650856..bc57b5fc7d283 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3077,6 +3077,10 @@ version "0.0.0" uid "" +"@kbn/content-management-utils@link:packages/kbn-content-management-utils": + version "0.0.0" + uid "" + "@kbn/controls-example-plugin@link:examples/controls_example": version "0.0.0" uid "" From 67322fe2a7f8f6932f3b26a84000c4bf9c8cc1d9 Mon Sep 17 00:00:00 2001 From: Andrew Macri Date: Mon, 24 Apr 2023 20:27:58 -0600 Subject: [PATCH 18/52] [Security Solution] Data Quality dashboard storage metrics (#155581) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # [Security Solution] Data Quality dashboard storage metrics ![storage_metrics_animated](https://user-images.githubusercontent.com/4459398/233871314-6894b380-63ac-4622-b64f-965752a96019.gif) _Above: The new storage metrics treemap updates as indices are checked_ ![storage_metrics](https://user-images.githubusercontent.com/4459398/233880225-8242733a-4bd6-40b3-bffa-e283ce0d77cd.png) _Above: Storage metrics in the Data Quality dashboard_ ## Summary This PR introduces [storage metrics](https://github.com/elastic/security-team/issues/6047) to the _Data Quality_ dashboard - Multiple views are enhanced to display the size of indices - A new interactive treemap visualizes the relative sizes of indices - Markdown reports include the size of indices - The Data Quality dashboard `Beta` tag is removed - Inline action buttons replace the `Take action` popover - The Global stats panel remains visible when the `Select one or more ILM phases` help is displayed - Code coverage is improved throughout the dashboard ## Details ### Multiple views enhanced to display the size of indices The following views have been enhanced to display the `Size` of indices, per the screenshots below: - The pattern table's `Size` column displays the size of a single index ![04_size_column](https://user-images.githubusercontent.com/4459398/233870161-d86eadbd-9f01-4ed6-aa6f-98f6044a4f57.png) - The pattern table's `Size` tooltip ![05_size_column_tooltip](https://user-images.githubusercontent.com/4459398/233868732-08059ba9-5e4b-4f68-a152-eb4b41db6f96.png) - The pattern rollup's `Size` stat displays the total size of indices in a pattern ![06_pattern_rollups_size](https://user-images.githubusercontent.com/4459398/233868817-babc96eb-c0aa-4b7f-bb45-54e3039d06f2.png) - The pattern rollup's `Size` stat tooltip ![07_pattern_rollups_size_tooltip](https://user-images.githubusercontent.com/4459398/233868858-14a43aa2-324f-40bd-a185-1cb7ac15c81b.png) - The global stats rollup `Size` stat displays the total size of all the patterns ![08_global_stats_rollup_size](https://user-images.githubusercontent.com/4459398/233868900-e3cbc00b-3b5a-4756-8246-cb31a1b8bac8.png) - The global stats rollup `Size` stat tooltip ![09_global_stats_rollup_size_tooltip](https://user-images.githubusercontent.com/4459398/233868952-b9c27432-c8a4-4ad5-9dda-5e1aa903758c.png) ### New interactive treemap A new interactive treemap visualizes the relative sizes of indices: - The color of indices in the treemap and its legend update as the data is checked ![storage_metrics_animated](https://user-images.githubusercontent.com/4459398/233871314-6894b380-63ac-4622-b64f-965752a96019.gif) - Clicking on an index in the treemap or the legend expands (and scrolls to) the index ### Markdown reports include the `Size` of indices Markdown reports are enhanced to include the new `Size` statistic in: - Pattern markdown tables | Result | Index | Docs | Incompatible fields | ILM Phase | Size | |--------|-------|------|---------------------|-----------|------| | ❌ | auditbeat-7.14.2-2023.04.09-000001 | 48,077 (4.3%) | 12 | `hot` | 41.3MB | | ❌ | auditbeat-7.3.2-2023.04.09-000001 | 48,068 (4.3%) | 8 | `hot` | 31MB | | ❌ | auditbeat-7.11.2-2023.04.09-000001 | 48,064 (4.3%) | 12 | `hot` | 40.8MB | - Pattern rollup markdown tables | Incompatible fields | Indices checked | Indices | Size | Docs | |---------------------|-----------------|---------|------|------| | 164 | 26 | 26 | 899.3MB | 1,118,155 | - The global stats markdown table | Incompatible fields | Indices checked | Indices | Size | Docs | |---------------------|-----------------|---------|------|------| | 166 | 32 | 32 | 9.2GB | 20,779,245 | ### Data Quality dashboard `Beta` tag removed The Data Quality dashboard `Beta` tag is removed from the following views: - The `Dashboards` page **Before:** ![11_dashboards_before](https://user-images.githubusercontent.com/4459398/233869434-d4d2ed14-4e6f-4eab-bae6-a9c9b976e20f.png) **After:** ![12_dashboards_after](https://user-images.githubusercontent.com/4459398/233869088-9dc62d7d-44cb-46cb-8880-976a7b7e9c56.png) - Security Solution side navigation **Before:** ![13_side_navigation_before](https://user-images.githubusercontent.com/4459398/233869467-e7725285-1199-40e1-ac65-054bea8b02f6.png) **After:** ![14_side_navigation_after](https://user-images.githubusercontent.com/4459398/233869146-7b89cb47-3509-478e-8675-9f1653749b18.png) - The Data Quality dashboard page header **Before:** ![15_page_header_before](https://user-images.githubusercontent.com/4459398/233869404-0b04c2ec-3d2e-4ba8-9520-68013f80e43a.png) **After:** ![16_page_header_after](https://user-images.githubusercontent.com/4459398/233869219-b54ee61e-07b7-470d-a668-b4f5ed4327e6.png) ### Inline action buttons replace the `Take action` popover Inline `Add to new case` and `Copy to clipboard` action buttons replace the `Take action` popover, the previous home of these actions: **Before:** ![17_actions_before](https://user-images.githubusercontent.com/4459398/233869306-0182145f-affc-4ad1-b63f-72e43d34234c.png) **After:** ![18_actions_after](https://user-images.githubusercontent.com/4459398/233869345-754b7448-9d28-4253-9186-5b2389acf4ff.png) ### Global stats panel remains visible when the `Select one or more ILM phases` help is displayed The Global stats panel now remains visible when the `Select one or more ILM phases` help is displayed: **Before:** ![19_select_ilm_phases_before](https://user-images.githubusercontent.com/4459398/233869754-2067fa5d-7153-407b-aa45-65332b16bc7a.png) **After:** ![20_select_ilm_phases_after](https://user-images.githubusercontent.com/4459398/233869762-38d069de-3191-4e28-8692-df42ab3b21a5.png) ### Code coverage improvements Code coverage is improved throughout the dashboard, as measured by running the following command: ```sh node scripts/jest --watch x-pack/packages/kbn-ecs-data-quality-dashboard --coverage ``` --- .../ecs_allowed_values/index.test.tsx | 53 ++ .../{helpers.test.tsx => index.test.tsx} | 51 +- .../{helpers.test.tsx => index.test.tsx} | 4 +- .../compare_fields_table/helpers.test.tsx | 414 +++++++++ .../compare_fields_table/helpers.tsx | 20 +- .../compare_fields_table/index.test.tsx | 39 + .../compare_fields_table/index.tsx | 3 +- .../index_invalid_values/index.test.tsx | 49 + .../index_invalid_values/index.tsx | 2 +- .../allowed_values/helpers.test.tsx | 207 +++++ .../body/data_quality_details/index.test.tsx | 111 +++ .../body/data_quality_details/index.tsx | 123 +++ .../indices_details/index.test.tsx | 93 ++ .../indices_details/index.tsx | 110 +++ .../storage_details/helpers.test.ts | 382 ++++++++ .../storage_details/helpers.ts | 223 +++++ .../storage_details/index.test.tsx | 59 ++ .../storage_details/index.tsx | 58 ++ .../data_quality_details}/translations.ts | 13 +- .../data_quality_panel/body/index.test.tsx | 100 ++ .../data_quality_panel/body/index.tsx | 49 +- .../check_status/index.test.tsx | 212 +++++ .../check_status/index.tsx | 21 +- .../errors_popover/index.test.tsx | 97 ++ .../errors_popover/index.tsx | 12 +- .../errors_viewer/helpers.test.tsx | 122 +++ .../errors_viewer/helpers.tsx | 9 +- .../errors_viewer/index.test.tsx | 77 ++ .../errors_viewer/index.tsx | 2 +- .../data_quality_summary/index.test.tsx | 103 +++ .../data_quality_summary/index.tsx | 56 +- .../summary_actions/actions/index.test.tsx | 104 +++ .../summary_actions/actions/index.tsx | 98 ++ .../check_all/check_index.test.ts | 338 +++++++ .../summary_actions/check_all/check_index.ts | 11 +- .../summary_actions/check_all/helpers.test.ts | 107 +++ .../summary_actions/check_all/helpers.ts | 8 +- .../summary_actions/check_all/index.test.tsx | 377 ++++++++ .../summary_actions/check_all/index.tsx | 28 +- .../summary_actions/index.test.tsx | 128 +++ .../summary_actions/index.tsx | 122 ++- .../take_action_menu/index.tsx | 125 --- .../error_empty_prompt/index.test.tsx | 26 + .../error_empty_prompt/index.tsx | 2 +- .../ilm_phase_counts/index.test.tsx | 86 ++ .../ilm_phase_counts/index.tsx | 2 +- .../empty_prompt_body.test.tsx | 26 + .../index_properties/empty_prompt_body.tsx | 4 +- .../empty_prompt_title.test.tsx | 26 + .../index_properties/empty_prompt_title.tsx | 4 +- .../index_properties/helpers.test.ts | 800 ++++++++++++++++ .../index_properties/index.test.tsx | 262 ++++++ .../index_properties/index.tsx | 31 +- .../index_properties/markdown/helpers.test.ts | 598 +++++++++++- .../index_properties/markdown/helpers.ts | 85 +- .../loading_empty_prompt/index.tsx | 4 +- .../panel_subtitle/index.tsx | 34 - .../pattern/helpers.test.ts | 865 ++++++++++++++++++ .../data_quality_panel/pattern/helpers.ts | 85 +- .../data_quality_panel/pattern/index.test.tsx | 17 +- .../data_quality_panel/pattern/index.tsx | 107 ++- .../pattern/pattern_summary/index.tsx | 12 +- .../pattern_label/helpers.test.ts | 97 ++ .../pattern_summary/stats_rollup/index.tsx | 51 +- .../pattern/translations.ts | 2 +- .../same_family/index.test.tsx | 2 +- .../stat_label/translations.ts | 18 + .../storage_treemap/index.test.tsx | 154 ++++ .../storage_treemap/index.tsx | 201 ++++ .../storage_treemap/no_data/index.test.tsx | 21 + .../storage_treemap/no_data/index.tsx | 43 + .../storage_treemap/translations.ts | 20 + .../summary_table/helpers.test.tsx | 543 +++++++++++ .../summary_table/helpers.tsx | 56 +- .../summary_table/index.test.tsx | 89 ++ .../summary_table/index.tsx | 91 +- .../summary_table/translations.ts | 4 + .../callouts/custom_callout/index.test.tsx | 7 +- .../incompatible_callout/helpers.test.ts | 2 +- .../incompatible_callout/index.test.tsx | 4 +- .../tabs/custom_tab/helpers.test.ts | 73 +- .../tabs/custom_tab/helpers.ts | 17 +- .../tabs/custom_tab/index.tsx | 28 +- .../data_quality_panel/tabs/helpers.test.tsx | 112 +++ .../data_quality_panel/tabs/helpers.tsx | 24 +- .../tabs/incompatible_tab/helpers.test.ts | 317 ++++++- .../tabs/incompatible_tab/helpers.ts | 17 +- .../tabs/incompatible_tab/index.tsx | 32 +- .../data_quality_panel/tabs/styles.tsx | 32 +- .../summary_tab/callout_summary/index.tsx | 19 +- .../tabs/summary_tab/helpers.test.ts | 235 +++++ .../tabs/summary_tab/helpers.ts | 6 + .../tabs/summary_tab/index.tsx | 12 +- .../chart_legend/chart_legend_item.tsx | 58 +- .../chart_legend/index.tsx | 53 +- .../ecs_summary_donut_chart/helpers.test.ts | 29 + .../impl/data_quality/helpers.test.ts | 218 +++-- .../impl/data_quality/helpers.ts | 20 + .../ilm_phases_empty_prompt/index.test.tsx | 28 + .../impl/data_quality/index.test.tsx | 30 +- .../impl/data_quality/index.tsx | 24 +- .../allowed_values/mock_allowed_values.ts | 128 +++ .../data_quality_check_result/mock_index.tsx | 41 + ...dex.ts => mock_enriched_field_metadata.ts} | 72 ++ .../{index.ts => mock_ilm_explain.ts} | 4 + ...ndices_get_mapping_index_mapping_record.ts | 73 ++ .../mock_mappings_properties.ts | 107 +++ .../mock_mappings_response.ts | 68 ++ .../mock_alerts_pattern_rollup.ts | 2 + .../mock_auditbeat_pattern_rollup.ts | 38 + .../mock_packetbeat_pattern_rollup.ts | 636 +++++++++++++ .../data_quality/mock/stats/mock_stats.tsx | 275 ++++++ .../{index.tsx => test_providers.tsx} | 0 .../unallowed_values/mock_unallowed_values.ts | 122 +++ .../impl/data_quality/styles.tsx | 9 +- .../impl/data_quality/translations.ts | 25 +- .../impl/data_quality/types.ts | 16 + .../data_quality/use_mappings/helpers.test.ts | 88 ++ .../use_results_rollup/helpers.test.ts | 511 +++++++++++ .../use_results_rollup/helpers.ts | 32 +- .../data_quality/use_results_rollup/index.tsx | 7 + .../use_unallowed_values/helpers.test.ts | 503 ++++++++++ .../use_unallowed_values/helpers.ts | 1 + .../public/overview/links.ts | 1 - .../public/overview/pages/data_quality.tsx | 16 +- .../translations/translations/fr-FR.json | 5 - .../translations/translations/ja-JP.json | 5 - .../translations/translations/zh-CN.json | 5 - 128 files changed, 12119 insertions(+), 856 deletions(-) create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/ecs_allowed_values/index.test.tsx rename x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/get_common_table_columns/{helpers.test.tsx => index.test.tsx} (87%) rename x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/get_incompatible_mappings_table_columns/{helpers.test.tsx => index.test.tsx} (97%) create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/helpers.test.tsx create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/index.test.tsx create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/index_invalid_values/index.test.tsx create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/allowed_values/helpers.test.tsx create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/index.test.tsx create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/index.tsx create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/indices_details/index.test.tsx create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/indices_details/index.tsx create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/helpers.test.ts create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/helpers.ts create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/index.test.tsx create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/index.tsx rename x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/{data_quality_summary/summary_actions/take_action_menu => body/data_quality_details}/translations.ts (51%) create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/index.test.tsx create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/check_status/index.test.tsx create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_popover/index.test.tsx create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/helpers.test.tsx create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/index.test.tsx create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/index.test.tsx create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/actions/index.test.tsx create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/actions/index.tsx create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/check_index.test.ts create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/helpers.test.ts create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/index.test.tsx create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/index.test.tsx delete mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/take_action_menu/index.tsx create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/error_empty_prompt/index.test.tsx create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/ilm_phase_counts/index.test.tsx create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/empty_prompt_body.test.tsx create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/empty_prompt_title.test.tsx create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/helpers.test.ts create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/index.test.tsx delete mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/panel_subtitle/index.tsx create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/helpers.test.ts create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/pattern_label/helpers.test.ts create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/storage_treemap/index.test.tsx create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/storage_treemap/index.tsx create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/storage_treemap/no_data/index.test.tsx create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/storage_treemap/no_data/index.tsx create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/storage_treemap/translations.ts create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/summary_table/helpers.test.tsx create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/summary_table/index.test.tsx create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/helpers.test.tsx create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/helpers.test.ts create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/ecs_summary_donut_chart/helpers.test.ts create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/ilm_phases_empty_prompt/index.test.tsx create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/allowed_values/mock_allowed_values.ts create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/data_quality_check_result/mock_index.tsx rename x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/enriched_field_metadata/{index.ts => mock_enriched_field_metadata.ts} (87%) rename x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/ilm_explain/{index.ts => mock_ilm_explain.ts} (94%) create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/indices_get_mapping_index_mapping_record/mock_indices_get_mapping_index_mapping_record.ts create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/mappings_properties/mock_mappings_properties.ts create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/mappings_response/mock_mappings_response.ts rename x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/test_providers/{index.tsx => test_providers.tsx} (100%) create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/unallowed_values/mock_unallowed_values.ts create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_mappings/helpers.test.ts create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_results_rollup/helpers.test.ts create mode 100644 x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_unallowed_values/helpers.test.ts diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/ecs_allowed_values/index.test.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/ecs_allowed_values/index.test.tsx new file mode 100644 index 0000000000000..07af65877da01 --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/ecs_allowed_values/index.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { render, screen } from '@testing-library/react'; +import React from 'react'; + +import { mockAllowedValues } from '../../mock/allowed_values/mock_allowed_values'; +import { TestProviders } from '../../mock/test_providers/test_providers'; +import { EcsAllowedValues } from '.'; + +describe('EcsAllowedValues', () => { + describe('when `allowedValues` exists', () => { + beforeEach(() => { + render( + + + + ); + }); + + test('it renders the allowed values', () => { + expect(screen.getByTestId('ecsAllowedValues')).toHaveTextContent( + mockAllowedValues.map(({ name }) => `${name}`).join('') + ); + }); + + test('it does NOT render the placeholder', () => { + expect(screen.queryByTestId('ecsAllowedValuesEmpty')).not.toBeInTheDocument(); + }); + }); + + describe('when `allowedValues` is undefined', () => { + beforeEach(() => { + render( + + + + ); + }); + + test('it does NOT render the allowed values', () => { + expect(screen.queryByTestId('ecsAllowedValues')).not.toBeInTheDocument(); + }); + + test('it renders the placeholder', () => { + expect(screen.getByTestId('ecsAllowedValuesEmpty')).toBeInTheDocument(); + }); + }); +}); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/get_common_table_columns/helpers.test.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/get_common_table_columns/index.test.tsx similarity index 87% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/get_common_table_columns/helpers.test.tsx rename to x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/get_common_table_columns/index.test.tsx index 75a34735abff1..e3e68152f4c06 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/get_common_table_columns/helpers.test.tsx +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/get_common_table_columns/index.test.tsx @@ -10,57 +10,57 @@ import { omit } from 'lodash/fp'; import React from 'react'; import { SAME_FAMILY } from '../../data_quality_panel/same_family/translations'; -import { TestProviders } from '../../mock/test_providers'; import { eventCategory, eventCategoryWithUnallowedValues, -} from '../../mock/enriched_field_metadata'; +} from '../../mock/enriched_field_metadata/mock_enriched_field_metadata'; +import { TestProviders } from '../../mock/test_providers/test_providers'; +import { + DOCUMENT_VALUES_ACTUAL, + ECS_DESCRIPTION, + ECS_MAPPING_TYPE_EXPECTED, + ECS_VALUES_EXPECTED, + FIELD, + INDEX_MAPPING_TYPE_ACTUAL, +} from '../translations'; import { EnrichedFieldMetadata } from '../../types'; import { EMPTY_PLACEHOLDER, getCommonTableColumns } from '.'; describe('getCommonTableColumns', () => { test('it returns the expected column configuration', () => { - const columns = getCommonTableColumns().map((x) => omit('render', x)); - - expect(columns).toEqual([ - { - field: 'indexFieldName', - name: 'Field', - sortable: true, - truncateText: false, - width: '20%', - }, + expect(getCommonTableColumns().map((x) => omit('render', x))).toEqual([ + { field: 'indexFieldName', name: FIELD, sortable: true, truncateText: false, width: '20%' }, { field: 'type', - name: 'ECS mapping type (expected)', + name: ECS_MAPPING_TYPE_EXPECTED, sortable: true, truncateText: false, width: '15%', }, { field: 'indexFieldType', - name: 'Index mapping type (actual)', + name: INDEX_MAPPING_TYPE_ACTUAL, sortable: true, truncateText: false, width: '15%', }, { field: 'allowed_values', - name: 'ECS values (expected)', + name: ECS_VALUES_EXPECTED, sortable: false, truncateText: false, width: '15%', }, { field: 'indexInvalidValues', - name: 'Document values (actual)', + name: DOCUMENT_VALUES_ACTUAL, sortable: false, truncateText: false, width: '15%', }, { field: 'description', - name: 'ECS description', + name: ECS_DESCRIPTION, sortable: false, truncateText: false, width: '20%', @@ -141,7 +141,7 @@ describe('getCommonTableColumns', () => { const withTypeMismatchDifferentFamily: EnrichedFieldMetadata = { ...eventCategory, // `event.category` is a `keyword` per the ECS spec indexFieldType, // this index has a mapping of `text` instead of `keyword` - isInSameFamily: false, // `text` and `keyword` are not in the same family + isInSameFamily: false, // `text` and `wildcard` are not in the same family }; render( @@ -159,29 +159,18 @@ describe('getCommonTableColumns', () => { }); describe('when the index field matches the ECS type', () => { - const indexFieldType = 'keyword'; - test('it renders the expected type with success styling', () => { const columns = getCommonTableColumns(); const indexFieldTypeColumnRender = columns[2].render; - const withTypeMismatchDifferentFamily: EnrichedFieldMetadata = { - ...eventCategory, // `event.category` is a `keyword` per the ECS spec - indexFieldType, // exactly matches the ECS spec - isInSameFamily: true, // `keyword` is a member of the `keyword` family - }; - render( {indexFieldTypeColumnRender != null && - indexFieldTypeColumnRender( - withTypeMismatchDifferentFamily.indexFieldType, - withTypeMismatchDifferentFamily - )} + indexFieldTypeColumnRender(eventCategory.indexFieldType, eventCategory)} ); - expect(screen.getByTestId('codeSuccess')).toHaveTextContent(indexFieldType); + expect(screen.getByTestId('codeSuccess')).toHaveTextContent(eventCategory.indexFieldType); }); }); }); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/get_incompatible_mappings_table_columns/helpers.test.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/get_incompatible_mappings_table_columns/index.test.tsx similarity index 97% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/get_incompatible_mappings_table_columns/helpers.test.tsx rename to x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/get_incompatible_mappings_table_columns/index.test.tsx index 160b300e08934..132e8b2fc302b 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/get_incompatible_mappings_table_columns/helpers.test.tsx +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/get_incompatible_mappings_table_columns/index.test.tsx @@ -10,8 +10,8 @@ import { omit } from 'lodash/fp'; import React from 'react'; import { SAME_FAMILY } from '../../data_quality_panel/same_family/translations'; -import { TestProviders } from '../../mock/test_providers'; -import { eventCategory } from '../../mock/enriched_field_metadata'; +import { TestProviders } from '../../mock/test_providers/test_providers'; +import { eventCategory } from '../../mock/enriched_field_metadata/mock_enriched_field_metadata'; import { EnrichedFieldMetadata } from '../../types'; import { EMPTY_PLACEHOLDER, getIncompatibleMappingsTableColumns } from '.'; diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/helpers.test.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/helpers.test.tsx new file mode 100644 index 0000000000000..7c72289290942 --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/helpers.test.tsx @@ -0,0 +1,414 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { render, screen } from '@testing-library/react'; +import { omit } from 'lodash/fp'; +import React from 'react'; + +import { + EMPTY_PLACEHOLDER, + getCustomTableColumns, + getEcsCompliantTableColumns, + getIncompatibleValuesTableColumns, +} from './helpers'; +import { + eventCategory, + eventCategoryWithUnallowedValues, + someField, +} from '../mock/enriched_field_metadata/mock_enriched_field_metadata'; +import { TestProviders } from '../mock/test_providers/test_providers'; + +describe('helpers', () => { + describe('getCustomTableColumns', () => { + test('it returns the expected columns', () => { + expect(getCustomTableColumns().map((x) => omit('render', x))).toEqual([ + { + field: 'indexFieldName', + name: 'Field', + sortable: true, + truncateText: false, + width: '50%', + }, + { + field: 'indexFieldType', + name: 'Index mapping type', + sortable: true, + truncateText: false, + width: '50%', + }, + ]); + }); + + describe('indexFieldType render()', () => { + test('it renders the indexFieldType', () => { + const columns = getCustomTableColumns(); + const indexFieldTypeRender = columns[1].render; + + render( + + <> + {indexFieldTypeRender != null && + indexFieldTypeRender(someField.indexFieldType, someField)} + + + ); + + expect(screen.getByTestId('indexFieldType')).toHaveTextContent(someField.indexFieldType); + }); + }); + }); + + describe('getEcsCompliantTableColumns', () => { + test('it returns the expected columns', () => { + expect(getEcsCompliantTableColumns().map((x) => omit('render', x))).toEqual([ + { + field: 'indexFieldName', + name: 'Field', + sortable: true, + truncateText: false, + width: '25%', + }, + { + field: 'type', + name: 'ECS mapping type', + sortable: true, + truncateText: false, + width: '25%', + }, + { + field: 'allowed_values', + name: 'ECS values', + sortable: false, + truncateText: false, + width: '25%', + }, + { + field: 'description', + name: 'ECS description', + sortable: false, + truncateText: false, + width: '25%', + }, + ]); + }); + + describe('type render()', () => { + describe('when `type` exists', () => { + beforeEach(() => { + const columns = getEcsCompliantTableColumns(); + const typeRender = columns[1].render; + + render( + + <>{typeRender != null && typeRender(eventCategory.type, eventCategory)} + + ); + }); + + test('it renders the expected `type`', () => { + expect(screen.getByTestId('type')).toHaveTextContent('keyword'); + }); + + test('it does NOT render the placeholder', () => { + expect(screen.queryByTestId('typePlaceholder')).not.toBeInTheDocument(); + }); + }); + + describe('when `type` is undefined', () => { + beforeEach(() => { + const withUndefinedType = { + ...eventCategory, + type: undefined, // <-- + }; + const columns = getEcsCompliantTableColumns(); + const typeRender = columns[1].render; + + render( + + <>{typeRender != null && typeRender(withUndefinedType.type, withUndefinedType)} + + ); + }); + + test('it does NOT render the `type`', () => { + expect(screen.queryByTestId('type')).not.toBeInTheDocument(); + }); + + test('it renders the placeholder', () => { + expect(screen.getByTestId('typePlaceholder')).toHaveTextContent(EMPTY_PLACEHOLDER); + }); + }); + }); + + describe('allowed values render()', () => { + describe('when `allowedValues` exists', () => { + beforeEach(() => { + const columns = getEcsCompliantTableColumns(); + const allowedValuesRender = columns[2].render; + + render( + + <> + {allowedValuesRender != null && + allowedValuesRender(eventCategory.allowed_values, eventCategory)} + + + ); + }); + + test('it renders the expected `AllowedValue` names', () => { + expect(screen.getByTestId('ecsAllowedValues')).toHaveTextContent( + eventCategory.allowed_values?.map(({ name }) => name).join('') ?? '' + ); + }); + + test('it does NOT render the placeholder', () => { + expect(screen.queryByTestId('ecsAllowedValuesEmpty')).not.toBeInTheDocument(); + }); + }); + + describe('when `allowedValues` is undefined', () => { + const withUndefinedAllowedValues = { + ...eventCategory, + allowed_values: undefined, // <-- + }; + + beforeEach(() => { + const columns = getEcsCompliantTableColumns(); + const allowedValuesRender = columns[2].render; + + render( + + <> + {allowedValuesRender != null && + allowedValuesRender( + withUndefinedAllowedValues.allowed_values, + withUndefinedAllowedValues + )} + + + ); + }); + + test('it does NOT render the `AllowedValue` names', () => { + expect(screen.queryByTestId('ecsAllowedValues')).not.toBeInTheDocument(); + }); + + test('it renders the placeholder', () => { + expect(screen.getByTestId('ecsAllowedValuesEmpty')).toBeInTheDocument(); + }); + }); + }); + + describe('description render()', () => { + describe('when `description` exists', () => { + beforeEach(() => { + const columns = getEcsCompliantTableColumns(); + const descriptionRender = columns[3].render; + + render( + + <> + {descriptionRender != null && + descriptionRender(eventCategory.description, eventCategory)} + + + ); + }); + + test('it renders the expected `description`', () => { + expect(screen.getByTestId('description')).toHaveTextContent( + eventCategory.description?.replaceAll('\n', ' ') ?? '' + ); + }); + + test('it does NOT render the placeholder', () => { + expect(screen.queryByTestId('emptyPlaceholder')).not.toBeInTheDocument(); + }); + }); + + describe('when `description` is undefined', () => { + const withUndefinedDescription = { + ...eventCategory, + description: undefined, // <-- + }; + + beforeEach(() => { + const columns = getEcsCompliantTableColumns(); + const descriptionRender = columns[3].render; + + render( + + <> + {descriptionRender != null && + descriptionRender(withUndefinedDescription.description, withUndefinedDescription)} + + + ); + }); + + test('it does NOT render the `description`', () => { + expect(screen.queryByTestId('description')).not.toBeInTheDocument(); + }); + + test('it renders the placeholder', () => { + expect(screen.getByTestId('emptyPlaceholder')).toBeInTheDocument(); + }); + }); + }); + }); + + describe('getIncompatibleValuesTableColumns', () => { + test('it returns the expected columns', () => { + expect(getIncompatibleValuesTableColumns().map((x) => omit('render', x))).toEqual([ + { + field: 'indexFieldName', + name: 'Field', + sortable: true, + truncateText: false, + width: '25%', + }, + { + field: 'allowed_values', + name: 'ECS values (expected)', + sortable: false, + truncateText: false, + width: '25%', + }, + { + field: 'indexInvalidValues', + name: 'Document values (actual)', + sortable: false, + truncateText: false, + width: '25%', + }, + { + field: 'description', + name: 'ECS description', + sortable: false, + truncateText: false, + width: '25%', + }, + ]); + }); + + describe('allowed values render()', () => { + describe('when `allowedValues` exists', () => { + beforeEach(() => { + const columns = getIncompatibleValuesTableColumns(); + const allowedValuesRender = columns[1].render; + + render( + + <> + {allowedValuesRender != null && + allowedValuesRender(eventCategory.allowed_values, eventCategory)} + + + ); + }); + + test('it renders the expected `AllowedValue` names', () => { + expect(screen.getByTestId('ecsAllowedValues')).toHaveTextContent( + eventCategory.allowed_values?.map(({ name }) => name).join('') ?? '' + ); + }); + + test('it does NOT render the placeholder', () => { + expect(screen.queryByTestId('ecsAllowedValuesEmpty')).not.toBeInTheDocument(); + }); + }); + + describe('when `allowedValues` is undefined', () => { + const withUndefinedAllowedValues = { + ...eventCategory, + allowed_values: undefined, // <-- + }; + + beforeEach(() => { + const columns = getIncompatibleValuesTableColumns(); + const allowedValuesRender = columns[1].render; + + render( + + <> + {allowedValuesRender != null && + allowedValuesRender( + withUndefinedAllowedValues.allowed_values, + withUndefinedAllowedValues + )} + + + ); + }); + + test('it does NOT render the `AllowedValue` names', () => { + expect(screen.queryByTestId('ecsAllowedValues')).not.toBeInTheDocument(); + }); + + test('it renders the placeholder', () => { + expect(screen.getByTestId('ecsAllowedValuesEmpty')).toBeInTheDocument(); + }); + }); + }); + + describe('indexInvalidValues render()', () => { + describe('when `indexInvalidValues` is populated', () => { + beforeEach(() => { + const columns = getIncompatibleValuesTableColumns(); + const indexInvalidValuesRender = columns[2].render; + + render( + + <> + {indexInvalidValuesRender != null && + indexInvalidValuesRender( + eventCategoryWithUnallowedValues.indexInvalidValues, + eventCategoryWithUnallowedValues + )} + + + ); + }); + + test('it renders the expected `indexInvalidValues`', () => { + expect(screen.getByTestId('indexInvalidValues')).toHaveTextContent( + 'an_invalid_category (2)theory (1)' + ); + }); + + test('it does NOT render the placeholder', () => { + expect(screen.queryByTestId('emptyPlaceholder')).not.toBeInTheDocument(); + }); + }); + + describe('when `indexInvalidValues` is empty', () => { + beforeEach(() => { + const columns = getIncompatibleValuesTableColumns(); + const indexInvalidValuesRender = columns[2].render; + + render( + + <> + {indexInvalidValuesRender != null && + indexInvalidValuesRender(eventCategory.indexInvalidValues, eventCategory)} + + + ); + }); + + test('it does NOT render the index invalid values', () => { + expect(screen.queryByTestId('indexInvalidValues')).not.toBeInTheDocument(); + }); + + test('it renders the placeholder', () => { + expect(screen.getByTestId('emptyPlaceholder')).toBeInTheDocument(); + }); + }); + }); + }); +}); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/helpers.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/helpers.tsx index e9a85c2908b89..8153380c140c3 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/helpers.tsx +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/helpers.tsx @@ -30,7 +30,9 @@ export const getCustomTableColumns = (): Array< { field: 'indexFieldType', name: i18n.INDEX_MAPPING_TYPE, - render: (indexFieldType: string) => {indexFieldType}, + render: (indexFieldType: string) => ( + {indexFieldType} + ), sortable: true, truncateText: false, width: '50%', @@ -50,8 +52,12 @@ export const getEcsCompliantTableColumns = (): Array< { field: 'type', name: i18n.ECS_MAPPING_TYPE, - render: (type: string) => - type != null ? {type} : {EMPTY_PLACEHOLDER}, + render: (type: string | undefined) => + type != null ? ( + {type} + ) : ( + {EMPTY_PLACEHOLDER} + ), sortable: true, truncateText: false, width: '25%', @@ -69,8 +75,12 @@ export const getEcsCompliantTableColumns = (): Array< { field: 'description', name: i18n.ECS_DESCRIPTION, - render: (description: string) => - description != null ? description : {EMPTY_PLACEHOLDER}, + render: (description: string | undefined) => + description != null ? ( + {description} + ) : ( + {EMPTY_PLACEHOLDER} + ), sortable: false, truncateText: false, width: '25%', diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/index.test.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/index.test.tsx new file mode 100644 index 0000000000000..8732f27702bc2 --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/index.test.tsx @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { render, screen } from '@testing-library/react'; +import React from 'react'; + +import { INCOMPATIBLE_FIELD_MAPPINGS_TABLE_TITLE } from '../data_quality_panel/tabs/incompatible_tab/translations'; +import { eventCategory } from '../mock/enriched_field_metadata/mock_enriched_field_metadata'; +import { TestProviders } from '../mock/test_providers/test_providers'; +import { CompareFieldsTable } from '.'; +import { getIncompatibleMappingsTableColumns } from './get_incompatible_mappings_table_columns'; + +describe('CompareFieldsTable', () => { + describe('rendering', () => { + beforeEach(() => { + render( + + + + ); + }); + + test('it renders the expected title', () => { + expect(screen.getByTestId('title')).toHaveTextContent('Incompatible field mappings - foo'); + }); + + test('it renders the table', () => { + expect(screen.getByTestId('table')).toBeInTheDocument(); + }); + }); +}); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/index.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/index.tsx index 2efc9355c710f..145686785cafa 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/index.tsx +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/index.tsx @@ -36,11 +36,12 @@ const CompareFieldsTableComponent: React.FC = ({ return ( <> - <>{title} + {title} { + test('it renders a placeholder with the expected content when `indexInvalidValues` is empty', () => { + render( + + + + ); + + expect(screen.getByTestId('emptyPlaceholder')).toHaveTextContent(EMPTY_PLACEHOLDER); + }); + + test('it renders the expected field names and counts when the index has invalid values', () => { + const indexInvalidValues: UnallowedValueCount[] = [ + { + count: 2, + fieldName: 'an_invalid_category', + }, + { + count: 1, + fieldName: 'theory', + }, + ]; + + render( + + + + ); + + expect(screen.getByTestId('indexInvalidValues')).toHaveTextContent( + 'an_invalid_category (2)theory (1)' + ); + }); +}); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/index_invalid_values/index.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/index_invalid_values/index.tsx index d3df809215d08..2b58ea98b8b28 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/index_invalid_values/index.tsx +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/compare_fields_table/index_invalid_values/index.tsx @@ -23,7 +23,7 @@ interface Props { const IndexInvalidValuesComponent: React.FC = ({ indexInvalidValues }) => indexInvalidValues.length === 0 ? ( - {EMPTY_PLACEHOLDER} + {EMPTY_PLACEHOLDER} ) : ( {indexInvalidValues.map(({ fieldName, count }, i) => ( diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/allowed_values/helpers.test.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/allowed_values/helpers.test.tsx new file mode 100644 index 0000000000000..7dde4254708b7 --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/allowed_values/helpers.test.tsx @@ -0,0 +1,207 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { EcsFlat } from '@kbn/ecs'; +import { omit } from 'lodash/fp'; + +import { getUnallowedValueRequestItems, getValidValues, hasAllowedValues } from './helpers'; +import { AllowedValue, EcsMetadata } from '../../types'; + +const ecsMetadata: Record = EcsFlat as unknown as Record; + +describe('helpers', () => { + describe('hasAllowedValues', () => { + test('it returns true for a field that has `allowed_values`', () => { + expect( + hasAllowedValues({ + ecsMetadata, + fieldName: 'event.category', + }) + ).toBe(true); + }); + + test('it returns false for a field that does NOT have `allowed_values`', () => { + expect( + hasAllowedValues({ + ecsMetadata, + fieldName: 'host.name', + }) + ).toBe(false); + }); + + test('it returns false for a field that does NOT exist in `ecsMetadata`', () => { + expect( + hasAllowedValues({ + ecsMetadata, + fieldName: 'does.NOT.exist', + }) + ).toBe(false); + }); + + test('it returns false when `ecsMetadata` is null', () => { + expect( + hasAllowedValues({ + ecsMetadata: null, // <-- + fieldName: 'event.category', + }) + ).toBe(false); + }); + }); + + describe('getValidValues', () => { + test('it returns the expected valid values', () => { + expect(getValidValues(ecsMetadata['event.category'])).toEqual([ + 'authentication', + 'configuration', + 'database', + 'driver', + 'email', + 'file', + 'host', + 'iam', + 'intrusion_detection', + 'malware', + 'network', + 'package', + 'process', + 'registry', + 'session', + 'threat', + 'vulnerability', + 'web', + ]); + }); + + test('it returns an empty array when the `field` does NOT have `allowed_values`', () => { + expect(getValidValues(ecsMetadata['host.name'])).toEqual([]); + }); + + test('it returns an empty array when `field` is undefined', () => { + expect(getValidValues(undefined)).toEqual([]); + }); + + test('it skips `allowed_values` where `name` is undefined', () => { + // omit the `name` property from the `database` `AllowedValue`: + const missingDatabase = + ecsMetadata['event.category'].allowed_values?.map((x) => + x.name === 'database' ? omit('name', x) : x + ) ?? []; + + const field = { + ...ecsMetadata['event.category'], + allowed_values: missingDatabase, + }; + + expect(getValidValues(field)).toEqual([ + 'authentication', + 'configuration', + // no entry for 'database' + 'driver', + 'email', + 'file', + 'host', + 'iam', + 'intrusion_detection', + 'malware', + 'network', + 'package', + 'process', + 'registry', + 'session', + 'threat', + 'vulnerability', + 'web', + ]); + }); + }); + + describe('getUnallowedValueRequestItems', () => { + test('it returns the expected request items', () => { + expect( + getUnallowedValueRequestItems({ + ecsMetadata, + indexName: 'auditbeat-*', + }) + ).toEqual([ + { + indexName: 'auditbeat-*', + indexFieldName: 'event.category', + allowedValues: [ + 'authentication', + 'configuration', + 'database', + 'driver', + 'email', + 'file', + 'host', + 'iam', + 'intrusion_detection', + 'malware', + 'network', + 'package', + 'process', + 'registry', + 'session', + 'threat', + 'vulnerability', + 'web', + ], + }, + { + indexName: 'auditbeat-*', + indexFieldName: 'event.kind', + allowedValues: [ + 'alert', + 'enrichment', + 'event', + 'metric', + 'state', + 'pipeline_error', + 'signal', + ], + }, + { + indexName: 'auditbeat-*', + indexFieldName: 'event.outcome', + allowedValues: ['failure', 'success', 'unknown'], + }, + { + indexName: 'auditbeat-*', + indexFieldName: 'event.type', + allowedValues: [ + 'access', + 'admin', + 'allowed', + 'change', + 'connection', + 'creation', + 'deletion', + 'denied', + 'end', + 'error', + 'group', + 'indicator', + 'info', + 'installation', + 'protocol', + 'start', + 'user', + ], + }, + ]); + }); + + test('it returns an empty array when `ecsMetadata` is null', () => { + expect( + getUnallowedValueRequestItems({ + ecsMetadata: null, // <-- + indexName: 'auditbeat-*', + }) + ).toEqual([]); + }); + }); +}); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/index.test.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/index.test.tsx new file mode 100644 index 0000000000000..8b7c9b01e3c5e --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/index.test.tsx @@ -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 { DARK_THEME } from '@elastic/charts'; +import numeral from '@elastic/numeral'; +import { render, screen } from '@testing-library/react'; +import React from 'react'; + +import { EMPTY_STAT } from '../../../helpers'; +import { alertIndexWithAllResults } from '../../../mock/pattern_rollup/mock_alerts_pattern_rollup'; +import { auditbeatWithAllResults } from '../../../mock/pattern_rollup/mock_auditbeat_pattern_rollup'; +import { packetbeatNoResults } from '../../../mock/pattern_rollup/mock_packetbeat_pattern_rollup'; +import { TestProviders } from '../../../mock/test_providers/test_providers'; +import { PatternRollup } from '../../../types'; +import { Props, DataQualityDetails } from '.'; + +const defaultBytesFormat = '0,0.[0]b'; +const formatBytes = (value: number | undefined) => + value != null ? numeral(value).format(defaultBytesFormat) : EMPTY_STAT; + +const defaultNumberFormat = '0,0.[000]'; +const formatNumber = (value: number | undefined) => + value != null ? numeral(value).format(defaultNumberFormat) : EMPTY_STAT; + +const ilmPhases = ['hot', 'warm', 'unmanaged']; +const patterns = ['.alerts-security.alerts-default', 'auditbeat-*', 'packetbeat-*']; + +const patternRollups: Record = { + '.alerts-security.alerts-default': alertIndexWithAllResults, + 'auditbeat-*': auditbeatWithAllResults, + 'packetbeat-*': packetbeatNoResults, +}; + +const patternIndexNames: Record = { + 'auditbeat-*': [ + '.ds-auditbeat-8.6.1-2023.02.07-000001', + 'auditbeat-custom-empty-index-1', + 'auditbeat-custom-index-1', + ], + '.alerts-security.alerts-default': ['.internal.alerts-security.alerts-default-000001'], + 'packetbeat-*': [ + '.ds-packetbeat-8.5.3-2023.02.04-000001', + '.ds-packetbeat-8.6.1-2023.02.04-000001', + ], +}; + +const defaultProps: Props = { + addSuccessToast: jest.fn(), + canUserCreateAndReadCases: jest.fn(), + formatBytes, + formatNumber, + getGroupByFieldsOnClick: jest.fn(), + ilmPhases, + openCreateCaseFlyout: jest.fn(), + patternIndexNames, + patternRollups, + patterns, + theme: DARK_THEME, + updatePatternIndexNames: jest.fn(), + updatePatternRollup: jest.fn(), +}; + +describe('DataQualityDetails', () => { + describe('when ILM phases are provided', () => { + beforeEach(() => { + jest.clearAllMocks(); + + render( + + + + ); + }); + + test('it renders the storage details', () => { + expect(screen.getByTestId('storageDetails')).toBeInTheDocument(); + }); + + test('it renders the indices details', () => { + expect(screen.getByTestId('indicesDetails')).toBeInTheDocument(); + }); + }); + + describe('when ILM phases are are empty', () => { + beforeEach(() => { + jest.clearAllMocks(); + + render( + + + + ); + }); + + test('it renders an empty prompt when ilmPhases is empty', () => { + expect(screen.getByTestId('ilmPhasesEmptyPrompt')).toBeInTheDocument(); + }); + + test('it does NOT render the storage details', () => { + expect(screen.queryByTestId('storageDetails')).not.toBeInTheDocument(); + }); + + test('it does NOT render the indices details', () => { + expect(screen.queryByTestId('indicesDetails')).not.toBeInTheDocument(); + }); + }); +}); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/index.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/index.tsx new file mode 100644 index 0000000000000..3c996dd095dc8 --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/index.tsx @@ -0,0 +1,123 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { + FlameElementEvent, + HeatmapElementEvent, + MetricElementEvent, + PartitionElementEvent, + Theme, + WordCloudElementEvent, + XYChartElementEvent, +} from '@elastic/charts'; + +import React, { useCallback, useState } from 'react'; + +import { IlmPhasesEmptyPrompt } from '../../../ilm_phases_empty_prompt'; +import { IndicesDetails } from './indices_details'; +import { StorageDetails } from './storage_details'; +import { PatternRollup, SelectedIndex } from '../../../types'; + +export interface Props { + addSuccessToast: (toast: { title: string }) => void; + canUserCreateAndReadCases: () => boolean; + formatBytes: (value: number | undefined) => string; + formatNumber: (value: number | undefined) => string; + getGroupByFieldsOnClick: ( + elements: Array< + | FlameElementEvent + | HeatmapElementEvent + | MetricElementEvent + | PartitionElementEvent + | WordCloudElementEvent + | XYChartElementEvent + > + ) => { + groupByField0: string; + groupByField1: string; + }; + ilmPhases: string[]; + openCreateCaseFlyout: ({ + comments, + headerContent, + }: { + comments: string[]; + headerContent?: React.ReactNode; + }) => void; + patternIndexNames: Record; + patternRollups: Record; + patterns: string[]; + theme: Theme; + updatePatternIndexNames: ({ + indexNames, + pattern, + }: { + indexNames: string[]; + pattern: string; + }) => void; + updatePatternRollup: (patternRollup: PatternRollup) => void; +} + +const DataQualityDetailsComponent: React.FC = ({ + addSuccessToast, + canUserCreateAndReadCases, + formatBytes, + formatNumber, + getGroupByFieldsOnClick, + ilmPhases, + openCreateCaseFlyout, + patternIndexNames, + patternRollups, + patterns, + theme, + updatePatternIndexNames, + updatePatternRollup, +}) => { + const [selectedIndex, setSelectedIndex] = useState(null); + + const onIndexSelected = useCallback(async ({ indexName, pattern }: SelectedIndex) => { + setSelectedIndex({ indexName, pattern }); + }, []); + + if (ilmPhases.length === 0) { + return ; + } + + return ( + <> + + + + + ); +}; + +DataQualityDetailsComponent.displayName = 'DataQualityDetailsComponent'; +export const DataQualityDetails = React.memo(DataQualityDetailsComponent); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/indices_details/index.test.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/indices_details/index.test.tsx new file mode 100644 index 0000000000000..ee4977ebe7858 --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/indices_details/index.test.tsx @@ -0,0 +1,93 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DARK_THEME } from '@elastic/charts'; +import numeral from '@elastic/numeral'; +import { render, screen } from '@testing-library/react'; +import React from 'react'; + +import { EMPTY_STAT } from '../../../../helpers'; +import { alertIndexWithAllResults } from '../../../../mock/pattern_rollup/mock_alerts_pattern_rollup'; +import { auditbeatWithAllResults } from '../../../../mock/pattern_rollup/mock_auditbeat_pattern_rollup'; +import { packetbeatNoResults } from '../../../../mock/pattern_rollup/mock_packetbeat_pattern_rollup'; +import { TestProviders } from '../../../../mock/test_providers/test_providers'; +import { PatternRollup } from '../../../../types'; +import { Props, IndicesDetails } from '.'; + +const defaultBytesFormat = '0,0.[0]b'; +const formatBytes = (value: number | undefined) => + value != null ? numeral(value).format(defaultBytesFormat) : EMPTY_STAT; + +const defaultNumberFormat = '0,0.[000]'; +const formatNumber = (value: number | undefined) => + value != null ? numeral(value).format(defaultNumberFormat) : EMPTY_STAT; + +const ilmPhases = ['hot', 'warm', 'unmanaged']; +const patterns = ['.alerts-security.alerts-default', 'auditbeat-*', 'packetbeat-*']; + +const patternRollups: Record = { + '.alerts-security.alerts-default': alertIndexWithAllResults, + 'auditbeat-*': auditbeatWithAllResults, + 'packetbeat-*': packetbeatNoResults, +}; + +const patternIndexNames: Record = { + 'auditbeat-*': [ + '.ds-auditbeat-8.6.1-2023.02.07-000001', + 'auditbeat-custom-empty-index-1', + 'auditbeat-custom-index-1', + ], + '.alerts-security.alerts-default': ['.internal.alerts-security.alerts-default-000001'], + 'packetbeat-*': [ + '.ds-packetbeat-8.5.3-2023.02.04-000001', + '.ds-packetbeat-8.6.1-2023.02.04-000001', + ], +}; + +const defaultProps: Props = { + addSuccessToast: jest.fn(), + canUserCreateAndReadCases: jest.fn(), + formatBytes, + formatNumber, + getGroupByFieldsOnClick: jest.fn(), + ilmPhases, + openCreateCaseFlyout: jest.fn(), + patternIndexNames, + patternRollups, + patterns, + selectedIndex: null, + setSelectedIndex: jest.fn(), + theme: DARK_THEME, + updatePatternIndexNames: jest.fn(), + updatePatternRollup: jest.fn(), +}; + +describe('IndicesDetails', () => { + beforeEach(() => { + jest.clearAllMocks(); + + render( + + + + ); + }); + + describe('rendering patterns', () => { + patterns.forEach((pattern) => { + test(`it renders the ${pattern} pattern`, () => { + expect(screen.getByTestId(`${pattern}PatternPanel`)).toBeInTheDocument(); + }); + }); + }); + + describe('rendering spacers', () => { + test('it renders the expected number of spacers', () => { + expect(screen.getAllByTestId('bodyPatternSpacer')).toHaveLength(patterns.length - 1); + }); + }); +}); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/indices_details/index.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/indices_details/index.tsx new file mode 100644 index 0000000000000..9b59a78430e1c --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/indices_details/index.tsx @@ -0,0 +1,110 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { + FlameElementEvent, + HeatmapElementEvent, + MetricElementEvent, + PartitionElementEvent, + Theme, + WordCloudElementEvent, + XYChartElementEvent, +} from '@elastic/charts'; +import { EuiFlexItem, EuiSpacer } from '@elastic/eui'; +import React from 'react'; + +import { Pattern } from '../../../pattern'; +import { PatternRollup, SelectedIndex } from '../../../../types'; + +export interface Props { + addSuccessToast: (toast: { title: string }) => void; + canUserCreateAndReadCases: () => boolean; + formatBytes: (value: number | undefined) => string; + formatNumber: (value: number | undefined) => string; + getGroupByFieldsOnClick: ( + elements: Array< + | FlameElementEvent + | HeatmapElementEvent + | MetricElementEvent + | PartitionElementEvent + | WordCloudElementEvent + | XYChartElementEvent + > + ) => { + groupByField0: string; + groupByField1: string; + }; + ilmPhases: string[]; + openCreateCaseFlyout: ({ + comments, + headerContent, + }: { + comments: string[]; + headerContent?: React.ReactNode; + }) => void; + patternIndexNames: Record; + patternRollups: Record; + patterns: string[]; + selectedIndex: SelectedIndex | null; + setSelectedIndex: (selectedIndex: SelectedIndex | null) => void; + theme: Theme; + updatePatternIndexNames: ({ + indexNames, + pattern, + }: { + indexNames: string[]; + pattern: string; + }) => void; + updatePatternRollup: (patternRollup: PatternRollup) => void; +} + +const IndicesDetailsComponent: React.FC = ({ + addSuccessToast, + canUserCreateAndReadCases, + formatBytes, + formatNumber, + getGroupByFieldsOnClick, + ilmPhases, + openCreateCaseFlyout, + patternIndexNames, + patternRollups, + patterns, + selectedIndex, + setSelectedIndex, + theme, + updatePatternIndexNames, + updatePatternRollup, +}) => ( +
+ {patterns.map((pattern, i) => ( + + + {patterns[i + 1] && } + + ))} +
+); + +IndicesDetailsComponent.displayName = 'IndicesDetailsComponent'; + +export const IndicesDetails = React.memo(IndicesDetailsComponent); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/helpers.test.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/helpers.test.ts new file mode 100644 index 0000000000000..45e8f1ba1b4ad --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/helpers.test.ts @@ -0,0 +1,382 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 numeral from '@elastic/numeral'; +import { euiThemeVars } from '@kbn/ui-theme'; + +import { EMPTY_STAT } from '../../../../helpers'; +import { + DEFAULT_INDEX_COLOR, + getFillColor, + getFlattenedBuckets, + getGroupFromPath, + getLayersMultiDimensional, + getLegendItems, + getLegendItemsForPattern, + getPathToFlattenedBucketMap, + getPatternLegendItem, + getPatternSizeInBytes, +} from './helpers'; +import { alertIndexWithAllResults } from '../../../../mock/pattern_rollup/mock_alerts_pattern_rollup'; +import { auditbeatWithAllResults } from '../../../../mock/pattern_rollup/mock_auditbeat_pattern_rollup'; +import { packetbeatNoResults } from '../../../../mock/pattern_rollup/mock_packetbeat_pattern_rollup'; +import { PatternRollup } from '../../../../types'; + +const defaultBytesFormat = '0,0.[0]b'; +const formatBytes = (value: number | undefined) => + value != null ? numeral(value).format(defaultBytesFormat) : EMPTY_STAT; + +const ilmPhases = ['hot', 'warm', 'unmanaged']; +const patterns = ['.alerts-security.alerts-default', 'auditbeat-*', 'packetbeat-*']; + +const patternRollups: Record = { + '.alerts-security.alerts-default': alertIndexWithAllResults, + 'auditbeat-*': auditbeatWithAllResults, + 'packetbeat-*': packetbeatNoResults, +}; + +/** a valid `PatternRollup` that has an undefined `sizeInBytes` */ +const noSizeInBytes: Record = { + 'valid-*': { + docsCount: 19127, + error: null, + ilmExplain: null, + ilmExplainPhaseCounts: { + hot: 1, + warm: 0, + cold: 0, + frozen: 0, + unmanaged: 2, + }, + indices: 3, + pattern: 'valid-*', + results: undefined, + sizeInBytes: undefined, // <-- + stats: null, + }, +}; + +describe('helpers', () => { + describe('getPatternSizeInBytes', () => { + test('it returns the expected size when the pattern exists in the rollup', () => { + const pattern = 'auditbeat-*'; + + expect(getPatternSizeInBytes({ pattern, patternRollups })).toEqual( + auditbeatWithAllResults.sizeInBytes + ); + }); + + test('it returns zero when the pattern exists in the rollup, but does not have a sizeInBytes', () => { + const pattern = 'valid-*'; + + expect(getPatternSizeInBytes({ pattern, patternRollups: noSizeInBytes })).toEqual(0); + }); + + test('it returns zero when the pattern does NOT exist in the rollup', () => { + const pattern = 'does-not-exist-*'; + + expect(getPatternSizeInBytes({ pattern, patternRollups })).toEqual(0); + }); + }); + + describe('getPatternLegendItem', () => { + test('it returns the expected legend item', () => { + const pattern = 'auditbeat-*'; + + expect(getPatternLegendItem({ pattern, patternRollups })).toEqual({ + color: null, + ilmPhase: null, + index: null, + pattern, + sizeInBytes: auditbeatWithAllResults.sizeInBytes, + }); + }); + }); + + describe('getLegendItemsForPattern', () => { + test('it returns the expected legend items', () => { + const pattern = 'auditbeat-*'; + const flattenedBuckets = getFlattenedBuckets({ + ilmPhases, + patternRollups, + }); + + expect(getLegendItemsForPattern({ pattern, flattenedBuckets })).toEqual([ + { + color: euiThemeVars.euiColorSuccess, + ilmPhase: 'hot', + index: '.ds-auditbeat-8.6.1-2023.02.07-000001', + pattern: 'auditbeat-*', + sizeInBytes: 18791790, + }, + { + color: euiThemeVars.euiColorDanger, + ilmPhase: 'unmanaged', + index: 'auditbeat-custom-index-1', + pattern: 'auditbeat-*', + sizeInBytes: 28409, + }, + { + color: euiThemeVars.euiColorDanger, + ilmPhase: 'unmanaged', + index: 'auditbeat-custom-empty-index-1', + pattern: 'auditbeat-*', + sizeInBytes: 247, + }, + ]); + }); + }); + + describe('getLegendItems', () => { + test('it returns the expected legend items', () => { + const flattenedBuckets = getFlattenedBuckets({ + ilmPhases, + patternRollups, + }); + + expect(getLegendItems({ flattenedBuckets, patterns, patternRollups })).toEqual([ + { + color: null, + ilmPhase: null, + index: null, + pattern: '.alerts-security.alerts-default', + sizeInBytes: 29717961631, + }, + { + color: euiThemeVars.euiColorSuccess, + ilmPhase: 'hot', + index: '.internal.alerts-security.alerts-default-000001', + pattern: '.alerts-security.alerts-default', + sizeInBytes: 0, + }, + { color: null, ilmPhase: null, index: null, pattern: 'auditbeat-*', sizeInBytes: 18820446 }, + { + color: euiThemeVars.euiColorSuccess, + ilmPhase: 'hot', + index: '.ds-auditbeat-8.6.1-2023.02.07-000001', + pattern: 'auditbeat-*', + sizeInBytes: 18791790, + }, + { + color: euiThemeVars.euiColorDanger, + ilmPhase: 'unmanaged', + index: 'auditbeat-custom-index-1', + pattern: 'auditbeat-*', + sizeInBytes: 28409, + }, + { + color: euiThemeVars.euiColorDanger, + ilmPhase: 'unmanaged', + index: 'auditbeat-custom-empty-index-1', + pattern: 'auditbeat-*', + sizeInBytes: 247, + }, + { + color: null, + ilmPhase: null, + index: null, + pattern: 'packetbeat-*', + sizeInBytes: 1096520898, + }, + { + color: euiThemeVars.euiColorPrimary, + ilmPhase: 'hot', + index: '.ds-packetbeat-8.5.3-2023.02.04-000001', + pattern: 'packetbeat-*', + sizeInBytes: 584326147, + }, + { + color: euiThemeVars.euiColorPrimary, + ilmPhase: 'hot', + index: '.ds-packetbeat-8.6.1-2023.02.04-000001', + pattern: 'packetbeat-*', + sizeInBytes: 512194751, + }, + ]); + }); + }); + + describe('getFlattenedBuckets', () => { + test('it returns the expected flattened buckets', () => { + expect( + getFlattenedBuckets({ + ilmPhases, + patternRollups, + }) + ).toEqual([ + { + ilmPhase: 'hot', + incompatible: 0, + indexName: '.internal.alerts-security.alerts-default-000001', + pattern: '.alerts-security.alerts-default', + sizeInBytes: 0, + }, + { + ilmPhase: 'hot', + incompatible: 0, + indexName: '.ds-auditbeat-8.6.1-2023.02.07-000001', + pattern: 'auditbeat-*', + sizeInBytes: 18791790, + }, + { + ilmPhase: 'unmanaged', + incompatible: 1, + indexName: 'auditbeat-custom-empty-index-1', + pattern: 'auditbeat-*', + sizeInBytes: 247, + }, + { + ilmPhase: 'unmanaged', + incompatible: 3, + indexName: 'auditbeat-custom-index-1', + pattern: 'auditbeat-*', + sizeInBytes: 28409, + }, + { + ilmPhase: 'hot', + indexName: '.ds-packetbeat-8.6.1-2023.02.04-000001', + pattern: 'packetbeat-*', + sizeInBytes: 512194751, + }, + { + ilmPhase: 'hot', + indexName: '.ds-packetbeat-8.5.3-2023.02.04-000001', + pattern: 'packetbeat-*', + sizeInBytes: 584326147, + }, + ]); + }); + }); + + describe('getFillColor', () => { + test('it returns success when `incompatible` is zero', () => { + const incompatible = 0; + + expect(getFillColor(incompatible)).toEqual(euiThemeVars.euiColorSuccess); + }); + + test('it returns danger when `incompatible` is greater than 0', () => { + const incompatible = 1; + + expect(getFillColor(incompatible)).toEqual(euiThemeVars.euiColorDanger); + }); + + test('it returns the default color when `incompatible` is undefined', () => { + const incompatible = undefined; + + expect(getFillColor(incompatible)).toEqual(DEFAULT_INDEX_COLOR); + }); + }); + + describe('getPathToFlattenedBucketMap', () => { + test('it returns the expected map', () => { + const flattenedBuckets = getFlattenedBuckets({ + ilmPhases, + patternRollups, + }); + + expect(getPathToFlattenedBucketMap(flattenedBuckets)).toEqual({ + '.alerts-security.alerts-default.internal.alerts-security.alerts-default-000001': { + pattern: '.alerts-security.alerts-default', + indexName: '.internal.alerts-security.alerts-default-000001', + ilmPhase: 'hot', + incompatible: 0, + sizeInBytes: 0, + }, + 'auditbeat-*.ds-auditbeat-8.6.1-2023.02.07-000001': { + pattern: 'auditbeat-*', + indexName: '.ds-auditbeat-8.6.1-2023.02.07-000001', + ilmPhase: 'hot', + incompatible: 0, + sizeInBytes: 18791790, + }, + 'auditbeat-*auditbeat-custom-empty-index-1': { + pattern: 'auditbeat-*', + indexName: 'auditbeat-custom-empty-index-1', + ilmPhase: 'unmanaged', + incompatible: 1, + sizeInBytes: 247, + }, + 'auditbeat-*auditbeat-custom-index-1': { + pattern: 'auditbeat-*', + indexName: 'auditbeat-custom-index-1', + ilmPhase: 'unmanaged', + incompatible: 3, + sizeInBytes: 28409, + }, + 'packetbeat-*.ds-packetbeat-8.6.1-2023.02.04-000001': { + pattern: 'packetbeat-*', + indexName: '.ds-packetbeat-8.6.1-2023.02.04-000001', + ilmPhase: 'hot', + sizeInBytes: 512194751, + }, + 'packetbeat-*.ds-packetbeat-8.5.3-2023.02.04-000001': { + pattern: 'packetbeat-*', + indexName: '.ds-packetbeat-8.5.3-2023.02.04-000001', + ilmPhase: 'hot', + sizeInBytes: 584326147, + }, + }); + }); + }); + + describe('getGroupFromPath', () => { + it('returns the expected group from the path', () => { + expect( + getGroupFromPath([ + { + index: 0, + value: '__null_small_multiples_key__', + }, + { + index: 0, + value: '__root_key__', + }, + { + index: 0, + value: 'auditbeat-*', + }, + { + index: 1, + value: 'auditbeat-custom-empty-index-1', + }, + ]) + ).toEqual('auditbeat-*'); + }); + + it('returns undefined when path is an empty array', () => { + expect(getGroupFromPath([])).toBeUndefined(); + }); + + it('returns undefined when path is an array with only one value', () => { + expect( + getGroupFromPath([{ index: 0, value: '__null_small_multiples_key__' }]) + ).toBeUndefined(); + }); + }); + + describe('getLayersMultiDimensional', () => { + const layer0FillColor = 'transparent'; + const flattenedBuckets = getFlattenedBuckets({ + ilmPhases, + patternRollups, + }); + const pathToFlattenedBucketMap = getPathToFlattenedBucketMap(flattenedBuckets); + + it('returns the expected number of layers', () => { + expect( + getLayersMultiDimensional({ formatBytes, layer0FillColor, pathToFlattenedBucketMap }).length + ).toEqual(2); + }); + + it('returns the expected fillLabel valueFormatter function', () => { + getLayersMultiDimensional({ formatBytes, layer0FillColor, pathToFlattenedBucketMap }).forEach( + (x) => expect(x.fillLabel.valueFormatter(123)).toEqual('123B') + ); + }); + }); +}); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/helpers.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/helpers.ts new file mode 100644 index 0000000000000..09ed53402a89f --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/helpers.ts @@ -0,0 +1,223 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { Datum, Key, ArrayNode } from '@elastic/charts'; +import { euiThemeVars } from '@kbn/ui-theme'; +import { orderBy } from 'lodash/fp'; + +import { getSizeInBytes } from '../../../../helpers'; +import { getIlmPhase } from '../../../pattern/helpers'; +import { PatternRollup } from '../../../../types'; + +export interface LegendItem { + color: string | null; + ilmPhase: string | null; + index: string | null; + pattern: string; + sizeInBytes: number; +} + +export interface FlattenedBucket { + ilmPhase: string | undefined; + incompatible: number | undefined; + indexName: string | undefined; + pattern: string; + sizeInBytes: number; +} + +export const getPatternSizeInBytes = ({ + pattern, + patternRollups, +}: { + pattern: string; + patternRollups: Record; +}): number => { + if (patternRollups[pattern] != null) { + return patternRollups[pattern].sizeInBytes ?? 0; + } else { + return 0; + } +}; + +export const getPatternLegendItem = ({ + pattern, + patternRollups, +}: { + pattern: string; + patternRollups: Record; +}): LegendItem => ({ + color: null, + ilmPhase: null, + index: null, + pattern, + sizeInBytes: getPatternSizeInBytes({ pattern, patternRollups }), +}); + +export const getLegendItemsForPattern = ({ + pattern, + flattenedBuckets, +}: { + pattern: string; + flattenedBuckets: FlattenedBucket[]; +}): LegendItem[] => + orderBy( + ['sizeInBytes'], + ['desc'], + flattenedBuckets + .filter((x) => x.pattern === pattern) + .map((flattenedBucket) => ({ + color: getFillColor(flattenedBucket.incompatible), + ilmPhase: flattenedBucket.ilmPhase ?? null, + index: flattenedBucket.indexName ?? null, + pattern: flattenedBucket.pattern, + sizeInBytes: flattenedBucket.sizeInBytes ?? 0, + })) + ); + +export const getLegendItems = ({ + patterns, + flattenedBuckets, + patternRollups, +}: { + patterns: string[]; + flattenedBuckets: FlattenedBucket[]; + patternRollups: Record; +}): LegendItem[] => + patterns.reduce( + (acc, pattern) => [ + ...acc, + getPatternLegendItem({ pattern, patternRollups }), + ...getLegendItemsForPattern({ pattern, flattenedBuckets }), + ], + [] + ); + +export const getFlattenedBuckets = ({ + ilmPhases, + patternRollups, +}: { + ilmPhases: string[]; + patternRollups: Record; +}): FlattenedBucket[] => + Object.values(patternRollups).reduce((acc, patternRollup) => { + // enables fast lookup of valid phase names: + const ilmPhasesMap = ilmPhases.reduce>( + (phasesMap, phase) => ({ ...phasesMap, [phase]: 0 }), + {} + ); + const { ilmExplain, pattern, results, stats } = patternRollup; + + if (ilmExplain != null && stats != null) { + return [ + ...acc, + ...Object.entries(stats).reduce( + (validStats, [indexName, indexStats]) => { + const ilmPhase = getIlmPhase(ilmExplain[indexName]); + const isSelectedPhase = ilmPhase != null && ilmPhasesMap[ilmPhase] != null; + + if (isSelectedPhase) { + const incompatible = + results != null && results[indexName] != null + ? results[indexName].incompatible + : undefined; + const sizeInBytes = getSizeInBytes({ indexName, stats }); + + return [ + ...validStats, + { + ilmPhase, + incompatible, + indexName, + pattern, + sizeInBytes, + }, + ]; + } else { + return validStats; + } + }, + [] + ), + ]; + } + + return acc; + }, []); + +const groupByRollup = (d: Datum) => d.pattern; // the treemap is grouped by this field + +export const DEFAULT_INDEX_COLOR = euiThemeVars.euiColorPrimary; + +export const getFillColor = (incompatible: number | undefined): string => { + if (incompatible === 0) { + return euiThemeVars.euiColorSuccess; + } else if (incompatible != null && incompatible > 0) { + return euiThemeVars.euiColorDanger; + } else { + return DEFAULT_INDEX_COLOR; + } +}; + +export const getPathToFlattenedBucketMap = ( + flattenedBuckets: FlattenedBucket[] +): Record => + flattenedBuckets.reduce>( + (acc, { pattern, indexName, ...remaining }) => ({ + ...acc, + [`${pattern}${indexName}`]: { pattern, indexName, ...remaining }, + }), + {} + ); + +/** + * Extracts the first group name from the data representing the second group + */ +export const getGroupFromPath = (path: ArrayNode['path']): string | undefined => { + const OFFSET_FROM_END = 2; // The offset from the end of the path array containing the group + const groupIndex = path.length - OFFSET_FROM_END; + return groupIndex > 0 ? path[groupIndex].value : undefined; +}; + +export const getLayersMultiDimensional = ({ + formatBytes, + layer0FillColor, + pathToFlattenedBucketMap, +}: { + formatBytes: (value: number | undefined) => string; + layer0FillColor: string; + pathToFlattenedBucketMap: Record; +}) => { + const valueFormatter = (d: number) => formatBytes(d); + + return [ + { + fillLabel: { + valueFormatter, + }, + groupByRollup, + nodeLabel: (ilmPhase: Datum) => ilmPhase, + shape: { + fillColor: layer0FillColor, + }, + }, + { + fillLabel: { + valueFormatter, + }, + groupByRollup: (d: Datum) => d.indexName, + nodeLabel: (indexName: Datum) => indexName, + shape: { + fillColor: (indexName: Key, sortIndex: number, node: Pick) => { + const pattern = getGroupFromPath(node.path) ?? ''; + const flattenedBucket = pathToFlattenedBucketMap[`${pattern}${indexName}`]; + + return getFillColor(flattenedBucket?.incompatible); + }, + }, + }, + ]; +}; diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/index.test.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/index.test.tsx new file mode 100644 index 0000000000000..366fb487309c3 --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/index.test.tsx @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DARK_THEME } from '@elastic/charts'; +import numeral from '@elastic/numeral'; +import { render, screen } from '@testing-library/react'; +import React from 'react'; + +import { EMPTY_STAT } from '../../../../helpers'; +import { alertIndexWithAllResults } from '../../../../mock/pattern_rollup/mock_alerts_pattern_rollup'; +import { auditbeatWithAllResults } from '../../../../mock/pattern_rollup/mock_auditbeat_pattern_rollup'; +import { packetbeatNoResults } from '../../../../mock/pattern_rollup/mock_packetbeat_pattern_rollup'; +import { TestProviders } from '../../../../mock/test_providers/test_providers'; +import { PatternRollup } from '../../../../types'; +import { Props, StorageDetails } from '.'; + +const defaultBytesFormat = '0,0.[0]b'; +const formatBytes = (value: number | undefined) => + value != null ? numeral(value).format(defaultBytesFormat) : EMPTY_STAT; + +const ilmPhases = ['hot', 'warm', 'unmanaged']; +const patterns = ['.alerts-security.alerts-default', 'auditbeat-*', 'packetbeat-*']; + +const patternRollups: Record = { + '.alerts-security.alerts-default': alertIndexWithAllResults, + 'auditbeat-*': auditbeatWithAllResults, + 'packetbeat-*': packetbeatNoResults, +}; + +const onIndexSelected = jest.fn(); + +const defaultProps: Props = { + formatBytes, + ilmPhases, + onIndexSelected, + patternRollups, + patterns, + theme: DARK_THEME, +}; + +describe('StorageDetails', () => { + beforeEach(() => { + jest.clearAllMocks(); + + render( + + + + ); + }); + + test('it renders the treemap', () => { + expect(screen.getByTestId('storageTreemap').querySelector('.echChart')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/index.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/index.tsx new file mode 100644 index 0000000000000..26340b31286fa --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/index.tsx @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Theme } from '@elastic/charts'; +import React, { useMemo } from 'react'; + +import { getFlattenedBuckets } from './helpers'; +import { StorageTreemap } from '../../../storage_treemap'; +import { DEFAULT_MAX_CHART_HEIGHT, StorageTreemapContainer } from '../../../tabs/styles'; +import { PatternRollup, SelectedIndex } from '../../../../types'; + +export interface Props { + formatBytes: (value: number | undefined) => string; + ilmPhases: string[]; + onIndexSelected: ({ indexName, pattern }: SelectedIndex) => void; + patternRollups: Record; + patterns: string[]; + theme: Theme; +} + +const StorageDetailsComponent: React.FC = ({ + formatBytes, + ilmPhases, + onIndexSelected, + patternRollups, + patterns, + theme, +}) => { + const flattenedBuckets = useMemo( + () => + getFlattenedBuckets({ + ilmPhases, + patternRollups, + }), + [ilmPhases, patternRollups] + ); + + return ( + + + + ); +}; + +StorageDetailsComponent.displayName = 'StorageDetailsComponent'; +export const StorageDetails = React.memo(StorageDetailsComponent); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/take_action_menu/translations.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/translations.ts similarity index 51% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/take_action_menu/translations.ts rename to x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/translations.ts index b5f77c455c1a2..6b8ffed70f8c9 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/take_action_menu/translations.ts +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/translations.ts @@ -7,9 +7,16 @@ import { i18n } from '@kbn/i18n'; -export const TAKE_ACTION = i18n.translate( - 'ecsDataQualityDashboard.takeActionMenu.takeActionButton', +export const INDICES_TAB_TITLE = i18n.translate( + 'ecsDataQualityDashboard.body.tabs.indicesTabTitle', { - defaultMessage: 'Take action', + defaultMessage: 'Indices', + } +); + +export const STORAGE_TAB_TITLE = i18n.translate( + 'ecsDataQualityDashboard.body.tabs.storageTabTitle', + { + defaultMessage: 'Storage', } ); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/index.test.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/index.test.tsx new file mode 100644 index 0000000000000..0f27f307f7913 --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/index.test.tsx @@ -0,0 +1,100 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { DARK_THEME } from '@elastic/charts'; +import numeral from '@elastic/numeral'; +import { render, screen } from '@testing-library/react'; +import React from 'react'; + +import { EMPTY_STAT } from '../../helpers'; +import { TestProviders } from '../../mock/test_providers/test_providers'; +import { Body } from '.'; + +const defaultBytesFormat = '0,0.[0]b'; +const formatBytes = (value: number | undefined) => + value != null ? numeral(value).format(defaultBytesFormat) : EMPTY_STAT; + +const defaultNumberFormat = '0,0.[000]'; +const formatNumber = (value: number | undefined) => + value != null ? numeral(value).format(defaultNumberFormat) : EMPTY_STAT; + +const ilmPhases: string[] = ['hot', 'warm', 'unmanaged']; + +describe('IndexInvalidValues', () => { + test('it renders the data quality summary', () => { + render( + + + + ); + + expect(screen.getByTestId('dataQualitySummary')).toBeInTheDocument(); + }); + + describe('patterns', () => { + const patterns = ['.alerts-security.alerts-default', 'auditbeat-*', 'logs-*', 'packetbeat-*']; + + patterns.forEach((pattern) => { + test(`it renders the '${pattern}' pattern`, () => { + render( + + + + ); + + expect(screen.getByTestId(`${pattern}PatternPanel`)).toBeInTheDocument(); + }); + }); + + test('it renders the expected number of spacers', async () => { + render( + + + + ); + + const items = await screen.findAllByTestId('bodyPatternSpacer'); + expect(items).toHaveLength(patterns.length - 1); + }); + }); +}); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/index.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/index.tsx index 87aed178043cc..69de3b8c110e5 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/index.tsx +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/body/index.tsx @@ -17,14 +17,15 @@ import type { import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import React from 'react'; +import { DataQualityDetails } from './data_quality_details'; import { DataQualitySummary } from '../data_quality_summary'; -import { Pattern } from '../pattern'; import { useResultsRollup } from '../../use_results_rollup'; interface Props { addSuccessToast: (toast: { title: string }) => void; canUserCreateAndReadCases: () => boolean; - defaultNumberFormat: string; + formatBytes: (value: number | undefined) => string; + formatNumber: (value: number | undefined) => string; getGroupByFieldsOnClick: ( elements: Array< | FlameElementEvent @@ -55,7 +56,8 @@ interface Props { const BodyComponent: React.FC = ({ addSuccessToast, canUserCreateAndReadCases, - defaultNumberFormat, + formatBytes, + formatNumber, getGroupByFieldsOnClick, ilmPhases, lastChecked, @@ -72,6 +74,7 @@ const BodyComponent: React.FC = ({ totalIncompatible, totalIndices, totalIndicesChecked, + totalSizeInBytes, updatePatternIndexNames, updatePatternRollup, } = useResultsRollup({ ilmPhases, patterns }); @@ -82,7 +85,8 @@ const BodyComponent: React.FC = ({ = ({ totalIncompatible={totalIncompatible} totalIndices={totalIndices} totalIndicesChecked={totalIndicesChecked} + totalSizeInBytes={totalSizeInBytes} onCheckCompleted={onCheckCompleted} />
- {patterns.map((pattern, i) => ( - - - {i !== patterns.length - 1 ? : null} - - ))} + + +
); }; diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/check_status/index.test.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/check_status/index.test.tsx new file mode 100644 index 0000000000000..5085db2a93e51 --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/check_status/index.test.tsx @@ -0,0 +1,212 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { act, render, screen } from '@testing-library/react'; +import React from 'react'; + +import { TestProviders } from '../../../mock/test_providers/test_providers'; +import { IndexToCheck } from '../../../types'; +import { CheckStatus, EMPTY_LAST_CHECKED_DATE } from '.'; + +const indexToCheck: IndexToCheck = { + pattern: 'auditbeat-*', + indexName: '.ds-auditbeat-8.6.1-2023.02.13-000001', +}; +const checkAllIndiciesChecked = 2; +const checkAllTotalIndiciesToCheck = 3; + +describe('CheckStatus', () => { + describe('when `indexToCheck` is not null', () => { + beforeEach(() => { + render( + + + + ); + }); + + test('it renders progress with the expected max value', () => { + expect(screen.getByTestId('progress')).toHaveAttribute( + 'max', + String(checkAllTotalIndiciesToCheck) + ); + }); + + test('it renders progress with the expected current value', () => { + expect(screen.getByTestId('progress')).toHaveAttribute( + 'value', + String(checkAllIndiciesChecked) + ); + }); + + test('it renders the expected "checking " message', () => { + expect(screen.getByTestId('checking')).toHaveTextContent( + `Checking ${indexToCheck.indexName}` + ); + }); + + test('it does NOT render the last checked message', () => { + expect(screen.queryByTestId('lastChecked')).not.toBeInTheDocument(); + }); + }); + + describe('when `indexToCheck` is null', () => { + beforeEach(() => { + render( + + + + ); + }); + + test('it does NOT render the progress bar', () => { + expect(screen.queryByTestId('progress')).not.toBeInTheDocument(); + }); + + test('it does NOT render the "checking " message', () => { + expect(screen.queryByTestId('checking')).not.toBeInTheDocument(); + }); + + test('it renders the expected last checked message', () => { + expect(screen.getByTestId('lastChecked')).toHaveTextContent(EMPTY_LAST_CHECKED_DATE); + }); + }); + + test('it renders the errors popover when errors have occurred', () => { + const errorSummary = [ + { + pattern: '.alerts-security.alerts-default', + indexName: null, + error: 'Error loading stats: Error: Forbidden', + }, + ]; + + render( + + + + ); + + expect(screen.getByTestId('errorsPopover')).toBeInTheDocument(); + }); + + test('it does NOT render the errors popover when errors have NOT occurred', () => { + render( + + + + ); + + expect(screen.queryByTestId('errorsPopover')).not.toBeInTheDocument(); + }); + + test('it invokes the `setLastChecked` callback when indexToCheck is not null', () => { + jest.useFakeTimers(); + const date = '2023-03-28T22:27:28.159Z'; + jest.setSystemTime(new Date(date)); + + const setLastChecked = jest.fn(); + + render( + + + + ); + + expect(setLastChecked).toBeCalledWith(date); + jest.useRealTimers(); + }); + + test('it updates the formatted date', async () => { + jest.useFakeTimers(); + const date = '2023-03-28T23:27:28.159Z'; + jest.setSystemTime(new Date(date)); + + const { rerender } = render( + + + + ); + + // re-render with an updated `lastChecked` + const lastChecked = '2023-03-28T22:27:28.159Z'; + + act(() => { + jest.advanceTimersByTime(1000 * 61); + }); + + rerender( + + + + ); + + act(() => { + // once again, advance time + jest.advanceTimersByTime(1000 * 61); + }); + + expect(await screen.getByTestId('lastChecked')).toHaveTextContent('Last checked: an hour ago'); + jest.useRealTimers(); + }); +}); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/check_status/index.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/check_status/index.tsx index 3ff69274ba06e..9245b0adee84c 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/check_status/index.tsx +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/check_status/index.tsx @@ -60,30 +60,31 @@ const CheckStatusComponent: React.FC = ({ }, [lastChecked]); return ( - + {indexToCheck != null && ( <> - + + {i18n.CHECKING(indexToCheck.indexName)} + - - {i18n.CHECKING(indexToCheck.indexName)} - + )} {indexToCheck == null && ( - + {i18n.LAST_CHECKED} {': '} {formattedDate} diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_popover/index.test.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_popover/index.test.tsx new file mode 100644 index 0000000000000..064ec92a1ca81 --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_popover/index.test.tsx @@ -0,0 +1,97 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import userEvent from '@testing-library/user-event'; +import { act, render, screen } from '@testing-library/react'; +import React from 'react'; + +import { TestProviders } from '../../../mock/test_providers/test_providers'; +import { ErrorsPopover } from '.'; + +const mockCopyToClipboard = jest.fn((value) => true); +jest.mock('@elastic/eui', () => { + const original = jest.requireActual('@elastic/eui'); + return { + ...original, + copyToClipboard: (value: string) => mockCopyToClipboard(value), + }; +}); + +const errorSummary = [ + { + pattern: '.alerts-security.alerts-default', + indexName: null, + error: 'Error loading stats: Error: Forbidden', + }, +]; + +describe('ErrorsPopover', () => { + beforeEach(() => { + document.execCommand = jest.fn(); + }); + + test('it disables the view errors button when `errorSummary` is empty', () => { + render( + + + + ); + + expect(screen.getByTestId('viewErrors')).toBeDisabled(); + }); + + test('it enables the view errors button when `errorSummary` is NOT empty', () => { + render( + + + + ); + + expect(screen.getByTestId('viewErrors')).not.toBeDisabled(); + }); + + describe('popover content', () => { + const addSuccessToast = jest.fn(); + + beforeEach(() => { + jest.resetAllMocks(); + + render( + + + + ); + + const viewErrorsButton = screen.getByTestId('viewErrors'); + + act(() => { + userEvent.click(viewErrorsButton); + }); + }); + + test('it renders the expected callout content', () => { + expect(screen.getByTestId('callout')).toHaveTextContent( + "ErrorsSome indices were not checked for Data QualityErrors may occur when pattern or index metadata is temporarily unavailable, or because you don't have the privileges required for accessThe following privileges are required to check an index:monitor or manageview_index_metadatareadCopy to clipboard" + ); + }); + + test('it invokes `addSuccessToast` when the copy button is clicked', () => { + const copyToClipboardButton = screen.getByTestId('copyToClipboard'); + act(() => { + userEvent.click(copyToClipboardButton, undefined, { skipPointerEventsCheck: true }); + }); + + expect(addSuccessToast).toBeCalledWith({ title: 'Copied errors to the clipboard' }); + }); + + test('it renders the expected error summary text in the errors viewer', () => { + expect(screen.getByTestId('errorsViewer').textContent?.includes(errorSummary[0].error)).toBe( + true + ); + }); + }); +}); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_popover/index.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_popover/index.tsx index b9e0fc61ec545..8f80e3fa3cab5 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_popover/index.tsx +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_popover/index.tsx @@ -60,6 +60,7 @@ const ErrorsPopoverComponent: React.FC = ({ addSuccessToast, errorSummary () => ( = ({ addSuccessToast, errorSummary - +

{i18n.ERRORS_CALLOUT_SUMMARY}

{i18n.ERRORS_MAY_OCCUR}

@@ -96,7 +98,13 @@ const ErrorsPopoverComponent: React.FC = ({ addSuccessToast, errorSummary - + {i18n.COPY_TO_CLIPBOARD}
diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/helpers.test.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/helpers.test.tsx new file mode 100644 index 0000000000000..1954e92ae5fc7 --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/helpers.test.tsx @@ -0,0 +1,122 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { render, screen } from '@testing-library/react'; +import { omit } from 'lodash/fp'; +import React from 'react'; + +import { getErrorsViewerTableColumns } from './helpers'; +import { TestProviders } from '../../../mock/test_providers/test_providers'; +import { ErrorSummary } from '../../../types'; + +const errorSummary: ErrorSummary[] = [ + { + pattern: '.alerts-security.alerts-default', + indexName: null, + error: 'Error loading stats: Error: Forbidden', + }, + { + error: + 'Error: Error loading unallowed values for index auditbeat-7.2.1-2023.02.13-000001: Forbidden', + indexName: 'auditbeat-7.2.1-2023.02.13-000001', + pattern: 'auditbeat-*', + }, +]; + +const noIndexName: ErrorSummary = errorSummary[0]; // <-- indexName: null +const hasIndexName: ErrorSummary = errorSummary[1]; + +describe('helpers', () => { + describe('getCommonTableColumns', () => { + test('it returns the expected column configuration', () => { + const columns = getErrorsViewerTableColumns().map((x) => omit('render', x)); + + expect(columns).toEqual([ + { + field: 'pattern', + name: 'Pattern', + sortable: true, + truncateText: false, + width: '25%', + }, + { + field: 'indexName', + name: 'Index', + sortable: false, + truncateText: false, + width: '25%', + }, + { + field: 'error', + name: 'Error', + sortable: false, + truncateText: false, + width: '50%', + }, + ]); + }); + + describe('indexName column render()', () => { + describe('when the `ErrorSummary` has an `indexName`', () => { + beforeEach(() => { + const columns = getErrorsViewerTableColumns(); + const indexNameRender = columns[1].render; + + render( + + {indexNameRender != null && indexNameRender(hasIndexName.indexName, hasIndexName)} + + ); + }); + + test('it renders the expected `indexName`', () => { + expect(screen.getByTestId('indexName')).toHaveTextContent(String(hasIndexName.indexName)); + }); + + test('it does NOT render the placeholder', () => { + expect(screen.queryByTestId('emptyPlaceholder')).not.toBeInTheDocument(); + }); + }); + + describe('when the `ErrorSummary` does NOT have an `indexName`', () => { + beforeEach(() => { + const columns = getErrorsViewerTableColumns(); + const indexNameRender = columns[1].render; + + render( + + {indexNameRender != null && indexNameRender(noIndexName.indexName, noIndexName)} + + ); + }); + + test('it does NOT render `indexName`', () => { + expect(screen.queryByTestId('indexName')).not.toBeInTheDocument(); + }); + + test('it renders the placeholder', () => { + expect(screen.getByTestId('emptyPlaceholder')).toBeInTheDocument(); + }); + }); + }); + + describe('indexName error render()', () => { + test('it renders the expected `error`', () => { + const columns = getErrorsViewerTableColumns(); + const indexNameRender = columns[2].render; + + render( + + {indexNameRender != null && indexNameRender(hasIndexName.error, hasIndexName)} + + ); + + expect(screen.getByTestId('error')).toHaveTextContent(hasIndexName.error); + }); + }); + }); +}); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/helpers.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/helpers.tsx index caac710cf8d13..35a4a74cca875 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/helpers.tsx +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/helpers.tsx @@ -28,7 +28,12 @@ export const getErrorsViewerTableColumns = (): Array (indexName != null && indexName !== '' ? indexName : EMPTY_PLACEHOLDER), + render: (indexName: string | null) => + indexName != null && indexName !== '' ? ( + {indexName} + ) : ( + {EMPTY_PLACEHOLDER} + ), sortable: false, truncateText: false, width: '25%', @@ -36,7 +41,7 @@ export const getErrorsViewerTableColumns = (): Array {errorText}, + render: (errorText) => {errorText}, sortable: false, truncateText: false, width: '50%', diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/index.test.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/index.test.tsx new file mode 100644 index 0000000000000..a1b6346eb2b8d --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/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 { render, screen } from '@testing-library/react'; +import React from 'react'; + +import { TestProviders } from '../../../mock/test_providers/test_providers'; +import { ERROR, INDEX, PATTERN } from './translations'; +import { ErrorSummary } from '../../../types'; +import { ErrorsViewer } from '.'; + +interface ExpectedColumns { + id: string; + expected: string; +} + +const errorSummary: ErrorSummary[] = [ + { + pattern: '.alerts-security.alerts-default', + indexName: null, + error: 'Error loading stats: Error: Forbidden', + }, + { + error: + 'Error: Error loading unallowed values for index auditbeat-7.2.1-2023.02.13-000001: Forbidden', + indexName: 'auditbeat-7.2.1-2023.02.13-000001', + pattern: 'auditbeat-*', + }, +]; + +describe('ErrorsViewer', () => { + const expectedColumns: ExpectedColumns[] = [ + { + id: 'pattern', + expected: PATTERN, + }, + { + id: 'indexName', + expected: INDEX, + }, + { + id: 'error', + expected: ERROR, + }, + ]; + + expectedColumns.forEach(({ id, expected }, i) => { + test(`it renders the expected '${id}' column header`, () => { + render( + + + + ); + + expect(screen.getByTestId(`tableHeaderCell_${id}_${i}`)).toHaveTextContent(expected); + }); + }); + + test(`it renders the expected the errors`, () => { + render( + + + + ); + + expect( + screen + .getAllByTestId('error') + .map((x) => x.textContent ?? '') + .reduce((acc, x) => acc.concat(x), '') + ).toEqual(`${errorSummary[0].error}${errorSummary[1].error}`); + }); +}); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/index.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/index.tsx index a40094c96b399..2336abe79c651 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/index.tsx +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/index.tsx @@ -29,7 +29,7 @@ const ErrorsViewerComponent: React.FC = ({ errorSummary }) => { const columns = useMemo(() => getErrorsViewerTableColumns(), []); return ( - + + value != null ? numeral(value).format(defaultBytesFormat) : EMPTY_STAT; + +const defaultNumberFormat = '0,0.[000]'; +const formatNumber = (value: number | undefined) => + value != null ? numeral(value).format(defaultNumberFormat) : EMPTY_STAT; + +const ilmPhases = ['hot', 'warm', 'unmanaged']; +const patterns = ['.alerts-security.alerts-default', 'auditbeat-*', 'packetbeat-*']; + +const patternRollups: Record = { + '.alerts-security.alerts-default': alertIndexWithAllResults, + 'auditbeat-*': auditbeatWithAllResults, + 'packetbeat-*': packetbeatNoResults, +}; + +const patternIndexNames: Record = { + 'auditbeat-*': [ + '.ds-auditbeat-8.6.1-2023.02.07-000001', + 'auditbeat-custom-empty-index-1', + 'auditbeat-custom-index-1', + ], + '.alerts-security.alerts-default': ['.internal.alerts-security.alerts-default-000001'], + 'packetbeat-*': [ + '.ds-packetbeat-8.5.3-2023.02.04-000001', + '.ds-packetbeat-8.6.1-2023.02.04-000001', + ], +}; + +const lastChecked = '2023-03-28T23:27:28.159Z'; + +const totalDocsCount = getTotalDocsCount(patternRollups); +const totalIncompatible = getTotalIncompatible(patternRollups); +const totalIndices = getTotalIndices(patternRollups); +const totalIndicesChecked = getTotalIndicesChecked(patternRollups); +const totalSizeInBytes = getTotalSizeInBytes(patternRollups); + +const defaultProps: Props = { + addSuccessToast: jest.fn(), + canUserCreateAndReadCases: jest.fn(), + formatBytes, + formatNumber, + ilmPhases, + lastChecked, + openCreateCaseFlyout: jest.fn(), + patternIndexNames, + patternRollups, + patterns, + setLastChecked: jest.fn(), + totalDocsCount, + totalIncompatible, + totalIndices, + totalIndicesChecked, + totalSizeInBytes, + onCheckCompleted: jest.fn(), +}; + +describe('DataQualitySummary', () => { + beforeEach(() => { + jest.clearAllMocks(); + + render( + + + + ); + }); + + test('it renders the summary actions', () => { + expect(screen.getByTestId('summaryActions')).toBeInTheDocument(); + }); + + test('it renders the stats rollup', () => { + expect(screen.getByTestId('statsRollup')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/index.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/index.tsx index c6874d861ddb8..d3f9ad9d23303 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/index.tsx +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/index.tsx @@ -5,15 +5,14 @@ * 2.0. */ -import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiSpacer } from '@elastic/eui'; -import React, { useCallback, useMemo, useState } from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; +import React, { useMemo } from 'react'; import styled from 'styled-components'; -import { CheckStatus } from './check_status'; +import { getErrorSummaries } from '../../helpers'; import { StatsRollup } from '../pattern/pattern_summary/stats_rollup'; import { SummaryActions } from './summary_actions'; -import type { IndexToCheck, OnCheckCompleted, PatternRollup } from '../../types'; -import { getErrorSummaries } from '../../helpers'; +import type { OnCheckCompleted, PatternRollup } from '../../types'; const MAX_SUMMARY_ACTIONS_CONTAINER_WIDTH = 400; const MIN_SUMMARY_ACTIONS_CONTAINER_WIDTH = 235; @@ -24,10 +23,11 @@ const SummaryActionsContainerFlexItem = styled(EuiFlexItem)` padding-right: ${({ theme }) => theme.eui.euiSizeXL}; `; -interface Props { +export interface Props { addSuccessToast: (toast: { title: string }) => void; canUserCreateAndReadCases: () => boolean; - defaultNumberFormat: string; + formatBytes: (value: number | undefined) => string; + formatNumber: (value: number | undefined) => string; ilmPhases: string[]; lastChecked: string; openCreateCaseFlyout: ({ @@ -45,13 +45,15 @@ interface Props { totalIncompatible: number | undefined; totalIndices: number | undefined; totalIndicesChecked: number | undefined; + totalSizeInBytes: number | undefined; onCheckCompleted: OnCheckCompleted; } const DataQualitySummaryComponent: React.FC = ({ addSuccessToast, canUserCreateAndReadCases, - defaultNumberFormat, + formatBytes, + formatNumber, ilmPhases, lastChecked, openCreateCaseFlyout, @@ -63,64 +65,46 @@ const DataQualitySummaryComponent: React.FC = ({ totalIncompatible, totalIndices, totalIndicesChecked, + totalSizeInBytes, onCheckCompleted, }) => { - const [indexToCheck, setIndexToCheck] = useState(null); - - const [checkAllIndiciesChecked, setCheckAllIndiciesChecked] = useState(0); - const [checkAllTotalIndiciesToCheck, setCheckAllTotalIndiciesToCheck] = useState(0); - - const incrementCheckAllIndiciesChecked = useCallback(() => { - setCheckAllIndiciesChecked((current) => current + 1); - }, []); - const errorSummary = useMemo(() => getErrorSummaries(patternRollups), [patternRollups]); return ( - + - - - - diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/actions/index.test.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/actions/index.test.tsx new file mode 100644 index 0000000000000..02b04225a544e --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/actions/index.test.tsx @@ -0,0 +1,104 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 userEvent from '@testing-library/user-event'; +import { render, screen } from '@testing-library/react'; +import React from 'react'; + +import { TestProviders } from '../../../../mock/test_providers/test_providers'; +import { Props, Actions } from '.'; + +const mockCopyToClipboard = jest.fn((value) => true); +jest.mock('@elastic/eui', () => { + const original = jest.requireActual('@elastic/eui'); + return { + ...original, + copyToClipboard: (value: string) => mockCopyToClipboard(value), + }; +}); + +const ilmPhases = ['hot', 'warm', 'unmanaged']; + +const defaultProps: Props = { + addSuccessToast: jest.fn(), + canUserCreateAndReadCases: () => true, + getMarkdownComments: () => [], + ilmPhases, + openCreateCaseFlyout: jest.fn(), +}; + +describe('Actions', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('when the action buttons are clicked', () => { + beforeEach(() => { + render( + + + + ); + }); + + test('it invokes openCreateCaseFlyout when the add to new case button is clicked', () => { + const button = screen.getByTestId('addToNewCase'); + + userEvent.click(button); + + expect(defaultProps.openCreateCaseFlyout).toBeCalled(); + }); + + test('it invokes addSuccessToast when the copy to clipboard button is clicked', () => { + const button = screen.getByTestId('copyToClipboard'); + + userEvent.click(button); + + expect(defaultProps.addSuccessToast).toBeCalledWith({ + title: 'Copied results to the clipboard', + }); + }); + }); + + test('it disables the add to new case button when the user cannot create cases', () => { + const canUserCreateAndReadCases = () => false; + + render( + + + + ); + + const button = screen.getByTestId('addToNewCase'); + + expect(button).toBeDisabled(); + }); + + test('it disables the add to new case button when `ilmPhases` is empty', () => { + render( + + + + ); + + const button = screen.getByTestId('addToNewCase'); + + expect(button).toBeDisabled(); + }); + + test('it disables the copy to clipboard button when `ilmPhases` is empty', () => { + render( + + + + ); + + const button = screen.getByTestId('copyToClipboard'); + + expect(button).toBeDisabled(); + }); +}); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/actions/index.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/actions/index.tsx new file mode 100644 index 0000000000000..549f420c3fa9d --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/actions/index.tsx @@ -0,0 +1,98 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { copyToClipboard, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import React, { useCallback } from 'react'; + +import { + ADD_TO_NEW_CASE, + COPIED_RESULTS_TOAST_TITLE, + COPY_TO_CLIPBOARD, +} from '../../../../translations'; +import { useAddToNewCase } from '../../../../use_add_to_new_case'; + +export interface Props { + addSuccessToast: (toast: { title: string }) => void; + canUserCreateAndReadCases: () => boolean; + getMarkdownComments: () => string[]; + ilmPhases: string[]; + openCreateCaseFlyout: ({ + comments, + headerContent, + }: { + comments: string[]; + headerContent?: React.ReactNode; + }) => void; +} + +const ActionsComponent: React.FC = ({ + addSuccessToast, + canUserCreateAndReadCases, + getMarkdownComments, + ilmPhases, + openCreateCaseFlyout, +}) => { + const { disabled: addToNewCaseDisabled, onAddToNewCase } = useAddToNewCase({ + canUserCreateAndReadCases, + openCreateCaseFlyout, + }); + + const onClickAddToCase = useCallback( + () => onAddToNewCase([getMarkdownComments().join('\n')]), + [getMarkdownComments, onAddToNewCase] + ); + + const onCopy = useCallback(() => { + const markdown = getMarkdownComments().join('\n'); + copyToClipboard(markdown); + + addSuccessToast({ + title: COPIED_RESULTS_TOAST_TITLE, + }); + }, [addSuccessToast, getMarkdownComments]); + + const addToNewCaseContextMenuOnClick = useCallback(() => { + onClickAddToCase(); + }, [onClickAddToCase]); + + const disableAll = ilmPhases.length === 0; + + return ( + + + + {ADD_TO_NEW_CASE} + + + + + + {COPY_TO_CLIPBOARD} + + + + ); +}; + +ActionsComponent.displayName = 'ActionsComponent'; + +export const Actions = React.memo(ActionsComponent); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/check_index.test.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/check_index.test.ts new file mode 100644 index 0000000000000..fd457193a9c6f --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/check_index.test.ts @@ -0,0 +1,338 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { EcsFlat, EcsVersion } from '@kbn/ecs'; + +import { checkIndex, EMPTY_PARTITIONED_FIELD_METADATA } from './check_index'; +import { EMPTY_STAT } from '../../../../helpers'; +import { mockMappingsResponse } from '../../../../mock/mappings_response/mock_mappings_response'; +import { mockUnallowedValuesResponse } from '../../../../mock/unallowed_values/mock_unallowed_values'; +import { EcsMetadata, UnallowedValueRequestItem } from '../../../../types'; + +const ecsMetadata = EcsFlat as unknown as Record; + +let mockFetchMappings = jest.fn( + ({ + abortController, + patternOrIndexName, + }: { + abortController: AbortController; + patternOrIndexName: string; + }) => + new Promise((resolve) => { + resolve(mockMappingsResponse); // happy path + }) +); + +jest.mock('../../../../use_mappings/helpers', () => ({ + fetchMappings: ({ + abortController, + patternOrIndexName, + }: { + abortController: AbortController; + patternOrIndexName: string; + }) => + mockFetchMappings({ + abortController, + patternOrIndexName, + }), +})); + +const mockFetchUnallowedValues = jest.fn( + ({ + abortController, + indexName, + requestItems, + }: { + abortController: AbortController; + indexName: string; + requestItems: UnallowedValueRequestItem[]; + }) => new Promise((resolve) => resolve(mockUnallowedValuesResponse)) +); + +jest.mock('../../../../use_unallowed_values/helpers', () => { + const original = jest.requireActual('../../../../use_unallowed_values/helpers'); + + return { + ...original, + fetchUnallowedValues: ({ + abortController, + indexName, + requestItems, + }: { + abortController: AbortController; + indexName: string; + requestItems: UnallowedValueRequestItem[]; + }) => + mockFetchUnallowedValues({ + abortController, + indexName, + requestItems, + }), + }; +}); + +describe('checkIndex', () => { + const defaultBytesFormat = '0,0.[0]b'; + const formatBytes = (value: number | undefined) => + value != null ? numeral(value).format(defaultBytesFormat) : EMPTY_STAT; + + const defaultNumberFormat = '0,0.[000]'; + const formatNumber = (value: number | undefined) => + value != null ? numeral(value).format(defaultNumberFormat) : EMPTY_STAT; + + const indexName = 'auditbeat-custom-index-1'; + const pattern = 'auditbeat-*'; + + describe('happy path', () => { + const onCheckCompleted = jest.fn(); + + beforeEach(async () => { + jest.clearAllMocks(); + + await checkIndex({ + abortController: new AbortController(), + ecsMetadata, + formatBytes, + formatNumber, + indexName, + onCheckCompleted, + pattern, + version: EcsVersion, + }); + }); + + test('it invokes onCheckCompleted with a null `error`', () => { + expect(onCheckCompleted.mock.calls[0][0].error).toBeNull(); + }); + + test('it invokes onCheckCompleted with the expected `indexName`', () => { + expect(onCheckCompleted.mock.calls[0][0].indexName).toEqual(indexName); + }); + + test('it invokes onCheckCompleted with the non-default `partitionedFieldMetadata`', () => { + expect(onCheckCompleted.mock.calls[0][0].partitionedFieldMetadata).not.toEqual( + EMPTY_PARTITIONED_FIELD_METADATA + ); + }); + + test('it invokes onCheckCompleted with the expected`pattern`', () => { + expect(onCheckCompleted.mock.calls[0][0].pattern).toEqual(pattern); + }); + + test('it invokes onCheckCompleted with the expected `version`', () => { + expect(onCheckCompleted.mock.calls[0][0].version).toEqual(EcsVersion); + }); + }); + + describe('happy path, when the signal is aborted', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('it does NOT invoke onCheckCompleted', async () => { + const onCheckCompleted = jest.fn(); + + const abortController = new AbortController(); + abortController.abort(); + + await checkIndex({ + abortController, + ecsMetadata, + formatBytes, + formatNumber, + indexName, + onCheckCompleted, + pattern, + version: EcsVersion, + }); + + expect(onCheckCompleted).not.toBeCalled(); + }); + }); + + describe('when `ecsMetadata` is null', () => { + const onCheckCompleted = jest.fn(); + + beforeEach(async () => { + jest.clearAllMocks(); + + await checkIndex({ + abortController: new AbortController(), + ecsMetadata: null, // <-- + formatBytes, + formatNumber, + indexName, + onCheckCompleted, + pattern, + version: EcsVersion, + }); + }); + + test('it invokes onCheckCompleted with a null `error`', () => { + expect(onCheckCompleted.mock.calls[0][0].error).toBeNull(); + }); + + test('it invokes onCheckCompleted with the expected `indexName`', () => { + expect(onCheckCompleted.mock.calls[0][0].indexName).toEqual(indexName); + }); + + test('it invokes onCheckCompleted with the default `partitionedFieldMetadata`', () => { + expect(onCheckCompleted.mock.calls[0][0].partitionedFieldMetadata).toEqual( + EMPTY_PARTITIONED_FIELD_METADATA + ); + }); + + test('it invokes onCheckCompleted with the expected `pattern`', () => { + expect(onCheckCompleted.mock.calls[0][0].pattern).toEqual(pattern); + }); + + test('it invokes onCheckCompleted with the expected `version`', () => { + expect(onCheckCompleted.mock.calls[0][0].version).toEqual(EcsVersion); + }); + }); + + describe('when an error occurs', () => { + const onCheckCompleted = jest.fn(); + const error = 'simulated fetch mappings error'; + + beforeEach(async () => { + jest.clearAllMocks(); + + mockFetchMappings = jest.fn( + ({ + abortController, + patternOrIndexName, + }: { + abortController: AbortController; + patternOrIndexName: string; + }) => new Promise((_, reject) => reject(new Error(error))) + ); + + await checkIndex({ + abortController: new AbortController(), + ecsMetadata, + formatBytes, + formatNumber, + indexName, + onCheckCompleted, + pattern, + version: EcsVersion, + }); + }); + + test('it invokes onCheckCompleted with the expected `error`', () => { + expect(onCheckCompleted.mock.calls[0][0].error).toEqual(`Error: ${error}`); + }); + + test('it invokes onCheckCompleted with the expected `indexName`', () => { + expect(onCheckCompleted.mock.calls[0][0].indexName).toEqual(indexName); + }); + + test('it invokes onCheckCompleted with null `partitionedFieldMetadata`', () => { + expect(onCheckCompleted.mock.calls[0][0].partitionedFieldMetadata).toBeNull(); + }); + + test('it invokes onCheckCompleted with the expected `pattern`', () => { + expect(onCheckCompleted.mock.calls[0][0].pattern).toEqual(pattern); + }); + + test('it invokes onCheckCompleted with the expected `version`', () => { + expect(onCheckCompleted.mock.calls[0][0].version).toEqual(EcsVersion); + }); + }); + + describe('when an error occurs, but the error does not have a toString', () => { + const onCheckCompleted = jest.fn(); + + beforeEach(async () => { + jest.clearAllMocks(); + + mockFetchMappings = jest.fn( + ({ + abortController, + patternOrIndexName, + }: { + abortController: AbortController; + patternOrIndexName: string; + // eslint-disable-next-line prefer-promise-reject-errors + }) => new Promise((_, reject) => reject(undefined)) + ); + + await checkIndex({ + abortController: new AbortController(), + ecsMetadata, + formatBytes, + formatNumber, + indexName, + onCheckCompleted, + pattern, + version: EcsVersion, + }); + }); + + test('it invokes onCheckCompleted with the fallback `error`', () => { + expect(onCheckCompleted.mock.calls[0][0].error).toEqual( + `An error occurred checking index ${indexName}` + ); + }); + + test('it invokes onCheckCompleted with the expected `indexName`', () => { + expect(onCheckCompleted.mock.calls[0][0].indexName).toEqual(indexName); + }); + + test('it invokes onCheckCompleted with null `partitionedFieldMetadata`', () => { + expect(onCheckCompleted.mock.calls[0][0].partitionedFieldMetadata).toBeNull(); + }); + + test('it invokes onCheckCompleted with the expected `pattern`', () => { + expect(onCheckCompleted.mock.calls[0][0].pattern).toEqual(pattern); + }); + + test('it invokes onCheckCompleted with the expected `version`', () => { + expect(onCheckCompleted.mock.calls[0][0].version).toEqual(EcsVersion); + }); + }); + + describe('when an error occurs, and the signal is aborted', () => { + const onCheckCompleted = jest.fn(); + const abortController = new AbortController(); + abortController.abort(); + + const error = 'simulated fetch mappings error'; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('it does NOT invoke onCheckCompleted', async () => { + mockFetchMappings = jest.fn( + ({ + // eslint-disable-next-line @typescript-eslint/no-shadow + abortController, + patternOrIndexName, + }: { + abortController: AbortController; + patternOrIndexName: string; + }) => new Promise((_, reject) => reject(new Error(error))) + ); + + await checkIndex({ + abortController, + ecsMetadata, + formatBytes, + formatNumber, + indexName, + onCheckCompleted, + pattern, + version: EcsVersion, + }); + + expect(onCheckCompleted).not.toBeCalled(); + }); + }); +}); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/check_index.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/check_index.ts index cd1ce0940b391..c65b3f7559071 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/check_index.ts +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/check_index.ts @@ -15,7 +15,7 @@ import type { EcsMetadata, OnCheckCompleted, PartitionedFieldMetadata } from '.. import { fetchMappings } from '../../../../use_mappings/helpers'; import { fetchUnallowedValues, getUnallowedValues } from '../../../../use_unallowed_values/helpers'; -const EMPTY_PARTITIONED_FIELD_METADATA: PartitionedFieldMetadata = { +export const EMPTY_PARTITIONED_FIELD_METADATA: PartitionedFieldMetadata = { all: [], custom: [], ecsCompliant: [], @@ -25,6 +25,7 @@ const EMPTY_PARTITIONED_FIELD_METADATA: PartitionedFieldMetadata = { export async function checkIndex({ abortController, ecsMetadata, + formatBytes, formatNumber, indexName, onCheckCompleted, @@ -33,6 +34,7 @@ export async function checkIndex({ }: { abortController: AbortController; ecsMetadata: Record | null; + formatBytes: (value: number | undefined) => string; formatNumber: (value: number | undefined) => string; indexName: string; onCheckCompleted: OnCheckCompleted; @@ -74,6 +76,7 @@ export async function checkIndex({ if (!abortController.signal.aborted) { onCheckCompleted({ error: null, + formatBytes, formatNumber, indexName, partitionedFieldMetadata, @@ -84,10 +87,8 @@ export async function checkIndex({ } catch (error) { if (!abortController.signal.aborted) { onCheckCompleted({ - error: - error.toString() != null - ? error.toString() - : i18n.AN_ERROR_OCCURRED_CHECKING_INDEX(indexName), + error: error != null ? error.toString() : i18n.AN_ERROR_OCCURRED_CHECKING_INDEX(indexName), + formatBytes, formatNumber, indexName, partitionedFieldMetadata: null, diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/helpers.test.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/helpers.test.ts new file mode 100644 index 0000000000000..5f96cfa9953a6 --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/helpers.test.ts @@ -0,0 +1,107 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { getAllIndicesToCheck, getIndexDocsCountFromRollup, getIndexToCheck } from './helpers'; +import { mockPacketbeatPatternRollup } from '../../../../mock/pattern_rollup/mock_packetbeat_pattern_rollup'; + +const patternIndexNames: Record = { + 'packetbeat-*': [ + '.ds-packetbeat-8.6.1-2023.02.04-000001', + '.ds-packetbeat-8.5.3-2023.02.04-000001', + ], + 'auditbeat-*': [ + 'auditbeat-7.17.9-2023.02.13-000001', + 'auditbeat-custom-index-1', + '.ds-auditbeat-8.6.1-2023.02.13-000001', + ], + 'logs-*': [ + '.ds-logs-endpoint.alerts-default-2023.02.24-000001', + '.ds-logs-endpoint.events.process-default-2023.02.24-000001', + ], + 'remote:*': [], + '.alerts-security.alerts-default': ['.internal.alerts-security.alerts-default-000001'], +}; + +describe('helpers', () => { + describe('getIndexToCheck', () => { + test('it returns the expected `IndexToCheck`', () => { + expect( + getIndexToCheck({ + indexName: 'auditbeat-custom-index-1', + pattern: 'auditbeat-*', + }) + ).toEqual({ + indexName: 'auditbeat-custom-index-1', + pattern: 'auditbeat-*', + }); + }); + }); + + describe('getAllIndicesToCheck', () => { + test('it returns the sorted collection of `IndexToCheck`', () => { + expect(getAllIndicesToCheck(patternIndexNames)).toEqual([ + { + indexName: '.internal.alerts-security.alerts-default-000001', + pattern: '.alerts-security.alerts-default', + }, + { + indexName: 'auditbeat-custom-index-1', + pattern: 'auditbeat-*', + }, + { + indexName: 'auditbeat-7.17.9-2023.02.13-000001', + pattern: 'auditbeat-*', + }, + { + indexName: '.ds-auditbeat-8.6.1-2023.02.13-000001', + pattern: 'auditbeat-*', + }, + { + indexName: '.ds-logs-endpoint.events.process-default-2023.02.24-000001', + pattern: 'logs-*', + }, + { + indexName: '.ds-logs-endpoint.alerts-default-2023.02.24-000001', + pattern: 'logs-*', + }, + { + indexName: '.ds-packetbeat-8.6.1-2023.02.04-000001', + pattern: 'packetbeat-*', + }, + { + indexName: '.ds-packetbeat-8.5.3-2023.02.04-000001', + pattern: 'packetbeat-*', + }, + ]); + }); + }); + + describe('getIndexDocsCountFromRollup', () => { + test('it returns the expected count when the `patternRollup` has `stats`', () => { + expect( + getIndexDocsCountFromRollup({ + indexName: '.ds-packetbeat-8.6.1-2023.02.04-000001', + patternRollup: mockPacketbeatPatternRollup, + }) + ).toEqual(1628343); + }); + + test('it returns zero when the `patternRollup` `stats` is null', () => { + const patternRollup = { + ...mockPacketbeatPatternRollup, + stats: null, // <-- + }; + + expect( + getIndexDocsCountFromRollup({ + indexName: '.ds-packetbeat-8.6.1-2023.02.04-000001', + patternRollup, + }) + ).toEqual(0); + }); + }); +}); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/helpers.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/helpers.ts index 4d07d4826f521..30f314c73ea3b 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/helpers.ts +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/helpers.ts @@ -6,7 +6,7 @@ */ import type { IndicesStatsIndicesStats } from '@elastic/elasticsearch/lib/api/types'; -import { sortBy } from 'lodash/fp'; +import { orderBy } from 'lodash/fp'; import { getDocsCount } from '../../../../helpers'; import type { IndexToCheck, PatternRollup } from '../../../../types'; @@ -34,14 +34,14 @@ export const getAllIndicesToCheck = ( return a.localeCompare(b); }); - // return all `IndexToCheck` sorted first by pattern A-Z, and then by `docsCount` within the pattern + // return all `IndexToCheck` sorted first by pattern A-Z: return sortedPatterns.reduce((acc, pattern) => { - const indexNames = patternIndexNames[pattern] ?? []; + const indexNames = patternIndexNames[pattern]; const indicesToCheck = indexNames.map((indexName) => getIndexToCheck({ indexName, pattern }) ); - const sortedIndicesToCheck = sortBy('indexName', indicesToCheck).reverse(); + const sortedIndicesToCheck = orderBy(['indexName'], ['desc'], indicesToCheck); return [...acc, ...sortedIndicesToCheck]; }, []); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/index.test.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/index.test.tsx new file mode 100644 index 0000000000000..f2aa7a2666c33 --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/index.test.tsx @@ -0,0 +1,377 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 numeral from '@elastic/numeral'; +import userEvent from '@testing-library/user-event'; +import { act, render, screen, waitFor } from '@testing-library/react'; +import React from 'react'; + +import { mockMappingsResponse } from '../../../../mock/mappings_response/mock_mappings_response'; +import { TestProviders } from '../../../../mock/test_providers/test_providers'; +import { mockUnallowedValuesResponse } from '../../../../mock/unallowed_values/mock_unallowed_values'; +import { CANCEL, CHECK_ALL } from '../../../../translations'; +import { + OnCheckCompleted, + PartitionedFieldMetadata, + UnallowedValueRequestItem, +} from '../../../../types'; +import { CheckAll } from '.'; +import { EMPTY_STAT } from '../../../../helpers'; + +const defaultBytesFormat = '0,0.[0]b'; +const mockFormatBytes = (value: number | undefined) => + value != null ? numeral(value).format(defaultBytesFormat) : EMPTY_STAT; + +const defaultNumberFormat = '0,0.[000]'; +const mockFormatNumber = (value: number | undefined) => + value != null ? numeral(value).format(defaultNumberFormat) : EMPTY_STAT; + +const mockFetchMappings = jest.fn( + ({ + abortController, + patternOrIndexName, + }: { + abortController: AbortController; + patternOrIndexName: string; + }) => + new Promise((resolve) => { + resolve(mockMappingsResponse); // happy path + }) +); + +jest.mock('../../../../use_mappings/helpers', () => ({ + fetchMappings: ({ + abortController, + patternOrIndexName, + }: { + abortController: AbortController; + patternOrIndexName: string; + }) => + mockFetchMappings({ + abortController, + patternOrIndexName, + }), +})); + +const mockFetchUnallowedValues = jest.fn( + ({ + abortController, + indexName, + requestItems, + }: { + abortController: AbortController; + indexName: string; + requestItems: UnallowedValueRequestItem[]; + }) => new Promise((resolve) => resolve(mockUnallowedValuesResponse)) +); + +jest.mock('../../../../use_unallowed_values/helpers', () => { + const original = jest.requireActual('../../../../use_unallowed_values/helpers'); + + return { + ...original, + fetchUnallowedValues: ({ + abortController, + indexName, + requestItems, + }: { + abortController: AbortController; + indexName: string; + requestItems: UnallowedValueRequestItem[]; + }) => + mockFetchUnallowedValues({ + abortController, + indexName, + requestItems, + }), + }; +}); + +const patternIndexNames = { + '.alerts-security.alerts-default': ['.internal.alerts-security.alerts-default-000001'], + 'auditbeat-*': [ + 'auditbeat-7.3.2-2023.03.27-000001', + '.ds-auditbeat-8.6.1-2023.03.29-000001', + 'auditbeat-custom-empty-index-1', + 'auditbeat-7.10.2-2023.03.27-000001', + 'auditbeat-7.2.1-2023.03.27-000001', + 'auditbeat-custom-index-1', + ], + 'logs-*': [ + '.ds-logs-endpoint.events.process-default-2023.03.27-000001', + '.ds-logs-endpoint.alerts-default-2023.03.27-000001', + ], + 'packetbeat-*': [ + '.ds-packetbeat-8.6.1-2023.03.27-000001', + '.ds-packetbeat-8.5.3-2023.03.27-000001', + ], +}; + +const ilmPhases: string[] = ['hot', 'warm', 'unmanaged']; + +describe('CheckAll', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('it renders the expected button text when a check is NOT running', () => { + render( + + + + ); + + expect(screen.getByTestId('checkAll')).toHaveTextContent(CHECK_ALL); + }); + + test('it renders the expected button text when a check is running', () => { + render( + + + + ); + + const button = screen.getByTestId('checkAll'); + + userEvent.click(button); // <-- START the check + + expect(screen.getByTestId('checkAll')).toHaveTextContent(CANCEL); + }); + + describe('formatNumber', () => { + test('it renders a comma-separated `value` via the `defaultNumberFormat`', async () => { + /** stores the result of invoking `CheckAll`'s `formatNumber` function */ + let formatNumberResult = ''; + + const onCheckCompleted: OnCheckCompleted = jest.fn( + ({ + formatBytes, + formatNumber, + }: { + error: string | null; + formatBytes: (value: number | undefined) => string; + formatNumber: (value: number | undefined) => string; + indexName: string; + partitionedFieldMetadata: PartitionedFieldMetadata | null; + pattern: string; + version: string; + }) => { + const value = 123456789; // numeric input to `CheckAll`'s `formatNumber` function + + formatNumberResult = formatNumber(value); + } + ); + + render( + + + + ); + + const button = screen.getByTestId('checkAll'); + + userEvent.click(button); // <-- START the check + + await waitFor(() => { + expect(formatNumberResult).toEqual('123,456,789'); // a comma-separated `value`, because it's numeric + }); + }); + + test('it renders an empty stat placeholder when `value` is undefined', async () => { + /** stores the result of invoking `CheckAll`'s `formatNumber` function */ + let formatNumberResult = ''; + + const onCheckCompleted: OnCheckCompleted = jest.fn( + ({ + formatBytes, + formatNumber, + }: { + error: string | null; + formatBytes: (value: number | undefined) => string; + formatNumber: (value: number | undefined) => string; + indexName: string; + partitionedFieldMetadata: PartitionedFieldMetadata | null; + pattern: string; + version: string; + }) => { + const value = undefined; // undefined input to `CheckAll`'s `formatNumber` function + + formatNumberResult = formatNumber(value); + } + ); + + render( + + + + ); + + const button = screen.getByTestId('checkAll'); + + userEvent.click(button); // <-- START the check + + await waitFor(() => { + expect(formatNumberResult).toEqual(EMPTY_STAT); // a placeholder, because `value` is undefined + }); + }); + }); + + describe('when a running check is cancelled', () => { + const setCheckAllIndiciesChecked = jest.fn(); + const setCheckAllTotalIndiciesToCheck = jest.fn(); + + beforeEach(() => { + jest.clearAllMocks(); + + render( + + + + ); + + const button = screen.getByTestId('checkAll'); + + userEvent.click(button); // <-- START the check + + userEvent.click(button); // <-- STOP the check + }); + + test('it invokes `setCheckAllIndiciesChecked` twice: when the check was started, and when it was cancelled', () => { + expect(setCheckAllIndiciesChecked).toHaveBeenCalledTimes(2); + }); + + test('it invokes `setCheckAllTotalIndiciesToCheck` with the expected index count when the check is STARTED', () => { + expect(setCheckAllTotalIndiciesToCheck.mock.calls[0][0]).toEqual(11); + }); + + test('it invokes `setCheckAllTotalIndiciesToCheck` with the expected index count when the check is STOPPED', () => { + expect(setCheckAllTotalIndiciesToCheck.mock.calls[1][0]).toEqual(0); + }); + }); + + describe('when all checks have completed', () => { + const setIndexToCheck = jest.fn(); + + beforeEach(async () => { + jest.clearAllMocks(); + jest.useFakeTimers(); + + render( + + + + ); + + const button = screen.getByTestId('checkAll'); + + userEvent.click(button); // <-- start the check + + const totalIndexNames = Object.values(patternIndexNames).reduce( + (total, indices) => total + indices.length, + 0 + ); + + // simulate the wall clock advancing + for (let i = 0; i < totalIndexNames + 1; i++) { + act(() => { + jest.advanceTimersByTime(1000 * 10); + }); + + await waitFor(() => {}); + } + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + test('it invokes setIndexToCheck with `null` after all the checks have completed', () => { + expect(setIndexToCheck).toBeCalledWith(null); + }); + + // test all the patterns + Object.entries(patternIndexNames).forEach((pattern) => { + const [patternName, indexNames] = pattern; + + // test each index in the pattern + indexNames.forEach((indexName) => { + test(`it invokes setIndexToCheck with the expected value for the '${patternName}' pattern's index, named '${indexName}'`, () => { + expect(setIndexToCheck).toBeCalledWith({ + indexName, + pattern: patternName, + }); + }); + }); + }); + }); +}); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/index.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/index.tsx index ab5de8d7ccdcb..ef768249aa2a4 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/index.tsx +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/index.tsx @@ -8,15 +8,18 @@ import { EcsFlat, EcsVersion } from '@kbn/ecs'; import { EuiButton } from '@elastic/eui'; -import numeral from '@elastic/numeral'; import React, { useCallback, useEffect, useRef, useState } from 'react'; +import styled from 'styled-components'; import { checkIndex } from './check_index'; -import { EMPTY_STAT } from '../../../../helpers'; import { getAllIndicesToCheck } from './helpers'; import * as i18n from '../../../../translations'; import type { EcsMetadata, IndexToCheck, OnCheckCompleted } from '../../../../types'; +const CheckAllButton = styled(EuiButton)` + width: 112px; +`; + async function wait(ms: number) { const delay = () => new Promise((resolve) => @@ -29,7 +32,8 @@ async function wait(ms: number) { } interface Props { - defaultNumberFormat: string; + formatBytes: (value: number | undefined) => string; + formatNumber: (value: number | undefined) => string; ilmPhases: string[]; incrementCheckAllIndiciesChecked: () => void; onCheckCompleted: OnCheckCompleted; @@ -43,7 +47,8 @@ interface Props { const DELAY_AFTER_EVERY_CHECK_COMPLETES = 3000; // ms const CheckAllComponent: React.FC = ({ - defaultNumberFormat, + formatBytes, + formatNumber, ilmPhases, incrementCheckAllIndiciesChecked, onCheckCompleted, @@ -55,11 +60,6 @@ const CheckAllComponent: React.FC = ({ }) => { const abortController = useRef(new AbortController()); const [isRunning, setIsRunning] = useState(false); - const formatNumber = useCallback( - (value: number | undefined): string => - value != null ? numeral(value).format(defaultNumberFormat) : EMPTY_STAT, - [defaultNumberFormat] - ); const cancelIfRunning = useCallback(() => { if (isRunning) { @@ -89,6 +89,7 @@ const CheckAllComponent: React.FC = ({ await checkIndex({ abortController: abortController.current, ecsMetadata: EcsFlat as unknown as Record, + formatBytes, formatNumber, indexName, onCheckCompleted, @@ -118,6 +119,7 @@ const CheckAllComponent: React.FC = ({ } }, [ cancelIfRunning, + formatBytes, formatNumber, incrementCheckAllIndiciesChecked, isRunning, @@ -141,14 +143,18 @@ const CheckAllComponent: React.FC = ({ }; }, [abortController]); + const disabled = ilmPhases.length === 0; + return ( - {isRunning ? i18n.CANCEL : i18n.CHECK_ALL} - + ); }; diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/index.test.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/index.test.tsx new file mode 100644 index 0000000000000..7d139121afbc4 --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/index.test.tsx @@ -0,0 +1,128 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 numeral from '@elastic/numeral'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; + +import { EMPTY_STAT } from '../../../helpers'; +import { alertIndexWithAllResults } from '../../../mock/pattern_rollup/mock_alerts_pattern_rollup'; +import { auditbeatWithAllResults } from '../../../mock/pattern_rollup/mock_auditbeat_pattern_rollup'; +import { packetbeatNoResults } from '../../../mock/pattern_rollup/mock_packetbeat_pattern_rollup'; +import { TestProviders } from '../../../mock/test_providers/test_providers'; +import { PatternRollup } from '../../../types'; +import { Props, SummaryActions } from '.'; +import { + getTotalDocsCount, + getTotalIncompatible, + getTotalIndices, + getTotalIndicesChecked, + getTotalSizeInBytes, +} from '../../../use_results_rollup/helpers'; + +const mockCopyToClipboard = jest.fn((value) => true); +jest.mock('@elastic/eui', () => { + const original = jest.requireActual('@elastic/eui'); + return { + ...original, + copyToClipboard: (value: string) => mockCopyToClipboard(value), + }; +}); + +const defaultBytesFormat = '0,0.[0]b'; +const formatBytes = (value: number | undefined) => + value != null ? numeral(value).format(defaultBytesFormat) : EMPTY_STAT; + +const defaultNumberFormat = '0,0.[000]'; +const formatNumber = (value: number | undefined) => + value != null ? numeral(value).format(defaultNumberFormat) : EMPTY_STAT; + +const ilmPhases = ['hot', 'warm', 'unmanaged']; +const patterns = ['.alerts-security.alerts-default', 'auditbeat-*', 'packetbeat-*']; + +const patternRollups: Record = { + '.alerts-security.alerts-default': alertIndexWithAllResults, + 'auditbeat-*': auditbeatWithAllResults, + 'packetbeat-*': packetbeatNoResults, +}; + +const patternIndexNames: Record = { + 'auditbeat-*': [ + '.ds-auditbeat-8.6.1-2023.02.07-000001', + 'auditbeat-custom-empty-index-1', + 'auditbeat-custom-index-1', + ], + '.alerts-security.alerts-default': ['.internal.alerts-security.alerts-default-000001'], + 'packetbeat-*': [ + '.ds-packetbeat-8.5.3-2023.02.04-000001', + '.ds-packetbeat-8.6.1-2023.02.04-000001', + ], +}; + +const lastChecked = '2023-03-28T23:27:28.159Z'; + +const totalDocsCount = getTotalDocsCount(patternRollups); +const totalIncompatible = getTotalIncompatible(patternRollups); +const totalIndices = getTotalIndices(patternRollups); +const totalIndicesChecked = getTotalIndicesChecked(patternRollups); +const totalSizeInBytes = getTotalSizeInBytes(patternRollups); + +const defaultProps: Props = { + addSuccessToast: jest.fn(), + canUserCreateAndReadCases: () => true, + errorSummary: [], + formatBytes, + formatNumber, + ilmPhases, + lastChecked, + openCreateCaseFlyout: jest.fn(), + onCheckCompleted: jest.fn(), + patternIndexNames, + patternRollups, + patterns, + setLastChecked: jest.fn(), + totalDocsCount, + totalIncompatible, + totalIndices, + totalIndicesChecked, + sizeInBytes: totalSizeInBytes, +}; + +describe('SummaryActions', () => { + beforeEach(() => { + jest.clearAllMocks(); + + render( + + + + ); + }); + + test('it renders the check all button', () => { + expect(screen.getByTestId('checkAll')).toBeInTheDocument(); + }); + + test('it renders the check status indicator', () => { + expect(screen.getByTestId('checkStatus')).toBeInTheDocument(); + }); + + test('it renders the actions', () => { + expect(screen.getByTestId('actions')).toBeInTheDocument(); + }); + + test('it invokes addSuccessToast when the copy to clipboard button is clicked', () => { + const button = screen.getByTestId('copyToClipboard'); + + userEvent.click(button); + + expect(defaultProps.addSuccessToast).toBeCalledWith({ + title: 'Copied results to the clipboard', + }); + }); +}); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/index.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/index.tsx index d659d81d93199..db53376746281 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/index.tsx +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/index.tsx @@ -6,15 +6,14 @@ */ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import numeral from '@elastic/numeral'; import { sortBy } from 'lodash/fp'; -import React, { useCallback } from 'react'; +import React, { useCallback, useState } from 'react'; import styled from 'styled-components'; import { CheckAll } from './check_all'; +import { CheckStatus } from '../check_status'; import { ERROR, INDEX, PATTERN } from '../errors_viewer/translations'; import { ERRORS } from '../errors_popover/translations'; -import { EMPTY_STAT } from '../../../helpers'; import { getDataQualitySummaryMarkdownComment, getErrorsMarkdownTable, @@ -23,8 +22,8 @@ import { getSummaryTableMarkdownHeader, getSummaryTableMarkdownRow, } from '../../index_properties/markdown/helpers'; -import { getSummaryTableItems } from '../../pattern/helpers'; -import { TakeActionMenu } from './take_action_menu'; +import { defaultSort, getSummaryTableItems } from '../../pattern/helpers'; +import { Actions } from './actions'; import type { DataQualityCheckResult, ErrorSummary, @@ -32,6 +31,7 @@ import type { OnCheckCompleted, PatternRollup, } from '../../../types'; +import { getSizeInBytes } from '../../../helpers'; const SummaryActionsFlexGroup = styled(EuiFlexGroup)` gap: ${({ theme }) => theme.eui.euiSizeS}; @@ -43,10 +43,12 @@ export const getResultsSortedByDocsCount = ( results != null ? sortBy('docsCount', Object.values(results)).reverse() : []; export const getAllMarkdownCommentsFromResults = ({ + formatBytes, formatNumber, patternIndexNames, patternRollup, }: { + formatBytes: (value: number | undefined) => string; formatNumber: (value: number | undefined) => string; patternIndexNames: Record; patternRollup: PatternRollup; @@ -60,6 +62,8 @@ export const getAllMarkdownCommentsFromResults = ({ pattern: patternRollup.pattern, patternDocsCount: patternRollup.docsCount ?? 0, results: patternRollup.results, + sortByColumn: defaultSort.sort.field, + sortByDirection: defaultSort.sort.direction, stats: patternRollup.stats, }); @@ -69,11 +73,13 @@ export const getAllMarkdownCommentsFromResults = ({ return getSummaryTableMarkdownRow({ docsCount: item.docsCount, + formatBytes, formatNumber, ilmPhase: item.ilmPhase, indexName: item.indexName, incompatible: result?.incompatible, patternDocsCount: patternRollup.docsCount ?? 0, + sizeInBytes: getSizeInBytes({ indexName: item.indexName, stats: patternRollup.stats }), }).trim(); }); @@ -89,10 +95,12 @@ export const getAllMarkdownCommentsFromResults = ({ }; export const getAllMarkdownComments = ({ + formatBytes, formatNumber, patternIndexNames, patternRollups, }: { + formatBytes: (value: number | undefined) => string; formatNumber: (value: number | undefined) => string; patternIndexNames: Record; patternRollups: Record; @@ -108,10 +116,12 @@ export const getAllMarkdownComments = ({ (acc, pattern) => [ ...acc, getPatternSummaryMarkdownComment({ + formatBytes, formatNumber, patternRollup: patternRollups[pattern], }), ...getAllMarkdownCommentsFromResults({ + formatBytes, formatNumber, patternRollup: patternRollups[pattern], patternIndexNames, @@ -121,13 +131,14 @@ export const getAllMarkdownComments = ({ ); }; -interface Props { +export interface Props { addSuccessToast: (toast: { title: string }) => void; canUserCreateAndReadCases: () => boolean; - defaultNumberFormat: string; + formatBytes: (value: number | undefined) => string; + formatNumber: (value: number | undefined) => string; errorSummary: ErrorSummary[]; ilmPhases: string[]; - incrementCheckAllIndiciesChecked: () => void; + lastChecked: string; onCheckCompleted: OnCheckCompleted; openCreateCaseFlyout: ({ comments, @@ -139,51 +150,54 @@ interface Props { patternIndexNames: Record; patternRollups: Record; patterns: string[]; - setCheckAllIndiciesChecked: (checkAllIndiciesChecked: number) => void; - setCheckAllTotalIndiciesToCheck: (checkAllTotalIndiciesToCheck: number) => void; - setIndexToCheck: (indexToCheck: IndexToCheck | null) => void; + setLastChecked: (lastChecked: string) => void; totalDocsCount: number | undefined; totalIncompatible: number | undefined; totalIndices: number | undefined; totalIndicesChecked: number | undefined; + sizeInBytes: number | undefined; } const SummaryActionsComponent: React.FC = ({ addSuccessToast, canUserCreateAndReadCases, - defaultNumberFormat, + formatBytes, + formatNumber, errorSummary, ilmPhases, - incrementCheckAllIndiciesChecked, + lastChecked, onCheckCompleted, openCreateCaseFlyout, patternIndexNames, patternRollups, patterns, + setLastChecked, totalDocsCount, - setCheckAllIndiciesChecked, - setCheckAllTotalIndiciesToCheck, - setIndexToCheck, totalIncompatible, totalIndices, totalIndicesChecked, + sizeInBytes, }) => { - const formatNumber = useCallback( - (value: number | undefined): string => - value != null ? numeral(value).format(defaultNumberFormat) : EMPTY_STAT, - [defaultNumberFormat] - ); + const [indexToCheck, setIndexToCheck] = useState(null); + const [checkAllIndiciesChecked, setCheckAllIndiciesChecked] = useState(0); + const [checkAllTotalIndiciesToCheck, setCheckAllTotalIndiciesToCheck] = useState(0); + const incrementCheckAllIndiciesChecked = useCallback(() => { + setCheckAllIndiciesChecked((current) => current + 1); + }, []); const getMarkdownComments = useCallback( (): string[] => [ getDataQualitySummaryMarkdownComment({ + formatBytes, formatNumber, totalDocsCount, totalIncompatible, totalIndices, totalIndicesChecked, + sizeInBytes, }), ...getAllMarkdownComments({ + formatBytes, formatNumber, patternIndexNames, patternRollups, @@ -197,9 +211,11 @@ const SummaryActionsComponent: React.FC = ({ ], [ errorSummary, + formatBytes, formatNumber, patternIndexNames, patternRollups, + sizeInBytes, totalDocsCount, totalIncompatible, totalIndices, @@ -208,30 +224,46 @@ const SummaryActionsComponent: React.FC = ({ ); return ( - - - - - - - - - + <> + + + + + + + + + + + + + + ); }; diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/take_action_menu/index.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/take_action_menu/index.tsx deleted file mode 100644 index 4683a96a76987..0000000000000 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/take_action_menu/index.tsx +++ /dev/null @@ -1,125 +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 { - copyToClipboard, - EuiButtonEmpty, - EuiContextMenuItem, - EuiContextMenuPanel, - EuiPopover, -} from '@elastic/eui'; -import React, { useCallback, useMemo, useState } from 'react'; - -import { - ADD_TO_NEW_CASE, - COPIED_RESULTS_TOAST_TITLE, - COPY_TO_CLIPBOARD, -} from '../../../../translations'; -import * as i18n from './translations'; -import { useAddToNewCase } from '../../../../use_add_to_new_case'; - -interface Props { - addSuccessToast: (toast: { title: string }) => void; - canUserCreateAndReadCases: () => boolean; - getMarkdownComments: () => string[]; - openCreateCaseFlyout: ({ - comments, - headerContent, - }: { - comments: string[]; - headerContent?: React.ReactNode; - }) => void; -} - -const TakeActionMenuComponent: React.FC = ({ - addSuccessToast, - canUserCreateAndReadCases, - getMarkdownComments, - openCreateCaseFlyout, -}) => { - const [isPopoverOpen, setIsPopoverOpen] = useState(false); - const closePopover = useCallback(() => { - setIsPopoverOpen(false); - }, []); - const onButtonClick = useCallback(() => { - setIsPopoverOpen((current) => !current); - }, []); - - const takeActionButton = useMemo( - () => ( - - {i18n.TAKE_ACTION} - - ), - [onButtonClick] - ); - - const { disabled: addToNewCaseDisabled, onAddToNewCase } = useAddToNewCase({ - canUserCreateAndReadCases, - openCreateCaseFlyout, - }); - - const onClickAddToCase = useCallback( - () => onAddToNewCase([getMarkdownComments().join('\n')]), - [getMarkdownComments, onAddToNewCase] - ); - - const onCopy = useCallback(() => { - const markdown = getMarkdownComments().join('\n'); - copyToClipboard(markdown); - - closePopover(); - - addSuccessToast({ - title: COPIED_RESULTS_TOAST_TITLE, - }); - }, [addSuccessToast, closePopover, getMarkdownComments]); - - const addToNewCaseContextMenuOnClick = useCallback(() => { - closePopover(); - onClickAddToCase(); - }, [closePopover, onClickAddToCase]); - - const items = useMemo( - () => [ - - {ADD_TO_NEW_CASE} - , - - - {COPY_TO_CLIPBOARD} - , - ], - [addToNewCaseContextMenuOnClick, addToNewCaseDisabled, onCopy] - ); - - return ( - - - - ); -}; - -TakeActionMenuComponent.displayName = 'TakeActionMenuComponent'; - -export const TakeActionMenu = React.memo(TakeActionMenuComponent); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/error_empty_prompt/index.test.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/error_empty_prompt/index.test.tsx new file mode 100644 index 0000000000000..f57d9f52737d7 --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/error_empty_prompt/index.test.tsx @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { render, screen } from '@testing-library/react'; +import React from 'react'; + +import { TestProviders } from '../../mock/test_providers/test_providers'; +import { ErrorEmptyPrompt } from '.'; + +describe('ErrorEmptyPrompt', () => { + test('it renders the expected content', () => { + const title = 'This is the title of this work'; + + render( + + + + ); + + expect(screen.getByTestId('errorEmptyPrompt').textContent?.includes(title)).toBe(true); + }); +}); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/error_empty_prompt/index.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/error_empty_prompt/index.tsx index a0b70daee0e40..3214b704dc685 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/error_empty_prompt/index.tsx +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/error_empty_prompt/index.tsx @@ -15,7 +15,7 @@ interface Props { } const ErrorEmptyPromptComponent: React.FC = ({ title }) => ( - +

{i18n.ERRORS_MAY_OCCUR}

{i18n.THE_FOLLOWING_PRIVILEGES_ARE_REQUIRED} diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/ilm_phase_counts/index.test.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/ilm_phase_counts/index.test.tsx new file mode 100644 index 0000000000000..6efe7579d7325 --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/ilm_phase_counts/index.test.tsx @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + IlmExplainLifecycleLifecycleExplain, + IlmExplainLifecycleLifecycleExplainManaged, + IlmExplainLifecycleLifecycleExplainUnmanaged, +} from '@elastic/elasticsearch/lib/api/types'; +import { render, screen } from '@testing-library/react'; +import React from 'react'; + +import { TestProviders } from '../../mock/test_providers/test_providers'; +import { IlmPhaseCounts } from '.'; +import { getIlmExplainPhaseCounts } from '../pattern/helpers'; + +const hot: IlmExplainLifecycleLifecycleExplainManaged = { + index: '.ds-packetbeat-8.6.1-2023.02.04-000001', + managed: true, + policy: 'packetbeat', + index_creation_date_millis: 1675536751379, + time_since_index_creation: '3.98d', + lifecycle_date_millis: 1675536751379, + age: '3.98d', + phase: 'hot', + phase_time_millis: 1675536751809, + action: 'rollover', + action_time_millis: 1675536751809, + step: 'check-rollover-ready', + step_time_millis: 1675536751809, + phase_execution: { + policy: 'packetbeat', + version: 1, + modified_date_in_millis: 1675536751205, + }, +}; +const warm = { + ...hot, + phase: 'warm', +}; +const cold = { + ...hot, + phase: 'cold', +}; +const frozen = { + ...hot, + phase: 'frozen', +}; + +const managed: Record = { + hot, + warm, + cold, + frozen, +}; + +const unmanaged: IlmExplainLifecycleLifecycleExplainUnmanaged = { + index: 'foo', + managed: false, +}; + +const ilmExplain: Record = { + ...managed, + [unmanaged.index]: unmanaged, +}; + +const ilmExplainPhaseCounts = getIlmExplainPhaseCounts(ilmExplain); + +const pattern = 'packetbeat-*'; + +describe('IlmPhaseCounts', () => { + test('it renders the expected counts', () => { + render( + + + + ); + + expect(screen.getByTestId('ilmPhaseCounts')).toHaveTextContent( + 'hot (1)unmanaged (1)warm (1)cold (1)frozen (1)' + ); + }); +}); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/ilm_phase_counts/index.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/ilm_phase_counts/index.tsx index 3aa738d4cb788..82664778becb0 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/ilm_phase_counts/index.tsx +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/ilm_phase_counts/index.tsx @@ -25,7 +25,7 @@ interface Props { } const IlmPhaseCountsComponent: React.FC = ({ ilmExplainPhaseCounts, pattern }) => ( - + {phases.map((phase) => ilmExplainPhaseCounts[phase] != null && ilmExplainPhaseCounts[phase] > 0 ? ( diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/empty_prompt_body.test.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/empty_prompt_body.test.tsx new file mode 100644 index 0000000000000..1c37ec799c53c --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/empty_prompt_body.test.tsx @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { render, screen } from '@testing-library/react'; +import React from 'react'; + +import { EmptyPromptBody } from './empty_prompt_body'; +import { TestProviders } from '../../mock/test_providers/test_providers'; + +describe('EmptyPromptBody', () => { + const content = 'foo bar baz @baz'; + + test('it renders the expected content', () => { + render( + + + + ); + + expect(screen.getByTestId('emptyPromptBody')).toHaveTextContent(content); + }); +}); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/empty_prompt_body.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/empty_prompt_body.tsx index 80c9151eaa050..33283d11649a8 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/empty_prompt_body.tsx +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/empty_prompt_body.tsx @@ -11,7 +11,9 @@ interface Props { body: string; } -const EmptyPromptBodyComponent: React.FC = ({ body }) =>

{body}

; +const EmptyPromptBodyComponent: React.FC = ({ body }) => ( +

{body}

+); EmptyPromptBodyComponent.displayName = 'EmptyPromptBodyComponent'; diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/empty_prompt_title.test.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/empty_prompt_title.test.tsx new file mode 100644 index 0000000000000..6bb3b72ed3ece --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/empty_prompt_title.test.tsx @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { render, screen } from '@testing-library/react'; +import React from 'react'; + +import { EmptyPromptTitle } from './empty_prompt_title'; +import { TestProviders } from '../../mock/test_providers/test_providers'; + +describe('EmptyPromptTitle', () => { + const title = 'What is a great title?'; + + test('it renders the expected content', () => { + render( + + + + ); + + expect(screen.getByTestId('emptyPromptTitle')).toHaveTextContent(title); + }); +}); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/empty_prompt_title.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/empty_prompt_title.tsx index a9d2e1f6a74d9..ee06b2f446858 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/empty_prompt_title.tsx +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/empty_prompt_title.tsx @@ -11,7 +11,9 @@ interface Props { title: string; } -const EmptyPromptTitleComponent: React.FC = ({ title }) =>

{title}

; +const EmptyPromptTitleComponent: React.FC = ({ title }) => ( +

{title}

+); EmptyPromptTitleComponent.displayName = 'EmptyPromptTitleComponent'; diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/helpers.test.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/helpers.test.ts new file mode 100644 index 0000000000000..e0644fdc4a5c0 --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/helpers.test.ts @@ -0,0 +1,800 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { EcsFlat } from '@kbn/ecs'; + +import { + getMappingsProperties, + getSortedPartitionedFieldMetadata, + hasAllDataFetchingCompleted, +} from './helpers'; +import { mockIndicesGetMappingIndexMappingRecords } from '../../mock/indices_get_mapping_index_mapping_record/mock_indices_get_mapping_index_mapping_record'; +import { mockMappingsProperties } from '../../mock/mappings_properties/mock_mappings_properties'; +import { EcsMetadata } from '../../types'; + +const ecsMetadata: Record = EcsFlat as unknown as Record; + +describe('helpers', () => { + describe('getSortedPartitionedFieldMetadata', () => { + test('it returns null when mappings are loading', () => { + expect( + getSortedPartitionedFieldMetadata({ + ecsMetadata, + loadingMappings: true, // <-- + mappingsProperties: mockMappingsProperties, + unallowedValues: {}, + }) + ).toBeNull(); + }); + + test('it returns null when `ecsMetadata` is null', () => { + expect( + getSortedPartitionedFieldMetadata({ + ecsMetadata: null, // <-- + loadingMappings: false, + mappingsProperties: mockMappingsProperties, + unallowedValues: {}, + }) + ).toBeNull(); + }); + + test('it returns null when `unallowedValues` is null', () => { + expect( + getSortedPartitionedFieldMetadata({ + ecsMetadata, + loadingMappings: false, + mappingsProperties: mockMappingsProperties, + unallowedValues: null, // <-- + }) + ).toBeNull(); + }); + + describe('when `mappingsProperties` is unknown', () => { + const expected = { + all: [], + custom: [], + ecsCompliant: [], + incompatible: [ + { + description: + 'Date/time when the event originated. This is the date/time extracted from the event, typically representing when the event was generated by the source. If the event source has no original timestamp, this value is typically populated by the first time the event was received by the pipeline. Required field for all events.', + hasEcsMetadata: true, + indexFieldName: '@timestamp', + indexFieldType: '-', + indexInvalidValues: [], + isEcsCompliant: false, + isInSameFamily: false, + type: 'date', + }, + ], + }; + + test('it returns a `PartitionedFieldMetadata` with an `incompatible` `@timestamp` when `mappingsProperties` is undefined', () => { + expect( + getSortedPartitionedFieldMetadata({ + ecsMetadata, + loadingMappings: false, + mappingsProperties: undefined, // <-- + unallowedValues: {}, + }) + ).toEqual(expected); + }); + + test('it returns a `PartitionedFieldMetadata` with an `incompatible` `@timestamp` when `mappingsProperties` is null', () => { + expect( + getSortedPartitionedFieldMetadata({ + ecsMetadata, + loadingMappings: false, + mappingsProperties: null, // <-- + unallowedValues: {}, + }) + ).toEqual(expected); + }); + }); + + test('it returns the expected sorted field metadata', () => { + const unallowedValues = { + 'event.category': [ + { + count: 2, + fieldName: 'an_invalid_category', + }, + { + count: 1, + fieldName: 'theory', + }, + ], + 'event.kind': [], + 'event.outcome': [], + 'event.type': [], + }; + + expect( + getSortedPartitionedFieldMetadata({ + ecsMetadata, + loadingMappings: false, + mappingsProperties: mockMappingsProperties, + unallowedValues, + }) + ).toEqual({ + all: [ + { + dashed_name: 'timestamp', + description: + 'Date/time when the event originated.\nThis is the date/time extracted from the event, typically representing when the event was generated by the source.\nIf the event source has no original timestamp, this value is typically populated by the first time the event was received by the pipeline.\nRequired field for all events.', + example: '2016-05-23T08:05:34.853Z', + flat_name: '@timestamp', + level: 'core', + name: '@timestamp', + normalize: [], + required: true, + short: 'Date/time when the event originated.', + type: 'date', + indexFieldName: '@timestamp', + indexFieldType: 'date', + indexInvalidValues: [], + hasEcsMetadata: true, + isEcsCompliant: true, + isInSameFamily: false, + }, + { + allowed_values: [ + { + description: + 'Events in this category are related to the challenge and response process in which credentials are supplied and verified to allow the creation of a session. Common sources for these logs are Windows event logs and ssh logs. Visualize and analyze events in this category to look for failed logins, and other authentication-related activity.', + expected_event_types: ['start', 'end', 'info'], + name: 'authentication', + }, + { + description: + 'Events in the configuration category have to deal with creating, modifying, or deleting the settings or parameters of an application, process, or system.\nExample sources include security policy change logs, configuration auditing logging, and system integrity monitoring.', + expected_event_types: ['access', 'change', 'creation', 'deletion', 'info'], + name: 'configuration', + }, + { + description: + 'The database category denotes events and metrics relating to a data storage and retrieval system. Note that use of this category is not limited to relational database systems. Examples include event logs from MS SQL, MySQL, Elasticsearch, MongoDB, etc. Use this category to visualize and analyze database activity such as accesses and changes.', + expected_event_types: ['access', 'change', 'info', 'error'], + name: 'database', + }, + { + description: + 'Events in the driver category have to do with operating system device drivers and similar software entities such as Windows drivers, kernel extensions, kernel modules, etc.\nUse events and metrics in this category to visualize and analyze driver-related activity and status on hosts.', + expected_event_types: ['change', 'end', 'info', 'start'], + name: 'driver', + }, + { + description: + 'This category is used for events relating to email messages, email attachments, and email network or protocol activity.\nEmails events can be produced by email security gateways, mail transfer agents, email cloud service providers, or mail server monitoring applications.', + expected_event_types: ['info'], + name: 'email', + }, + { + description: + 'Relating to a set of information that has been created on, or has existed on a filesystem. Use this category of events to visualize and analyze the creation, access, and deletions of files. Events in this category can come from both host-based and network-based sources. An example source of a network-based detection of a file transfer would be the Zeek file.log.', + expected_event_types: ['change', 'creation', 'deletion', 'info'], + name: 'file', + }, + { + description: + 'Use this category to visualize and analyze information such as host inventory or host lifecycle events.\nMost of the events in this category can usually be observed from the outside, such as from a hypervisor or a control plane\'s point of view. Some can also be seen from within, such as "start" or "end".\nNote that this category is for information about hosts themselves; it is not meant to capture activity "happening on a host".', + expected_event_types: ['access', 'change', 'end', 'info', 'start'], + name: 'host', + }, + { + description: + 'Identity and access management (IAM) events relating to users, groups, and administration. Use this category to visualize and analyze IAM-related logs and data from active directory, LDAP, Okta, Duo, and other IAM systems.', + expected_event_types: [ + 'admin', + 'change', + 'creation', + 'deletion', + 'group', + 'info', + 'user', + ], + name: 'iam', + }, + { + description: + 'Relating to intrusion detections from IDS/IPS systems and functions, both network and host-based. Use this category to visualize and analyze intrusion detection alerts from systems such as Snort, Suricata, and Palo Alto threat detections.', + expected_event_types: ['allowed', 'denied', 'info'], + name: 'intrusion_detection', + }, + { + description: + 'Malware detection events and alerts. Use this category to visualize and analyze malware detections from EDR/EPP systems such as Elastic Endpoint Security, Symantec Endpoint Protection, Crowdstrike, and network IDS/IPS systems such as Suricata, or other sources of malware-related events such as Palo Alto Networks threat logs and Wildfire logs.', + expected_event_types: ['info'], + name: 'malware', + }, + { + description: + 'Relating to all network activity, including network connection lifecycle, network traffic, and essentially any event that includes an IP address. Many events containing decoded network protocol transactions fit into this category. Use events in this category to visualize or analyze counts of network ports, protocols, addresses, geolocation information, etc.', + expected_event_types: [ + 'access', + 'allowed', + 'connection', + 'denied', + 'end', + 'info', + 'protocol', + 'start', + ], + name: 'network', + }, + { + description: + 'Relating to software packages installed on hosts. Use this category to visualize and analyze inventory of software installed on various hosts, or to determine host vulnerability in the absence of vulnerability scan data.', + expected_event_types: [ + 'access', + 'change', + 'deletion', + 'info', + 'installation', + 'start', + ], + name: 'package', + }, + { + description: + 'Use this category of events to visualize and analyze process-specific information such as lifecycle events or process ancestry.', + expected_event_types: ['access', 'change', 'end', 'info', 'start'], + name: 'process', + }, + { + description: + 'Having to do with settings and assets stored in the Windows registry. Use this category to visualize and analyze activity such as registry access and modifications.', + expected_event_types: ['access', 'change', 'creation', 'deletion'], + name: 'registry', + }, + { + description: + 'The session category is applied to events and metrics regarding logical persistent connections to hosts and services. Use this category to visualize and analyze interactive or automated persistent connections between assets. Data for this category may come from Windows Event logs, SSH logs, or stateless sessions such as HTTP cookie-based sessions, etc.', + expected_event_types: ['start', 'end', 'info'], + name: 'session', + }, + { + description: + "Use this category to visualize and analyze events describing threat actors' targets, motives, or behaviors.", + expected_event_types: ['indicator'], + name: 'threat', + }, + { + description: + 'Relating to vulnerability scan results. Use this category to analyze vulnerabilities detected by Tenable, Qualys, internal scanners, and other vulnerability management sources.', + expected_event_types: ['info'], + name: 'vulnerability', + }, + { + description: + 'Relating to web server access. Use this category to create a dashboard of web server/proxy activity from apache, IIS, nginx web servers, etc. Note: events from network observers such as Zeek http log may also be included in this category.', + expected_event_types: ['access', 'error', 'info'], + name: 'web', + }, + ], + dashed_name: 'event-category', + description: + 'This is one of four ECS Categorization Fields, and indicates the second level in the ECS category hierarchy.\n`event.category` represents the "big buckets" of ECS categories. For example, filtering on `event.category:process` yields all events relating to process activity. This field is closely related to `event.type`, which is used as a subcategory.\nThis field is an array. This will allow proper categorization of some events that fall in multiple categories.', + example: 'authentication', + flat_name: 'event.category', + ignore_above: 1024, + level: 'core', + name: 'category', + normalize: ['array'], + short: 'Event category. The second categorization field in the hierarchy.', + type: 'keyword', + indexFieldName: 'event.category', + indexFieldType: 'keyword', + indexInvalidValues: [ + { + count: 2, + fieldName: 'an_invalid_category', + }, + { + count: 1, + fieldName: 'theory', + }, + ], + hasEcsMetadata: true, + isEcsCompliant: false, + isInSameFamily: true, + }, + { + dashed_name: 'host-name', + description: + 'Name of the host.\nIt can contain what `hostname` returns on Unix systems, the fully qualified domain name, or a name specified by the user. The sender decides which value to use.', + flat_name: 'host.name', + ignore_above: 1024, + level: 'core', + name: 'name', + normalize: [], + short: 'Name of the host.', + type: 'keyword', + indexFieldName: 'host.name', + indexFieldType: 'text', + indexInvalidValues: [], + hasEcsMetadata: true, + isEcsCompliant: false, + isInSameFamily: false, + }, + { + indexFieldName: 'host.name.keyword', + indexFieldType: 'keyword', + indexInvalidValues: [], + hasEcsMetadata: false, + isEcsCompliant: false, + isInSameFamily: false, + }, + { + indexFieldName: 'some.field', + indexFieldType: 'text', + indexInvalidValues: [], + hasEcsMetadata: false, + isEcsCompliant: false, + isInSameFamily: false, + }, + { + indexFieldName: 'some.field.keyword', + indexFieldType: 'keyword', + indexInvalidValues: [], + hasEcsMetadata: false, + isEcsCompliant: false, + isInSameFamily: false, + }, + { + dashed_name: 'source-ip', + description: 'IP address of the source (IPv4 or IPv6).', + flat_name: 'source.ip', + level: 'core', + name: 'ip', + normalize: [], + short: 'IP address of the source.', + type: 'ip', + indexFieldName: 'source.ip', + indexFieldType: 'text', + indexInvalidValues: [], + hasEcsMetadata: true, + isEcsCompliant: false, + isInSameFamily: false, + }, + { + indexFieldName: 'source.ip.keyword', + indexFieldType: 'keyword', + indexInvalidValues: [], + hasEcsMetadata: false, + isEcsCompliant: false, + isInSameFamily: false, + }, + { + dashed_name: 'source-port', + description: 'Port of the source.', + flat_name: 'source.port', + format: 'string', + level: 'core', + name: 'port', + normalize: [], + short: 'Port of the source.', + type: 'long', + indexFieldName: 'source.port', + indexFieldType: 'long', + indexInvalidValues: [], + hasEcsMetadata: true, + isEcsCompliant: true, + isInSameFamily: false, + }, + ], + ecsCompliant: [ + { + dashed_name: 'timestamp', + description: + 'Date/time when the event originated.\nThis is the date/time extracted from the event, typically representing when the event was generated by the source.\nIf the event source has no original timestamp, this value is typically populated by the first time the event was received by the pipeline.\nRequired field for all events.', + example: '2016-05-23T08:05:34.853Z', + flat_name: '@timestamp', + level: 'core', + name: '@timestamp', + normalize: [], + required: true, + short: 'Date/time when the event originated.', + type: 'date', + indexFieldName: '@timestamp', + indexFieldType: 'date', + indexInvalidValues: [], + hasEcsMetadata: true, + isEcsCompliant: true, + isInSameFamily: false, + }, + { + dashed_name: 'source-port', + description: 'Port of the source.', + flat_name: 'source.port', + format: 'string', + level: 'core', + name: 'port', + normalize: [], + short: 'Port of the source.', + type: 'long', + indexFieldName: 'source.port', + indexFieldType: 'long', + indexInvalidValues: [], + hasEcsMetadata: true, + isEcsCompliant: true, + isInSameFamily: false, + }, + ], + custom: [ + { + indexFieldName: 'host.name.keyword', + indexFieldType: 'keyword', + indexInvalidValues: [], + hasEcsMetadata: false, + isEcsCompliant: false, + isInSameFamily: false, + }, + { + indexFieldName: 'some.field', + indexFieldType: 'text', + indexInvalidValues: [], + hasEcsMetadata: false, + isEcsCompliant: false, + isInSameFamily: false, + }, + { + indexFieldName: 'some.field.keyword', + indexFieldType: 'keyword', + indexInvalidValues: [], + hasEcsMetadata: false, + isEcsCompliant: false, + isInSameFamily: false, + }, + { + indexFieldName: 'source.ip.keyword', + indexFieldType: 'keyword', + indexInvalidValues: [], + hasEcsMetadata: false, + isEcsCompliant: false, + isInSameFamily: false, + }, + ], + incompatible: [ + { + allowed_values: [ + { + description: + 'Events in this category are related to the challenge and response process in which credentials are supplied and verified to allow the creation of a session. Common sources for these logs are Windows event logs and ssh logs. Visualize and analyze events in this category to look for failed logins, and other authentication-related activity.', + expected_event_types: ['start', 'end', 'info'], + name: 'authentication', + }, + { + description: + 'Events in the configuration category have to deal with creating, modifying, or deleting the settings or parameters of an application, process, or system.\nExample sources include security policy change logs, configuration auditing logging, and system integrity monitoring.', + expected_event_types: ['access', 'change', 'creation', 'deletion', 'info'], + name: 'configuration', + }, + { + description: + 'The database category denotes events and metrics relating to a data storage and retrieval system. Note that use of this category is not limited to relational database systems. Examples include event logs from MS SQL, MySQL, Elasticsearch, MongoDB, etc. Use this category to visualize and analyze database activity such as accesses and changes.', + expected_event_types: ['access', 'change', 'info', 'error'], + name: 'database', + }, + { + description: + 'Events in the driver category have to do with operating system device drivers and similar software entities such as Windows drivers, kernel extensions, kernel modules, etc.\nUse events and metrics in this category to visualize and analyze driver-related activity and status on hosts.', + expected_event_types: ['change', 'end', 'info', 'start'], + name: 'driver', + }, + { + description: + 'This category is used for events relating to email messages, email attachments, and email network or protocol activity.\nEmails events can be produced by email security gateways, mail transfer agents, email cloud service providers, or mail server monitoring applications.', + expected_event_types: ['info'], + name: 'email', + }, + { + description: + 'Relating to a set of information that has been created on, or has existed on a filesystem. Use this category of events to visualize and analyze the creation, access, and deletions of files. Events in this category can come from both host-based and network-based sources. An example source of a network-based detection of a file transfer would be the Zeek file.log.', + expected_event_types: ['change', 'creation', 'deletion', 'info'], + name: 'file', + }, + { + description: + 'Use this category to visualize and analyze information such as host inventory or host lifecycle events.\nMost of the events in this category can usually be observed from the outside, such as from a hypervisor or a control plane\'s point of view. Some can also be seen from within, such as "start" or "end".\nNote that this category is for information about hosts themselves; it is not meant to capture activity "happening on a host".', + expected_event_types: ['access', 'change', 'end', 'info', 'start'], + name: 'host', + }, + { + description: + 'Identity and access management (IAM) events relating to users, groups, and administration. Use this category to visualize and analyze IAM-related logs and data from active directory, LDAP, Okta, Duo, and other IAM systems.', + expected_event_types: [ + 'admin', + 'change', + 'creation', + 'deletion', + 'group', + 'info', + 'user', + ], + name: 'iam', + }, + { + description: + 'Relating to intrusion detections from IDS/IPS systems and functions, both network and host-based. Use this category to visualize and analyze intrusion detection alerts from systems such as Snort, Suricata, and Palo Alto threat detections.', + expected_event_types: ['allowed', 'denied', 'info'], + name: 'intrusion_detection', + }, + { + description: + 'Malware detection events and alerts. Use this category to visualize and analyze malware detections from EDR/EPP systems such as Elastic Endpoint Security, Symantec Endpoint Protection, Crowdstrike, and network IDS/IPS systems such as Suricata, or other sources of malware-related events such as Palo Alto Networks threat logs and Wildfire logs.', + expected_event_types: ['info'], + name: 'malware', + }, + { + description: + 'Relating to all network activity, including network connection lifecycle, network traffic, and essentially any event that includes an IP address. Many events containing decoded network protocol transactions fit into this category. Use events in this category to visualize or analyze counts of network ports, protocols, addresses, geolocation information, etc.', + expected_event_types: [ + 'access', + 'allowed', + 'connection', + 'denied', + 'end', + 'info', + 'protocol', + 'start', + ], + name: 'network', + }, + { + description: + 'Relating to software packages installed on hosts. Use this category to visualize and analyze inventory of software installed on various hosts, or to determine host vulnerability in the absence of vulnerability scan data.', + expected_event_types: [ + 'access', + 'change', + 'deletion', + 'info', + 'installation', + 'start', + ], + name: 'package', + }, + { + description: + 'Use this category of events to visualize and analyze process-specific information such as lifecycle events or process ancestry.', + expected_event_types: ['access', 'change', 'end', 'info', 'start'], + name: 'process', + }, + { + description: + 'Having to do with settings and assets stored in the Windows registry. Use this category to visualize and analyze activity such as registry access and modifications.', + expected_event_types: ['access', 'change', 'creation', 'deletion'], + name: 'registry', + }, + { + description: + 'The session category is applied to events and metrics regarding logical persistent connections to hosts and services. Use this category to visualize and analyze interactive or automated persistent connections between assets. Data for this category may come from Windows Event logs, SSH logs, or stateless sessions such as HTTP cookie-based sessions, etc.', + expected_event_types: ['start', 'end', 'info'], + name: 'session', + }, + { + description: + "Use this category to visualize and analyze events describing threat actors' targets, motives, or behaviors.", + expected_event_types: ['indicator'], + name: 'threat', + }, + { + description: + 'Relating to vulnerability scan results. Use this category to analyze vulnerabilities detected by Tenable, Qualys, internal scanners, and other vulnerability management sources.', + expected_event_types: ['info'], + name: 'vulnerability', + }, + { + description: + 'Relating to web server access. Use this category to create a dashboard of web server/proxy activity from apache, IIS, nginx web servers, etc. Note: events from network observers such as Zeek http log may also be included in this category.', + expected_event_types: ['access', 'error', 'info'], + name: 'web', + }, + ], + dashed_name: 'event-category', + description: + 'This is one of four ECS Categorization Fields, and indicates the second level in the ECS category hierarchy.\n`event.category` represents the "big buckets" of ECS categories. For example, filtering on `event.category:process` yields all events relating to process activity. This field is closely related to `event.type`, which is used as a subcategory.\nThis field is an array. This will allow proper categorization of some events that fall in multiple categories.', + example: 'authentication', + flat_name: 'event.category', + ignore_above: 1024, + level: 'core', + name: 'category', + normalize: ['array'], + short: 'Event category. The second categorization field in the hierarchy.', + type: 'keyword', + indexFieldName: 'event.category', + indexFieldType: 'keyword', + indexInvalidValues: [ + { + count: 2, + fieldName: 'an_invalid_category', + }, + { + count: 1, + fieldName: 'theory', + }, + ], + hasEcsMetadata: true, + isEcsCompliant: false, + isInSameFamily: true, + }, + { + dashed_name: 'host-name', + description: + 'Name of the host.\nIt can contain what `hostname` returns on Unix systems, the fully qualified domain name, or a name specified by the user. The sender decides which value to use.', + flat_name: 'host.name', + ignore_above: 1024, + level: 'core', + name: 'name', + normalize: [], + short: 'Name of the host.', + type: 'keyword', + indexFieldName: 'host.name', + indexFieldType: 'text', + indexInvalidValues: [], + hasEcsMetadata: true, + isEcsCompliant: false, + isInSameFamily: false, + }, + { + dashed_name: 'source-ip', + description: 'IP address of the source (IPv4 or IPv6).', + flat_name: 'source.ip', + level: 'core', + name: 'ip', + normalize: [], + short: 'IP address of the source.', + type: 'ip', + indexFieldName: 'source.ip', + indexFieldType: 'text', + indexInvalidValues: [], + hasEcsMetadata: true, + isEcsCompliant: false, + isInSameFamily: false, + }, + ], + }); + }); + }); + + describe('getMappingsProperties', () => { + test('it returns the expected mapping properties', () => { + expect( + getMappingsProperties({ + indexes: mockIndicesGetMappingIndexMappingRecords, + indexName: 'auditbeat-custom-index-1', + }) + ).toEqual({ + '@timestamp': { + type: 'date', + }, + event: { + properties: { + category: { + ignore_above: 1024, + type: 'keyword', + }, + }, + }, + host: { + properties: { + name: { + fields: { + keyword: { + ignore_above: 256, + type: 'keyword', + }, + }, + type: 'text', + }, + }, + }, + some: { + properties: { + field: { + fields: { + keyword: { + ignore_above: 256, + type: 'keyword', + }, + }, + type: 'text', + }, + }, + }, + source: { + properties: { + ip: { + fields: { + keyword: { + ignore_above: 256, + type: 'keyword', + }, + }, + type: 'text', + }, + port: { + type: 'long', + }, + }, + }, + }); + }); + + test('it returns null when `indexes` is null', () => { + expect( + getMappingsProperties({ + indexes: null, // <-- + indexName: 'auditbeat-custom-index-1', + }) + ).toBeNull(); + }); + + test('it returns null when `indexName` does not exist in `indexes`', () => { + expect( + getMappingsProperties({ + indexes: mockIndicesGetMappingIndexMappingRecords, + indexName: 'does-not-exist', // <-- + }) + ).toBeNull(); + }); + + test('it returns null when `properties` does not exist in the mappings', () => { + const missingProperties = { + ...mockIndicesGetMappingIndexMappingRecords, + foozle: { + mappings: {}, // <-- does not have a `properties` + }, + }; + + expect( + getMappingsProperties({ + indexes: missingProperties, + indexName: 'foozle', + }) + ).toBeNull(); + }); + }); + + describe('hasAllDataFetchingCompleted', () => { + test('it returns false when both the mappings and unallowed values are loading', () => { + expect( + hasAllDataFetchingCompleted({ + loadingMappings: true, + loadingUnallowedValues: true, + }) + ).toBe(false); + }); + + test('it returns false when mappings are loading, and unallowed values are NOT loading', () => { + expect( + hasAllDataFetchingCompleted({ + loadingMappings: true, + loadingUnallowedValues: false, + }) + ).toBe(false); + }); + + test('it returns false when mappings are NOT loading, and unallowed values are loading', () => { + expect( + hasAllDataFetchingCompleted({ + loadingMappings: false, + loadingUnallowedValues: true, + }) + ).toBe(false); + }); + + test('it returns true when both the mappings and unallowed values have finished loading', () => { + expect( + hasAllDataFetchingCompleted({ + loadingMappings: false, + loadingUnallowedValues: false, + }) + ).toBe(true); + }); + }); +}); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/index.test.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/index.test.tsx new file mode 100644 index 0000000000000..b6914daef0e7d --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/index.test.tsx @@ -0,0 +1,262 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { DARK_THEME } from '@elastic/charts'; +import numeral from '@elastic/numeral'; +import { render, screen, waitFor } from '@testing-library/react'; +import React from 'react'; + +import { EMPTY_STAT } from '../../helpers'; +import { mockMappingsResponse } from '../../mock/mappings_response/mock_mappings_response'; +import { auditbeatWithAllResults } from '../../mock/pattern_rollup/mock_auditbeat_pattern_rollup'; +import { TestProviders } from '../../mock/test_providers/test_providers'; +import { mockUnallowedValuesResponse } from '../../mock/unallowed_values/mock_unallowed_values'; +import { LOADING_MAPPINGS, LOADING_UNALLOWED_VALUES } from './translations'; +import { UnallowedValueRequestItem } from '../../types'; +import { IndexProperties, Props } from '.'; + +const defaultBytesFormat = '0,0.[0]b'; +const formatBytes = (value: number | undefined) => + value != null ? numeral(value).format(defaultBytesFormat) : EMPTY_STAT; + +const defaultNumberFormat = '0,0.[000]'; +const formatNumber = (value: number | undefined) => + value != null ? numeral(value).format(defaultNumberFormat) : EMPTY_STAT; + +const pattern = 'auditbeat-*'; +const patternRollup = auditbeatWithAllResults; + +let mockFetchMappings = jest.fn( + ({ + abortController, + patternOrIndexName, + }: { + abortController: AbortController; + patternOrIndexName: string; + }) => + new Promise((resolve) => { + resolve(mockMappingsResponse); // happy path + }) +); + +jest.mock('../../use_mappings/helpers', () => ({ + fetchMappings: ({ + abortController, + patternOrIndexName, + }: { + abortController: AbortController; + patternOrIndexName: string; + }) => + mockFetchMappings({ + abortController, + patternOrIndexName, + }), +})); + +let mockFetchUnallowedValues = jest.fn( + ({ + abortController, + indexName, + requestItems, + }: { + abortController: AbortController; + indexName: string; + requestItems: UnallowedValueRequestItem[]; + }) => new Promise((resolve) => resolve(mockUnallowedValuesResponse)) +); + +jest.mock('../../use_unallowed_values/helpers', () => { + const original = jest.requireActual('../../use_unallowed_values/helpers'); + + return { + ...original, + fetchUnallowedValues: ({ + abortController, + indexName, + requestItems, + }: { + abortController: AbortController; + indexName: string; + requestItems: UnallowedValueRequestItem[]; + }) => + mockFetchUnallowedValues({ + abortController, + indexName, + requestItems, + }), + }; +}); + +const defaultProps: Props = { + addSuccessToast: jest.fn(), + canUserCreateAndReadCases: jest.fn(), + docsCount: auditbeatWithAllResults.docsCount ?? 0, + formatBytes, + formatNumber, + getGroupByFieldsOnClick: jest.fn(), + ilmPhase: 'hot', + indexName: 'auditbeat-custom-index-1', + openCreateCaseFlyout: jest.fn(), + pattern, + patternRollup, + theme: DARK_THEME, + updatePatternRollup: jest.fn(), +}; + +describe('IndexProperties', () => { + test('it renders the tab content', async () => { + render( + + + + ); + + await waitFor(() => { + expect(screen.getByTestId('incompatibleTab')).toBeInTheDocument(); + }); + }); + + describe('when an error occurs loading mappings', () => { + const abortController = new AbortController(); + abortController.abort(); + + const error = 'simulated fetch mappings error'; + + beforeEach(() => { + jest.resetAllMocks(); + }); + + test('it displays the expected empty prompt content', async () => { + mockFetchMappings = jest.fn( + ({ + // eslint-disable-next-line @typescript-eslint/no-shadow + abortController, + patternOrIndexName, + }: { + abortController: AbortController; + patternOrIndexName: string; + }) => new Promise((_, reject) => reject(new Error(error))) + ); + + render( + + + + ); + + await waitFor(() => { + expect( + screen + .getByTestId('errorEmptyPrompt') + .textContent?.includes('Unable to load index mappings') + ).toBe(true); + }); + }); + }); + + describe('when an error occurs loading unallowed values', () => { + const abortController = new AbortController(); + abortController.abort(); + + const error = 'simulated fetch unallowed values error'; + + beforeEach(() => { + jest.resetAllMocks(); + }); + + test('it displays the expected empty prompt content', async () => { + mockFetchUnallowedValues = jest.fn( + ({ + // eslint-disable-next-line @typescript-eslint/no-shadow + abortController, + indexName, + requestItems, + }: { + abortController: AbortController; + indexName: string; + requestItems: UnallowedValueRequestItem[]; + }) => new Promise((_, reject) => reject(new Error(error))) + ); + + render( + + + + ); + + await waitFor(() => { + expect( + screen + .getByTestId('errorEmptyPrompt') + .textContent?.includes('Unable to load unallowed values') + ).toBe(true); + }); + }); + }); + + describe('when mappings are loading', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + test('it displays the expected loading prompt content', async () => { + mockFetchMappings = jest.fn( + ({ + abortController, + patternOrIndexName, + }: { + abortController: AbortController; + patternOrIndexName: string; + }) => new Promise(() => {}) // <-- will never resolve or reject + ); + + render( + + + + ); + + await waitFor(() => { + expect( + screen.getByTestId('loadingEmptyPrompt').textContent?.includes(LOADING_MAPPINGS) + ).toBe(true); + }); + }); + }); + + describe('when unallowed values are loading', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + test('it displays the expected loading prompt content', async () => { + mockFetchUnallowedValues = jest.fn( + ({ + abortController, + indexName, + requestItems, + }: { + abortController: AbortController; + indexName: string; + requestItems: UnallowedValueRequestItem[]; + }) => new Promise(() => {}) // <-- will never resolve or reject + ); + + render( + + + + ); + + await waitFor(() => { + expect( + screen.getByTestId('loadingEmptyPrompt').textContent?.includes(LOADING_UNALLOWED_VALUES) + ).toBe(true); + }); + }); + }); +}); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/index.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/index.tsx index 34fac241a2d82..ca9eda507f8dd 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/index.tsx +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/index.tsx @@ -16,7 +16,6 @@ import type { XYChartElementEvent, } from '@elastic/charts'; import { EuiSpacer, EuiTab, EuiTabs } from '@elastic/eui'; -import numeral from '@elastic/numeral'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { getUnallowedValueRequestItems } from '../allowed_values/helpers'; @@ -28,8 +27,8 @@ import { hasAllDataFetchingCompleted, INCOMPATIBLE_TAB_ID, } from './helpers'; -import { EMPTY_STAT } from '../../helpers'; import { LoadingEmptyPrompt } from '../loading_empty_prompt'; +import { getIndexPropertiesContainerId } from '../pattern/helpers'; import { getTabs } from '../tabs/helpers'; import { getAllIncompatibleMarkdownComments } from '../tabs/incompatible_tab/helpers'; import * as i18n from './translations'; @@ -40,10 +39,11 @@ import { useUnallowedValues } from '../../use_unallowed_values'; const EMPTY_MARKDOWN_COMMENTS: string[] = []; -interface Props { +export interface Props { addSuccessToast: (toast: { title: string }) => void; canUserCreateAndReadCases: () => boolean; - defaultNumberFormat: string; + formatBytes: (value: number | undefined) => string; + formatNumber: (value: number | undefined) => string; docsCount: number; getGroupByFieldsOnClick: ( elements: Array< @@ -76,7 +76,8 @@ interface Props { const IndexPropertiesComponent: React.FC = ({ addSuccessToast, canUserCreateAndReadCases, - defaultNumberFormat, + formatBytes, + formatNumber, docsCount, getGroupByFieldsOnClick, ilmPhase, @@ -87,11 +88,6 @@ const IndexPropertiesComponent: React.FC = ({ theme, updatePatternRollup, }) => { - const formatNumber = useCallback( - (value: number | undefined): string => - value != null ? numeral(value).format(defaultNumberFormat) : EMPTY_STAT, - [defaultNumberFormat] - ); const { error: mappingsError, indexes, loading: loadingMappings } = useMappings(indexName); const requestItems = useMemo( @@ -142,7 +138,8 @@ const IndexPropertiesComponent: React.FC = ({ getTabs({ addSuccessToast, addToNewCaseDisabled, - defaultNumberFormat, + formatBytes, + formatNumber, docsCount, getGroupByFieldsOnClick, ilmPhase, @@ -152,13 +149,15 @@ const IndexPropertiesComponent: React.FC = ({ pattern, patternDocsCount: patternRollup?.docsCount ?? 0, setSelectedTabId, + stats: patternRollup?.stats ?? null, theme, }), [ addSuccessToast, addToNewCaseDisabled, - defaultNumberFormat, docsCount, + formatBytes, + formatNumber, getGroupByFieldsOnClick, ilmPhase, indexName, @@ -166,6 +165,7 @@ const IndexPropertiesComponent: React.FC = ({ partitionedFieldMetadata, pattern, patternRollup?.docsCount, + patternRollup?.stats, theme, ] ); @@ -212,11 +212,13 @@ const IndexPropertiesComponent: React.FC = ({ partitionedFieldMetadata != null ? getAllIncompatibleMarkdownComments({ docsCount, + formatBytes, formatNumber, ilmPhase, indexName, partitionedFieldMetadata, patternDocsCount: patternRollup.docsCount ?? 0, + sizeInBytes: patternRollup.sizeInBytes, }) : EMPTY_MARKDOWN_COMMENTS; @@ -239,6 +241,7 @@ const IndexPropertiesComponent: React.FC = ({ } }, [ docsCount, + formatBytes, formatNumber, ilmPhase, indexName, @@ -265,10 +268,10 @@ const IndexPropertiesComponent: React.FC = ({ } return indexes != null ? ( - <> +
{renderTabs()} {selectedTabContent} - +
) : null; }; IndexPropertiesComponent.displayName = 'IndexPropertiesComponent'; diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/markdown/helpers.test.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/markdown/helpers.test.ts index 0ea7a13d1c710..b51a49ecc89df 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/markdown/helpers.test.ts +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/markdown/helpers.test.ts @@ -5,11 +5,244 @@ * 2.0. */ -import { eventCategory, sourceIpWithTextMapping } from '../../../mock/enriched_field_metadata'; +import numeral from '@elastic/numeral'; + +import { + ECS_MAPPING_TYPE_EXPECTED, + FIELD, + INDEX_MAPPING_TYPE_ACTUAL, +} from '../../../compare_fields_table/translations'; +import { ERRORS } from '../../data_quality_summary/errors_popover/translations'; +import { ERROR, INDEX, PATTERN } from '../../data_quality_summary/errors_viewer/translations'; +import { + escape, + escapePreserveNewlines, + getAllowedValues, + getCodeFormattedValue, + getCustomMarkdownTableRows, + getDataQualitySummaryMarkdownComment, + getErrorsMarkdownTable, + getErrorsMarkdownTableRows, + getHeaderSeparator, + getIlmExplainPhaseCountsMarkdownComment, + getIncompatibleMappingsMarkdownTableRows, + getIncompatibleValuesMarkdownTableRows, + getIndexInvalidValues, + getMarkdownComment, + getMarkdownTable, + getMarkdownTableHeader, + getPatternSummaryMarkdownComment, + getResultEmoji, + getSameFamilyBadge, + getStatsRollupMarkdownComment, + getSummaryMarkdownComment, + getSummaryTableMarkdownComment, + getSummaryTableMarkdownHeader, + getSummaryTableMarkdownRow, + getTabCountsMarkdownComment, +} from './helpers'; +import { EMPTY_STAT } from '../../../helpers'; +import { mockAllowedValues } from '../../../mock/allowed_values/mock_allowed_values'; +import { + eventCategory, + mockCustomFields, + mockIncompatibleMappings, + sourceIpWithTextMapping, +} from '../../../mock/enriched_field_metadata/mock_enriched_field_metadata'; +import { mockPartitionedFieldMetadata } from '../../../mock/partitioned_field_metadata/mock_partitioned_field_metadata'; +import { + auditbeatNoResults, + auditbeatWithAllResults, +} from '../../../mock/pattern_rollup/mock_auditbeat_pattern_rollup'; import { SAME_FAMILY } from '../../same_family/translations'; -import { getIncompatibleMappingsMarkdownTableRows, getSameFamilyBadge } from './helpers'; +import { INCOMPATIBLE_FIELD_MAPPINGS_TABLE_TITLE } from '../../tabs/incompatible_tab/translations'; +import { + EnrichedFieldMetadata, + ErrorSummary, + PatternRollup, + UnallowedValueCount, +} from '../../../types'; + +const errorSummary: ErrorSummary[] = [ + { + pattern: '.alerts-security.alerts-default', + indexName: null, + error: 'Error loading stats: Error: Forbidden', + }, + { + error: + 'Error: Error loading unallowed values for index auditbeat-7.2.1-2023.02.13-000001: Forbidden', + indexName: 'auditbeat-7.2.1-2023.02.13-000001', + pattern: 'auditbeat-*', + }, +]; + +const indexName = 'auditbeat-custom-index-1'; + +const defaultBytesFormat = '0,0.[0]b'; +const formatBytes = (value: number | undefined) => + value != null ? numeral(value).format(defaultBytesFormat) : EMPTY_STAT; + +const defaultNumberFormat = '0,0.[000]'; +const formatNumber = (value: number | undefined) => + value != null ? numeral(value).format(defaultNumberFormat) : EMPTY_STAT; describe('helpers', () => { + describe('escape', () => { + test('it returns undefined when `content` is undefined', () => { + expect(escape(undefined)).toBeUndefined(); + }); + + test("it returns the content unmodified when there's nothing to escape", () => { + const content = "there's nothing to escape in this content"; + expect(escape(content)).toEqual(content); + }); + + test('it replaces all newlines in the content with spaces', () => { + const content = '\nthere were newlines in the beginning, middle,\nand end\n'; + expect(escape(content)).toEqual(' there were newlines in the beginning, middle, and end '); + }); + + test('it escapes all column separators in the content with spaces', () => { + const content = '|there were column separators in the beginning, middle,|and end|'; + expect(escape(content)).toEqual( + '\\|there were column separators in the beginning, middle,\\|and end\\|' + ); + }); + + test('it escapes content containing BOTH newlines and column separators', () => { + const content = + '|\nthere were newlines and column separators in the beginning, middle,\n|and end|\n'; + expect(escape(content)).toEqual( + '\\| there were newlines and column separators in the beginning, middle, \\|and end\\| ' + ); + }); + }); + + describe('escapePreserveNewlines', () => { + test('it returns undefined when `content` is undefined', () => { + expect(escapePreserveNewlines(undefined)).toBeUndefined(); + }); + + test("it returns the content unmodified when there's nothing to escape", () => { + const content = "there's (also) nothing to escape in this content"; + expect(escapePreserveNewlines(content)).toEqual(content); + }); + + test('it escapes all column separators in the content with spaces', () => { + const content = '|there were column separators in the beginning, middle,|and end|'; + expect(escapePreserveNewlines(content)).toEqual( + '\\|there were column separators in the beginning, middle,\\|and end\\|' + ); + }); + + test('it does NOT escape newlines in the content', () => { + const content = + '|\nthere were newlines and column separators in the beginning, middle,\n|and end|\n'; + expect(escapePreserveNewlines(content)).toEqual( + '\\|\nthere were newlines and column separators in the beginning, middle,\n\\|and end\\|\n' + ); + }); + }); + + describe('getHeaderSeparator', () => { + test('it returns a sequence of dashes equal to the length of the header, plus two additional dashes to pad each end of the cntent', () => { + const content = '0123456789'; // content.length === 10 + const expected = '------------'; // expected.length === 12 + + expect(getHeaderSeparator(content)).toEqual(expected); + }); + }); + + describe('getMarkdownTableHeader', () => { + const headerNames = [ + '|\nthere were newlines and column separators in the beginning, middle,\n|and end|\n', + 'A second column', + 'A third column', + ]; + + test('it returns the expected table header', () => { + expect(getMarkdownTableHeader(headerNames)).toEqual( + '\n| \\| there were newlines and column separators in the beginning, middle, \\|and end\\| | A second column | A third column | \n|----------------------------------------------------------------------------------|-----------------|----------------|' + ); + }); + }); + + describe('getCodeFormattedValue', () => { + test('it returns the expected placeholder when `value` is undefined', () => { + expect(getCodeFormattedValue(undefined)).toEqual('`--`'); + }); + + test('it returns the content formatted as markdown code', () => { + const value = 'foozle'; + + expect(getCodeFormattedValue(value)).toEqual('`foozle`'); + }); + + test('it escapes content such that `value` may be included in a markdown table cell', () => { + const value = + '|\nthere were newlines and column separators in the beginning, middle,\n|and end|\n'; + + expect(getCodeFormattedValue(value)).toEqual( + '`\\| there were newlines and column separators in the beginning, middle, \\|and end\\| `' + ); + }); + }); + + describe('getAllowedValues', () => { + test('it returns the expected placeholder when `allowedValues` is undefined', () => { + expect(getAllowedValues(undefined)).toEqual('`--`'); + }); + + test('it joins the `allowedValues` `name`s as a markdown-code-formatted, comma separated, string', () => { + expect(getAllowedValues(mockAllowedValues)).toEqual( + '`authentication`, `configuration`, `database`, `driver`, `email`, `file`, `host`, `iam`, `intrusion_detection`, `malware`, `network`, `package`, `process`, `registry`, `session`, `threat`, `vulnerability`, `web`' + ); + }); + }); + + describe('getIndexInvalidValues', () => { + test('it returns the expected placeholder when `indexInvalidValues` is empty', () => { + expect(getIndexInvalidValues([])).toEqual('`--`'); + }); + + test('it returns markdown-code-formatted `fieldName`s, and their associated `count`s', () => { + const indexInvalidValues: UnallowedValueCount[] = [ + { + count: 2, + fieldName: 'an_invalid_category', + }, + { + count: 1, + fieldName: 'theory', + }, + ]; + + expect(getIndexInvalidValues(indexInvalidValues)).toEqual( + `\`an_invalid_category\` (2), \`theory\` (1)` + ); + }); + }); + + describe('getCustomMarkdownTableRows', () => { + test('it returns the expected table rows', () => { + expect(getCustomMarkdownTableRows(mockCustomFields)).toEqual( + '| host.name.keyword | `keyword` | `--` |\n| some.field | `text` | `--` |\n| some.field.keyword | `keyword` | `--` |\n| source.ip.keyword | `keyword` | `--` |' + ); + }); + + test('it returns the expected table rows when some have allowed values', () => { + const withAllowedValues = [ + ...mockCustomFields, + eventCategory, // note: this is not a real-world use case, because custom fields don't have allowed values + ]; + + expect(getCustomMarkdownTableRows(withAllowedValues)).toEqual( + '| host.name.keyword | `keyword` | `--` |\n| some.field | `text` | `--` |\n| some.field.keyword | `keyword` | `--` |\n| source.ip.keyword | `keyword` | `--` |\n| event.category | `keyword` | `authentication`, `configuration`, `database`, `driver`, `email`, `file`, `host`, `iam`, `intrusion_detection`, `malware`, `network`, `package`, `process`, `registry`, `session`, `threat`, `vulnerability`, `web` |' + ); + }); + }); + describe('getSameFamilyBadge', () => { test('it returns the expected badge text when the field is in the same family', () => { const inSameFamily = { @@ -32,7 +265,7 @@ describe('helpers', () => { describe('getIncompatibleMappingsMarkdownTableRows', () => { test('it returns the expected table rows when the field is in the same family', () => { - const eventCategoryWithWildcard = { + const eventCategoryWithWildcard: EnrichedFieldMetadata = { ...eventCategory, // `event.category` is a `keyword` per the ECS spec indexFieldType: 'wildcard', // this index has a mapping of `wildcard` instead of `keyword` isInSameFamily: true, // `wildcard` and `keyword` are in the same family @@ -49,18 +282,367 @@ describe('helpers', () => { }); test('it returns the expected table rows when the field is NOT in the same family', () => { - const eventCategoryWithWildcard = { + const eventCategoryWithText: EnrichedFieldMetadata = { ...eventCategory, // `event.category` is a `keyword` per the ECS spec indexFieldType: 'text', // this index has a mapping of `text` instead of `keyword` isInSameFamily: false, // `text` and `keyword` are NOT in the same family }; expect( - getIncompatibleMappingsMarkdownTableRows([ - eventCategoryWithWildcard, - sourceIpWithTextMapping, - ]) + getIncompatibleMappingsMarkdownTableRows([eventCategoryWithText, sourceIpWithTextMapping]) ).toEqual('| event.category | `keyword` | `text` |\n| source.ip | `ip` | `text` |'); }); }); + + describe('getIncompatibleValuesMarkdownTableRows', () => { + test('it returns the expected table rows', () => { + expect( + getIncompatibleValuesMarkdownTableRows([ + { + ...eventCategory, + hasEcsMetadata: true, + indexInvalidValues: [ + { + count: 2, + fieldName: 'an_invalid_category', + }, + { + count: 1, + fieldName: 'theory', + }, + ], + isEcsCompliant: false, + }, + ]) + ).toEqual( + '| event.category | `authentication`, `configuration`, `database`, `driver`, `email`, `file`, `host`, `iam`, `intrusion_detection`, `malware`, `network`, `package`, `process`, `registry`, `session`, `threat`, `vulnerability`, `web` | `an_invalid_category` (2), `theory` (1) |' + ); + }); + }); + + describe('getMarkdownComment', () => { + test('it returns the expected markdown comment', () => { + const suggestedAction = + '|\nthere were newlines and column separators in this suggestedAction beginning, middle,\n|and end|\n'; + const title = + '|\nthere were newlines and column separators in this title beginning, middle,\n|and end|\n'; + + expect(getMarkdownComment({ suggestedAction, title })).toEqual( + '#### \\| there were newlines and column separators in this title beginning, middle, \\|and end\\| \n\n\\|\nthere were newlines and column separators in this suggestedAction beginning, middle,\n\\|and end\\|\n' + ); + }); + }); + + describe('getErrorsMarkdownTableRows', () => { + test('it returns the expected markdown table rows', () => { + expect(getErrorsMarkdownTableRows(errorSummary)).toEqual( + '| .alerts-security.alerts-default | -- | `Error loading stats: Error: Forbidden` |\n| auditbeat-* | auditbeat-7.2.1-2023.02.13-000001 | `Error: Error loading unallowed values for index auditbeat-7.2.1-2023.02.13-000001: Forbidden` |' + ); + }); + }); + + describe('getErrorsMarkdownTable', () => { + test('it returns the expected table contents', () => { + expect( + getErrorsMarkdownTable({ + errorSummary, + getMarkdownTableRows: getErrorsMarkdownTableRows, + headerNames: [PATTERN, INDEX, ERROR], + title: ERRORS, + }) + ).toEqual( + `## Errors\n\nSome indices were not checked for Data Quality\n\nErrors may occur when pattern or index metadata is temporarily unavailable, or because you don't have the privileges required for access\n\nThe following privileges are required to check an index:\n- \`monitor\` or \`manage\`\n- \`view_index_metadata\`\n- \`read\`\n\n\n| Pattern | Index | Error | \n|---------|-------|-------|\n| .alerts-security.alerts-default | -- | \`Error loading stats: Error: Forbidden\` |\n| auditbeat-* | auditbeat-7.2.1-2023.02.13-000001 | \`Error: Error loading unallowed values for index auditbeat-7.2.1-2023.02.13-000001: Forbidden\` |\n` + ); + }); + + test('it returns an empty string when the error summary is empty', () => { + expect( + getErrorsMarkdownTable({ + errorSummary: [], // <-- empty + getMarkdownTableRows: getErrorsMarkdownTableRows, + headerNames: [PATTERN, INDEX, ERROR], + title: ERRORS, + }) + ).toEqual(''); + }); + }); + + describe('getMarkdownTable', () => { + test('it returns the expected table contents', () => { + expect( + getMarkdownTable({ + enrichedFieldMetadata: mockIncompatibleMappings, + getMarkdownTableRows: getIncompatibleMappingsMarkdownTableRows, + headerNames: [FIELD, ECS_MAPPING_TYPE_EXPECTED, INDEX_MAPPING_TYPE_ACTUAL], + title: INCOMPATIBLE_FIELD_MAPPINGS_TABLE_TITLE(indexName), + }) + ).toEqual( + '#### Incompatible field mappings - auditbeat-custom-index-1\n\n\n| Field | ECS mapping type (expected) | Index mapping type (actual) | \n|-------|-----------------------------|-----------------------------|\n| host.name | `keyword` | `text` |\n| source.ip | `ip` | `text` |\n' + ); + }); + + test('it returns an empty string when `enrichedFieldMetadata` is empty', () => { + expect( + getMarkdownTable({ + enrichedFieldMetadata: [], // <-- empty + getMarkdownTableRows: getIncompatibleMappingsMarkdownTableRows, + headerNames: [FIELD, ECS_MAPPING_TYPE_EXPECTED, INDEX_MAPPING_TYPE_ACTUAL], + title: INCOMPATIBLE_FIELD_MAPPINGS_TABLE_TITLE(indexName), + }) + ).toEqual(''); + }); + }); + + describe('getSummaryMarkdownComment', () => { + test('it returns the expected markdown comment', () => { + expect(getSummaryMarkdownComment(indexName)).toEqual('### auditbeat-custom-index-1\n'); + }); + }); + + describe('getTabCountsMarkdownComment', () => { + test('it returns a comment with the expected counts', () => { + expect(getTabCountsMarkdownComment(mockPartitionedFieldMetadata)).toBe( + '### **Incompatible fields** `3` **Custom fields** `4` **ECS compliant fields** `2` **All fields** `9`\n' + ); + }); + }); + + describe('getResultEmoji', () => { + test('it returns the expected placeholder when `incompatible` is undefined', () => { + expect(getResultEmoji(undefined)).toEqual('--'); + }); + + test('it returns a ✅ when the incompatible count is zero', () => { + expect(getResultEmoji(0)).toEqual('✅'); + }); + + test('it returns a ❌ when the incompatible count is NOT zero', () => { + expect(getResultEmoji(1)).toEqual('❌'); + }); + }); + + describe('getSummaryTableMarkdownHeader', () => { + test('it returns the expected header', () => { + expect(getSummaryTableMarkdownHeader()).toEqual( + '| Result | Index | Docs | Incompatible fields | ILM Phase | Size |\n|--------|-------|------|---------------------|-----------|------|' + ); + }); + }); + + describe('getSummaryTableMarkdownRow', () => { + test('it returns the expected row when all values are provided', () => { + expect( + getSummaryTableMarkdownRow({ + docsCount: 4, + formatBytes, + formatNumber, + incompatible: 3, + ilmPhase: 'unmanaged', + indexName: 'auditbeat-custom-index-1', + patternDocsCount: 57410, + sizeInBytes: 28413, + }) + ).toEqual('| ❌ | auditbeat-custom-index-1 | 4 (0.0%) | 3 | `unmanaged` | 27.7KB |\n'); + }); + + test('it returns the expected row when optional values are NOT provided', () => { + expect( + getSummaryTableMarkdownRow({ + docsCount: 4, + formatBytes, + formatNumber, + incompatible: undefined, // <-- + ilmPhase: undefined, // <-- + indexName: 'auditbeat-custom-index-1', + patternDocsCount: 57410, + sizeInBytes: 28413, + }) + ).toEqual('| -- | auditbeat-custom-index-1 | 4 (0.0%) | -- | -- | 27.7KB |\n'); + }); + }); + + describe('getSummaryTableMarkdownComment', () => { + test('it returns the expected comment', () => { + expect( + getSummaryTableMarkdownComment({ + docsCount: 4, + formatBytes, + formatNumber, + ilmPhase: 'unmanaged', + indexName: 'auditbeat-custom-index-1', + partitionedFieldMetadata: mockPartitionedFieldMetadata, + patternDocsCount: 57410, + sizeInBytes: 28413, + }) + ).toEqual( + '| Result | Index | Docs | Incompatible fields | ILM Phase | Size |\n|--------|-------|------|---------------------|-----------|------|\n| ❌ | auditbeat-custom-index-1 | 4 (0.0%) | 3 | `unmanaged` | 27.7KB |\n\n' + ); + }); + }); + + describe('getStatsRollupMarkdownComment', () => { + test('it returns the expected comment', () => { + expect( + getStatsRollupMarkdownComment({ + docsCount: 57410, + formatBytes, + formatNumber, + incompatible: 3, + indices: 25, + indicesChecked: 1, + sizeInBytes: 28413, + }) + ).toEqual( + '| Incompatible fields | Indices checked | Indices | Size | Docs |\n|---------------------|-----------------|---------|------|------|\n| 3 | 1 | 25 | 27.7KB | 57,410 |\n' + ); + }); + + test('it returns the expected comment when optional values are undefined', () => { + expect( + getStatsRollupMarkdownComment({ + docsCount: 0, + formatBytes, + formatNumber, + incompatible: undefined, + indices: undefined, + indicesChecked: undefined, + sizeInBytes: undefined, + }) + ).toEqual( + '| Incompatible fields | Indices checked | Indices | Size | Docs |\n|---------------------|-----------------|---------|------|------|\n| -- | -- | -- | -- | 0 |\n' + ); + }); + }); + + describe('getDataQualitySummaryMarkdownComment', () => { + test('it returns the expected comment', () => { + expect( + getDataQualitySummaryMarkdownComment({ + formatBytes, + formatNumber, + totalDocsCount: 3343719, + totalIncompatible: 4, + totalIndices: 30, + totalIndicesChecked: 2, + sizeInBytes: 4294967296, + }) + ).toEqual( + '# Data quality\n\n| Incompatible fields | Indices checked | Indices | Size | Docs |\n|---------------------|-----------------|---------|------|------|\n| 4 | 2 | 30 | 4GB | 3,343,719 |\n\n' + ); + }); + + test('it returns the expected comment when optional values are undefined', () => { + expect( + getDataQualitySummaryMarkdownComment({ + formatBytes, + formatNumber, + totalDocsCount: undefined, + totalIncompatible: undefined, + totalIndices: undefined, + totalIndicesChecked: undefined, + sizeInBytes: undefined, + }) + ).toEqual( + '# Data quality\n\n| Incompatible fields | Indices checked | Indices | Size | Docs |\n|---------------------|-----------------|---------|------|------|\n| -- | -- | -- | -- | 0 |\n\n' + ); + }); + }); + + describe('getIlmExplainPhaseCountsMarkdownComment', () => { + test('it returns the expected comment when _all_ of the counts are greater than zero', () => { + expect( + getIlmExplainPhaseCountsMarkdownComment({ + hot: 99, + warm: 8, + unmanaged: 77, + cold: 6, + frozen: 55, + }) + ).toEqual('`hot(99)` `warm(8)` `unmanaged(77)` `cold(6)` `frozen(55)`'); + }); + + test('it returns the expected comment when _some_ of the counts are greater than zero', () => { + expect( + getIlmExplainPhaseCountsMarkdownComment({ + hot: 9, + warm: 0, + unmanaged: 2, + cold: 1, + frozen: 0, + }) + ).toEqual('`hot(9)` `unmanaged(2)` `cold(1)`'); + }); + + test('it returns the expected comment when _none_ of the counts are greater than zero', () => { + expect( + getIlmExplainPhaseCountsMarkdownComment({ + hot: 0, + warm: 0, + unmanaged: 0, + cold: 0, + frozen: 0, + }) + ).toEqual(''); + }); + }); + + describe('getPatternSummaryMarkdownComment', () => { + test('it returns the expected comment when the rollup contains results for all of the indices in the pattern', () => { + expect( + getPatternSummaryMarkdownComment({ + formatBytes, + formatNumber, + patternRollup: auditbeatWithAllResults, + }) + ).toEqual( + '## auditbeat-*\n`hot(1)` `unmanaged(2)`\n\n| Incompatible fields | Indices checked | Indices | Size | Docs |\n|---------------------|-----------------|---------|------|------|\n| 4 | 3 | 3 | 17.9MB | 19,127 |\n\n' + ); + }); + + test('it returns the expected comment when the rollup contains no results', () => { + expect( + getPatternSummaryMarkdownComment({ + formatBytes, + formatNumber, + patternRollup: auditbeatNoResults, + }) + ).toEqual( + '## auditbeat-*\n`hot(1)` `unmanaged(2)`\n\n| Incompatible fields | Indices checked | Indices | Size | Docs |\n|---------------------|-----------------|---------|------|------|\n| -- | 0 | 3 | 17.9MB | 19,127 |\n\n' + ); + }); + + test('it returns the expected comment when the rollup does NOT have `ilmExplainPhaseCounts`', () => { + const noIlmExplainPhaseCounts: PatternRollup = { + ...auditbeatWithAllResults, + ilmExplainPhaseCounts: undefined, // <-- + }; + + expect( + getPatternSummaryMarkdownComment({ + formatBytes, + formatNumber, + patternRollup: noIlmExplainPhaseCounts, + }) + ).toEqual( + '## auditbeat-*\n\n\n| Incompatible fields | Indices checked | Indices | Size | Docs |\n|---------------------|-----------------|---------|------|------|\n| 4 | 3 | 3 | 17.9MB | 19,127 |\n\n' + ); + }); + + test('it returns the expected comment when `docsCount` is undefined', () => { + const noDocsCount: PatternRollup = { + ...auditbeatWithAllResults, + docsCount: undefined, // <-- + }; + + expect( + getPatternSummaryMarkdownComment({ + formatBytes, + formatNumber, + patternRollup: noDocsCount, + }) + ).toEqual( + '## auditbeat-*\n`hot(1)` `unmanaged(2)`\n\n| Incompatible fields | Indices checked | Indices | Size | Docs |\n|---------------------|-----------------|---------|------|------|\n| 4 | 3 | 3 | 17.9MB | 0 |\n\n' + ); + }); + }); }); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/markdown/helpers.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/markdown/helpers.ts index 9137bb346d660..32424dff66883 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/markdown/helpers.ts +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/index_properties/markdown/helpers.ts @@ -5,8 +5,6 @@ * 2.0. */ -import { repeat } from 'lodash/fp'; - import { ERRORS_MAY_OCCUR, ERRORS_CALLOUT_SUMMARY, @@ -44,6 +42,7 @@ import { INDICES, INDICES_CHECKED, RESULT, + SIZE, } from '../../summary_table/translations'; import { DATA_QUALITY_TITLE } from '../../../translations'; @@ -65,11 +64,11 @@ export const escape = (content: string | undefined): string | undefined => export const escapePreserveNewlines = (content: string | undefined): string | undefined => content != null ? content.replaceAll('|', '\\|') : content; -export const getHeaderSeparator = (headerLength: number): string => repeat(headerLength + 2, '-'); +export const getHeaderSeparator = (headerText: string): string => '-'.repeat(headerText.length + 2); // 2 extra, for the spaces on both sides of the column name export const getMarkdownTableHeader = (headerNames: string[]) => ` | ${headerNames.map((name) => `${escape(name)} | `).join('')} -|${headerNames.map((name) => `${getHeaderSeparator(name.length)}|`).join('')}`; +|${headerNames.map((name) => `${getHeaderSeparator(name)}|`).join('')}`; export const getCodeFormattedValue = (value: string | undefined) => `\`${escape(value ?? EMPTY_PLACEHOLDER)}\``; @@ -84,21 +83,7 @@ export const getIndexInvalidValues = (indexInvalidValues: UnallowedValueCount[]) ? getCodeFormattedValue(undefined) : indexInvalidValues .map(({ fieldName, count }) => `${getCodeFormattedValue(escape(fieldName))} (${count})`) - .join(',\n'); - -export const getCommonMarkdownTableRows = ( - enrichedFieldMetadata: EnrichedFieldMetadata[] -): string => - enrichedFieldMetadata - .map( - (x) => - `| ${escape(x.indexFieldName)} | ${getCodeFormattedValue(x.type)} | ${getCodeFormattedValue( - x.indexFieldType - )} | ${getAllowedValues(x.allowed_values)} | ${getIndexInvalidValues( - x.indexInvalidValues - )} | ${escape(x.description ?? EMPTY_PLACEHOLDER)} |` - ) - .join('\n'); + .join(', '); // newlines are instead joined with spaces export const getCustomMarkdownTableRows = ( enrichedFieldMetadata: EnrichedFieldMetadata[] @@ -207,19 +192,7 @@ ${getMarkdownTableRows(enrichedFieldMetadata)} ` : ''; -export const getSummaryMarkdownComment = ({ - ecsFieldReferenceUrl, - ecsReferenceUrl, - incompatible, - indexName, - mappingUrl, -}: { - ecsFieldReferenceUrl: string; - ecsReferenceUrl: string; - incompatible: number | undefined; - indexName: string; - mappingUrl: string; -}): string => +export const getSummaryMarkdownComment = (indexName: string) => `### ${escape(indexName)} `; @@ -244,23 +217,31 @@ export const getResultEmoji = (incompatible: number | undefined): string => { }; export const getSummaryTableMarkdownHeader = (): string => - `| ${RESULT} | ${INDEX} | ${DOCS} | ${INCOMPATIBLE_FIELDS} | ${ILM_PHASE} | -|--------|-------|------|---------------------|-----------|`; + `| ${RESULT} | ${INDEX} | ${DOCS} | ${INCOMPATIBLE_FIELDS} | ${ILM_PHASE} | ${SIZE} | +|${getHeaderSeparator(RESULT)}|${getHeaderSeparator(INDEX)}|${getHeaderSeparator( + DOCS + )}|${getHeaderSeparator(INCOMPATIBLE_FIELDS)}|${getHeaderSeparator( + ILM_PHASE + )}|${getHeaderSeparator(SIZE)}|`; export const getSummaryTableMarkdownRow = ({ docsCount, + formatBytes, formatNumber, ilmPhase, incompatible, indexName, patternDocsCount, + sizeInBytes, }: { docsCount: number; + formatBytes: (value: number | undefined) => string; formatNumber: (value: number | undefined) => string; ilmPhase: IlmPhase | undefined; incompatible: number | undefined; indexName: string; patternDocsCount: number; + sizeInBytes: number | undefined; }): string => `| ${getResultEmoji(incompatible)} | ${escape(indexName)} | ${formatNumber( docsCount @@ -268,77 +249,95 @@ export const getSummaryTableMarkdownRow = ({ docsCount, patternDocsCount, })}) | ${incompatible ?? EMPTY_PLACEHOLDER} | ${ - ilmPhase != null ? getCodeFormattedValue(ilmPhase) : '' - } | + ilmPhase != null ? getCodeFormattedValue(ilmPhase) : EMPTY_PLACEHOLDER + } | ${formatBytes(sizeInBytes)} | `; export const getSummaryTableMarkdownComment = ({ docsCount, + formatBytes, formatNumber, ilmPhase, indexName, partitionedFieldMetadata, patternDocsCount, + sizeInBytes, }: { docsCount: number; + formatBytes: (value: number | undefined) => string; formatNumber: (value: number | undefined) => string; ilmPhase: IlmPhase | undefined; indexName: string; partitionedFieldMetadata: PartitionedFieldMetadata; patternDocsCount: number; + sizeInBytes: number | undefined; }): string => `${getSummaryTableMarkdownHeader()} ${getSummaryTableMarkdownRow({ docsCount, + formatBytes, formatNumber, ilmPhase, indexName, incompatible: partitionedFieldMetadata.incompatible.length, patternDocsCount, + sizeInBytes, })} `; export const getStatsRollupMarkdownComment = ({ docsCount, + formatBytes, formatNumber, incompatible, indices, indicesChecked, + sizeInBytes, }: { docsCount: number; + formatBytes: (value: number | undefined) => string; formatNumber: (value: number | undefined) => string; incompatible: number | undefined; indices: number | undefined; indicesChecked: number | undefined; + sizeInBytes: number | undefined; }): string => - `| ${INCOMPATIBLE_FIELDS} | ${INDICES_CHECKED} | ${INDICES} | ${DOCS} | -|---------------------|-----------------|---------|------| + `| ${INCOMPATIBLE_FIELDS} | ${INDICES_CHECKED} | ${INDICES} | ${SIZE} | ${DOCS} | +|${getHeaderSeparator(INCOMPATIBLE_FIELDS)}|${getHeaderSeparator( + INDICES_CHECKED + )}|${getHeaderSeparator(INDICES)}|${getHeaderSeparator(SIZE)}|${getHeaderSeparator(DOCS)}| | ${incompatible ?? EMPTY_STAT} | ${indicesChecked ?? EMPTY_STAT} | ${ indices ?? EMPTY_STAT - } | ${formatNumber(docsCount)} | + } | ${formatBytes(sizeInBytes)} | ${formatNumber(docsCount)} | `; export const getDataQualitySummaryMarkdownComment = ({ + formatBytes, formatNumber, totalDocsCount, totalIncompatible, totalIndices, totalIndicesChecked, + sizeInBytes, }: { + formatBytes: (value: number | undefined) => string; formatNumber: (value: number | undefined) => string; totalDocsCount: number | undefined; totalIncompatible: number | undefined; totalIndices: number | undefined; totalIndicesChecked: number | undefined; + sizeInBytes: number | undefined; }): string => `# ${DATA_QUALITY_TITLE} ${getStatsRollupMarkdownComment({ docsCount: totalDocsCount ?? 0, + formatBytes, formatNumber, incompatible: totalIncompatible, indices: totalIndices, indicesChecked: totalIndicesChecked, + sizeInBytes, })} `; @@ -355,13 +354,17 @@ export const getIlmExplainPhaseCountsMarkdownComment = ({ unmanaged > 0 ? getCodeFormattedValue(`${UNMANAGED}(${unmanaged})`) : '', cold > 0 ? getCodeFormattedValue(`${COLD}(${cold})`) : '', frozen > 0 ? getCodeFormattedValue(`${FROZEN}(${frozen})`) : '', - ].join(' '); + ] + .filter((x) => x !== '') // prevents extra whitespace + .join(' '); export const getPatternSummaryMarkdownComment = ({ + formatBytes, formatNumber, patternRollup, patternRollup: { docsCount, indices, ilmExplainPhaseCounts, pattern, results }, }: { + formatBytes: (value: number | undefined) => string; formatNumber: (value: number | undefined) => string; patternRollup: PatternRollup; }): string => @@ -374,9 +377,11 @@ ${ ${getStatsRollupMarkdownComment({ docsCount: docsCount ?? 0, + formatBytes, formatNumber, incompatible: getTotalPatternIncompatible(results), indices, indicesChecked: getTotalPatternIndicesChecked(patternRollup), + sizeInBytes: patternRollup.sizeInBytes, })} `; diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/loading_empty_prompt/index.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/loading_empty_prompt/index.tsx index 84e701c0aa244..7eda6d039ffe0 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/loading_empty_prompt/index.tsx +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/loading_empty_prompt/index.tsx @@ -15,7 +15,9 @@ interface Props { const LoadingEmptyPromptComponent: React.FC = ({ loading }) => { const icon = useMemo(() => , []); - return {loading}} />; + return ( + {loading}} /> + ); }; LoadingEmptyPromptComponent.displayName = 'LoadingEmptyPromptComponent'; diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/panel_subtitle/index.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/panel_subtitle/index.tsx deleted file mode 100644 index 10e460d9c24f5..0000000000000 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/panel_subtitle/index.tsx +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { EuiCode, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; -import React from 'react'; - -import * as i18n from '../../translations'; - -interface Props { - error: string | null; - loading: boolean; - version: string | null; - versionLoading: boolean; -} - -const PanelSubtitleComponent: React.FC = ({ error, loading, version, versionLoading }) => { - const allDataLoaded = !loading && !versionLoading && error == null && version != null; - - return allDataLoaded ? ( - - - - {i18n.SELECT_AN_INDEX} {version} - - - - ) : null; -}; - -export const PanelSubtitle = React.memo(PanelSubtitleComponent); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/helpers.test.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/helpers.test.ts new file mode 100644 index 0000000000000..c40f6a73ffa03 --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/helpers.test.ts @@ -0,0 +1,865 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { + IlmExplainLifecycleLifecycleExplain, + IlmExplainLifecycleLifecycleExplainManaged, + IlmExplainLifecycleLifecycleExplainUnmanaged, +} from '@elastic/elasticsearch/lib/api/types'; + +import { + defaultSort, + getIlmPhase, + getIndexPropertiesContainerId, + getIlmExplainPhaseCounts, + getIndexIncompatible, + getPageIndex, + getPhaseCount, + getSummaryTableItems, + isManaged, + shouldCreateIndexNames, + shouldCreatePatternRollup, +} from './helpers'; +import { mockIlmExplain } from '../../mock/ilm_explain/mock_ilm_explain'; +import { mockDataQualityCheckResult } from '../../mock/data_quality_check_result/mock_index'; +import { auditbeatWithAllResults } from '../../mock/pattern_rollup/mock_auditbeat_pattern_rollup'; +import { mockStats } from '../../mock/stats/mock_stats'; +import { IndexSummaryTableItem } from '../summary_table/helpers'; +import { DataQualityCheckResult } from '../../types'; + +const hot: IlmExplainLifecycleLifecycleExplainManaged = { + index: '.ds-packetbeat-8.6.1-2023.02.04-000001', + managed: true, + policy: 'packetbeat', + index_creation_date_millis: 1675536751379, + time_since_index_creation: '3.98d', + lifecycle_date_millis: 1675536751379, + age: '3.98d', + phase: 'hot', + phase_time_millis: 1675536751809, + action: 'rollover', + action_time_millis: 1675536751809, + step: 'check-rollover-ready', + step_time_millis: 1675536751809, + phase_execution: { + policy: 'packetbeat', + version: 1, + modified_date_in_millis: 1675536751205, + }, +}; +const warm = { + ...hot, + phase: 'warm', +}; +const cold = { + ...hot, + phase: 'cold', +}; +const frozen = { + ...hot, + phase: 'frozen', +}; +const other = { + ...hot, + phase: 'other', // not a valid phase +}; + +const managed: Record = { + hot, + warm, + cold, + frozen, +}; + +const unmanaged: IlmExplainLifecycleLifecycleExplainUnmanaged = { + index: 'michael', + managed: false, +}; + +describe('helpers', () => { + const indexName = '.ds-packetbeat-8.6.1-2023.02.04-000001'; + + describe('isManaged', () => { + test('it returns true when the `ilmExplainRecord` `managed` property is true', () => { + const ilmExplain = mockIlmExplain[indexName]; + + expect(isManaged(ilmExplain)).toBe(true); + }); + + test('it returns false when the `ilmExplainRecord` is undefined', () => { + expect(isManaged(undefined)).toBe(false); + }); + }); + + describe('getPhaseCount', () => { + test('it returns the expected count when an index with the specified `ilmPhase` exists in the `IlmExplainLifecycleLifecycleExplain` record', () => { + expect( + getPhaseCount({ + ilmExplain: mockIlmExplain, + ilmPhase: 'hot', // this phase is in the record + indexName, // valid index name + }) + ).toEqual(1); + }); + + test('it returns zero when `ilmPhase` is null', () => { + expect( + getPhaseCount({ + ilmExplain: null, + ilmPhase: 'hot', + indexName, + }) + ).toEqual(0); + }); + + test('it returns zero when the `indexName` does NOT exist in the `IlmExplainLifecycleLifecycleExplain` record', () => { + expect( + getPhaseCount({ + ilmExplain: mockIlmExplain, + ilmPhase: 'hot', + indexName: 'invalid', // this index does NOT exist + }) + ).toEqual(0); + }); + + test('it returns zero when the specified `ilmPhase` does NOT exist in the `IlmExplainLifecycleLifecycleExplain` record', () => { + expect( + getPhaseCount({ + ilmExplain: mockIlmExplain, + ilmPhase: 'warm', // this phase is NOT in the record + indexName, // valid index name + }) + ).toEqual(0); + }); + + describe('when `ilmPhase` is `unmanaged`', () => { + test('it returns the expected count for an `unmanaged` index', () => { + const index = 'auditbeat-custom-index-1'; + const ilmExplainRecord: IlmExplainLifecycleLifecycleExplain = { + index, + managed: false, + }; + const ilmExplain = { + [index]: ilmExplainRecord, + }; + + expect( + getPhaseCount({ + ilmExplain, + ilmPhase: 'unmanaged', // ilmPhase is unmanaged + indexName: index, // an unmanaged index + }) + ).toEqual(1); + }); + + test('it returns zero for a managed index', () => { + expect( + getPhaseCount({ + ilmExplain: mockIlmExplain, + ilmPhase: 'unmanaged', // ilmPhase is unmanaged + indexName, // a managed (`hot`) index + }) + ).toEqual(0); + }); + }); + }); + + describe('getIlmPhase', () => { + test('it returns undefined when the `ilmExplainRecord` is undefined', () => { + expect(getIlmPhase(undefined)).toBeUndefined(); + }); + + describe('when the `ilmExplainRecord` is a `IlmExplainLifecycleLifecycleExplainManaged` record', () => { + Object.keys(managed).forEach((phase) => + test(`it returns the expected phase when 'phase' is '${phase}'`, () => { + expect(getIlmPhase(managed[phase])).toEqual(phase); + }) + ); + + test(`it returns undefined when the 'phase' is unknown`, () => { + expect(getIlmPhase(other)).toBeUndefined(); + }); + }); + + describe('when the `ilmExplainRecord` is a `IlmExplainLifecycleLifecycleExplainUnmanaged` record', () => { + test('it returns `unmanaged`', () => { + expect(getIlmPhase(unmanaged)).toEqual('unmanaged'); + }); + }); + }); + + describe('getIlmExplainPhaseCounts', () => { + test('it returns the expected counts (all zeros) when `ilmExplain` is null', () => { + expect(getIlmExplainPhaseCounts(null)).toEqual({ + cold: 0, + frozen: 0, + hot: 0, + unmanaged: 0, + warm: 0, + }); + }); + + test('it returns the expected counts', () => { + const ilmExplain: Record = { + ...managed, + [unmanaged.index]: unmanaged, + }; + + expect(getIlmExplainPhaseCounts(ilmExplain)).toEqual({ + cold: 1, + frozen: 1, + hot: 1, + unmanaged: 1, + warm: 1, + }); + }); + }); + + describe('getIndexIncompatible', () => { + test('it returns undefined when `results` is undefined', () => { + expect( + getIndexIncompatible({ + indexName, + results: undefined, // <-- + }) + ).toBeUndefined(); + }); + + test('it returns undefined when `indexName` is not in the `results`', () => { + expect( + getIndexIncompatible({ + indexName: 'not_in_the_results', // <-- + results: mockDataQualityCheckResult, + }) + ).toBeUndefined(); + }); + + test('it returns the expected count', () => { + expect( + getIndexIncompatible({ + indexName: 'auditbeat-custom-index-1', + results: mockDataQualityCheckResult, + }) + ).toEqual(3); + }); + }); + + describe('getSummaryTableItems', () => { + const indexNames = [ + '.ds-packetbeat-8.6.1-2023.02.04-000001', + '.ds-packetbeat-8.5.3-2023.02.04-000001', + 'auditbeat-custom-index-1', + ]; + const pattern = 'auditbeat-*'; + const patternDocsCount = 4; + const results: Record = { + 'auditbeat-custom-index-1': { + docsCount: 4, + error: null, + ilmPhase: 'unmanaged', + incompatible: 3, + indexName: 'auditbeat-custom-index-1', + markdownComments: [ + '### auditbeat-custom-index-1\n', + '| Result | Index | Docs | Incompatible fields | ILM Phase |\n|--------|-------|------|---------------------|-----------|\n| ❌ | auditbeat-custom-index-1 | 4 (0.0%) | 3 | `unmanaged` |\n\n', + '### **Incompatible fields** `3` **Custom fields** `4` **ECS compliant fields** `2` **All fields** `9`\n', + "#### 3 incompatible fields, 0 fields with mappings in the same family\n\nFields are incompatible with ECS when index mappings, or the values of the fields in the index, don't conform to the Elastic Common Schema (ECS), version 8.6.1.\n\nIncompatible fields with mappings in the same family have exactly the same search behavior but may have different space usage or performance characteristics.\n\nWhen an incompatible field is not in the same family:\n❌ Detection engine rules referencing these fields may not match them correctly\n❌ Pages may not display some events or fields due to unexpected field mappings or values\n❌ Mappings or field values that don't comply with ECS are not supported\n", + '\n#### Incompatible field mappings - auditbeat-custom-index-1\n\n\n| Field | ECS mapping type (expected) | Index mapping type (actual) | \n|-------|-----------------------------|-----------------------------|\n| host.name | `keyword` | `text` |\n| source.ip | `ip` | `text` |\n\n#### Incompatible field values - auditbeat-custom-index-1\n\n\n| Field | ECS values (expected) | Document values (actual) | \n|-------|-----------------------|--------------------------|\n| event.category | `authentication`, `configuration`, `database`, `driver`, `email`, `file`, `host`, `iam`, `intrusion_detection`, `malware`, `network`, `package`, `process`, `registry`, `session`, `threat`, `vulnerability`, `web` | `an_invalid_category` (2),\n`theory` (1) |\n\n', + ], + pattern: 'auditbeat-*', + }, + }; + + test('it returns the expected summary table items', () => { + expect( + getSummaryTableItems({ + ilmExplain: mockIlmExplain, + indexNames, + pattern, + patternDocsCount, + results, + sortByColumn: defaultSort.sort.field, + sortByDirection: defaultSort.sort.direction, + stats: mockStats, + }) + ).toEqual([ + { + docsCount: 1630289, + ilmPhase: 'hot', + incompatible: undefined, + indexName: '.ds-packetbeat-8.5.3-2023.02.04-000001', + pattern: 'auditbeat-*', + patternDocsCount: 4, + sizeInBytes: 733175040, + }, + { + docsCount: 1628343, + ilmPhase: 'hot', + incompatible: undefined, + indexName: '.ds-packetbeat-8.6.1-2023.02.04-000001', + pattern: 'auditbeat-*', + patternDocsCount: 4, + sizeInBytes: 731583142, + }, + { + docsCount: 4, + ilmPhase: 'unmanaged', + incompatible: 3, + indexName: 'auditbeat-custom-index-1', + pattern: 'auditbeat-*', + patternDocsCount: 4, + sizeInBytes: 28413, + }, + ]); + }); + + test('it returns the expected summary table items when `sortByDirection` is ascending', () => { + expect( + getSummaryTableItems({ + ilmExplain: mockIlmExplain, + indexNames, + pattern, + patternDocsCount, + results, + sortByColumn: defaultSort.sort.field, + sortByDirection: 'asc', // <-- ascending + stats: mockStats, + }) + ).toEqual([ + { + docsCount: 4, + ilmPhase: 'unmanaged', + incompatible: 3, + indexName: 'auditbeat-custom-index-1', + pattern: 'auditbeat-*', + patternDocsCount: 4, + sizeInBytes: 28413, + }, + { + docsCount: 1628343, + ilmPhase: 'hot', + incompatible: undefined, + indexName: '.ds-packetbeat-8.6.1-2023.02.04-000001', + pattern: 'auditbeat-*', + patternDocsCount: 4, + sizeInBytes: 731583142, + }, + { + docsCount: 1630289, + ilmPhase: 'hot', + incompatible: undefined, + indexName: '.ds-packetbeat-8.5.3-2023.02.04-000001', + pattern: 'auditbeat-*', + patternDocsCount: 4, + sizeInBytes: 733175040, + }, + ]); + }); + + test('it returns the expected summary table items when data is unavailable', () => { + expect( + getSummaryTableItems({ + ilmExplain: null, // <-- no data + indexNames, + pattern, + patternDocsCount, + results: undefined, // <-- no data + sortByColumn: defaultSort.sort.field, + sortByDirection: defaultSort.sort.direction, + stats: null, // <-- no data + }) + ).toEqual([ + { + docsCount: 0, + ilmPhase: undefined, + incompatible: undefined, + indexName: '.ds-packetbeat-8.6.1-2023.02.04-000001', + pattern: 'auditbeat-*', + patternDocsCount: 4, + sizeInBytes: 0, + }, + { + docsCount: 0, + ilmPhase: undefined, + incompatible: undefined, + indexName: '.ds-packetbeat-8.5.3-2023.02.04-000001', + pattern: 'auditbeat-*', + patternDocsCount: 4, + sizeInBytes: 0, + }, + { + docsCount: 0, + ilmPhase: undefined, + incompatible: undefined, + indexName: 'auditbeat-custom-index-1', + pattern: 'auditbeat-*', + patternDocsCount: 4, + sizeInBytes: 0, + }, + ]); + }); + }); + + describe('shouldCreateIndexNames', () => { + const indexNames = [ + '.ds-packetbeat-8.6.1-2023.02.04-000001', + '.ds-packetbeat-8.5.3-2023.02.04-000001', + 'auditbeat-custom-index-1', + ]; + + test('returns true when `indexNames` does NOT exist, and the required `stats` and `ilmExplain` are available', () => { + expect( + shouldCreateIndexNames({ + ilmExplain: mockIlmExplain, + indexNames: undefined, + stats: mockStats, + }) + ).toBe(true); + }); + + test('returns false when `indexNames` exists, and the required `stats` and `ilmExplain` are available', () => { + expect( + shouldCreateIndexNames({ + ilmExplain: mockIlmExplain, + indexNames, + stats: mockStats, + }) + ).toBe(false); + }); + + test('returns false when `indexNames` does NOT exist, `stats` is NOT available, and `ilmExplain` is available', () => { + expect( + shouldCreateIndexNames({ + ilmExplain: mockIlmExplain, + indexNames: undefined, + stats: null, + }) + ).toBe(false); + }); + + test('returns false when `indexNames` does NOT exist, `stats` is available, and `ilmExplain` is NOT available', () => { + expect( + shouldCreateIndexNames({ + ilmExplain: null, + indexNames: undefined, + stats: mockStats, + }) + ).toBe(false); + }); + + test('returns false when `indexNames` does NOT exist, `stats` is NOT available, and `ilmExplain` is NOT available', () => { + expect( + shouldCreateIndexNames({ + ilmExplain: null, + indexNames: undefined, + stats: null, + }) + ).toBe(false); + }); + + test('returns false when `indexNames` exists, `stats` is NOT available, and `ilmExplain` is NOT available', () => { + expect( + shouldCreateIndexNames({ + ilmExplain: null, + indexNames, + stats: null, + }) + ).toBe(false); + }); + }); + + describe('shouldCreatePatternRollup', () => { + test('it returns false when the `patternRollup` already exists', () => { + expect( + shouldCreatePatternRollup({ + error: null, + ilmExplain: mockIlmExplain, + patternRollup: auditbeatWithAllResults, + stats: mockStats, + }) + ).toBe(false); + }); + + test('it returns true when all data was loaded', () => { + expect( + shouldCreatePatternRollup({ + error: null, + ilmExplain: mockIlmExplain, + patternRollup: undefined, + stats: mockStats, + }) + ).toBe(true); + }); + + test('it returns false when `stats`, but NOT `ilmExplain` was loaded', () => { + expect( + shouldCreatePatternRollup({ + error: null, + ilmExplain: null, + patternRollup: undefined, + stats: mockStats, + }) + ).toBe(false); + }); + + test('it returns false when `stats` was NOT loaded, and `ilmExplain` was loaded', () => { + expect( + shouldCreatePatternRollup({ + error: null, + ilmExplain: mockIlmExplain, + patternRollup: undefined, + stats: null, + }) + ).toBe(false); + }); + + test('it returns true if an error occurred, and NO data was loaded', () => { + expect( + shouldCreatePatternRollup({ + error: 'whoops', + ilmExplain: null, + patternRollup: undefined, + stats: null, + }) + ).toBe(true); + }); + + test('it returns true if an error occurred, and just `stats` was loaded', () => { + expect( + shouldCreatePatternRollup({ + error: 'something went', + ilmExplain: null, + patternRollup: undefined, + stats: mockStats, + }) + ).toBe(true); + }); + + test('it returns true if an error occurred, and just `ilmExplain` was loaded', () => { + expect( + shouldCreatePatternRollup({ + error: 'horribly wrong', + ilmExplain: mockIlmExplain, + patternRollup: undefined, + stats: null, + }) + ).toBe(true); + }); + + test('it returns true if an error occurred, and all data was loaded', () => { + expect( + shouldCreatePatternRollup({ + error: 'over here', + ilmExplain: mockIlmExplain, + patternRollup: undefined, + stats: mockStats, + }) + ).toBe(true); + }); + }); + + describe('getIndexPropertiesContainerId', () => { + const pattern = 'auditbeat-*'; + + test('it returns the expected id', () => { + expect(getIndexPropertiesContainerId({ indexName, pattern })).toEqual( + 'index-properties-container-auditbeat-*.ds-packetbeat-8.6.1-2023.02.04-000001' + ); + }); + }); + + describe('getPageIndex', () => { + const getPageIndexArgs: { + indexName: string; + items: IndexSummaryTableItem[]; + pageSize: number; + } = { + indexName: 'auditbeat-7.17.9-2023.04.09-000001', // <-- on page 2 of 3 (page index 1) + items: [ + { + docsCount: 48077, + incompatible: undefined, + indexName: 'auditbeat-7.14.2-2023.04.09-000001', + ilmPhase: 'hot', + pattern: 'auditbeat-*', + patternDocsCount: 1118155, + sizeInBytes: 43357342, + }, + { + docsCount: 48068, + incompatible: undefined, + indexName: 'auditbeat-7.3.2-2023.04.09-000001', + ilmPhase: 'hot', + pattern: 'auditbeat-*', + patternDocsCount: 1118155, + sizeInBytes: 32460397, + }, + { + docsCount: 48064, + incompatible: undefined, + indexName: 'auditbeat-7.11.2-2023.04.09-000001', + ilmPhase: 'hot', + pattern: 'auditbeat-*', + patternDocsCount: 1118155, + sizeInBytes: 42782794, + }, + { + docsCount: 47868, + incompatible: undefined, + indexName: 'auditbeat-7.6.2-2023.04.09-000001', + ilmPhase: 'hot', + pattern: 'auditbeat-*', + patternDocsCount: 1118155, + sizeInBytes: 31575964, + }, + { + docsCount: 47827, + incompatible: 20, + indexName: 'auditbeat-7.15.2-2023.04.09-000001', + ilmPhase: 'hot', + pattern: 'auditbeat-*', + patternDocsCount: 1118155, + sizeInBytes: 44130657, + }, + { + docsCount: 47642, + incompatible: undefined, + indexName: '.ds-auditbeat-8.4.3-2023.04.09-000001', + ilmPhase: 'hot', + pattern: 'auditbeat-*', + patternDocsCount: 1118155, + sizeInBytes: 42412521, + }, + { + docsCount: 47545, + incompatible: undefined, + indexName: 'auditbeat-7.16.3-2023.04.09-000001', + ilmPhase: 'hot', + pattern: 'auditbeat-*', + patternDocsCount: 1118155, + sizeInBytes: 41423244, + }, + { + docsCount: 47531, + incompatible: undefined, + indexName: 'auditbeat-7.5.2-2023.04.09-000001', + ilmPhase: 'hot', + pattern: 'auditbeat-*', + patternDocsCount: 1118155, + sizeInBytes: 32394133, + }, + { + docsCount: 47530, + incompatible: undefined, + indexName: 'auditbeat-7.12.1-2023.04.09-000001', + ilmPhase: 'hot', + pattern: 'auditbeat-*', + patternDocsCount: 1118155, + sizeInBytes: 43015519, + }, + { + docsCount: 47520, + incompatible: undefined, + indexName: '.ds-auditbeat-8.0.1-2023.04.09-000001', + ilmPhase: 'hot', + pattern: 'auditbeat-*', + patternDocsCount: 1118155, + sizeInBytes: 42230604, + }, + { + docsCount: 47496, + incompatible: undefined, + indexName: '.ds-auditbeat-8.2.3-2023.04.09-000001', + ilmPhase: 'hot', + pattern: 'auditbeat-*', + patternDocsCount: 1118155, + sizeInBytes: 41710968, + }, + { + docsCount: 47486, + incompatible: undefined, + indexName: '.ds-auditbeat-8.5.3-2023.04.09-000001', + ilmPhase: 'hot', + pattern: 'auditbeat-*', + patternDocsCount: 1118155, + sizeInBytes: 42295944, + }, + { + docsCount: 47486, + incompatible: undefined, + indexName: '.ds-auditbeat-8.3.3-2023.04.09-000001', + ilmPhase: 'hot', + pattern: 'auditbeat-*', + patternDocsCount: 1118155, + sizeInBytes: 41761321, + }, + { + docsCount: 47460, + incompatible: undefined, + indexName: 'auditbeat-7.2.1-2023.04.09-000001', + ilmPhase: 'hot', + pattern: 'auditbeat-*', + patternDocsCount: 1118155, + sizeInBytes: 30481198, + }, + { + docsCount: 47439, + incompatible: undefined, + indexName: 'auditbeat-7.17.9-2023.04.09-000001', + ilmPhase: 'hot', + pattern: 'auditbeat-*', + patternDocsCount: 1118155, + sizeInBytes: 41554041, + }, + { + docsCount: 47395, + incompatible: undefined, + indexName: 'auditbeat-7.9.3-2023.04.09-000001', + ilmPhase: 'hot', + pattern: 'auditbeat-*', + patternDocsCount: 1118155, + sizeInBytes: 42815907, + }, + { + docsCount: 47394, + incompatible: undefined, + indexName: '.ds-auditbeat-8.7.0-2023.04.09-000001', + ilmPhase: 'hot', + pattern: 'auditbeat-*', + patternDocsCount: 1118155, + sizeInBytes: 41157112, + }, + { + docsCount: 47372, + incompatible: undefined, + indexName: 'auditbeat-7.4.2-2023.04.09-000001', + ilmPhase: 'hot', + pattern: 'auditbeat-*', + patternDocsCount: 1118155, + sizeInBytes: 31626792, + }, + { + docsCount: 47369, + incompatible: undefined, + indexName: 'auditbeat-7.13.4-2023.04.09-000001', + ilmPhase: 'hot', + pattern: 'auditbeat-*', + patternDocsCount: 1118155, + sizeInBytes: 41828969, + }, + { + docsCount: 47348, + incompatible: undefined, + indexName: 'auditbeat-7.7.1-2023.04.09-000001', + ilmPhase: 'hot', + pattern: 'auditbeat-*', + patternDocsCount: 1118155, + sizeInBytes: 40010773, + }, + { + docsCount: 47339, + incompatible: undefined, + indexName: 'auditbeat-7.10.2-2023.04.09-000001', + ilmPhase: 'hot', + pattern: 'auditbeat-*', + patternDocsCount: 1118155, + sizeInBytes: 43480570, + }, + { + docsCount: 47325, + incompatible: undefined, + indexName: '.ds-auditbeat-8.1.3-2023.04.09-000001', + ilmPhase: 'hot', + pattern: 'auditbeat-*', + patternDocsCount: 1118155, + sizeInBytes: 41822475, + }, + { + docsCount: 47294, + incompatible: undefined, + indexName: 'auditbeat-7.8.0-2023.04.09-000001', + ilmPhase: 'hot', + pattern: 'auditbeat-*', + patternDocsCount: 1118155, + sizeInBytes: 43018490, + }, + { + docsCount: 24276, + incompatible: undefined, + indexName: '.ds-auditbeat-8.6.1-2023.04.09-000001', + ilmPhase: 'hot', + pattern: 'auditbeat-*', + patternDocsCount: 1118155, + sizeInBytes: 23579440, + }, + { + docsCount: 4, + incompatible: undefined, + indexName: 'auditbeat-custom-index-1', + ilmPhase: 'unmanaged', + pattern: 'auditbeat-*', + patternDocsCount: 1118155, + sizeInBytes: 28409, + }, + { + docsCount: 0, + incompatible: undefined, + indexName: 'auditbeat-custom-empty-index-1', + ilmPhase: 'unmanaged', + pattern: 'auditbeat-*', + patternDocsCount: 1118155, + sizeInBytes: 247, + }, + ], + pageSize: 10, + }; + + test('it returns the expected page index', () => { + expect(getPageIndex(getPageIndexArgs)).toEqual(1); + }); + + test('it returns the expected page index for the first item', () => { + const firstItemIndexName = 'auditbeat-7.14.2-2023.04.09-000001'; + + expect( + getPageIndex({ + ...getPageIndexArgs, + indexName: firstItemIndexName, + }) + ).toEqual(0); + }); + + test('it returns the expected page index for the last item', () => { + const lastItemIndexName = 'auditbeat-custom-empty-index-1'; + + expect( + getPageIndex({ + ...getPageIndexArgs, + indexName: lastItemIndexName, + }) + ).toEqual(2); + }); + + test('it returns null when the index cannot be found', () => { + expect( + getPageIndex({ + ...getPageIndexArgs, + indexName: 'does_not_exist', // <-- this index is not in the items + }) + ).toBeNull(); + }); + + test('it returns null when `pageSize` is zero', () => { + expect( + getPageIndex({ + ...getPageIndexArgs, + pageSize: 0, // <-- invalid + }) + ).toBeNull(); + }); + }); +}); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/helpers.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/helpers.ts index efa9dbfa69d5e..40d0bbfb26293 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/helpers.ts +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/helpers.ts @@ -9,7 +9,7 @@ import type { IlmExplainLifecycleLifecycleExplain, IndicesStatsIndicesStats, } from '@elastic/elasticsearch/lib/api/types'; -import { sortBy } from 'lodash/fp'; +import { orderBy } from 'lodash/fp'; import type { IndexSummaryTableItem } from '../summary_table/helpers'; import type { @@ -17,8 +17,9 @@ import type { IlmExplainPhaseCounts, DataQualityCheckResult, PatternRollup, + SortConfig, } from '../../types'; -import { getDocsCount } from '../../helpers'; +import { getDocsCount, getSizeInBytes } from '../../helpers'; export const isManaged = ( ilmExplainRecord: IlmExplainLifecycleLifecycleExplain | undefined @@ -144,6 +145,8 @@ export const getSummaryTableItems = ({ pattern, patternDocsCount, results, + sortByColumn, + sortByDirection, stats, }: { ilmExplain: Record | null; @@ -151,6 +154,8 @@ export const getSummaryTableItems = ({ pattern: string; patternDocsCount: number; results: Record | undefined; + sortByColumn: string; + sortByDirection: 'desc' | 'asc'; stats: Record | null; }): IndexSummaryTableItem[] => { const summaryTableItems = indexNames.map((indexName) => ({ @@ -160,47 +165,10 @@ export const getSummaryTableItems = ({ ilmPhase: ilmExplain != null ? getIlmPhase(ilmExplain[indexName]) : undefined, pattern, patternDocsCount, + sizeInBytes: getSizeInBytes({ stats, indexName }), })); - return sortBy('docsCount', summaryTableItems).reverse(); -}; - -export const getDefaultIndexIncompatibleCounts = ( - indexNames: string[] -): Record => - indexNames.reduce>( - (acc, indexName) => ({ - ...acc, - [indexName]: undefined, - }), - {} - ); - -export const createPatternIncompatibleEntries = ({ - indexNames, - patternIncompatible, -}: { - indexNames: string[]; - patternIncompatible: Record; -}): Record => - indexNames.reduce>( - (acc, indexName) => - indexName in patternIncompatible - ? { ...acc, [indexName]: patternIncompatible[indexName] } - : { ...acc, [indexName]: undefined }, - {} - ); - -export const getIncompatible = ( - patternIncompatible: Record -): number | undefined => { - const allIndexes = Object.values(patternIncompatible); - const allIndexesHaveValues = allIndexes.every((incompatible) => Number.isInteger(incompatible)); - - // only return a number when all indexes have an `incompatible` count: - return allIndexesHaveValues - ? allIndexes.reduce((acc, incompatible) => acc + Number(incompatible), 0) - : undefined; + return orderBy([sortByColumn], [sortByDirection], summaryTableItems); }; export const shouldCreateIndexNames = ({ @@ -233,3 +201,38 @@ export const shouldCreatePatternRollup = ({ return allDataLoaded || errorOccurred; }; + +export const getIndexPropertiesContainerId = ({ + indexName, + pattern, +}: { + indexName: string; + pattern: string; +}): string => `index-properties-container-${pattern}${indexName}`; + +export const defaultSort: SortConfig = { + sort: { + direction: 'desc', + field: 'docsCount', + }, +}; + +export const MIN_PAGE_SIZE = 10; + +export const getPageIndex = ({ + indexName, + items, + pageSize, +}: { + indexName: string; + items: IndexSummaryTableItem[]; + pageSize: number; +}): number | null => { + const index = items.findIndex((x) => x.indexName === indexName); + + if (index !== -1 && pageSize !== 0) { + return Math.floor(index / pageSize); + } else { + return null; + } +}; diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/index.test.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/index.test.tsx index 4fa3b34884929..d9b002a63dc68 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/index.test.tsx +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/index.test.tsx @@ -6,12 +6,22 @@ */ import { DARK_THEME } from '@elastic/charts'; +import numeral from '@elastic/numeral'; import { render, screen } from '@testing-library/react'; import React from 'react'; -import { TestProviders } from '../../mock/test_providers'; +import { EMPTY_STAT } from '../../helpers'; +import { TestProviders } from '../../mock/test_providers/test_providers'; import { Pattern } from '.'; +const defaultBytesFormat = '0,0.[0]b'; +const formatBytes = (value: number | undefined) => + value != null ? numeral(value).format(defaultBytesFormat) : EMPTY_STAT; + +const defaultNumberFormat = '0,0.[000]'; +const formatNumber = (value: number | undefined) => + value != null ? numeral(value).format(defaultNumberFormat) : EMPTY_STAT; + jest.mock('../../use_stats', () => ({ useStats: jest.fn(() => ({ stats: {}, @@ -31,12 +41,15 @@ jest.mock('../../use_ilm_explain', () => ({ const defaultProps = { addSuccessToast: jest.fn(), canUserCreateAndReadCases: jest.fn(), - defaultNumberFormat: '0,0.[000]', + formatBytes, + formatNumber, getGroupByFieldsOnClick: jest.fn(), ilmPhases: ['hot', 'warm', 'unmanaged'], indexNames: undefined, openCreateCaseFlyout: jest.fn(), patternRollup: undefined, + selectedIndex: null, + setSelectedIndex: jest.fn(), theme: DARK_THEME, updatePatternIndexNames: jest.fn(), updatePatternRollup: jest.fn(), diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/index.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/index.tsx index bd6479490dab8..cb430d75ef12e 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/index.tsx +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/index.tsx @@ -16,14 +16,17 @@ import type { } from '@elastic/charts'; import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiSpacer } from '@elastic/eui'; import { euiThemeVars } from '@kbn/ui-theme'; -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import styled from 'styled-components'; import { ErrorEmptyPrompt } from '../error_empty_prompt'; import { + defaultSort, getIlmExplainPhaseCounts, getIlmPhase, + getPageIndex, getSummaryTableItems, + MIN_PAGE_SIZE, shouldCreateIndexNames, shouldCreatePatternRollup, } from './helpers'; @@ -33,6 +36,7 @@ import { getTotalDocsCount, getTotalPatternIncompatible, getTotalPatternIndicesChecked, + getTotalSizeInBytes, } from '../../helpers'; import { IndexProperties } from '../index_properties'; import { LoadingEmptyPrompt } from '../loading_empty_prompt'; @@ -41,7 +45,7 @@ import { RemoteClustersCallout } from '../remote_clusters_callout'; import { SummaryTable } from '../summary_table'; import { getSummaryTableColumns } from '../summary_table/helpers'; import * as i18n from './translations'; -import type { PatternRollup } from '../../types'; +import type { PatternRollup, SelectedIndex, SortConfig } from '../../types'; import { useIlmExplain } from '../../use_ilm_explain'; import { useStats } from '../../use_stats'; @@ -55,7 +59,8 @@ const EMPTY_INDEX_NAMES: string[] = []; interface Props { addSuccessToast: (toast: { title: string }) => void; canUserCreateAndReadCases: () => boolean; - defaultNumberFormat: string; + formatBytes: (value: number | undefined) => string; + formatNumber: (value: number | undefined) => string; getGroupByFieldsOnClick: ( elements: Array< | FlameElementEvent @@ -80,6 +85,8 @@ interface Props { }) => void; pattern: string; patternRollup: PatternRollup | undefined; + selectedIndex: SelectedIndex | null; + setSelectedIndex: (selectedIndex: SelectedIndex | null) => void; theme: Theme; updatePatternIndexNames: ({ indexNames, @@ -94,17 +101,25 @@ interface Props { const PatternComponent: React.FC = ({ addSuccessToast, canUserCreateAndReadCases, - defaultNumberFormat, + formatBytes, + formatNumber, getGroupByFieldsOnClick, indexNames, ilmPhases, openCreateCaseFlyout, pattern, patternRollup, + selectedIndex, + setSelectedIndex, theme, updatePatternIndexNames, updatePatternRollup, }) => { + const containerRef = useRef(null); + const [sorting, setSorting] = useState(defaultSort); + const [pageIndex, setPageIndex] = useState(0); + const [pageSize, setPageSize] = useState(MIN_PAGE_SIZE); + const { error: statsError, loading: loadingStats, stats } = useStats(pattern); const { error: ilmExplainError, loading: loadingIlmExplain, ilmExplain } = useIlmExplain(pattern); @@ -129,7 +144,8 @@ const PatternComponent: React.FC = ({ = ({ [ addSuccessToast, canUserCreateAndReadCases, - defaultNumberFormat, + formatBytes, + formatNumber, getGroupByFieldsOnClick, ilmExplain, itemIdToExpandedRowMap, @@ -171,9 +188,20 @@ const PatternComponent: React.FC = ({ pattern, patternDocsCount: patternRollup?.docsCount ?? 0, results: patternRollup?.results, + sortByColumn: sorting.sort.field, + sortByDirection: sorting.sort.direction, stats, }), - [ilmExplain, indexNames, pattern, patternRollup, stats] + [ + ilmExplain, + indexNames, + pattern, + patternRollup?.docsCount, + patternRollup?.results, + sorting.sort.direction, + sorting.sort.field, + stats, + ] ); useEffect(() => { @@ -196,6 +224,10 @@ const PatternComponent: React.FC = ({ indices: getIndexNames({ stats, ilmExplain, ilmPhases }).length, pattern, results: undefined, + sizeInBytes: getTotalSizeInBytes({ + indexNames: getIndexNames({ stats, ilmExplain, ilmPhases }), + stats, + }), stats, }); } @@ -212,18 +244,49 @@ const PatternComponent: React.FC = ({ updatePatternRollup, ]); + useEffect(() => { + if (selectedIndex?.pattern === pattern) { + const selectedPageIndex = getPageIndex({ + indexName: selectedIndex.indexName, + items, + pageSize, + }); + + if (selectedPageIndex != null) { + setPageIndex(selectedPageIndex); + } + + if (itemIdToExpandedRowMap[selectedIndex.indexName] == null) { + toggleExpanded(selectedIndex.indexName); // expand the selected index + } + + containerRef.current?.scrollIntoView(); + setSelectedIndex(null); + } + }, [ + itemIdToExpandedRowMap, + items, + pageSize, + pattern, + selectedIndex, + setSelectedIndex, + toggleExpanded, + ]); + return ( - + @@ -242,19 +305,27 @@ const PatternComponent: React.FC = ({ {loading && } {!loading && error == null && ( - +
+ +
)}
); }; -PatternComponent.displayName = 'PatternComponent'; - export const Pattern = React.memo(PatternComponent); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/index.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/index.tsx index a8ae7a673b92e..233de9fc93a1f 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/index.tsx +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/index.tsx @@ -13,23 +13,27 @@ import { PatternLabel } from './pattern_label'; import { StatsRollup } from './stats_rollup'; interface Props { - defaultNumberFormat: string; + formatBytes: (value: number | undefined) => string; + formatNumber: (value: number | undefined) => string; ilmExplainPhaseCounts: IlmExplainPhaseCounts; incompatible: number | undefined; indices: number | undefined; indicesChecked: number | undefined; pattern: string; patternDocsCount: number; + patternSizeInBytes: number; } const PatternSummaryComponent: React.FC = ({ - defaultNumberFormat, + formatBytes, + formatNumber, ilmExplainPhaseCounts, incompatible, indices, indicesChecked, pattern, patternDocsCount, + patternSizeInBytes, }) => ( @@ -44,12 +48,14 @@ const PatternSummaryComponent: React.FC = ({ diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/pattern_label/helpers.test.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/pattern_label/helpers.test.ts new file mode 100644 index 0000000000000..dfa285d60b40a --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/pattern_label/helpers.test.ts @@ -0,0 +1,97 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getResultToolTip, showResult } from './helpers'; +import { ALL_PASSED, SOME_FAILED, SOME_UNCHECKED } from './translations'; + +describe('helpers', () => { + describe('getResultToolTip', () => { + test('it returns the expected tool tip when `incompatible` is undefined', () => { + expect(getResultToolTip(undefined)).toEqual(SOME_UNCHECKED); + }); + + test('it returns the expected tool tip when `incompatible` is zero', () => { + expect(getResultToolTip(0)).toEqual(ALL_PASSED); + }); + + test('it returns the expected tool tip when `incompatible` is non-zero', () => { + expect(getResultToolTip(1)).toEqual(SOME_FAILED); + }); + }); + + describe('showResult', () => { + test('it returns true when `incompatible` is defined, and `indicesChecked` equals `indices`', () => { + const incompatible = 0; // none of the indices checked had incompatible fields + const indicesChecked = 2; // all indices were checked + const indices = 2; // total indices + + expect( + showResult({ + incompatible, + indices, + indicesChecked, + }) + ).toBe(true); + }); + + test('it returns false when `incompatible` is defined, and `indices` does NOT equal `indicesChecked`', () => { + const incompatible = 0; // the one index checked (so far) didn't have any incompatible fields + const indicesChecked = 1; // only one index has been checked so far + const indices = 2; // total indices + + expect( + showResult({ + incompatible, + indices, + indicesChecked, + }) + ).toBe(false); + }); + + test('it returns false when `incompatible` is undefined', () => { + const incompatible = undefined; // a state of undefined indicates there are no results + const indicesChecked = 1; // all indices were checked + const indices = 1; // total indices + + expect( + showResult({ + incompatible, + indices, + indicesChecked, + }) + ).toBe(false); + }); + + test('it returns false when `indices` is undefined', () => { + const incompatible = 0; // none of the indices checked had incompatible fields + const indicesChecked = 2; // all indices were checked + const indices = undefined; // the total number of indices is unknown + + expect( + showResult({ + incompatible, + indices, + indicesChecked, + }) + ).toBe(false); + }); + + test('it returns false when `indicesChecked` is undefined', () => { + const incompatible = 0; // none of the indices checked had incompatible fields + const indicesChecked = undefined; // no indices were checked + const indices = 2; // total indices + + expect( + showResult({ + incompatible, + indices, + indicesChecked, + }) + ).toBe(false); + }); + }); +}); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/stats_rollup/index.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/stats_rollup/index.tsx index dcf28487a091c..4a51880404542 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/stats_rollup/index.tsx +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/stats_rollup/index.tsx @@ -6,7 +6,6 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiStat, EuiToolTip } from '@elastic/eui'; -import numeral from '@elastic/numeral'; import React, { useMemo } from 'react'; import styled from 'styled-components'; @@ -29,21 +28,25 @@ const DocsContainer = styled.div` const STAT_TITLE_SIZE = 's'; interface Props { - defaultNumberFormat: string; docsCount: number | undefined; + formatBytes: (value: number | undefined) => string; + formatNumber: (value: number | undefined) => string; incompatible: number | undefined; indices: number | undefined; indicesChecked: number | undefined; pattern?: string; + sizeInBytes: number | undefined; } const StatsRollupComponent: React.FC = ({ - defaultNumberFormat, docsCount, + formatBytes, + formatNumber, incompatible, indices, indicesChecked, pattern, + sizeInBytes, }) => { const incompatibleDescription = useMemo( () => , @@ -53,11 +56,17 @@ const StatsRollupComponent: React.FC = ({ () => , [] ); + const sizeDescription = useMemo(() => , []); const docsDescription = useMemo(() => , []); const indicesDescription = useMemo(() => , []); return ( - + = ({ > @@ -88,11 +95,7 @@ const StatsRollupComponent: React.FC = ({ > @@ -110,7 +113,25 @@ const StatsRollupComponent: React.FC = ({ > + + + + + + + + @@ -126,9 +147,7 @@ const StatsRollupComponent: React.FC = ({ > diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/translations.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/translations.ts index 776ceef776172..79375b1956169 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/translations.ts +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/pattern/translations.ts @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; export const ERROR_LOADING_METADATA_TITLE = (pattern: string) => i18n.translate('ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingMetadataTitle', { values: { pattern }, - defaultMessage: "Indices matching the {pattern} pattern won't checked", + defaultMessage: "Indices matching the {pattern} pattern won't be checked", }); export const ERROR_LOADING_METADATA_BODY = ({ diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/same_family/index.test.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/same_family/index.test.tsx index 94983d262404b..0c4a439f999bc 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/same_family/index.test.tsx +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/same_family/index.test.tsx @@ -9,7 +9,7 @@ import { render, screen } from '@testing-library/react'; import React from 'react'; import { SAME_FAMILY } from './translations'; -import { TestProviders } from '../../mock/test_providers'; +import { TestProviders } from '../../mock/test_providers/test_providers'; import { SameFamily } from '.'; describe('SameFamily', () => { diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/stat_label/translations.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/stat_label/translations.ts index afaad5d08ed6e..1c52f29858ac4 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/stat_label/translations.ts +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/stat_label/translations.ts @@ -69,6 +69,17 @@ export const INDICES = i18n.translate('ecsDataQualityDashboard.statLabels.indice defaultMessage: 'Indices', }); +export const SIZE = i18n.translate('ecsDataQualityDashboard.statLabels.sizeLabel', { + defaultMessage: 'Size', +}); + +export const INDICES_SIZE_PATTERN_TOOL_TIP = (pattern: string) => + i18n.translate('ecsDataQualityDashboard.statLabels.indicesSizePatternToolTip', { + values: { pattern }, + defaultMessage: + 'The total size of the primary indices matching the {pattern} pattern (does not include replicas)', + }); + export const TOTAL_COUNT_OF_INDICES_CHECKED_MATCHING_PATTERN_TOOL_TIP = (pattern: string) => i18n.translate( 'ecsDataQualityDashboard.statLabels.totalCountOfIndicesCheckedMatchingPatternToolTip', @@ -112,3 +123,10 @@ export const TOTAL_INDICES_TOOL_TIP = i18n.translate( defaultMessage: 'The total count of all indices', } ); + +export const TOTAL_SIZE_TOOL_TIP = i18n.translate( + 'ecsDataQualityDashboard.statLabels.totalSizeToolTip', + { + defaultMessage: 'The total size of all primary indices (does not include replicas)', + } +); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/storage_treemap/index.test.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/storage_treemap/index.test.tsx new file mode 100644 index 0000000000000..f523c96cc0801 --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/storage_treemap/index.test.tsx @@ -0,0 +1,154 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { DARK_THEME, Settings } from '@elastic/charts'; +import numeral from '@elastic/numeral'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; + +import { + FlattenedBucket, + getFlattenedBuckets, + getLegendItems, +} from '../body/data_quality_details/storage_details/helpers'; +import { EMPTY_STAT } from '../../helpers'; +import { alertIndexWithAllResults } from '../../mock/pattern_rollup/mock_alerts_pattern_rollup'; +import { auditbeatWithAllResults } from '../../mock/pattern_rollup/mock_auditbeat_pattern_rollup'; +import { packetbeatNoResults } from '../../mock/pattern_rollup/mock_packetbeat_pattern_rollup'; +import { TestProviders } from '../../mock/test_providers/test_providers'; +import type { Props } from '.'; +import { StorageTreemap } from '.'; +import { DEFAULT_MAX_CHART_HEIGHT } from '../tabs/styles'; +import { NO_DATA_LABEL } from './translations'; +import { PatternRollup } from '../../types'; + +const defaultBytesFormat = '0,0.[0]b'; +const formatBytes = (value: number | undefined) => + value != null ? numeral(value).format(defaultBytesFormat) : EMPTY_STAT; + +const ilmPhases = ['hot', 'warm', 'unmanaged']; +const patterns = ['.alerts-security.alerts-default', 'auditbeat-*', 'packetbeat-*']; + +const patternRollups: Record = { + '.alerts-security.alerts-default': alertIndexWithAllResults, + 'auditbeat-*': auditbeatWithAllResults, + 'packetbeat-*': packetbeatNoResults, +}; + +const flattenedBuckets = getFlattenedBuckets({ + ilmPhases, + patternRollups, +}); + +const onIndexSelected = jest.fn(); + +const defaultProps: Props = { + flattenedBuckets, + formatBytes, + maxChartHeight: DEFAULT_MAX_CHART_HEIGHT, + onIndexSelected, + patternRollups, + patterns, + theme: DARK_THEME, +}; + +jest.mock('@elastic/charts', () => { + const actual = jest.requireActual('@elastic/charts'); + return { + ...actual, + Settings: jest.fn().mockReturnValue(null), + }; +}); + +describe('StorageTreemap', () => { + describe('when data is provided', () => { + beforeEach(() => { + jest.clearAllMocks(); + + render( + + + + ); + }); + + test('it renders the treemap', () => { + expect(screen.getByTestId('storageTreemap').querySelector('.echChart')).toBeInTheDocument(); + }); + + test('it renders the legend with the expected overflow-y style', () => { + expect(screen.getByTestId('legend')).toHaveClass('eui-yScroll'); + }); + + test('it uses a theme with the expected `minFontSize` to show more labels at various screen resolutions', () => { + expect((Settings as jest.Mock).mock.calls[0][0].theme[0].partition.minFontSize).toEqual(4); + }); + + describe('legend items', () => { + const allLegendItems = getLegendItems({ patterns, flattenedBuckets, patternRollups }); + + describe('pattern legend items', () => { + const justPatterns = allLegendItems.filter((x) => x.ilmPhase == null); + + justPatterns.forEach(({ ilmPhase, index, pattern, sizeInBytes }) => { + test(`it renders the expend legend item for pattern: ilmPhase ${ilmPhase} pattern ${pattern} index ${index}`, () => { + expect( + screen.getByTestId(`chart-legend-item-${ilmPhase}${pattern}${index}`) + ).toHaveTextContent(`${pattern}${formatBytes(sizeInBytes)}`); + }); + }); + }); + + describe('index legend items', () => { + const justIndices = allLegendItems.filter((x) => x.ilmPhase != null); + + justIndices.forEach(({ ilmPhase, index, pattern, sizeInBytes }) => { + test(`it renders the expend legend item for index: ilmPhase ${ilmPhase} pattern ${pattern} index ${index}`, () => { + expect( + screen.getByTestId(`chart-legend-item-${ilmPhase}${pattern}${index}`) + ).toHaveTextContent(`${index}${formatBytes(sizeInBytes)}`); + }); + + test(`it invokes onIndexSelected() with the expected values for ilmPhase ${ilmPhase} pattern ${pattern} index ${index}`, () => { + const legendItem = screen.getByTestId( + `chart-legend-item-${ilmPhase}${pattern}${index}` + ); + + userEvent.click(legendItem); + + expect(onIndexSelected).toBeCalledWith({ indexName: index, pattern }); + }); + }); + }); + }); + }); + + describe('when the response does NOT have data', () => { + const emptyFlattenedBuckets: FlattenedBucket[] = []; + + beforeEach(() => { + render( + + + + ); + }); + + test('it does NOT render the treemap', () => { + expect(screen.queryByTestId('storageTreemap')).not.toBeInTheDocument(); + }); + + test('it does NOT render the legend', () => { + expect(screen.queryByTestId('legend')).not.toBeInTheDocument(); + }); + + test('it renders the "no data" message', () => { + expect(screen.getByText(NO_DATA_LABEL)).toBeInTheDocument(); + }); + }); +}); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/storage_treemap/index.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/storage_treemap/index.tsx new file mode 100644 index 0000000000000..b42cd6072ad82 --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/storage_treemap/index.tsx @@ -0,0 +1,201 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isEmpty } from 'lodash/fp'; +import type { + Datum, + ElementClickListener, + FlameElementEvent, + HeatmapElementEvent, + MetricElementEvent, + PartialTheme, + PartitionElementEvent, + Theme, + WordCloudElementEvent, + XYChartElementEvent, +} from '@elastic/charts'; +import { Chart, Partition, PartitionLayout, Settings } from '@elastic/charts'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import React, { useCallback, useMemo } from 'react'; + +import { + FlattenedBucket, + getLayersMultiDimensional, + getLegendItems, + getPathToFlattenedBucketMap, +} from '../body/data_quality_details/storage_details/helpers'; +import { ChartLegendItem } from '../../ecs_summary_donut_chart/chart_legend/chart_legend_item'; +import { NoData } from './no_data'; +import { ChartFlexItem, LegendContainer } from '../tabs/styles'; +import { PatternRollup, SelectedIndex } from '../../types'; + +export const DEFAULT_MIN_CHART_HEIGHT = 240; // px +export const LEGEND_WIDTH = 220; // px +export const LEGEND_TEXT_WITH = 120; // px + +export interface Props { + flattenedBuckets: FlattenedBucket[]; + formatBytes: (value: number | undefined) => string; + maxChartHeight?: number; + minChartHeight?: number; + onIndexSelected: ({ indexName, pattern }: SelectedIndex) => void; + patternRollups: Record; + patterns: string[]; + theme: Theme; +} + +interface GetGroupByFieldsResult { + pattern: string; + indexName: string; +} + +export const getGroupByFieldsOnClick = ( + elements: Array< + | FlameElementEvent + | HeatmapElementEvent + | MetricElementEvent + | PartitionElementEvent + | WordCloudElementEvent + | XYChartElementEvent + > +): GetGroupByFieldsResult => { + const flattened = elements.flat(2); + + const pattern = + flattened.length > 0 && 'groupByRollup' in flattened[0] && flattened[0].groupByRollup != null + ? `${flattened[0].groupByRollup}` + : ''; + + const indexName = + flattened.length > 1 && 'groupByRollup' in flattened[1] && flattened[1].groupByRollup != null + ? `${flattened[1].groupByRollup}` + : ''; + + return { + pattern, + indexName, + }; +}; + +const StorageTreemapComponent: React.FC = ({ + flattenedBuckets, + formatBytes, + maxChartHeight, + minChartHeight = DEFAULT_MIN_CHART_HEIGHT, + onIndexSelected, + patternRollups, + patterns, + theme, +}: Props) => { + const fillColor = useMemo(() => theme.background.color, [theme.background.color]); + + const treemapTheme: PartialTheme[] = useMemo( + () => [ + { + partition: { + fillLabel: { valueFont: { fontWeight: 700 } }, + idealFontSizeJump: 1.15, + maxFontSize: 16, + minFontSize: 4, + sectorLineStroke: fillColor, // draws the light or dark "lines" between partitions + sectorLineWidth: 1.5, + }, + }, + ], + [fillColor] + ); + + const onElementClick: ElementClickListener = useCallback( + (event) => { + const { indexName, pattern } = getGroupByFieldsOnClick(event); + + if (!isEmpty(indexName) && !isEmpty(pattern)) { + onIndexSelected({ indexName, pattern }); + } + }, + [onIndexSelected] + ); + + const pathToFlattenedBucketMap = getPathToFlattenedBucketMap(flattenedBuckets); + + const layers = useMemo( + () => + getLayersMultiDimensional({ + formatBytes, + layer0FillColor: fillColor, + pathToFlattenedBucketMap, + }), + [fillColor, formatBytes, pathToFlattenedBucketMap] + ); + + const valueAccessor = useCallback(({ sizeInBytes }: Datum) => sizeInBytes, []); + + const legendItems = useMemo( + () => getLegendItems({ patterns, flattenedBuckets, patternRollups }), + [flattenedBuckets, patternRollups, patterns] + ); + + if (flattenedBuckets.length === 0) { + return ; + } + + return ( + + + {flattenedBuckets.length === 0 ? ( + + ) : ( + + + formatBytes(d)} + /> + + )} + + + + + {legendItems.map(({ color, ilmPhase, index, pattern, sizeInBytes }) => ( + { + onIndexSelected({ indexName: index, pattern }); + } + : undefined + } + text={index ?? pattern} + textWidth={LEGEND_TEXT_WITH} + /> + ))} + + + + ); +}; + +export const StorageTreemap = React.memo(StorageTreemapComponent); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/storage_treemap/no_data/index.test.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/storage_treemap/no_data/index.test.tsx new file mode 100644 index 0000000000000..0cf39beae7b2d --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/storage_treemap/no_data/index.test.tsx @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { render, screen } from '@testing-library/react'; +import React from 'react'; + +import * as i18n from '../translations'; + +import { NoData } from '.'; + +describe('NoData', () => { + test('renders the expected "no data" message', () => { + render(); + + expect(screen.getByText(i18n.NO_DATA_LABEL)).toBeInTheDocument(); + }); +}); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/storage_treemap/no_data/index.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/storage_treemap/no_data/index.tsx new file mode 100644 index 0000000000000..a5edca17291d2 --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/storage_treemap/no_data/index.tsx @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText } from '@elastic/eui'; +import React from 'react'; +import styled from 'styled-components'; + +import * as i18n from '../translations'; + +const NoDataLabel = styled(EuiText)` + text-align: center; +`; + +interface Props { + reason?: string; +} + +const NoDataComponent: React.FC = ({ reason }) => ( + + + + {i18n.NO_DATA_LABEL} + + + {reason != null && ( + <> + + + {reason} + + + )} + + +); + +NoDataComponent.displayName = 'NoDataComponent'; + +export const NoData = React.memo(NoDataComponent); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/storage_treemap/translations.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/storage_treemap/translations.ts new file mode 100644 index 0000000000000..f60cb2366cf36 --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/storage_treemap/translations.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const NO_DATA_LABEL = i18n.translate('ecsDataQualityDashboard.storageTreemap.noDataLabel', { + defaultMessage: 'No data to display', +}); + +export const NO_DATA_REASON_LABEL = (stackByField1: string) => + i18n.translate('ecsDataQualityDashboard.storageTreemap.noDataReasonLabel', { + values: { + stackByField1, + }, + defaultMessage: 'The {stackByField1} field was not present in any groups', + }); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/summary_table/helpers.test.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/summary_table/helpers.test.tsx new file mode 100644 index 0000000000000..e913d38dbb07c --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/summary_table/helpers.test.tsx @@ -0,0 +1,543 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { EuiScreenReaderOnly, EuiTableFieldDataColumnType } from '@elastic/eui'; +import numeral from '@elastic/numeral'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { omit } from 'lodash/fp'; +import React from 'react'; + +import { TestProviders } from '../../mock/test_providers/test_providers'; +import { EMPTY_STAT } from '../../helpers'; +import { + getDocsCountPercent, + getResultIcon, + getResultIconColor, + getResultToolTip, + getShowPagination, + getSummaryTableColumns, + getToggleButtonId, + IndexSummaryTableItem, +} from './helpers'; +import { COLLAPSE, EXPAND, FAILED, PASSED, THIS_INDEX_HAS_NOT_BEEN_CHECKED } from './translations'; + +const defaultBytesFormat = '0,0.[0]b'; +const formatBytes = (value: number | undefined) => + value != null ? numeral(value).format(defaultBytesFormat) : EMPTY_STAT; + +const defaultNumberFormat = '0,0.[000]'; +const formatNumber = (value: number | undefined) => + value != null ? numeral(value).format(defaultNumberFormat) : EMPTY_STAT; + +describe('helpers', () => { + describe('getResultToolTip', () => { + test('it shows a "this index has not been checked" tool tip when `incompatible` is undefined', () => { + expect(getResultToolTip(undefined)).toEqual(THIS_INDEX_HAS_NOT_BEEN_CHECKED); + }); + + test('it returns Passed when `incompatible` is zero', () => { + expect(getResultToolTip(0)).toEqual(PASSED); + }); + + test('it returns Failed when `incompatible` is NOT zero', () => { + expect(getResultToolTip(1)).toEqual(FAILED); + }); + }); + + describe('getResultIconColor', () => { + test('it returns `ghost` when `incompatible` is undefined', () => { + expect(getResultIconColor(undefined)).toEqual('ghost'); + }); + + test('it returns `success` when `incompatible` is zero', () => { + expect(getResultIconColor(0)).toEqual('success'); + }); + + test('it returns `danger` when `incompatible` is NOT zero', () => { + expect(getResultIconColor(1)).toEqual('danger'); + }); + }); + + describe('getResultIcon', () => { + test('it returns `cross` when `incompatible` is undefined', () => { + expect(getResultIcon(undefined)).toEqual('cross'); + }); + + test('it returns `check` when `incompatible` is zero', () => { + expect(getResultIcon(0)).toEqual('check'); + }); + + test('it returns `cross` when `incompatible` is NOT zero', () => { + expect(getResultIcon(1)).toEqual('cross'); + }); + }); + + describe('getDocsCountPercent', () => { + test('it returns an empty string when `patternDocsCount` is zero', () => { + expect( + getDocsCountPercent({ + docsCount: 0, + patternDocsCount: 0, + }) + ).toEqual(''); + }); + + test('it returns the expected format when when `patternDocsCount` is non-zero, and `locales` is undefined', () => { + expect( + getDocsCountPercent({ + docsCount: 2904, + locales: undefined, + patternDocsCount: 57410, + }) + ).toEqual('5.1%'); + }); + + test('it returns the expected format when when `patternDocsCount` is non-zero, and `locales` is provided', () => { + expect( + getDocsCountPercent({ + docsCount: 2904, + locales: 'en-US', + patternDocsCount: 57410, + }) + ).toEqual('5.1%'); + }); + }); + + describe('getToggleButtonId', () => { + test('it returns the expected id when the button is expanded', () => { + expect( + getToggleButtonId({ + indexName: 'auditbeat-custom-index-1', + isExpanded: true, + pattern: 'auditbeat-*', + }) + ).toEqual('collapseauditbeat-custom-index-1auditbeat-*'); + }); + + test('it returns the expected id when the button is collapsed', () => { + expect( + getToggleButtonId({ + indexName: 'auditbeat-custom-index-1', + isExpanded: false, + pattern: 'auditbeat-*', + }) + ).toEqual('expandauditbeat-custom-index-1auditbeat-*'); + }); + }); + + describe('getSummaryTableColumns', () => { + const indexName = '.ds-auditbeat-8.6.1-2023.02.07-000001'; + + const indexSummaryTableItem: IndexSummaryTableItem = { + indexName, + docsCount: 2796, + incompatible: undefined, + ilmPhase: 'hot', + pattern: 'auditbeat-*', + patternDocsCount: 57410, + sizeInBytes: 103344068, + }; + + const hasIncompatible: IndexSummaryTableItem = { + ...indexSummaryTableItem, + incompatible: 1, // <-- one incompatible field + }; + + test('it returns the expected column configuration', () => { + const columns = getSummaryTableColumns({ + formatBytes, + formatNumber, + itemIdToExpandedRowMap: {}, + pattern: 'auditbeat-*', + toggleExpanded: jest.fn(), + }).map((x) => omit('render', x)); + + expect(columns).toEqual([ + { + align: 'right', + isExpander: true, + name: ( + + {'Expand rows'} + + ), + width: '40px', + }, + { + field: 'incompatible', + name: 'Result', + sortable: true, + truncateText: false, + width: '50px', + }, + { field: 'indexName', name: 'Index', sortable: true, truncateText: false, width: '300px' }, + { field: 'docsCount', name: 'Docs', sortable: true, truncateText: false }, + { + field: 'incompatible', + name: 'Incompatible fields', + sortable: true, + truncateText: false, + }, + { field: 'ilmPhase', name: 'ILM Phase', sortable: true, truncateText: false }, + { field: 'sizeInBytes', name: 'Size', sortable: true, truncateText: false }, + ]); + }); + + describe('expand rows render()', () => { + test('it renders an Expand button when the row is NOT expanded', () => { + const columns = getSummaryTableColumns({ + formatBytes, + formatNumber, + itemIdToExpandedRowMap: {}, + pattern: 'auditbeat-*', + toggleExpanded: jest.fn(), + }); + const expandRowsRender = (columns[0] as EuiTableFieldDataColumnType) + .render; + + render( + + {expandRowsRender != null && + expandRowsRender(indexSummaryTableItem, indexSummaryTableItem)} + + ); + + expect(screen.getByLabelText(EXPAND)).toBeInTheDocument(); + }); + + test('it renders a Collapse button when the row is expanded', () => { + const itemIdToExpandedRowMap: Record = { + [indexName]: () => null, + }; + + const columns = getSummaryTableColumns({ + formatBytes, + formatNumber, + itemIdToExpandedRowMap, + pattern: 'auditbeat-*', + toggleExpanded: jest.fn(), + }); + const expandRowsRender = (columns[0] as EuiTableFieldDataColumnType) + .render; + + render( + + {expandRowsRender != null && + expandRowsRender(indexSummaryTableItem, indexSummaryTableItem)} + + ); + + expect(screen.getByLabelText(COLLAPSE)).toBeInTheDocument(); + }); + + test('it invokes the `toggleExpanded` with the index name when the button is clicked', () => { + const toggleExpanded = jest.fn(); + + const columns = getSummaryTableColumns({ + formatBytes, + formatNumber, + itemIdToExpandedRowMap: {}, + pattern: 'auditbeat-*', + toggleExpanded, + }); + const expandRowsRender = (columns[0] as EuiTableFieldDataColumnType) + .render; + + render( + + {expandRowsRender != null && + expandRowsRender(indexSummaryTableItem, indexSummaryTableItem)} + + ); + + const button = screen.getByLabelText(EXPAND); + userEvent.click(button); + + expect(toggleExpanded).toBeCalledWith(indexName); + }); + }); + + describe('incompatible render()', () => { + test('it renders a placeholder when incompatible is undefined', () => { + const incompatibleIsUndefined: IndexSummaryTableItem = { + ...indexSummaryTableItem, + incompatible: undefined, // <-- + }; + + const columns = getSummaryTableColumns({ + formatBytes, + formatNumber, + itemIdToExpandedRowMap: {}, + pattern: 'auditbeat-*', + toggleExpanded: jest.fn(), + }); + const incompatibleRender = ( + columns[1] as EuiTableFieldDataColumnType + ).render; + + render( + + {incompatibleRender != null && + incompatibleRender(incompatibleIsUndefined, incompatibleIsUndefined)} + + ); + + expect(screen.getByTestId('incompatiblePlaceholder')).toHaveTextContent(EMPTY_STAT); + }); + + test('it renders the expected icon when there are incompatible fields', () => { + const columns = getSummaryTableColumns({ + formatBytes, + formatNumber, + itemIdToExpandedRowMap: {}, + pattern: 'auditbeat-*', + toggleExpanded: jest.fn(), + }); + const incompatibleRender = ( + columns[1] as EuiTableFieldDataColumnType + ).render; + + render( + + {incompatibleRender != null && incompatibleRender(hasIncompatible, hasIncompatible)} + + ); + + expect(screen.getByTestId('resultIcon')).toHaveAttribute('data-euiicon-type', 'cross'); + }); + + test('it renders the expected icon when there are zero fields', () => { + const zeroIncompatible: IndexSummaryTableItem = { + ...indexSummaryTableItem, + incompatible: 0, // <-- one incompatible field + }; + + const columns = getSummaryTableColumns({ + formatBytes, + formatNumber, + itemIdToExpandedRowMap: {}, + pattern: 'auditbeat-*', + toggleExpanded: jest.fn(), + }); + const incompatibleRender = ( + columns[1] as EuiTableFieldDataColumnType + ).render; + + render( + + {incompatibleRender != null && incompatibleRender(zeroIncompatible, zeroIncompatible)} + + ); + + expect(screen.getByTestId('resultIcon')).toHaveAttribute('data-euiicon-type', 'check'); + }); + }); + + describe('indexName render()', () => { + test('it renders the index name', () => { + const columns = getSummaryTableColumns({ + formatBytes, + formatNumber, + itemIdToExpandedRowMap: {}, + pattern: 'auditbeat-*', + toggleExpanded: jest.fn(), + }); + const indexNameRender = (columns[2] as EuiTableFieldDataColumnType) + .render; + + render( + + {indexNameRender != null && + indexNameRender(indexSummaryTableItem, indexSummaryTableItem)} + + ); + + expect(screen.getByTestId('indexName')).toHaveTextContent(indexName); + }); + }); + + describe('docsCount render()', () => { + beforeEach(() => { + const columns = getSummaryTableColumns({ + formatBytes, + formatNumber, + itemIdToExpandedRowMap: {}, + pattern: 'auditbeat-*', + toggleExpanded: jest.fn(), + }); + const docsCountRender = (columns[3] as EuiTableFieldDataColumnType) + .render; + + render( + + {docsCountRender != null && docsCountRender(hasIncompatible, hasIncompatible)} + + ); + }); + + test('it renders the expected value', () => { + expect(screen.getByTestId('docsCount')).toHaveAttribute( + 'value', + String(hasIncompatible.docsCount) + ); + }); + + test('it renders the expected max (progress)', () => { + expect(screen.getByTestId('docsCount')).toHaveAttribute( + 'max', + String(hasIncompatible.patternDocsCount) + ); + }); + }); + + describe('incompatible column render()', () => { + test('it renders the expected value', () => { + const columns = getSummaryTableColumns({ + formatBytes, + formatNumber, + itemIdToExpandedRowMap: {}, + pattern: 'auditbeat-*', + toggleExpanded: jest.fn(), + }); + const incompatibleRender = ( + columns[4] as EuiTableFieldDataColumnType + ).render; + + render( + + {incompatibleRender != null && incompatibleRender(hasIncompatible, hasIncompatible)} + + ); + + expect(screen.getByTestId('incompatibleStat')).toHaveTextContent('1'); + }); + + test('it renders the expected placeholder when incompatible is undefined', () => { + const columns = getSummaryTableColumns({ + formatBytes, + formatNumber, + itemIdToExpandedRowMap: {}, + pattern: 'auditbeat-*', + toggleExpanded: jest.fn(), + }); + const incompatibleRender = ( + columns[4] as EuiTableFieldDataColumnType + ).render; + + render( + + {incompatibleRender != null && + incompatibleRender(indexSummaryTableItem, indexSummaryTableItem)} + + ); + + expect(screen.getByTestId('incompatibleStat')).toHaveTextContent('-- --'); // the euiScreenReaderOnly content renders an additional set of -- + }); + }); + + describe('ilmPhase column render()', () => { + test('it renders the expected ilmPhase badge content', () => { + const columns = getSummaryTableColumns({ + formatBytes, + formatNumber, + itemIdToExpandedRowMap: {}, + pattern: 'auditbeat-*', + toggleExpanded: jest.fn(), + }); + const ilmPhaseRender = (columns[5] as EuiTableFieldDataColumnType) + .render; + + render( + + {ilmPhaseRender != null && ilmPhaseRender(hasIncompatible, hasIncompatible)} + + ); + + expect(screen.getByTestId('ilmPhase')).toHaveTextContent('hot'); + }); + + test('it does NOT render the ilmPhase badge when `ilmPhase` is undefined', () => { + const ilmPhaseIsUndefined: IndexSummaryTableItem = { + ...indexSummaryTableItem, + ilmPhase: undefined, // <-- + }; + + const columns = getSummaryTableColumns({ + formatBytes, + formatNumber, + itemIdToExpandedRowMap: {}, + pattern: 'auditbeat-*', + toggleExpanded: jest.fn(), + }); + const ilmPhaseRender = (columns[5] as EuiTableFieldDataColumnType) + .render; + + render( + + {ilmPhaseRender != null && ilmPhaseRender(ilmPhaseIsUndefined, ilmPhaseIsUndefined)} + + ); + + expect(screen.queryByTestId('ilmPhase')).not.toBeInTheDocument(); + }); + }); + + describe('sizeInBytes render()', () => { + test('it renders the expected formatted bytes', () => { + const columns = getSummaryTableColumns({ + formatBytes, + formatNumber, + itemIdToExpandedRowMap: {}, + pattern: 'auditbeat-*', + toggleExpanded: jest.fn(), + }); + + const sizeInBytesRender = (columns[6] as EuiTableFieldDataColumnType) + .render; + + render( + + {sizeInBytesRender != null && + sizeInBytesRender(indexSummaryTableItem, indexSummaryTableItem)} + + ); + + expect(screen.getByTestId('sizeInBytes')).toHaveTextContent('98.6MB'); + }); + }); + }); + + describe('getShowPagination', () => { + test('it returns true when `totalItemCount` is greater than `minPageSize`', () => { + expect( + getShowPagination({ + minPageSize: 10, + totalItemCount: 11, + }) + ).toBe(true); + }); + + test('it returns false when `totalItemCount` equals `minPageSize`', () => { + expect( + getShowPagination({ + minPageSize: 10, + totalItemCount: 10, + }) + ).toBe(false); + }); + + test('it returns false when `totalItemCount` is less than `minPageSize`', () => { + expect( + getShowPagination({ + minPageSize: 10, + totalItemCount: 9, + }) + ).toBe(false); + }); + }); +}); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/summary_table/helpers.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/summary_table/helpers.tsx index e3789fce0bb5b..89e0d78fddb6b 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/summary_table/helpers.tsx +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/summary_table/helpers.tsx @@ -21,6 +21,7 @@ import styled from 'styled-components'; import { EMPTY_STAT, getIlmPhaseDescription, getIncompatibleStatColor } from '../../helpers'; import { INCOMPATIBLE_INDEX_TOOL_TIP } from '../stat_label/translations'; +import { INDEX_SIZE_TOOLTIP } from '../../translations'; import * as i18n from './translations'; import type { IlmPhase } from '../../types'; @@ -39,6 +40,7 @@ export interface IndexSummaryTableItem { ilmPhase: IlmPhase | undefined; pattern: string; patternDocsCount: number; + sizeInBytes: number; } export const getResultToolTip = (incompatible: number | undefined): string => { @@ -83,13 +85,27 @@ export const getDocsCountPercent = ({ }) : ''; +export const getToggleButtonId = ({ + indexName, + isExpanded, + pattern, +}: { + indexName: string; + isExpanded: boolean; + pattern: string; +}): string => (isExpanded ? `collapse${indexName}${pattern}` : `expand${indexName}${pattern}`); + export const getSummaryTableColumns = ({ + formatBytes, formatNumber, itemIdToExpandedRowMap, + pattern, toggleExpanded, }: { + formatBytes: (value: number | undefined) => string; formatNumber: (value: number | undefined) => string; itemIdToExpandedRowMap: Record; + pattern: string; toggleExpanded: (indexName: string) => void; }): Array> => [ { @@ -103,6 +119,11 @@ export const getSummaryTableColumns = ({ render: ({ indexName }: IndexSummaryTableItem) => ( toggleExpanded(indexName)} iconType={itemIdToExpandedRowMap[indexName] ? 'arrowDown' : 'arrowRight'} /> @@ -115,11 +136,15 @@ export const getSummaryTableColumns = ({ render: (_, { incompatible }) => incompatible != null ? ( - + ) : ( - {EMPTY_STAT} + {EMPTY_STAT} ), sortable: true, @@ -129,9 +154,11 @@ export const getSummaryTableColumns = ({ { field: 'indexName', name: i18n.INDEX, - render: (_, { indexName, pattern }) => ( + render: (_, { indexName }) => ( - {indexName} + + {indexName} + ), sortable: true, @@ -144,6 +171,7 @@ export const getSummaryTableColumns = ({ render: (_, { docsCount, patternDocsCount }) => ( ( ), - sortable: false, + sortable: true, truncateText: false, }, { @@ -178,10 +207,23 @@ export const getSummaryTableColumns = ({ render: (_, { ilmPhase }) => ilmPhase != null ? ( - {ilmPhase} + + {ilmPhase} + ) : null, - sortable: false, + sortable: true, + truncateText: false, + }, + { + field: 'sizeInBytes', + name: i18n.SIZE, + render: (_, { sizeInBytes }) => ( + + {formatBytes(sizeInBytes)} + + ), + sortable: true, truncateText: false, }, ]; diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/summary_table/index.test.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/summary_table/index.test.tsx new file mode 100644 index 0000000000000..235ec61a204af --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/summary_table/index.test.tsx @@ -0,0 +1,89 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import numeral from '@elastic/numeral'; +import { render, screen } from '@testing-library/react'; +import React from 'react'; + +import { EMPTY_STAT } from '../../helpers'; +import { getSummaryTableColumns } from './helpers'; +import { mockIlmExplain } from '../../mock/ilm_explain/mock_ilm_explain'; +import { auditbeatWithAllResults } from '../../mock/pattern_rollup/mock_auditbeat_pattern_rollup'; +import { mockStats } from '../../mock/stats/mock_stats'; +import { TestProviders } from '../../mock/test_providers/test_providers'; +import { getSummaryTableItems } from '../pattern/helpers'; +import { SortConfig } from '../../types'; +import { Props, SummaryTable } from '.'; + +const defaultBytesFormat = '0,0.[0]b'; +const formatBytes = (value: number | undefined) => + value != null ? numeral(value).format(defaultBytesFormat) : EMPTY_STAT; + +const defaultNumberFormat = '0,0.[000]'; +const formatNumber = (value: number | undefined) => + value != null ? numeral(value).format(defaultNumberFormat) : EMPTY_STAT; + +const indexNames = [ + '.ds-auditbeat-8.6.1-2023.02.07-000001', + 'auditbeat-custom-empty-index-1', + 'auditbeat-custom-index-1', + '.internal.alerts-security.alerts-default-000001', + '.ds-packetbeat-8.5.3-2023.02.04-000001', + '.ds-packetbeat-8.6.1-2023.02.04-000001', +]; + +export const defaultSort: SortConfig = { + sort: { + direction: 'desc', + field: 'docsCount', + }, +}; + +const pattern = 'auditbeat-*'; + +const items = getSummaryTableItems({ + ilmExplain: mockIlmExplain, + indexNames: indexNames ?? [], + pattern, + patternDocsCount: auditbeatWithAllResults?.docsCount ?? 0, + results: auditbeatWithAllResults?.results, + sortByColumn: defaultSort.sort.field, + sortByDirection: defaultSort.sort.direction, + stats: mockStats, +}); + +const defaultProps: Props = { + formatBytes, + formatNumber, + getTableColumns: getSummaryTableColumns, + itemIdToExpandedRowMap: {}, + items, + pageIndex: 0, + pageSize: 10, + pattern, + setPageIndex: jest.fn(), + setPageSize: jest.fn(), + setSorting: jest.fn(), + sorting: defaultSort, + toggleExpanded: jest.fn(), +}; + +describe('SummaryTable', () => { + beforeEach(() => { + jest.clearAllMocks(); + + render( + + + + ); + }); + + test('it renders the summary table', () => { + expect(screen.getByTestId('summaryTable')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/summary_table/index.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/summary_table/index.tsx index 7b02add151803..2dd2c4e214dc0 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/summary_table/index.tsx +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/summary_table/index.tsx @@ -5,79 +5,79 @@ * 2.0. */ -import type { - CriteriaWithPagination, - Direction, - EuiBasicTableColumn, - Pagination, -} from '@elastic/eui'; +import type { CriteriaWithPagination, EuiBasicTableColumn, Pagination } from '@elastic/eui'; import { EuiInMemoryTable } from '@elastic/eui'; -import numeral from '@elastic/numeral'; -import React, { useCallback, useMemo, useState } from 'react'; +import React, { useCallback, useMemo } from 'react'; -import { EMPTY_STAT } from '../../helpers'; import type { IndexSummaryTableItem } from './helpers'; import { getShowPagination } from './helpers'; +import { defaultSort, MIN_PAGE_SIZE } from '../pattern/helpers'; +import { SortConfig } from '../../types'; -const MIN_PAGE_SIZE = 10; - -interface SortConfig { - sort: { - direction: Direction; - field: string; - }; -} - -const defaultSort: SortConfig = { - sort: { - direction: 'desc', - field: 'docsCount', - }, -}; - -interface Props { - defaultNumberFormat: string; +export interface Props { + formatBytes: (value: number | undefined) => string; + formatNumber: (value: number | undefined) => string; getTableColumns: ({ + formatBytes, formatNumber, itemIdToExpandedRowMap, + pattern, toggleExpanded, }: { + formatBytes: (value: number | undefined) => string; formatNumber: (value: number | undefined) => string; itemIdToExpandedRowMap: Record; + pattern: string; toggleExpanded: (indexName: string) => void; }) => Array>; itemIdToExpandedRowMap: Record; items: IndexSummaryTableItem[]; + pageIndex: number; + pageSize: number; + pattern: string; + setPageIndex: (pageIndex: number) => void; + setPageSize: (pageSize: number) => void; + setSorting: (sortConfig: SortConfig) => void; + sorting: SortConfig; toggleExpanded: (indexName: string) => void; } const SummaryTableComponent: React.FC = ({ - defaultNumberFormat, + formatBytes, + formatNumber, getTableColumns, itemIdToExpandedRowMap, items, + pageIndex, + pageSize, + pattern, + setPageIndex, + setPageSize, + setSorting, + sorting, toggleExpanded, }) => { - const [sorting, setSorting] = useState(defaultSort); - const formatNumber = useCallback( - (value: number | undefined): string => - value != null ? numeral(value).format(defaultNumberFormat) : EMPTY_STAT, - [defaultNumberFormat] - ); - const [pageIndex, setPageIndex] = useState(0); - const [pageSize, setPageSize] = useState(MIN_PAGE_SIZE); const columns = useMemo( - () => getTableColumns({ formatNumber, itemIdToExpandedRowMap, toggleExpanded }), - [formatNumber, getTableColumns, itemIdToExpandedRowMap, toggleExpanded] + () => + getTableColumns({ + formatBytes, + formatNumber, + itemIdToExpandedRowMap, + pattern, + toggleExpanded, + }), + [formatBytes, formatNumber, getTableColumns, itemIdToExpandedRowMap, pattern, toggleExpanded] ); const getItemId = useCallback((item: IndexSummaryTableItem) => item.indexName, []); - const onChange = useCallback(({ page, sort }: CriteriaWithPagination) => { - setSorting({ sort: sort ?? defaultSort.sort }); - - setPageIndex(page.index); - setPageSize(page.size); - }, []); + const onChange = useCallback( + ({ page, sort }: CriteriaWithPagination) => { + setSorting({ sort: sort ?? defaultSort.sort }); + setPageIndex(page.index); + setPageSize(page.size); + }, + [setPageIndex, setPageSize, setSorting] + ); const pagination: Pagination = useMemo( () => ({ @@ -91,9 +91,10 @@ const SummaryTableComponent: React.FC = ({ return ( + value != null ? numeral(value).format(defaultBytesFormat) : EMPTY_STAT; + +const defaultNumberFormat = '0,0.[000]'; +const formatNumber = (value: number | undefined) => + value != null ? numeral(value).format(defaultNumberFormat) : EMPTY_STAT; describe('helpers', () => { describe('getCustomMarkdownComment', () => { @@ -23,4 +44,52 @@ ${ECS_IS_A_PERMISSIVE_SCHEMA} `); }); }); + + describe('showCustomCallout', () => { + test('it returns false when `enrichedFieldMetadata` is empty', () => { + expect(showCustomCallout([])).toBe(false); + }); + + test('it returns true when `enrichedFieldMetadata` is NOT empty', () => { + expect(showCustomCallout([someField])).toBe(true); + }); + }); + + describe('getCustomColor', () => { + test('it returns the expected color when there are custom fields', () => { + expect(getCustomColor(mockPartitionedFieldMetadata)).toEqual(euiThemeVars.euiColorLightShade); + }); + + test('it returns the expected color when custom fields is empty', () => { + const noCustomFields: PartitionedFieldMetadata = { + ...mockPartitionedFieldMetadata, + custom: [], // <-- empty + }; + + expect(getCustomColor(noCustomFields)).toEqual(euiThemeVars.euiTextColor); + }); + }); + + describe('getAllCustomMarkdownComments', () => { + test('it returns the expected comment', () => { + expect( + getAllCustomMarkdownComments({ + docsCount: 4, + formatBytes, + formatNumber, + ilmPhase: 'unmanaged', + indexName: 'auditbeat-custom-index-1', + partitionedFieldMetadata: mockPartitionedFieldMetadata, + patternDocsCount: 57410, + sizeInBytes: 28413, + }) + ).toEqual([ + '### auditbeat-custom-index-1\n', + '| Result | Index | Docs | Incompatible fields | ILM Phase | Size |\n|--------|-------|------|---------------------|-----------|------|\n| ❌ | auditbeat-custom-index-1 | 4 (0.0%) | 3 | `unmanaged` | 27.7KB |\n\n', + '### **Incompatible fields** `3` **Custom fields** `4` **ECS compliant fields** `2` **All fields** `9`\n', + `#### 4 Custom field mappings\n\nThese fields are not defined by the Elastic Common Schema (ECS), version ${EcsVersion}.\n\nECS is a permissive schema. If your events have additional data that cannot be mapped to ECS, you can simply add them to your events, using custom field names.\n`, + '#### Custom fields - auditbeat-custom-index-1\n\n\n| Field | Index mapping type | \n|-------|--------------------|\n| host.name.keyword | `keyword` | `--` |\n| some.field | `text` | `--` |\n| some.field.keyword | `keyword` | `--` |\n| source.ip.keyword | `keyword` | `--` |\n', + ]); + }); + }); }); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/custom_tab/helpers.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/custom_tab/helpers.ts index 1d8de8ac57593..7701db46d6c98 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/custom_tab/helpers.ts +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/custom_tab/helpers.ts @@ -10,14 +10,11 @@ import { euiThemeVars } from '@kbn/ui-theme'; import { FIELD, INDEX_MAPPING_TYPE } from '../../../compare_fields_table/translations'; import { - ECS_FIELD_REFERENCE_URL, - ECS_REFERENCE_URL, getSummaryMarkdownComment, getCustomMarkdownTableRows, getMarkdownComment, getMarkdownTable, getTabCountsMarkdownComment, - MAPPING_URL, getSummaryTableMarkdownComment, } from '../../index_properties/markdown/helpers'; import * as i18n from '../../index_properties/translations'; @@ -50,33 +47,33 @@ export const getCustomColor = (partitionedFieldMetadata: PartitionedFieldMetadat export const getAllCustomMarkdownComments = ({ docsCount, + formatBytes, formatNumber, ilmPhase, indexName, partitionedFieldMetadata, patternDocsCount, + sizeInBytes, }: { docsCount: number; + formatBytes: (value: number | undefined) => string; formatNumber: (value: number | undefined) => string; ilmPhase: IlmPhase | undefined; indexName: string; partitionedFieldMetadata: PartitionedFieldMetadata; patternDocsCount: number; + sizeInBytes: number | undefined; }): string[] => [ - getSummaryMarkdownComment({ - ecsFieldReferenceUrl: ECS_FIELD_REFERENCE_URL, - ecsReferenceUrl: ECS_REFERENCE_URL, - incompatible: partitionedFieldMetadata.incompatible.length, - indexName, - mappingUrl: MAPPING_URL, - }), + getSummaryMarkdownComment(indexName), getSummaryTableMarkdownComment({ docsCount, + formatBytes, formatNumber, ilmPhase, indexName, partitionedFieldMetadata, patternDocsCount, + sizeInBytes, }), getTabCountsMarkdownComment(partitionedFieldMetadata), getCustomMarkdownComment({ diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/custom_tab/index.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/custom_tab/index.tsx index bfffbd1393ef6..09f7136d7108a 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/custom_tab/index.tsx +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/custom_tab/index.tsx @@ -13,13 +13,11 @@ import { EuiEmptyPrompt, EuiSpacer, } from '@elastic/eui'; -import numeral from '@elastic/numeral'; import React, { useCallback, useMemo } from 'react'; import { CustomCallout } from '../callouts/custom_callout'; import { CompareFieldsTable } from '../../../compare_fields_table'; import { getCustomTableColumns } from '../../../compare_fields_table/helpers'; -import { EMPTY_STAT } from '../../../helpers'; import { EmptyPromptBody } from '../../index_properties/empty_prompt_body'; import { EmptyPromptTitle } from '../../index_properties/empty_prompt_title'; import { getAllCustomMarkdownComments, showCustomCallout } from './helpers'; @@ -29,39 +27,49 @@ import type { IlmPhase, PartitionedFieldMetadata } from '../../../types'; interface Props { addSuccessToast: (toast: { title: string }) => void; - defaultNumberFormat: string; docsCount: number; + formatBytes: (value: number | undefined) => string; + formatNumber: (value: number | undefined) => string; ilmPhase: IlmPhase | undefined; indexName: string; partitionedFieldMetadata: PartitionedFieldMetadata; patternDocsCount: number; + sizeInBytes: number | undefined; } const CustomTabComponent: React.FC = ({ addSuccessToast, - defaultNumberFormat, docsCount, + formatBytes, + formatNumber, ilmPhase, indexName, partitionedFieldMetadata, patternDocsCount, + sizeInBytes, }) => { - const formatNumber = useCallback( - (value: number | undefined): string => - value != null ? numeral(value).format(defaultNumberFormat) : EMPTY_STAT, - [defaultNumberFormat] - ); const markdownComments: string[] = useMemo( () => getAllCustomMarkdownComments({ docsCount, + formatBytes, formatNumber, ilmPhase, indexName, partitionedFieldMetadata, patternDocsCount, + sizeInBytes, }), - [docsCount, formatNumber, ilmPhase, indexName, partitionedFieldMetadata, patternDocsCount] + [ + docsCount, + formatBytes, + formatNumber, + ilmPhase, + indexName, + partitionedFieldMetadata, + patternDocsCount, + sizeInBytes, + ] ); const body = useMemo(() => , []); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/helpers.test.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/helpers.test.tsx new file mode 100644 index 0000000000000..c0dc6a8aaafe2 --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/helpers.test.tsx @@ -0,0 +1,112 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { DARK_THEME } from '@elastic/charts'; +import { euiThemeVars } from '@kbn/ui-theme'; +import { omit } from 'lodash/fp'; + +import { + eventCategory, + someField, + timestamp, +} from '../../mock/enriched_field_metadata/mock_enriched_field_metadata'; +import { mockPartitionedFieldMetadata } from '../../mock/partitioned_field_metadata/mock_partitioned_field_metadata'; +import { mockStatsGreenIndex } from '../../mock/stats/mock_stats_green_index'; +import { + getEcsCompliantColor, + getMissingTimestampComment, + getTabs, + showMissingTimestampCallout, +} from './helpers'; + +describe('helpers', () => { + describe('getMissingTimestampComment', () => { + test('it returns the expected comment', () => { + expect(getMissingTimestampComment()).toEqual( + '#### Missing an @timestamp (date) field mapping for this index\n\nConsider adding an @timestamp (date) field mapping to this index, as required by the Elastic Common Schema (ECS), because:\n\n❌ Detection engine rules referencing these fields may not match them correctly\n❌ Pages may not display some events or fields due to unexpected field mappings or values\n' + ); + }); + }); + + describe('showMissingTimestampCallout', () => { + test('it returns true when `enrichedFieldMetadata` is empty', () => { + expect(showMissingTimestampCallout([])).toBe(true); + }); + + test('it returns false when `enrichedFieldMetadata` contains an @timestamp field', () => { + expect(showMissingTimestampCallout([timestamp, eventCategory, someField])).toBe(false); + }); + + test('it returns true when `enrichedFieldMetadata` does NOT contain an @timestamp field', () => { + expect(showMissingTimestampCallout([eventCategory, someField])).toBe(true); + }); + }); + + describe('getEcsCompliantColor', () => { + test('it returns the expected color for the ECS compliant data when the data includes an @timestamp', () => { + expect(getEcsCompliantColor(mockPartitionedFieldMetadata)).toEqual( + euiThemeVars.euiColorSuccess + ); + }); + + test('it returns the expected color for the ECS compliant data does NOT includes an @timestamp', () => { + const noTimestamp = { + ...mockPartitionedFieldMetadata, + ecsCompliant: mockPartitionedFieldMetadata.ecsCompliant.filter( + ({ name }) => name !== '@timestamp' + ), + }; + + expect(getEcsCompliantColor(noTimestamp)).toEqual(euiThemeVars.euiColorDanger); + }); + }); + + describe('getTabs', () => { + test('it returns the expected tabs', () => { + expect( + getTabs({ + addSuccessToast: jest.fn(), + addToNewCaseDisabled: false, + docsCount: 4, + formatBytes: jest.fn(), + formatNumber: jest.fn(), + getGroupByFieldsOnClick: jest.fn(), + ilmPhase: 'unmanaged', + indexName: 'auditbeat-custom-index-1', + onAddToNewCase: jest.fn(), + partitionedFieldMetadata: mockPartitionedFieldMetadata, + pattern: 'auditbeat-*', + patternDocsCount: 57410, + setSelectedTabId: jest.fn(), + stats: mockStatsGreenIndex, + theme: DARK_THEME, + }).map((x) => omit(['append', 'content'], x)) + ).toEqual([ + { + id: 'summaryTab', + name: 'Summary', + }, + { + id: 'incompatibleTab', + name: 'Incompatible fields', + }, + { + id: 'customTab', + name: 'Custom fields', + }, + { + id: 'ecsCompliantTab', + name: 'ECS compliant fields', + }, + { + id: 'allTab', + name: 'All fields', + }, + ]); + }); + }); +}); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/helpers.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/helpers.tsx index a98ad92ba6cd9..c0cbebd45cb8d 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/helpers.tsx +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/helpers.tsx @@ -14,6 +14,7 @@ import type { WordCloudElementEvent, XYChartElementEvent, } from '@elastic/charts'; +import type { IndicesStatsIndicesStats } from '@elastic/elasticsearch/lib/api/types'; import { EuiBadge } from '@elastic/eui'; import { euiThemeVars } from '@kbn/ui-theme'; import React from 'react'; @@ -35,6 +36,7 @@ import { getFillColor } from './summary_tab/helpers'; import * as i18n from '../index_properties/translations'; import { SummaryTab } from './summary_tab'; import type { EnrichedFieldMetadata, IlmPhase, PartitionedFieldMetadata } from '../../types'; +import { getSizeInBytes } from '../../helpers'; export const getMissingTimestampComment = (): string => getMarkdownComment({ @@ -48,7 +50,7 @@ ${i18n.PAGES_MAY_NOT_DISPLAY_EVENTS} export const showMissingTimestampCallout = ( enrichedFieldMetadata: EnrichedFieldMetadata[] -): boolean => enrichedFieldMetadata.length === 0; +): boolean => !enrichedFieldMetadata.some((x) => x.name === '@timestamp'); export const getEcsCompliantColor = (partitionedFieldMetadata: PartitionedFieldMetadata): string => showMissingTimestampCallout(partitionedFieldMetadata.ecsCompliant) @@ -58,8 +60,9 @@ export const getEcsCompliantColor = (partitionedFieldMetadata: PartitionedFieldM export const getTabs = ({ addSuccessToast, addToNewCaseDisabled, - defaultNumberFormat, docsCount, + formatBytes, + formatNumber, getGroupByFieldsOnClick, ilmPhase, indexName, @@ -68,11 +71,13 @@ export const getTabs = ({ pattern, patternDocsCount, setSelectedTabId, + stats, theme, }: { addSuccessToast: (toast: { title: string }) => void; addToNewCaseDisabled: boolean; - defaultNumberFormat: string; + formatBytes: (value: number | undefined) => string; + formatNumber: (value: number | undefined) => string; docsCount: number; getGroupByFieldsOnClick: ( elements: Array< @@ -94,6 +99,7 @@ export const getTabs = ({ pattern: string; patternDocsCount: number; setSelectedTabId: (tabId: string) => void; + stats: Record | null; theme: Theme; }) => [ { @@ -101,7 +107,8 @@ export const getTabs = ({ ), @@ -127,13 +135,15 @@ export const getTabs = ({ ), id: INCOMPATIBLE_TAB_ID, @@ -148,12 +158,14 @@ export const getTabs = ({ content: ( ), id: 'customTab', diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/helpers.test.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/helpers.test.ts index e82bfd4d2efdc..54babce560f25 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/helpers.test.ts +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/helpers.test.ts @@ -5,11 +5,20 @@ * 2.0. */ -import { EcsVersion } from '@kbn/ecs'; import numeral from '@elastic/numeral'; +import { EcsVersion } from '@kbn/ecs'; +import { euiThemeVars } from '@kbn/ui-theme'; +import { + getAllIncompatibleMarkdownComments, + getIncompatibleColor, + getIncompatibleFieldsMarkdownComment, + getIncompatibleFieldsMarkdownTablesComment, + getIncompatibleMappings, + getIncompatibleValues, + showInvalidCallout, +} from './helpers'; import { EMPTY_STAT } from '../../../helpers'; -import { mockPartitionedFieldMetadata } from '../../../mock/partitioned_field_metadata/mock_partitioned_field_metadata'; import { DETECTION_ENGINE_RULES_MAY_NOT_MATCH, INCOMPATIBLE_FIELDS_WITH, @@ -17,10 +26,8 @@ import { PAGES_MAY_NOT_DISPLAY_EVENTS, WHEN_AN_INCOMPATIBLE_FIELD, } from '../../index_properties/translations'; -import { - getIncompatibleFieldsMarkdownComment, - getAllIncompatibleMarkdownComments, -} from './helpers'; +import { mockPartitionedFieldMetadata } from '../../../mock/partitioned_field_metadata/mock_partitioned_field_metadata'; +import { PartitionedFieldMetadata } from '../../../types'; describe('helpers', () => { describe('getIncompatibleFieldsMarkdownComment', () => { @@ -44,27 +51,313 @@ ${MAPPINGS_THAT_CONFLICT_WITH_ECS} }); }); + describe('showInvalidCallout', () => { + test('it returns false when the `enrichedFieldMetadata` is empty', () => { + expect(showInvalidCallout([])).toBe(false); + }); + + test('it returns true when the `enrichedFieldMetadata` is NOT empty', () => { + expect(showInvalidCallout(mockPartitionedFieldMetadata.incompatible)).toBe(true); + }); + }); + + describe('getIncompatibleColor', () => { + test('it returns the expected color', () => { + expect(getIncompatibleColor()).toEqual(euiThemeVars.euiColorDanger); + }); + }); + + describe('getIncompatibleMappings', () => { + test('it (only) returns the mappings where type !== indexFieldType', () => { + expect(getIncompatibleMappings(mockPartitionedFieldMetadata.incompatible)).toEqual([ + { + dashed_name: 'host-name', + description: + 'Name of the host.\nIt can contain what `hostname` returns on Unix systems, the fully qualified domain name, or a name specified by the user. The sender decides which value to use.', + flat_name: 'host.name', + hasEcsMetadata: true, + ignore_above: 1024, + indexFieldName: 'host.name', + indexFieldType: 'text', + indexInvalidValues: [], + isEcsCompliant: false, + isInSameFamily: false, + level: 'core', + name: 'name', + normalize: [], + short: 'Name of the host.', + type: 'keyword', + }, + { + dashed_name: 'source-ip', + description: 'IP address of the source (IPv4 or IPv6).', + flat_name: 'source.ip', + hasEcsMetadata: true, + indexFieldName: 'source.ip', + indexFieldType: 'text', + indexInvalidValues: [], + isEcsCompliant: false, + isInSameFamily: false, + level: 'core', + name: 'ip', + normalize: [], + short: 'IP address of the source.', + type: 'ip', + }, + ]); + }); + + test('it filters-out ECS complaint fields', () => { + expect(getIncompatibleMappings(mockPartitionedFieldMetadata.ecsCompliant)).toEqual([]); + }); + }); + + describe('getIncompatibleValues', () => { + test('it (only) returns the mappings with indexInvalidValues', () => { + expect(getIncompatibleValues(mockPartitionedFieldMetadata.incompatible)).toEqual([ + { + allowed_values: [ + { + description: + 'Events in this category are related to the challenge and response process in which credentials are supplied and verified to allow the creation of a session. Common sources for these logs are Windows event logs and ssh logs. Visualize and analyze events in this category to look for failed logins, and other authentication-related activity.', + expected_event_types: ['start', 'end', 'info'], + name: 'authentication', + }, + { + description: + 'Events in the configuration category have to deal with creating, modifying, or deleting the settings or parameters of an application, process, or system.\nExample sources include security policy change logs, configuration auditing logging, and system integrity monitoring.', + expected_event_types: ['access', 'change', 'creation', 'deletion', 'info'], + name: 'configuration', + }, + { + description: + 'The database category denotes events and metrics relating to a data storage and retrieval system. Note that use of this category is not limited to relational database systems. Examples include event logs from MS SQL, MySQL, Elasticsearch, MongoDB, etc. Use this category to visualize and analyze database activity such as accesses and changes.', + expected_event_types: ['access', 'change', 'info', 'error'], + name: 'database', + }, + { + description: + 'Events in the driver category have to do with operating system device drivers and similar software entities such as Windows drivers, kernel extensions, kernel modules, etc.\nUse events and metrics in this category to visualize and analyze driver-related activity and status on hosts.', + expected_event_types: ['change', 'end', 'info', 'start'], + name: 'driver', + }, + { + description: + 'This category is used for events relating to email messages, email attachments, and email network or protocol activity.\nEmails events can be produced by email security gateways, mail transfer agents, email cloud service providers, or mail server monitoring applications.', + expected_event_types: ['info'], + name: 'email', + }, + { + description: + 'Relating to a set of information that has been created on, or has existed on a filesystem. Use this category of events to visualize and analyze the creation, access, and deletions of files. Events in this category can come from both host-based and network-based sources. An example source of a network-based detection of a file transfer would be the Zeek file.log.', + expected_event_types: ['change', 'creation', 'deletion', 'info'], + name: 'file', + }, + { + description: + 'Use this category to visualize and analyze information such as host inventory or host lifecycle events.\nMost of the events in this category can usually be observed from the outside, such as from a hypervisor or a control plane\'s point of view. Some can also be seen from within, such as "start" or "end".\nNote that this category is for information about hosts themselves; it is not meant to capture activity "happening on a host".', + expected_event_types: ['access', 'change', 'end', 'info', 'start'], + name: 'host', + }, + { + description: + 'Identity and access management (IAM) events relating to users, groups, and administration. Use this category to visualize and analyze IAM-related logs and data from active directory, LDAP, Okta, Duo, and other IAM systems.', + expected_event_types: [ + 'admin', + 'change', + 'creation', + 'deletion', + 'group', + 'info', + 'user', + ], + name: 'iam', + }, + { + description: + 'Relating to intrusion detections from IDS/IPS systems and functions, both network and host-based. Use this category to visualize and analyze intrusion detection alerts from systems such as Snort, Suricata, and Palo Alto threat detections.', + expected_event_types: ['allowed', 'denied', 'info'], + name: 'intrusion_detection', + }, + { + description: + 'Malware detection events and alerts. Use this category to visualize and analyze malware detections from EDR/EPP systems such as Elastic Endpoint Security, Symantec Endpoint Protection, Crowdstrike, and network IDS/IPS systems such as Suricata, or other sources of malware-related events such as Palo Alto Networks threat logs and Wildfire logs.', + expected_event_types: ['info'], + name: 'malware', + }, + { + description: + 'Relating to all network activity, including network connection lifecycle, network traffic, and essentially any event that includes an IP address. Many events containing decoded network protocol transactions fit into this category. Use events in this category to visualize or analyze counts of network ports, protocols, addresses, geolocation information, etc.', + expected_event_types: [ + 'access', + 'allowed', + 'connection', + 'denied', + 'end', + 'info', + 'protocol', + 'start', + ], + name: 'network', + }, + { + description: + 'Relating to software packages installed on hosts. Use this category to visualize and analyze inventory of software installed on various hosts, or to determine host vulnerability in the absence of vulnerability scan data.', + expected_event_types: [ + 'access', + 'change', + 'deletion', + 'info', + 'installation', + 'start', + ], + name: 'package', + }, + { + description: + 'Use this category of events to visualize and analyze process-specific information such as lifecycle events or process ancestry.', + expected_event_types: ['access', 'change', 'end', 'info', 'start'], + name: 'process', + }, + { + description: + 'Having to do with settings and assets stored in the Windows registry. Use this category to visualize and analyze activity such as registry access and modifications.', + expected_event_types: ['access', 'change', 'creation', 'deletion'], + name: 'registry', + }, + { + description: + 'The session category is applied to events and metrics regarding logical persistent connections to hosts and services. Use this category to visualize and analyze interactive or automated persistent connections between assets. Data for this category may come from Windows Event logs, SSH logs, or stateless sessions such as HTTP cookie-based sessions, etc.', + expected_event_types: ['start', 'end', 'info'], + name: 'session', + }, + { + description: + "Use this category to visualize and analyze events describing threat actors' targets, motives, or behaviors.", + expected_event_types: ['indicator'], + name: 'threat', + }, + { + description: + 'Relating to vulnerability scan results. Use this category to analyze vulnerabilities detected by Tenable, Qualys, internal scanners, and other vulnerability management sources.', + expected_event_types: ['info'], + name: 'vulnerability', + }, + { + description: + 'Relating to web server access. Use this category to create a dashboard of web server/proxy activity from apache, IIS, nginx web servers, etc. Note: events from network observers such as Zeek http log may also be included in this category.', + expected_event_types: ['access', 'error', 'info'], + name: 'web', + }, + ], + dashed_name: 'event-category', + description: + 'This is one of four ECS Categorization Fields, and indicates the second level in the ECS category hierarchy.\n`event.category` represents the "big buckets" of ECS categories. For example, filtering on `event.category:process` yields all events relating to process activity. This field is closely related to `event.type`, which is used as a subcategory.\nThis field is an array. This will allow proper categorization of some events that fall in multiple categories.', + example: 'authentication', + flat_name: 'event.category', + ignore_above: 1024, + level: 'core', + name: 'category', + normalize: ['array'], + short: 'Event category. The second categorization field in the hierarchy.', + type: 'keyword', + indexFieldName: 'event.category', + indexFieldType: 'keyword', + indexInvalidValues: [ + { count: 2, fieldName: 'an_invalid_category' }, + { count: 1, fieldName: 'theory' }, + ], + hasEcsMetadata: true, + isEcsCompliant: false, + isInSameFamily: true, + }, + ]); + }); + + test('it filters-out ECS complaint fields', () => { + expect(getIncompatibleValues(mockPartitionedFieldMetadata.ecsCompliant)).toEqual([]); + }); + }); + + describe('getIncompatibleFieldsMarkdownTablesComment', () => { + test('it returns the expected comment when the index has `incompatibleMappings` and `incompatibleValues`', () => { + expect( + getIncompatibleFieldsMarkdownTablesComment({ + incompatibleMappings: [ + mockPartitionedFieldMetadata.incompatible[1], + mockPartitionedFieldMetadata.incompatible[2], + ], + incompatibleValues: [mockPartitionedFieldMetadata.incompatible[0]], + indexName: 'auditbeat-custom-index-1', + }) + ).toEqual( + '\n#### Incompatible field mappings - auditbeat-custom-index-1\n\n\n| Field | ECS mapping type (expected) | Index mapping type (actual) | \n|-------|-----------------------------|-----------------------------|\n| host.name | `keyword` | `text` |\n| source.ip | `ip` | `text` |\n\n#### Incompatible field values - auditbeat-custom-index-1\n\n\n| Field | ECS values (expected) | Document values (actual) | \n|-------|-----------------------|--------------------------|\n| event.category | `authentication`, `configuration`, `database`, `driver`, `email`, `file`, `host`, `iam`, `intrusion_detection`, `malware`, `network`, `package`, `process`, `registry`, `session`, `threat`, `vulnerability`, `web` | `an_invalid_category` (2), `theory` (1) |\n\n' + ); + }); + + test('it returns the expected comment when the index does NOT have `incompatibleMappings` and `incompatibleValues`', () => { + expect( + getIncompatibleFieldsMarkdownTablesComment({ + incompatibleMappings: [], // <-- no `incompatibleMappings` + incompatibleValues: [], // <-- no `incompatibleValues` + indexName: 'auditbeat-custom-index-1', + }) + ).toEqual('\n\n\n'); + }); + }); + describe('getAllIncompatibleMarkdownComments', () => { - test('it returns the expected collection of comments', () => { - const defaultNumberFormat = '0,0.[000]'; - const formatNumber = (value: number | undefined): string => - value != null ? numeral(value).format(defaultNumberFormat) : EMPTY_STAT; + const defaultBytesFormat = '0,0.[0]b'; + const formatBytes = (value: number | undefined) => + value != null ? numeral(value).format(defaultBytesFormat) : EMPTY_STAT; + + const defaultNumberFormat = '0,0.[000]'; + const formatNumber = (value: number | undefined) => + value != null ? numeral(value).format(defaultNumberFormat) : EMPTY_STAT; + test('it returns the expected collection of comments', () => { expect( getAllIncompatibleMarkdownComments({ docsCount: 4, + formatBytes, formatNumber, ilmPhase: 'unmanaged', indexName: 'auditbeat-custom-index-1', partitionedFieldMetadata: mockPartitionedFieldMetadata, patternDocsCount: 57410, + sizeInBytes: 28413, }) ).toEqual([ '### auditbeat-custom-index-1\n', - '| Result | Index | Docs | Incompatible fields | ILM Phase |\n|--------|-------|------|---------------------|-----------|\n| ❌ | auditbeat-custom-index-1 | 4 (0.0%) | 3 | `unmanaged` |\n\n', + '| Result | Index | Docs | Incompatible fields | ILM Phase | Size |\n|--------|-------|------|---------------------|-----------|------|\n| ❌ | auditbeat-custom-index-1 | 4 (0.0%) | 3 | `unmanaged` | 27.7KB |\n\n', '### **Incompatible fields** `3` **Custom fields** `4` **ECS compliant fields** `2` **All fields** `9`\n', `#### 3 incompatible fields, 0 fields with mappings in the same family\n\nFields are incompatible with ECS when index mappings, or the values of the fields in the index, don't conform to the Elastic Common Schema (ECS), version ${EcsVersion}.\n\n${INCOMPATIBLE_FIELDS_WITH}\n\n${WHEN_AN_INCOMPATIBLE_FIELD}\n${DETECTION_ENGINE_RULES_MAY_NOT_MATCH}\n${PAGES_MAY_NOT_DISPLAY_EVENTS}\n${MAPPINGS_THAT_CONFLICT_WITH_ECS}\n`, - '\n#### Incompatible field mappings - auditbeat-custom-index-1\n\n\n| Field | ECS mapping type (expected) | Index mapping type (actual) | \n|-------|-----------------------------|-----------------------------|\n| host.name | `keyword` | `text` |\n| source.ip | `ip` | `text` |\n\n#### Incompatible field values - auditbeat-custom-index-1\n\n\n| Field | ECS values (expected) | Document values (actual) | \n|-------|-----------------------|--------------------------|\n| event.category | `authentication`, `configuration`, `database`, `driver`, `email`, `file`, `host`, `iam`, `intrusion_detection`, `malware`, `network`, `package`, `process`, `registry`, `session`, `threat`, `vulnerability`, `web` | `an_invalid_category` (2),\n`theory` (1) |\n\n', + '\n#### Incompatible field mappings - auditbeat-custom-index-1\n\n\n| Field | ECS mapping type (expected) | Index mapping type (actual) | \n|-------|-----------------------------|-----------------------------|\n| host.name | `keyword` | `text` |\n| source.ip | `ip` | `text` |\n\n#### Incompatible field values - auditbeat-custom-index-1\n\n\n| Field | ECS values (expected) | Document values (actual) | \n|-------|-----------------------|--------------------------|\n| event.category | `authentication`, `configuration`, `database`, `driver`, `email`, `file`, `host`, `iam`, `intrusion_detection`, `malware`, `network`, `package`, `process`, `registry`, `session`, `threat`, `vulnerability`, `web` | `an_invalid_category` (2), `theory` (1) |\n\n', + ]); + }); + + test('it returns the expected comment when `incompatible` is empty', () => { + const emptyIncompatible: PartitionedFieldMetadata = { + ...mockPartitionedFieldMetadata, + incompatible: [], // <-- empty + }; + + expect( + getAllIncompatibleMarkdownComments({ + docsCount: 4, + formatBytes, + formatNumber, + ilmPhase: 'unmanaged', + indexName: 'auditbeat-custom-index-1', + partitionedFieldMetadata: emptyIncompatible, + patternDocsCount: 57410, + sizeInBytes: 28413, + }) + ).toEqual([ + '### auditbeat-custom-index-1\n', + '| Result | Index | Docs | Incompatible fields | ILM Phase | Size |\n|--------|-------|------|---------------------|-----------|------|\n| ✅ | auditbeat-custom-index-1 | 4 (0.0%) | 0 | `unmanaged` | 27.7KB |\n\n', + '### **Incompatible fields** `0` **Custom fields** `4` **ECS compliant fields** `2` **All fields** `9`\n', + '\n\n\n', ]); }); }); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/helpers.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/helpers.ts index 2e44ec53142b5..1f4e0b62b1c58 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/helpers.ts +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/helpers.ts @@ -9,8 +9,6 @@ import { EcsVersion } from '@kbn/ecs'; import { getIncompatiableFieldsInSameFamilyCount } from '../callouts/incompatible_callout/helpers'; import { - ECS_FIELD_REFERENCE_URL, - ECS_REFERENCE_URL, getSummaryMarkdownComment, getIncompatibleMappingsMarkdownTableRows, getIncompatibleValuesMarkdownTableRows, @@ -18,7 +16,6 @@ import { getMarkdownTable, getSummaryTableMarkdownComment, getTabCountsMarkdownComment, - MAPPING_URL, } from '../../index_properties/markdown/helpers'; import { getFillColor } from '../summary_tab/helpers'; import * as i18n from '../../index_properties/translations'; @@ -106,18 +103,22 @@ ${ export const getAllIncompatibleMarkdownComments = ({ docsCount, + formatBytes, formatNumber, ilmPhase, indexName, partitionedFieldMetadata, patternDocsCount, + sizeInBytes, }: { docsCount: number; + formatBytes: (value: number | undefined) => string; formatNumber: (value: number | undefined) => string; ilmPhase: IlmPhase | undefined; indexName: string; partitionedFieldMetadata: PartitionedFieldMetadata; patternDocsCount: number; + sizeInBytes: number | undefined; }): string[] => { const incompatibleMappings = getIncompatibleMappings(partitionedFieldMetadata.incompatible); const incompatibleValues = getIncompatibleValues(partitionedFieldMetadata.incompatible); @@ -134,20 +135,16 @@ export const getAllIncompatibleMarkdownComments = ({ : ''; return [ - getSummaryMarkdownComment({ - ecsFieldReferenceUrl: ECS_FIELD_REFERENCE_URL, - ecsReferenceUrl: ECS_REFERENCE_URL, - incompatible: partitionedFieldMetadata.incompatible.length, - indexName, - mappingUrl: MAPPING_URL, - }), + getSummaryMarkdownComment(indexName), getSummaryTableMarkdownComment({ docsCount, + formatBytes, formatNumber, ilmPhase, indexName, partitionedFieldMetadata, patternDocsCount, + sizeInBytes, }), getTabCountsMarkdownComment(partitionedFieldMetadata), incompatibleFieldsMarkdownComment, diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/index.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/index.tsx index f4def1393c07c..2fa4fcdb4f8ed 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/index.tsx +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/index.tsx @@ -13,14 +13,12 @@ import { EuiEmptyPrompt, EuiSpacer, } from '@elastic/eui'; -import numeral from '@elastic/numeral'; import React, { useCallback, useMemo } from 'react'; import { IncompatibleCallout } from '../callouts/incompatible_callout'; import { CompareFieldsTable } from '../../../compare_fields_table'; import { getIncompatibleMappingsTableColumns } from '../../../compare_fields_table/get_incompatible_mappings_table_columns'; import { getIncompatibleValuesTableColumns } from '../../../compare_fields_table/helpers'; -import { EMPTY_STAT } from '../../../helpers'; import { EmptyPromptBody } from '../../index_properties/empty_prompt_body'; import { EmptyPromptTitle } from '../../index_properties/empty_prompt_title'; import { @@ -41,31 +39,30 @@ import type { IlmPhase, PartitionedFieldMetadata } from '../../../types'; interface Props { addSuccessToast: (toast: { title: string }) => void; addToNewCaseDisabled: boolean; - defaultNumberFormat: string; docsCount: number; + formatBytes: (value: number | undefined) => string; + formatNumber: (value: number | undefined) => string; ilmPhase: IlmPhase | undefined; indexName: string; onAddToNewCase: (markdownComments: string[]) => void; partitionedFieldMetadata: PartitionedFieldMetadata; patternDocsCount: number; + sizeInBytes: number | undefined; } const IncompatibleTabComponent: React.FC = ({ addSuccessToast, addToNewCaseDisabled, - defaultNumberFormat, docsCount, + formatBytes, + formatNumber, ilmPhase, indexName, onAddToNewCase, partitionedFieldMetadata, patternDocsCount, + sizeInBytes, }) => { - const formatNumber = useCallback( - (value: number | undefined): string => - value != null ? numeral(value).format(defaultNumberFormat) : EMPTY_STAT, - [defaultNumberFormat] - ); const body = useMemo(() => , []); const title = useMemo(() => , []); const incompatibleMappings = useMemo( @@ -80,13 +77,24 @@ const IncompatibleTabComponent: React.FC = ({ () => getAllIncompatibleMarkdownComments({ docsCount, + formatBytes, formatNumber, ilmPhase, indexName, partitionedFieldMetadata, patternDocsCount, + sizeInBytes, }), - [docsCount, formatNumber, ilmPhase, indexName, partitionedFieldMetadata, patternDocsCount] + [ + docsCount, + formatBytes, + formatNumber, + ilmPhase, + indexName, + partitionedFieldMetadata, + patternDocsCount, + sizeInBytes, + ] ); const onClickAddToCase = useCallback( () => onAddToNewCase([markdownComments.join('\n')]), @@ -101,7 +109,7 @@ const IncompatibleTabComponent: React.FC = ({ }, [addSuccessToast, markdownComments]); return ( - <> +
{showInvalidCallout(partitionedFieldMetadata.incompatible) ? ( <> @@ -161,7 +169,7 @@ const IncompatibleTabComponent: React.FC = ({ titleSize="s" /> )} - +
); }; diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/styles.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/styles.tsx index cf83cf96d2812..2714d1002c40c 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/styles.tsx +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/styles.tsx @@ -5,13 +5,43 @@ * 2.0. */ -import { EuiButtonEmpty } from '@elastic/eui'; +import { EuiButtonEmpty, EuiFlexItem, EuiLink } from '@elastic/eui'; import styled from 'styled-components'; +export const DEFAULT_LEGEND_HEIGHT = 300; // px +export const DEFAULT_MAX_CHART_HEIGHT = 300; // px + export const CalloutItem = styled.div` margin-left: ${({ theme }) => theme.eui.euiSizeS}; `; +export const ChartFlexItem = styled(EuiFlexItem)<{ + $maxChartHeight: number | undefined; + $minChartHeight: number; +}>` + ${({ $maxChartHeight }) => ($maxChartHeight != null ? `max-height: ${$maxChartHeight}px;` : '')} + min-height: ${({ $minChartHeight }) => `${$minChartHeight}px`}; +`; + export const CopyToClipboardButton = styled(EuiButtonEmpty)` margin-left: ${({ theme }) => theme.eui.euiSizeXS}; `; + +export const LegendContainer = styled.div<{ + $height?: number; + $width?: number; +}>` + margin-left: ${({ theme }) => theme.eui.euiSizeM}; + margin-top: ${({ theme }) => theme.eui.euiSizeM}; + ${({ $height }) => ($height != null ? `height: ${$height}px;` : '')} + scrollbar-width: thin; + ${({ $width }) => ($width != null ? `width: ${$width}px;` : '')} +`; + +export const StorageTreemapContainer = styled.div` + padding: ${({ theme }) => theme.eui.euiSizeM}; +`; + +export const ChartLegendLink = styled(EuiLink)` + width: 100%; +`; diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/callout_summary/index.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/callout_summary/index.tsx index 054ac5e003326..6d36fdd50370a 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/callout_summary/index.tsx +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/callout_summary/index.tsx @@ -6,14 +6,12 @@ */ import { copyToClipboard, EuiButton, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; -import numeral from '@elastic/numeral'; import React, { useCallback, useMemo } from 'react'; import { MissingTimestampCallout } from '../../callouts/missing_timestamp_callout'; import { IncompatibleCallout } from '../../callouts/incompatible_callout'; import { showMissingTimestampCallout } from '../../helpers'; import { getMarkdownComments } from '../helpers'; -import { EMPTY_STAT } from '../../../../helpers'; import { showInvalidCallout } from '../../incompatible_tab/helpers'; import { CopyToClipboardButton } from '../../styles'; import * as i18n from '../../../index_properties/translations'; @@ -23,52 +21,55 @@ import type { IlmPhase, PartitionedFieldMetadata } from '../../../../types'; interface Props { addSuccessToast: (toast: { title: string }) => void; addToNewCaseDisabled: boolean; - defaultNumberFormat: string; docsCount: number; + formatBytes: (value: number | undefined) => string; + formatNumber: (value: number | undefined) => string; ilmPhase: IlmPhase | undefined; indexName: string; onAddToNewCase: (markdownComment: string[]) => void; partitionedFieldMetadata: PartitionedFieldMetadata; pattern: string; patternDocsCount: number; + sizeInBytes: number | undefined; } const CalloutSummaryComponent: React.FC = ({ addSuccessToast, addToNewCaseDisabled, - defaultNumberFormat, docsCount, + formatBytes, + formatNumber, ilmPhase, indexName, onAddToNewCase, partitionedFieldMetadata, pattern, patternDocsCount, + sizeInBytes, }) => { - const formatNumber = useCallback( - (value: number | undefined): string => - value != null ? numeral(value).format(defaultNumberFormat) : EMPTY_STAT, - [defaultNumberFormat] - ); const markdownComments: string[] = useMemo( () => getMarkdownComments({ docsCount, + formatBytes, formatNumber, ilmPhase, indexName, partitionedFieldMetadata, pattern, patternDocsCount, + sizeInBytes, }), [ docsCount, + formatBytes, formatNumber, ilmPhase, indexName, partitionedFieldMetadata, pattern, patternDocsCount, + sizeInBytes, ] ); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/helpers.test.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/helpers.test.ts new file mode 100644 index 0000000000000..64e78a4a88cfb --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/helpers.test.ts @@ -0,0 +1,235 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 numeral from '@elastic/numeral'; +import { EcsVersion } from '@kbn/ecs'; +import { euiThemeVars } from '@kbn/ui-theme'; +import { EMPTY_STAT } from '../../../helpers'; + +import { mockPartitionedFieldMetadata } from '../../../mock/partitioned_field_metadata/mock_partitioned_field_metadata'; +import { PartitionedFieldMetadata } from '../../../types'; +import { + ALL_TAB_ID, + CUSTOM_TAB_ID, + ECS_COMPLIANT_TAB_ID, + INCOMPATIBLE_TAB_ID, +} from '../../index_properties/helpers'; +import { + CUSTOM_FIELDS, + ECS_COMPLIANT_FIELDS, + INCOMPATIBLE_FIELDS, + UNKNOWN, +} from '../../index_properties/translations'; +import { + CategoryId, + getFillColor, + getMarkdownComments, + getNodeLabel, + getSummaryData, + getTabId, +} from './helpers'; + +describe('helpers', () => { + describe('getSummaryData', () => { + test('it returns the expected `SummaryData`', () => { + expect(getSummaryData(mockPartitionedFieldMetadata)).toEqual([ + { categoryId: 'incompatible', mappings: 3 }, + { categoryId: 'custom', mappings: 4 }, + { categoryId: 'ecs-compliant', mappings: 2 }, + ]); + }); + }); + + describe('getFillColor', () => { + const invalid: CategoryId = 'invalid-category-id' as CategoryId; + + const categories: Array<{ + categoryId: CategoryId; + expectedColor: string; + }> = [ + { + categoryId: 'incompatible', + expectedColor: euiThemeVars.euiColorDanger, + }, + { + categoryId: 'custom', + expectedColor: euiThemeVars.euiColorLightShade, + }, + { + categoryId: 'ecs-compliant', + expectedColor: euiThemeVars.euiColorSuccess, + }, + { + categoryId: invalid, + expectedColor: euiThemeVars.euiColorGhost, + }, + ]; + + categories.forEach(({ categoryId, expectedColor }) => { + test(`it returns the expected color for category '${categoryId}'`, () => { + expect(getFillColor(categoryId)).toEqual(expectedColor); + }); + }); + }); + + describe('getNodeLabel', () => { + const invalid: CategoryId = 'invalid-category-id' as CategoryId; + + const categories: Array<{ + categoryId: CategoryId; + expectedLabel: string; + }> = [ + { + categoryId: 'incompatible', + expectedLabel: INCOMPATIBLE_FIELDS, + }, + { + categoryId: 'custom', + expectedLabel: CUSTOM_FIELDS, + }, + { + categoryId: 'ecs-compliant', + expectedLabel: ECS_COMPLIANT_FIELDS, + }, + { + categoryId: invalid, + expectedLabel: UNKNOWN, + }, + ]; + + categories.forEach(({ categoryId, expectedLabel }) => { + test(`it returns the expected label for category '${categoryId}'`, () => { + expect(getNodeLabel(categoryId)).toEqual(expectedLabel); + }); + }); + }); + + describe('getTabId', () => { + const groupByFields: Array<{ + groupByField: string; + expectedTabId: string; + }> = [ + { + groupByField: 'incompatible', + expectedTabId: INCOMPATIBLE_TAB_ID, + }, + { + groupByField: 'custom', + expectedTabId: CUSTOM_TAB_ID, + }, + { + groupByField: 'ecs-compliant', + expectedTabId: ECS_COMPLIANT_TAB_ID, + }, + { + groupByField: 'some-other-group', + expectedTabId: ALL_TAB_ID, + }, + ]; + + groupByFields.forEach(({ groupByField, expectedTabId }) => { + test(`it returns the expected tab ID for groupByField '${groupByField}'`, () => { + expect(getTabId(groupByField)).toEqual(expectedTabId); + }); + }); + }); + + describe('getMarkdownComments', () => { + const defaultBytesFormat = '0,0.[0]b'; + const formatBytes = (value: number | undefined) => + value != null ? numeral(value).format(defaultBytesFormat) : EMPTY_STAT; + + const defaultNumberFormat = '0,0.[000]'; + const formatNumber = (value: number | undefined) => + value != null ? numeral(value).format(defaultNumberFormat) : EMPTY_STAT; + + test('it returns the expected comment when the index has incompatible fields ', () => { + expect( + getMarkdownComments({ + docsCount: 4, + formatBytes, + formatNumber, + ilmPhase: 'unmanaged', + indexName: 'auditbeat-custom-index-1', + partitionedFieldMetadata: mockPartitionedFieldMetadata, + pattern: 'auditbeat-*', + patternDocsCount: 57410, + sizeInBytes: 28413, + }) + ).toEqual([ + '### auditbeat-custom-index-1\n', + '| Result | Index | Docs | Incompatible fields | ILM Phase | Size |\n|--------|-------|------|---------------------|-----------|------|\n| ❌ | auditbeat-custom-index-1 | 4 (0.0%) | 3 | `unmanaged` | 27.7KB |\n\n', + '### **Incompatible fields** `3` **Custom fields** `4` **ECS compliant fields** `2` **All fields** `9`\n', + `#### 3 incompatible fields, 0 fields with mappings in the same family\n\nFields are incompatible with ECS when index mappings, or the values of the fields in the index, don't conform to the Elastic Common Schema (ECS), version ${EcsVersion}.\n\nIncompatible fields with mappings in the same family have exactly the same search behavior but may have different space usage or performance characteristics.\n\nWhen an incompatible field is not in the same family:\n❌ Detection engine rules referencing these fields may not match them correctly\n❌ Pages may not display some events or fields due to unexpected field mappings or values\n❌ Mappings or field values that don't comply with ECS are not supported\n`, + '\n#### Incompatible field mappings - auditbeat-custom-index-1\n\n\n| Field | ECS mapping type (expected) | Index mapping type (actual) | \n|-------|-----------------------------|-----------------------------|\n| host.name | `keyword` | `text` |\n| source.ip | `ip` | `text` |\n\n#### Incompatible field values - auditbeat-custom-index-1\n\n\n| Field | ECS values (expected) | Document values (actual) | \n|-------|-----------------------|--------------------------|\n| event.category | `authentication`, `configuration`, `database`, `driver`, `email`, `file`, `host`, `iam`, `intrusion_detection`, `malware`, `network`, `package`, `process`, `registry`, `session`, `threat`, `vulnerability`, `web` | `an_invalid_category` (2), `theory` (1) |\n\n', + ]); + }); + + test('it returns an empty array when the index does NOT have incompatible fields ', () => { + const noIncompatible: PartitionedFieldMetadata = { + ...mockPartitionedFieldMetadata, + incompatible: [], // <-- no incompatible fields + }; + + expect( + getMarkdownComments({ + docsCount: 4, + formatBytes, + formatNumber, + ilmPhase: 'unmanaged', + indexName: 'auditbeat-custom-index-1', + partitionedFieldMetadata: noIncompatible, + pattern: 'auditbeat-*', + patternDocsCount: 57410, + sizeInBytes: 28413, + }) + ).toEqual([]); + }); + + test('it returns a missing timestamp comment for an empty index', () => { + const emptyIndex: PartitionedFieldMetadata = { + all: [], + ecsCompliant: [], + custom: [], + incompatible: [ + { + description: + 'Date/time when the event originated. This is the date/time extracted from the event, typically representing when the event was generated by the source. If the event source has no original timestamp, this value is typically populated by the first time the event was received by the pipeline. Required field for all events.', + hasEcsMetadata: true, + indexFieldName: '@timestamp', + indexFieldType: '-', + indexInvalidValues: [], + isEcsCompliant: false, + isInSameFamily: false, + type: 'date', + }, + ], + }; + + expect( + getMarkdownComments({ + docsCount: 0, + formatBytes, + formatNumber, + ilmPhase: 'unmanaged', + indexName: 'auditbeat-custom-empty-index-1', + partitionedFieldMetadata: emptyIndex, + pattern: 'auditbeat-*', + patternDocsCount: 57410, + sizeInBytes: 247, + }) + ).toEqual([ + '### auditbeat-custom-empty-index-1\n', + '| Result | Index | Docs | Incompatible fields | ILM Phase | Size |\n|--------|-------|------|---------------------|-----------|------|\n| ❌ | auditbeat-custom-empty-index-1 | 0 (0.0%) | 1 | `unmanaged` | 247B |\n\n', + '### **Incompatible fields** `1` **Custom fields** `0` **ECS compliant fields** `0` **All fields** `0`\n', + `#### 1 incompatible field, 0 fields with mappings in the same family\n\nFields are incompatible with ECS when index mappings, or the values of the fields in the index, don't conform to the Elastic Common Schema (ECS), version ${EcsVersion}.\n\nIncompatible fields with mappings in the same family have exactly the same search behavior but may have different space usage or performance characteristics.\n\nWhen an incompatible field is not in the same family:\n❌ Detection engine rules referencing these fields may not match them correctly\n❌ Pages may not display some events or fields due to unexpected field mappings or values\n❌ Mappings or field values that don't comply with ECS are not supported\n`, + '\n#### Incompatible field mappings - auditbeat-custom-empty-index-1\n\n\n| Field | ECS mapping type (expected) | Index mapping type (actual) | \n|-------|-----------------------------|-----------------------------|\n| @timestamp | `date` | `-` |\n\n\n', + '#### Missing an @timestamp (date) field mapping for this index\n\nConsider adding an @timestamp (date) field mapping to this index, as required by the Elastic Common Schema (ECS), because:\n\n❌ Detection engine rules referencing these fields may not match them correctly\n❌ Pages may not display some events or fields due to unexpected field mappings or values\n', + ]); + }); + }); +}); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/helpers.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/helpers.ts index 4ab85f87e01d0..1f728e3b60c86 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/helpers.ts +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/helpers.ts @@ -79,28 +79,34 @@ const isString = (x: string | null): x is string => typeof x === 'string'; export const getMarkdownComments = ({ docsCount, + formatBytes, formatNumber, ilmPhase, indexName, partitionedFieldMetadata, patternDocsCount, + sizeInBytes, }: { docsCount: number; + formatBytes: (value: number | undefined) => string; formatNumber: (value: number | undefined) => string; ilmPhase: IlmPhase | undefined; indexName: string; partitionedFieldMetadata: PartitionedFieldMetadata; pattern: string; patternDocsCount: number; + sizeInBytes: number | undefined; }): string[] => { const invalidMarkdownComments = showInvalidCallout(partitionedFieldMetadata.incompatible) ? getAllIncompatibleMarkdownComments({ docsCount, + formatBytes, formatNumber, ilmPhase, indexName, partitionedFieldMetadata, patternDocsCount, + sizeInBytes, }) : []; diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/index.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/index.tsx index 087470b7e86dc..c830ac2f6c7be 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/index.tsx +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/index.tsx @@ -24,8 +24,9 @@ import type { IlmPhase, PartitionedFieldMetadata } from '../../../types'; interface Props { addSuccessToast: (toast: { title: string }) => void; addToNewCaseDisabled: boolean; - defaultNumberFormat: string; docsCount: number; + formatBytes: (value: number | undefined) => string; + formatNumber: (value: number | undefined) => string; getGroupByFieldsOnClick: ( elements: Array< | FlameElementEvent @@ -46,13 +47,15 @@ interface Props { pattern: string; patternDocsCount: number; setSelectedTabId: (tabId: string) => void; + sizeInBytes: number | undefined; theme: Theme; } const SummaryTabComponent: React.FC = ({ addSuccessToast, addToNewCaseDisabled, - defaultNumberFormat, + formatBytes, + formatNumber, docsCount, getGroupByFieldsOnClick, ilmPhase, @@ -62,13 +65,15 @@ const SummaryTabComponent: React.FC = ({ pattern, patternDocsCount, setSelectedTabId, + sizeInBytes, theme, }) => ( <> = ({ partitionedFieldMetadata={partitionedFieldMetadata} pattern={pattern} patternDocsCount={patternDocsCount} + sizeInBytes={sizeInBytes} /> void; + color: string | null; + count: number | string; + dataTestSubj?: string; + onClick: (() => void) | undefined; text: string; + textWidth?: number; } -const ChartLegendItemComponent: React.FC = ({ color, count, onClick, text }) => { - return ( +const ChartLegendItemComponent: React.FC = ({ + color, + count, + dataTestSubj = DEFAULT_DATA_TEST_SUBJ, + onClick, + text, + textWidth, +}) => ( + - - - {text} - - + + {color != null ? ( + + + {text} + + + ) : ( + + {text} + + )} + + - -
{count}
-
+ {count}
- ); -}; +
+); ChartLegendItemComponent.displayName = 'ChartLegendItemComponent'; diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/ecs_summary_donut_chart/chart_legend/index.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/ecs_summary_donut_chart/chart_legend/index.tsx index 21dd3e0450f40..b7c05e75300d5 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/ecs_summary_donut_chart/chart_legend/index.tsx +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/ecs_summary_donut_chart/chart_legend/index.tsx @@ -5,9 +5,7 @@ * 2.0. */ -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import React, { useCallback } from 'react'; -import styled from 'styled-components'; import { ChartLegendItem } from './chart_legend_item'; import { getEcsCompliantColor } from '../../data_quality_panel/tabs/helpers'; @@ -20,10 +18,9 @@ import { getCustomColor } from '../../data_quality_panel/tabs/custom_tab/helpers import { getIncompatibleColor } from '../../data_quality_panel/tabs/incompatible_tab/helpers'; import type { PartitionedFieldMetadata } from '../../types'; import * as i18n from '../../data_quality_panel/index_properties/translations'; +import { LegendContainer } from '../../data_quality_panel/tabs/styles'; -const ChartLegendFlexGroup = styled(EuiFlexGroup)` - width: 210px; -`; +const LEGEND_WIDTH = 200; // px interface Props { partitionedFieldMetadata: PartitionedFieldMetadata; @@ -44,40 +41,34 @@ const ChartLegendComponent: React.FC = ({ partitionedFieldMetadata, setSe ); return ( - + {partitionedFieldMetadata.incompatible.length > 0 && ( - - - + )} {partitionedFieldMetadata.custom.length > 0 && ( - - - + )} {partitionedFieldMetadata.ecsCompliant.length > 0 && ( - - - + )} - + ); }; diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/ecs_summary_donut_chart/helpers.test.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/ecs_summary_donut_chart/helpers.test.ts new file mode 100644 index 0000000000000..30a5e9d13e4fc --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/ecs_summary_donut_chart/helpers.test.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { allMetadataIsEmpty } from './helpers'; +import { mockPartitionedFieldMetadata } from '../mock/partitioned_field_metadata/mock_partitioned_field_metadata'; +import { PartitionedFieldMetadata } from '../types'; + +describe('helpers', () => { + describe('allMetadataIsEmpty', () => { + test('it returns false when `all` is NOT is empty', () => { + expect(allMetadataIsEmpty(mockPartitionedFieldMetadata)).toBe(false); + }); + + test('it returns true when `all` is is empty', () => { + const allIsEmpty: PartitionedFieldMetadata = { + all: [], // <-- empty + custom: [], + ecsCompliant: [], + incompatible: [], + }; + + expect(allMetadataIsEmpty(allIsEmpty)).toBe(true); + }); + }); +}); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/helpers.test.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/helpers.test.ts index 14448d1a3baac..81e968bfe73a4 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/helpers.test.ts +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/helpers.test.ts @@ -5,10 +5,7 @@ * 2.0. */ -import { - IlmExplainLifecycleLifecycleExplain, - MappingProperty, -} from '@elastic/elasticsearch/lib/api/types'; +import { IlmExplainLifecycleLifecycleExplain } from '@elastic/elasticsearch/lib/api/types'; import { EcsFlat } from '@kbn/ecs'; import { omit } from 'lodash/fp'; @@ -28,9 +25,11 @@ import { getMissingTimestampFieldMetadata, getPartitionedFieldMetadata, getPartitionedFieldMetadataStats, + getSizeInBytes, getTotalDocsCount, getTotalPatternIncompatible, getTotalPatternIndicesChecked, + getTotalSizeInBytes, hasValidTimestampMapping, isMappingCompatible, } from './helpers'; @@ -44,8 +43,9 @@ import { sourcePort, timestamp, eventCategoryWithUnallowedValues, -} from './mock/enriched_field_metadata'; -import { mockIlmExplain } from './mock/ilm_explain'; +} from './mock/enriched_field_metadata/mock_enriched_field_metadata'; +import { mockIlmExplain } from './mock/ilm_explain/mock_ilm_explain'; +import { mockMappingsProperties } from './mock/mappings_properties/mock_mappings_properties'; import { alertIndexNoResults } from './mock/pattern_rollup/mock_alerts_pattern_rollup'; import { packetbeatNoResults, @@ -79,7 +79,7 @@ const ecsMetadata: Record = EcsFlat as unknown as Record { describe('getIndexNames', () => { - const ilmPhases: string[] = ['hot', 'warm', 'unmanaged']; + const ilmPhases = ['hot', 'warm', 'unmanaged']; test('returns the expected index names when they have an ILM phase included in the ilmPhases list', () => { expect( @@ -91,17 +91,18 @@ describe('helpers', () => { ).toEqual([ '.ds-packetbeat-8.6.1-2023.02.04-000001', '.ds-packetbeat-8.5.3-2023.02.04-000001', + 'auditbeat-custom-index-1', ]); }); test('returns the expected filtered index names when they do NOT have an ILM phase included in the ilmPhases list', () => { expect( getIndexNames({ - ilmExplain: mockIlmExplain, // <-- the mock indexes have 'hot' ILM phases... + ilmExplain: mockIlmExplain, // <-- the mock indexes have 'hot' and 'unmanaged' ILM phases... ilmPhases: ['warm', 'unmanaged'], // <-- ...but we don't ask for 'hot' stats: mockStats, }) - ).toEqual([]); + ).toEqual(['auditbeat-custom-index-1']); // <-- the 'unmanaged' index }); test('returns the expected index names when the `ilmExplain` is missing a record for an index', () => { @@ -117,7 +118,7 @@ describe('helpers', () => { ilmPhases: ['hot', 'warm', 'unmanaged'], stats: mockStats, }) - ).toEqual(['.ds-packetbeat-8.5.3-2023.02.04-000001']); // <-- only includes one of the two indexes, because the other one is missing an ILM explain record + ).toEqual(['.ds-packetbeat-8.5.3-2023.02.04-000001', 'auditbeat-custom-index-1']); // <-- only includes two of the three indices, because the other one is missing an ILM explain record }); test('returns empty index names when `ilmPhases` is empty', () => { @@ -162,105 +163,6 @@ describe('helpers', () => { }); describe('getFieldTypes', () => { - /** - * These `mappingsProperties` represent mappings that were generated by - * Elasticsearch automatically, for an index named `auditbeat-custom-index-1`: - * - * ``` - * DELETE auditbeat-custom-index-1 - * - * PUT auditbeat-custom-index-1 - * - * PUT auditbeat-custom-index-1/_mapping - * { - * "properties": { - * "@timestamp": { - * "type": "date" - * }, - * "event.category": { - * "type": "keyword", - * "ignore_above": 1024 - * } - * } - * } - * ``` - * - * when the following document was inserted: - * - * ``` - * POST auditbeat-custom-index-1/_doc - * { - * "@timestamp": "2023-02-06T09:41:49.668Z", - * "host": { - * "name": "foo" - * }, - * "event": { - * "category": "an_invalid_category" - * }, - * "some.field": "this", - * "source": { - * "port": 90210, - * "ip": "10.1.2.3" - * } - * } - * ``` - */ - const mappingsProperties: Record = { - '@timestamp': { - type: 'date', - }, - event: { - properties: { - category: { - type: 'keyword', - ignore_above: 1024, - }, - }, - }, - host: { - properties: { - name: { - type: 'text', - fields: { - keyword: { - type: 'keyword', - ignore_above: 256, - }, - }, - }, - }, - }, - some: { - properties: { - field: { - type: 'text', - fields: { - keyword: { - type: 'keyword', - ignore_above: 256, - }, - }, - }, - }, - }, - source: { - properties: { - ip: { - type: 'text', - fields: { - keyword: { - type: 'keyword', - ignore_above: 256, - }, - }, - }, - port: { - type: 'long', - }, - }, - }, - }; - const expected = [ { field: '@timestamp', @@ -301,7 +203,7 @@ describe('helpers', () => { ]; test('it flattens the field names and types in the mapping properties', () => { - expect(getFieldTypes(mappingsProperties)).toEqual(expected); + expect(getFieldTypes(mockMappingsProperties)).toEqual(expected); }); test('it throws a type error when mappingsProperties is not flatten-able', () => { @@ -876,6 +778,53 @@ describe('helpers', () => { }); }); + describe('getSizeInBytes', () => { + test('it returns the expected size when `stats` contains the `indexName`', () => { + const indexName = '.ds-packetbeat-8.6.1-2023.02.04-000001'; + const expectedCount = mockStatsYellowIndex[indexName].primaries?.store?.size_in_bytes; + + expect( + getSizeInBytes({ + indexName, + stats: mockStatsYellowIndex, + }) + ).toEqual(expectedCount); + }); + + test('it returns zero when `stats` does NOT contain the `indexName`', () => { + const indexName = 'not-gonna-find-it'; + + expect( + getSizeInBytes({ + indexName, + stats: mockStatsYellowIndex, + }) + ).toEqual(0); + }); + + test('it returns zero when `stats` is null', () => { + const indexName = '.ds-packetbeat-8.6.1-2023.02.04-000001'; + + expect( + getSizeInBytes({ + indexName, + stats: null, + }) + ).toEqual(0); + }); + + test('it returns the expected size for a green index, where `primaries.store.size_in_bytes` and `total.store.size_in_bytes` have different values', () => { + const indexName = 'auditbeat-custom-index-1'; + + expect( + getSizeInBytes({ + indexName, + stats: mockStatsGreenIndex, + }) + ).toEqual(mockStatsGreenIndex[indexName].primaries?.store?.size_in_bytes); + }); + }); + describe('getTotalDocsCount', () => { test('it returns the expected total given a subset of index names in the stats', () => { const indexName = '.ds-packetbeat-8.5.3-2023.02.04-000001'; @@ -925,6 +874,55 @@ describe('helpers', () => { }); }); + describe('getTotalSizeInBytes', () => { + test('it returns the expected total given a subset of index names in the stats', () => { + const indexName = '.ds-packetbeat-8.5.3-2023.02.04-000001'; + const expectedCount = mockStatsYellowIndex[indexName].primaries?.store?.size_in_bytes; + + expect( + getTotalSizeInBytes({ + indexNames: [indexName], + stats: mockStatsYellowIndex, + }) + ).toEqual(expectedCount); + }); + + test('it returns the expected total given all index names in the stats', () => { + const allIndexNamesInStats = [ + '.ds-packetbeat-8.6.1-2023.02.04-000001', + '.ds-packetbeat-8.5.3-2023.02.04-000001', + ]; + + expect( + getTotalSizeInBytes({ + indexNames: allIndexNamesInStats, + stats: mockStatsYellowIndex, + }) + ).toEqual(1464758182); + }); + + test('it returns zero given an empty collection of index names', () => { + expect( + getTotalSizeInBytes({ + indexNames: [], // <-- empty + stats: mockStatsYellowIndex, + }) + ).toEqual(0); + }); + + test('it returns the expected total for a green index', () => { + const indexName = 'auditbeat-custom-index-1'; + const expectedCount = mockStatsGreenIndex[indexName].primaries?.store?.size_in_bytes; + + expect( + getTotalSizeInBytes({ + indexNames: [indexName], + stats: mockStatsGreenIndex, + }) + ).toEqual(expectedCount); + }); + }); + describe('getIlmPhaseDescription', () => { const phases: Array<{ phase: string; diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/helpers.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/helpers.ts index ea8a50a41580f..7cb638ad11550 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/helpers.ts +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/helpers.ts @@ -255,6 +255,14 @@ export const getDocsCount = ({ stats: Record | null; }): number => (stats && stats[indexName]?.primaries?.docs?.count) ?? 0; +export const getSizeInBytes = ({ + indexName, + stats, +}: { + indexName: string; + stats: Record | null; +}): number => (stats && stats[indexName]?.primaries?.store?.size_in_bytes) ?? 0; + export const getTotalDocsCount = ({ indexNames, stats, @@ -267,6 +275,18 @@ export const getTotalDocsCount = ({ 0 ); +export const getTotalSizeInBytes = ({ + indexNames, + stats, +}: { + indexNames: string[]; + stats: Record | null; +}): number => + indexNames.reduce( + (acc: number, indexName: string) => acc + getSizeInBytes({ stats, indexName }), + 0 + ); + export const EMPTY_STAT = '--'; /** diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/ilm_phases_empty_prompt/index.test.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/ilm_phases_empty_prompt/index.test.tsx new file mode 100644 index 0000000000000..417f1419a7ca5 --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/ilm_phases_empty_prompt/index.test.tsx @@ -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 { render, screen } from '@testing-library/react'; +import React from 'react'; + +import { TestProviders } from '../mock/test_providers/test_providers'; +import { IlmPhasesEmptyPrompt } from '.'; + +describe('IlmPhasesEmptyPrompt', () => { + beforeEach(() => { + render( + + + + ); + }); + + test('it renders the expected content', () => { + expect(screen.getByTestId('ilmPhasesEmptyPrompt')).toHaveTextContent( + "ILM phases that can be checked for data qualityhot: The index is actively being updated and queriedwarm: The index is no longer being updated but is still being queriedunmanaged: The index isn't managed by Index Lifecycle Management (ILM)ILM phases that cannot be checkedThe following ILM phases cannot be checked for data quality because they are slower to accesscold: The index is no longer being updated and is queried infrequently. The information still needs to be searchable, but it’s okay if those queries are slower.frozen: The index is no longer being updated and is queried rarely. The information still needs to be searchable, but it's okay if those queries are extremely slow." + ); + }); +}); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/index.test.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/index.test.tsx index 72affc8aec491..5f6711814a904 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/index.test.tsx +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/index.test.tsx @@ -9,12 +9,12 @@ import { DARK_THEME } from '@elastic/charts'; import { render, screen } from '@testing-library/react'; import React from 'react'; -import { TestProviders } from './mock/test_providers'; +import { TestProviders } from './mock/test_providers/test_providers'; import { DataQualityPanel } from '.'; describe('DataQualityPanel', () => { - describe('when no ILM phases are provided', () => { - const ilmPhases: string[] = []; + describe('when ILM phases are provided', () => { + const ilmPhases: string[] = ['hot', 'warm', 'unmanaged']; beforeEach(() => { render( @@ -22,6 +22,7 @@ describe('DataQualityPanel', () => { { ); }); - test('it renders the ILM phases empty prompt', () => { - expect(screen.getByTestId('ilmPhasesEmptyPrompt')).toBeInTheDocument(); + test('it does NOT render the ILM phases empty prompt', () => { + expect(screen.queryByTestId('ilmPhasesEmptyPrompt')).not.toBeInTheDocument(); }); - test('it does NOT render the body', () => { - expect(screen.queryByTestId('body')).not.toBeInTheDocument(); + test('it renders the body', () => { + expect(screen.getByTestId('body')).toBeInTheDocument(); }); }); - describe('when ILM phases are provided', () => { - const ilmPhases: string[] = ['hot', 'warm', 'unmanaged']; + describe('when ILM phases are NOT provided', () => { + test('it renders the ILM phases empty prompt', () => { + const ilmPhases: string[] = []; - beforeEach(() => { render( { /> ); - }); - - test('it does NOT render the ILM phases empty prompt', () => { - expect(screen.queryByTestId('ilmPhasesEmptyPrompt')).not.toBeInTheDocument(); - }); - test('it renders the body', () => { - expect(screen.getByTestId('body')).toBeInTheDocument(); + expect(screen.getByTestId('ilmPhasesEmptyPrompt')).toBeInTheDocument(); }); }); }); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/index.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/index.tsx index 05ccaf9c4fc5e..dde54d8e97f64 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/index.tsx +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/index.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import numeral from '@elastic/numeral'; import type { FlameElementEvent, HeatmapElementEvent, @@ -14,15 +15,16 @@ import type { WordCloudElementEvent, XYChartElementEvent, } from '@elastic/charts'; -import React from 'react'; +import React, { useCallback } from 'react'; import { Body } from './data_quality_panel/body'; -import { IlmPhasesEmptyPrompt } from './ilm_phases_empty_prompt'; +import { EMPTY_STAT } from './helpers'; interface Props { addSuccessToast: (toast: { title: string }) => void; canUserCreateAndReadCases: () => boolean; defaultNumberFormat: string; + defaultBytesFormat: string; getGroupByFieldsOnClick: ( elements: Array< | FlameElementEvent @@ -54,6 +56,7 @@ interface Props { const DataQualityPanelComponent: React.FC = ({ addSuccessToast, canUserCreateAndReadCases, + defaultBytesFormat, defaultNumberFormat, getGroupByFieldsOnClick, ilmPhases, @@ -63,15 +66,24 @@ const DataQualityPanelComponent: React.FC = ({ setLastChecked, theme, }) => { - if (ilmPhases.length === 0) { - return ; - } + const formatBytes = useCallback( + (value: number | undefined): string => + value != null ? numeral(value).format(defaultBytesFormat) : EMPTY_STAT, + [defaultBytesFormat] + ); + + const formatNumber = useCallback( + (value: number | undefined): string => + value != null ? numeral(value).format(defaultNumberFormat) : EMPTY_STAT, + [defaultNumberFormat] + ); return ( = { + 'auditbeat-custom-index-1': { + docsCount: 4, + error: null, + ilmPhase: 'unmanaged', + incompatible: 3, + indexName: 'auditbeat-custom-index-1', + markdownComments: [ + '### auditbeat-custom-index-1\n', + '| Result | Index | Docs | Incompatible fields | ILM Phase |\n|--------|-------|------|---------------------|-----------|\n| ❌ | auditbeat-custom-index-1 | 4 (0.0%) | 3 | `unmanaged` |\n\n', + '### **Incompatible fields** `3` **Custom fields** `4` **ECS compliant fields** `2` **All fields** `9`\n', + "#### 3 incompatible fields, 0 fields with mappings in the same family\n\nFields are incompatible with ECS when index mappings, or the values of the fields in the index, don't conform to the Elastic Common Schema (ECS), version 8.6.1.\n\nIncompatible fields with mappings in the same family have exactly the same search behavior but may have different space usage or performance characteristics.\n\nWhen an incompatible field is not in the same family:\n❌ Detection engine rules referencing these fields may not match them correctly\n❌ Pages may not display some events or fields due to unexpected field mappings or values\n❌ Mappings or field values that don't comply with ECS are not supported\n", + '\n#### Incompatible field mappings - auditbeat-custom-index-1\n\n\n| Field | ECS mapping type (expected) | Index mapping type (actual) | \n|-------|-----------------------------|-----------------------------|\n| host.name | `keyword` | `text` |\n| source.ip | `ip` | `text` |\n\n#### Incompatible field values - auditbeat-custom-index-1\n\n\n| Field | ECS values (expected) | Document values (actual) | \n|-------|-----------------------|--------------------------|\n| event.category | `authentication`, `configuration`, `database`, `driver`, `email`, `file`, `host`, `iam`, `intrusion_detection`, `malware`, `network`, `package`, `process`, `registry`, `session`, `threat`, `vulnerability`, `web` | `an_invalid_category` (2),\n`theory` (1) |\n\n', + ], + pattern: 'auditbeat-*', + }, + 'auditbeat-7.9.3-2023.02.13-000001': { + docsCount: 2438, + error: null, + ilmPhase: 'hot', + incompatible: 12, + indexName: 'auditbeat-7.9.3-2023.02.13-000001', + markdownComments: [ + '### auditbeat-7.9.3-2023.02.13-000001\n', + '| Result | Index | Docs | Incompatible fields | ILM Phase |\n|--------|-------|------|---------------------|-----------|\n| ❌ | auditbeat-7.9.3-2023.02.13-000001 | 2,438 (4.2%) | 12 | `hot` |\n\n', + '### **Incompatible fields** `12` **Custom fields** `439` **ECS compliant fields** `506` **All fields** `957`\n', + "#### 12 incompatible fields, 11 fields with mappings in the same family\n\nFields are incompatible with ECS when index mappings, or the values of the fields in the index, don't conform to the Elastic Common Schema (ECS), version 8.6.1.\n\nIncompatible fields with mappings in the same family have exactly the same search behavior but may have different space usage or performance characteristics.\n\nWhen an incompatible field is not in the same family:\n❌ Detection engine rules referencing these fields may not match them correctly\n❌ Pages may not display some events or fields due to unexpected field mappings or values\n❌ Mappings or field values that don't comply with ECS are not supported\n", + '\n#### Incompatible field mappings - auditbeat-7.9.3-2023.02.13-000001\n\n\n| Field | ECS mapping type (expected) | Index mapping type (actual) | \n|-------|-----------------------------|-----------------------------|\n| error.message | `match_only_text` | `text` `same family` |\n| error.stack_trace | `wildcard` | `keyword` `same family` |\n| http.request.body.content | `wildcard` | `keyword` `same family` |\n| http.response.body.content | `wildcard` | `keyword` `same family` |\n| message | `match_only_text` | `text` `same family` |\n| process.command_line | `wildcard` | `keyword` `same family` |\n| process.parent.command_line | `wildcard` | `keyword` `same family` |\n| registry.data.strings | `wildcard` | `keyword` `same family` |\n| url.full | `wildcard` | `keyword` `same family` |\n| url.original | `wildcard` | `keyword` `same family` |\n| url.path | `wildcard` | `keyword` `same family` |\n\n#### Incompatible field values - auditbeat-7.9.3-2023.02.13-000001\n\n\n| Field | ECS values (expected) | Document values (actual) | \n|-------|-----------------------|--------------------------|\n| event.kind | `alert`, `enrichment`, `event`, `metric`, `state`, `pipeline_error`, `signal` | `error` (7) |\n\n', + ], + pattern: 'auditbeat-*', + }, +}; diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/enriched_field_metadata/index.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/enriched_field_metadata/mock_enriched_field_metadata.ts similarity index 87% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/enriched_field_metadata/index.ts rename to x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/enriched_field_metadata/mock_enriched_field_metadata.ts index 654d608d7d351..be445b6ee6b51 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/enriched_field_metadata/index.ts +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/enriched_field_metadata/mock_enriched_field_metadata.ts @@ -270,3 +270,75 @@ export const sourcePort: EnrichedFieldMetadata = { isEcsCompliant: true, isInSameFamily: false, // `long` is not a member of any families }; + +export const mockCustomFields: EnrichedFieldMetadata[] = [ + { + indexFieldName: 'host.name.keyword', + indexFieldType: 'keyword', + indexInvalidValues: [], + hasEcsMetadata: false, + isEcsCompliant: false, + isInSameFamily: false, + }, + { + indexFieldName: 'some.field', + indexFieldType: 'text', + indexInvalidValues: [], + hasEcsMetadata: false, + isEcsCompliant: false, + isInSameFamily: false, + }, + { + indexFieldName: 'some.field.keyword', + indexFieldType: 'keyword', + indexInvalidValues: [], + hasEcsMetadata: false, + isEcsCompliant: false, + isInSameFamily: false, + }, + { + indexFieldName: 'source.ip.keyword', + indexFieldType: 'keyword', + indexInvalidValues: [], + hasEcsMetadata: false, + isEcsCompliant: false, + isInSameFamily: false, + }, +]; + +export const mockIncompatibleMappings: EnrichedFieldMetadata[] = [ + { + dashed_name: 'host-name', + description: + 'Name of the host.\nIt can contain what `hostname` returns on Unix systems, the fully qualified domain name, or a name specified by the user. The sender decides which value to use.', + flat_name: 'host.name', + ignore_above: 1024, + level: 'core', + name: 'name', + normalize: [], + short: 'Name of the host.', + type: 'keyword', + indexFieldName: 'host.name', + indexFieldType: 'text', + indexInvalidValues: [], + hasEcsMetadata: true, + isEcsCompliant: false, + isInSameFamily: false, + }, + { + dashed_name: 'source-ip', + description: 'IP address of the source (IPv4 or IPv6).', + flat_name: 'source.ip', + level: 'core', + name: 'ip', + normalize: [], + short: 'IP address of the source.', + type: 'ip', + indexFieldName: 'source.ip', + indexFieldType: 'text', + indexInvalidValues: [], + hasEcsMetadata: true, + isEcsCompliant: false, + isInSameFamily: false, + }, +]; diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/ilm_explain/index.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/ilm_explain/mock_ilm_explain.ts similarity index 94% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/ilm_explain/index.ts rename to x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/ilm_explain/mock_ilm_explain.ts index bf7bc667c9aaf..b5b35064a7320 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/ilm_explain/index.ts +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/ilm_explain/mock_ilm_explain.ts @@ -48,4 +48,8 @@ export const mockIlmExplain: Record modified_date_in_millis: 1675536751205, }, }, + 'auditbeat-custom-index-1': { + index: 'auditbeat-custom-index-1', + managed: false, + }, }; diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/indices_get_mapping_index_mapping_record/mock_indices_get_mapping_index_mapping_record.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/indices_get_mapping_index_mapping_record/mock_indices_get_mapping_index_mapping_record.ts new file mode 100644 index 0000000000000..e63bae0530961 --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/indices_get_mapping_index_mapping_record/mock_indices_get_mapping_index_mapping_record.ts @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { IndicesGetMappingIndexMappingRecord } from '@elastic/elasticsearch/lib/api/types'; + +export const mockIndicesGetMappingIndexMappingRecords: Record< + string, + IndicesGetMappingIndexMappingRecord +> = { + 'auditbeat-custom-index-1': { + mappings: { + properties: { + '@timestamp': { + type: 'date', + }, + event: { + properties: { + category: { + type: 'keyword', + ignore_above: 1024, + }, + }, + }, + host: { + properties: { + name: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + }, + }, + some: { + properties: { + field: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + }, + }, + source: { + properties: { + ip: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + port: { + type: 'long', + }, + }, + }, + }, + }, + }, +}; diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/mappings_properties/mock_mappings_properties.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/mappings_properties/mock_mappings_properties.ts new file mode 100644 index 0000000000000..42b22d9c99aaa --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/mappings_properties/mock_mappings_properties.ts @@ -0,0 +1,107 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { MappingProperty } from '@elastic/elasticsearch/lib/api/types'; + +/** + * These `mappingsProperties` represent mappings that were generated by + * Elasticsearch automatically, for an index named `auditbeat-custom-index-1`: + * + * ``` + * DELETE auditbeat-custom-index-1 + * + * PUT auditbeat-custom-index-1 + * + * PUT auditbeat-custom-index-1/_mapping + * { + * "properties": { + * "@timestamp": { + * "type": "date" + * }, + * "event.category": { + * "type": "keyword", + * "ignore_above": 1024 + * } + * } + * } + * ``` + * + * when the following document was inserted: + * + * ``` + * POST auditbeat-custom-index-1/_doc + * { + * "@timestamp": "2023-02-06T09:41:49.668Z", + * "host": { + * "name": "foo" + * }, + * "event": { + * "category": "an_invalid_category" + * }, + * "some.field": "this", + * "source": { + * "port": 90210, + * "ip": "10.1.2.3" + * } + * } + * ``` + */ +export const mockMappingsProperties: Record = { + '@timestamp': { + type: 'date', + }, + event: { + properties: { + category: { + type: 'keyword', + ignore_above: 1024, + }, + }, + }, + host: { + properties: { + name: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + }, + }, + some: { + properties: { + field: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + }, + }, + source: { + properties: { + ip: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + port: { + type: 'long', + }, + }, + }, +}; diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/mappings_response/mock_mappings_response.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/mappings_response/mock_mappings_response.ts new file mode 100644 index 0000000000000..5e15bb4e2efdd --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/mappings_response/mock_mappings_response.ts @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 mockMappingsResponse = { + 'auditbeat-custom-index-1': { + mappings: { + properties: { + '@timestamp': { + type: 'date', + }, + event: { + properties: { + category: { + type: 'keyword', + ignore_above: 1024, + }, + }, + }, + host: { + properties: { + name: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + }, + }, + some: { + properties: { + field: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + }, + }, + source: { + properties: { + ip: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + port: { + type: 'long', + }, + }, + }, + }, + }, + }, +}; diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/pattern_rollup/mock_alerts_pattern_rollup.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/pattern_rollup/mock_alerts_pattern_rollup.ts index cbca7ab9e1965..39c25cbc77c10 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/pattern_rollup/mock_alerts_pattern_rollup.ts +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/pattern_rollup/mock_alerts_pattern_rollup.ts @@ -33,6 +33,7 @@ export const alertIndexNoResults: PatternRollup = { indices: 1, pattern: '.alerts-security.alerts-default', results: undefined, // <-- no results + sizeInBytes: 6423408623, stats: { '.internal.alerts-security.alerts-default-000001': { health: 'green', @@ -83,6 +84,7 @@ export const alertIndexWithAllResults: PatternRollup = { pattern: '.alerts-security.alerts-default', }, }, + sizeInBytes: 29717961631, stats: { '.internal.alerts-security.alerts-default-000001': { health: 'green', diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/pattern_rollup/mock_auditbeat_pattern_rollup.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/pattern_rollup/mock_auditbeat_pattern_rollup.ts index 776f02f9f4e74..3ece4fb4c248f 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/pattern_rollup/mock_auditbeat_pattern_rollup.ts +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/pattern_rollup/mock_auditbeat_pattern_rollup.ts @@ -45,10 +45,17 @@ export const auditbeatNoResults: PatternRollup = { indices: 3, pattern: 'auditbeat-*', results: undefined, // <-- no results + sizeInBytes: 18820446, stats: { '.ds-auditbeat-8.6.1-2023.02.07-000001': { uuid: 'YpxavlUVTw2x_E_QtADrpg', health: 'yellow', + primaries: { + store: { + size_in_bytes: 18791790, + reserved_in_bytes: 0, + }, + }, status: 'open', total: { docs: { @@ -60,6 +67,12 @@ export const auditbeatNoResults: PatternRollup = { 'auditbeat-custom-empty-index-1': { uuid: 'Iz5FJjsLQla34mD6kBAQBw', health: 'yellow', + primaries: { + store: { + size_in_bytes: 247, + reserved_in_bytes: 0, + }, + }, status: 'open', total: { docs: { @@ -71,6 +84,12 @@ export const auditbeatNoResults: PatternRollup = { 'auditbeat-custom-index-1': { uuid: 'xJvgb2QCQPSjlr7UnW8tFA', health: 'yellow', + primaries: { + store: { + size_in_bytes: 28409, + reserved_in_bytes: 0, + }, + }, status: 'open', total: { docs: { @@ -152,10 +171,17 @@ export const auditbeatWithAllResults: PatternRollup = { pattern: 'auditbeat-*', }, }, + sizeInBytes: 18820446, stats: { '.ds-auditbeat-8.6.1-2023.02.07-000001': { uuid: 'YpxavlUVTw2x_E_QtADrpg', health: 'yellow', + primaries: { + store: { + size_in_bytes: 18791790, + reserved_in_bytes: 0, + }, + }, status: 'open', total: { docs: { @@ -167,6 +193,12 @@ export const auditbeatWithAllResults: PatternRollup = { 'auditbeat-custom-empty-index-1': { uuid: 'Iz5FJjsLQla34mD6kBAQBw', health: 'yellow', + primaries: { + store: { + size_in_bytes: 247, + reserved_in_bytes: 0, + }, + }, status: 'open', total: { docs: { @@ -178,6 +210,12 @@ export const auditbeatWithAllResults: PatternRollup = { 'auditbeat-custom-index-1': { uuid: 'xJvgb2QCQPSjlr7UnW8tFA', health: 'yellow', + primaries: { + store: { + size_in_bytes: 28409, + reserved_in_bytes: 0, + }, + }, status: 'open', total: { docs: { diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/pattern_rollup/mock_packetbeat_pattern_rollup.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/pattern_rollup/mock_packetbeat_pattern_rollup.ts index 2b39901b9c954..f26ef180a3641 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/pattern_rollup/mock_packetbeat_pattern_rollup.ts +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/pattern_rollup/mock_packetbeat_pattern_rollup.ts @@ -43,10 +43,17 @@ export const packetbeatNoResults: PatternRollup = { indices: 2, pattern: 'packetbeat-*', results: undefined, + sizeInBytes: 1096520898, stats: { '.ds-packetbeat-8.6.1-2023.02.04-000001': { uuid: 'x5Uuw4j4QM2YidHLNixCwg', health: 'yellow', + primaries: { + store: { + size_in_bytes: 512194751, + reserved_in_bytes: 0, + }, + }, status: 'open', total: { docs: { @@ -58,6 +65,12 @@ export const packetbeatNoResults: PatternRollup = { '.ds-packetbeat-8.5.3-2023.02.04-000001': { uuid: 'we0vNWm2Q6iz6uHubyHS6Q', health: 'yellow', + primaries: { + store: { + size_in_bytes: 584326147, + reserved_in_bytes: 0, + }, + }, status: 'open', total: { docs: { @@ -127,27 +140,650 @@ export const packetbeatWithSomeErrors: PatternRollup = { pattern: 'packetbeat-*', }, }, + sizeInBytes: 1096520898, + stats: { + '.ds-packetbeat-8.6.1-2023.02.04-000001': { + uuid: 'x5Uuw4j4QM2YidHLNixCwg', + health: 'yellow', + primaries: { + store: { + size_in_bytes: 512194751, + reserved_in_bytes: 0, + }, + }, + status: 'open', + total: { + docs: { + count: 1628343, + deleted: 0, + }, + }, + }, + '.ds-packetbeat-8.5.3-2023.02.04-000001': { + uuid: 'we0vNWm2Q6iz6uHubyHS6Q', + health: 'yellow', + primaries: { + store: { + size_in_bytes: 584326147, + reserved_in_bytes: 0, + }, + }, + status: 'open', + total: { + docs: { + count: 1630289, + deleted: 0, + }, + }, + }, + }, +}; + +export const mockPacketbeatPatternRollup: PatternRollup = { + docsCount: 3258632, + error: null, + ilmExplain: { + '.ds-packetbeat-8.6.1-2023.02.04-000001': { + index: '.ds-packetbeat-8.6.1-2023.02.04-000001', + managed: true, + policy: 'packetbeat', + index_creation_date_millis: 1675536751379, + time_since_index_creation: '25.26d', + lifecycle_date_millis: 1675536751379, + age: '25.26d', + phase: 'hot', + phase_time_millis: 1675536751809, + action: 'rollover', + action_time_millis: 1675536751809, + step: 'check-rollover-ready', + step_time_millis: 1675536751809, + phase_execution: { + policy: 'packetbeat', + version: 1, + modified_date_in_millis: 1675536751205, + }, + }, + '.ds-packetbeat-8.5.3-2023.02.04-000001': { + index: '.ds-packetbeat-8.5.3-2023.02.04-000001', + managed: true, + policy: 'packetbeat', + index_creation_date_millis: 1675536774084, + time_since_index_creation: '25.26d', + lifecycle_date_millis: 1675536774084, + age: '25.26d', + phase: 'hot', + phase_time_millis: 1675536774416, + action: 'rollover', + action_time_millis: 1675536774416, + step: 'check-rollover-ready', + step_time_millis: 1675536774416, + phase_execution: { + policy: 'packetbeat', + version: 1, + modified_date_in_millis: 1675536751205, + }, + }, + }, + ilmExplainPhaseCounts: { + hot: 2, + warm: 0, + cold: 0, + frozen: 0, + unmanaged: 0, + }, + indices: 2, + pattern: 'packetbeat-*', + results: undefined, + sizeInBytes: 1464758182, stats: { '.ds-packetbeat-8.6.1-2023.02.04-000001': { uuid: 'x5Uuw4j4QM2YidHLNixCwg', health: 'yellow', status: 'open', + primaries: { + docs: { + count: 1628343, + deleted: 0, + }, + shard_stats: { + total_count: 1, + }, + store: { + size_in_bytes: 731583142, + total_data_set_size_in_bytes: 731583142, + reserved_in_bytes: 0, + }, + indexing: { + index_total: 0, + index_time_in_millis: 0, + index_current: 0, + index_failed: 0, + delete_total: 0, + delete_time_in_millis: 0, + delete_current: 0, + noop_update_total: 0, + is_throttled: false, + throttle_time_in_millis: 0, + }, + get: { + total: 0, + time_in_millis: 0, + exists_total: 0, + exists_time_in_millis: 0, + missing_total: 0, + missing_time_in_millis: 0, + current: 0, + }, + search: { + open_contexts: 0, + query_total: 120726, + query_time_in_millis: 234865, + query_current: 0, + fetch_total: 109324, + fetch_time_in_millis: 500584, + fetch_current: 0, + scroll_total: 10432, + scroll_time_in_millis: 3874632, + scroll_current: 0, + suggest_total: 0, + suggest_time_in_millis: 0, + suggest_current: 0, + }, + merges: { + current: 0, + current_docs: 0, + current_size_in_bytes: 0, + total: 0, + total_time_in_millis: 0, + total_docs: 0, + total_size_in_bytes: 0, + total_stopped_time_in_millis: 0, + total_throttled_time_in_millis: 0, + total_auto_throttle_in_bytes: 20971520, + }, + refresh: { + total: 2, + total_time_in_millis: 0, + external_total: 2, + external_total_time_in_millis: 1, + listeners: 0, + }, + flush: { + total: 1, + periodic: 1, + total_time_in_millis: 0, + }, + warmer: { + current: 0, + total: 1, + total_time_in_millis: 1, + }, + query_cache: { + memory_size_in_bytes: 8316098, + total_count: 34248343, + hit_count: 3138879, + miss_count: 31109464, + cache_size: 4585, + cache_count: 4585, + evictions: 0, + }, + fielddata: { + memory_size_in_bytes: 12424, + evictions: 0, + }, + completion: { + size_in_bytes: 0, + }, + segments: { + count: 19, + memory_in_bytes: 0, + terms_memory_in_bytes: 0, + stored_fields_memory_in_bytes: 0, + term_vectors_memory_in_bytes: 0, + norms_memory_in_bytes: 0, + points_memory_in_bytes: 0, + doc_values_memory_in_bytes: 0, + index_writer_memory_in_bytes: 0, + version_map_memory_in_bytes: 0, + fixed_bit_set_memory_in_bytes: 304, + max_unsafe_auto_id_timestamp: -1, + file_sizes: {}, + }, + translog: { + operations: 0, + size_in_bytes: 55, + uncommitted_operations: 0, + uncommitted_size_in_bytes: 55, + earliest_last_modified_age: 606298841, + }, + request_cache: { + memory_size_in_bytes: 89216, + evictions: 0, + hit_count: 704, + miss_count: 38, + }, + recovery: { + current_as_source: 0, + current_as_target: 0, + throttle_time_in_millis: 0, + }, + bulk: { + total_operations: 0, + total_time_in_millis: 0, + total_size_in_bytes: 0, + avg_time_in_millis: 0, + avg_size_in_bytes: 0, + }, + }, total: { docs: { count: 1628343, deleted: 0, }, + shard_stats: { + total_count: 1, + }, + store: { + size_in_bytes: 731583142, + total_data_set_size_in_bytes: 731583142, + reserved_in_bytes: 0, + }, + indexing: { + index_total: 0, + index_time_in_millis: 0, + index_current: 0, + index_failed: 0, + delete_total: 0, + delete_time_in_millis: 0, + delete_current: 0, + noop_update_total: 0, + is_throttled: false, + throttle_time_in_millis: 0, + }, + get: { + total: 0, + time_in_millis: 0, + exists_total: 0, + exists_time_in_millis: 0, + missing_total: 0, + missing_time_in_millis: 0, + current: 0, + }, + search: { + open_contexts: 0, + query_total: 120726, + query_time_in_millis: 234865, + query_current: 0, + fetch_total: 109324, + fetch_time_in_millis: 500584, + fetch_current: 0, + scroll_total: 10432, + scroll_time_in_millis: 3874632, + scroll_current: 0, + suggest_total: 0, + suggest_time_in_millis: 0, + suggest_current: 0, + }, + merges: { + current: 0, + current_docs: 0, + current_size_in_bytes: 0, + total: 0, + total_time_in_millis: 0, + total_docs: 0, + total_size_in_bytes: 0, + total_stopped_time_in_millis: 0, + total_throttled_time_in_millis: 0, + total_auto_throttle_in_bytes: 20971520, + }, + refresh: { + total: 2, + total_time_in_millis: 0, + external_total: 2, + external_total_time_in_millis: 1, + listeners: 0, + }, + flush: { + total: 1, + periodic: 1, + total_time_in_millis: 0, + }, + warmer: { + current: 0, + total: 1, + total_time_in_millis: 1, + }, + query_cache: { + memory_size_in_bytes: 8316098, + total_count: 34248343, + hit_count: 3138879, + miss_count: 31109464, + cache_size: 4585, + cache_count: 4585, + evictions: 0, + }, + fielddata: { + memory_size_in_bytes: 12424, + evictions: 0, + }, + completion: { + size_in_bytes: 0, + }, + segments: { + count: 19, + memory_in_bytes: 0, + terms_memory_in_bytes: 0, + stored_fields_memory_in_bytes: 0, + term_vectors_memory_in_bytes: 0, + norms_memory_in_bytes: 0, + points_memory_in_bytes: 0, + doc_values_memory_in_bytes: 0, + index_writer_memory_in_bytes: 0, + version_map_memory_in_bytes: 0, + fixed_bit_set_memory_in_bytes: 304, + max_unsafe_auto_id_timestamp: -1, + file_sizes: {}, + }, + translog: { + operations: 0, + size_in_bytes: 55, + uncommitted_operations: 0, + uncommitted_size_in_bytes: 55, + earliest_last_modified_age: 606298841, + }, + request_cache: { + memory_size_in_bytes: 89216, + evictions: 0, + hit_count: 704, + miss_count: 38, + }, + recovery: { + current_as_source: 0, + current_as_target: 0, + throttle_time_in_millis: 0, + }, + bulk: { + total_operations: 0, + total_time_in_millis: 0, + total_size_in_bytes: 0, + avg_time_in_millis: 0, + avg_size_in_bytes: 0, + }, }, }, '.ds-packetbeat-8.5.3-2023.02.04-000001': { uuid: 'we0vNWm2Q6iz6uHubyHS6Q', health: 'yellow', status: 'open', + primaries: { + docs: { + count: 1630289, + deleted: 0, + }, + shard_stats: { + total_count: 1, + }, + store: { + size_in_bytes: 733175040, + total_data_set_size_in_bytes: 733175040, + reserved_in_bytes: 0, + }, + indexing: { + index_total: 0, + index_time_in_millis: 0, + index_current: 0, + index_failed: 0, + delete_total: 0, + delete_time_in_millis: 0, + delete_current: 0, + noop_update_total: 0, + is_throttled: false, + throttle_time_in_millis: 0, + }, + get: { + total: 0, + time_in_millis: 0, + exists_total: 0, + exists_time_in_millis: 0, + missing_total: 0, + missing_time_in_millis: 0, + current: 0, + }, + search: { + open_contexts: 0, + query_total: 120726, + query_time_in_millis: 248138, + query_current: 0, + fetch_total: 109484, + fetch_time_in_millis: 500514, + fetch_current: 0, + scroll_total: 10432, + scroll_time_in_millis: 3871379, + scroll_current: 0, + suggest_total: 0, + suggest_time_in_millis: 0, + suggest_current: 0, + }, + merges: { + current: 0, + current_docs: 0, + current_size_in_bytes: 0, + total: 0, + total_time_in_millis: 0, + total_docs: 0, + total_size_in_bytes: 0, + total_stopped_time_in_millis: 0, + total_throttled_time_in_millis: 0, + total_auto_throttle_in_bytes: 20971520, + }, + refresh: { + total: 2, + total_time_in_millis: 0, + external_total: 2, + external_total_time_in_millis: 2, + listeners: 0, + }, + flush: { + total: 1, + periodic: 1, + total_time_in_millis: 0, + }, + warmer: { + current: 0, + total: 1, + total_time_in_millis: 1, + }, + query_cache: { + memory_size_in_bytes: 5387543, + total_count: 24212135, + hit_count: 2223357, + miss_count: 21988778, + cache_size: 3275, + cache_count: 3275, + evictions: 0, + }, + fielddata: { + memory_size_in_bytes: 12336, + evictions: 0, + }, + completion: { + size_in_bytes: 0, + }, + segments: { + count: 20, + memory_in_bytes: 0, + terms_memory_in_bytes: 0, + stored_fields_memory_in_bytes: 0, + term_vectors_memory_in_bytes: 0, + norms_memory_in_bytes: 0, + points_memory_in_bytes: 0, + doc_values_memory_in_bytes: 0, + index_writer_memory_in_bytes: 0, + version_map_memory_in_bytes: 0, + fixed_bit_set_memory_in_bytes: 320, + max_unsafe_auto_id_timestamp: -1, + file_sizes: {}, + }, + translog: { + operations: 0, + size_in_bytes: 55, + uncommitted_operations: 0, + uncommitted_size_in_bytes: 55, + earliest_last_modified_age: 606298805, + }, + request_cache: { + memory_size_in_bytes: 89320, + evictions: 0, + hit_count: 704, + miss_count: 38, + }, + recovery: { + current_as_source: 0, + current_as_target: 0, + throttle_time_in_millis: 0, + }, + bulk: { + total_operations: 0, + total_time_in_millis: 0, + total_size_in_bytes: 0, + avg_time_in_millis: 0, + avg_size_in_bytes: 0, + }, + }, total: { docs: { count: 1630289, deleted: 0, }, + shard_stats: { + total_count: 1, + }, + store: { + size_in_bytes: 733175040, + total_data_set_size_in_bytes: 733175040, + reserved_in_bytes: 0, + }, + indexing: { + index_total: 0, + index_time_in_millis: 0, + index_current: 0, + index_failed: 0, + delete_total: 0, + delete_time_in_millis: 0, + delete_current: 0, + noop_update_total: 0, + is_throttled: false, + throttle_time_in_millis: 0, + }, + get: { + total: 0, + time_in_millis: 0, + exists_total: 0, + exists_time_in_millis: 0, + missing_total: 0, + missing_time_in_millis: 0, + current: 0, + }, + search: { + open_contexts: 0, + query_total: 120726, + query_time_in_millis: 248138, + query_current: 0, + fetch_total: 109484, + fetch_time_in_millis: 500514, + fetch_current: 0, + scroll_total: 10432, + scroll_time_in_millis: 3871379, + scroll_current: 0, + suggest_total: 0, + suggest_time_in_millis: 0, + suggest_current: 0, + }, + merges: { + current: 0, + current_docs: 0, + current_size_in_bytes: 0, + total: 0, + total_time_in_millis: 0, + total_docs: 0, + total_size_in_bytes: 0, + total_stopped_time_in_millis: 0, + total_throttled_time_in_millis: 0, + total_auto_throttle_in_bytes: 20971520, + }, + refresh: { + total: 2, + total_time_in_millis: 0, + external_total: 2, + external_total_time_in_millis: 2, + listeners: 0, + }, + flush: { + total: 1, + periodic: 1, + total_time_in_millis: 0, + }, + warmer: { + current: 0, + total: 1, + total_time_in_millis: 1, + }, + query_cache: { + memory_size_in_bytes: 5387543, + total_count: 24212135, + hit_count: 2223357, + miss_count: 21988778, + cache_size: 3275, + cache_count: 3275, + evictions: 0, + }, + fielddata: { + memory_size_in_bytes: 12336, + evictions: 0, + }, + completion: { + size_in_bytes: 0, + }, + segments: { + count: 20, + memory_in_bytes: 0, + terms_memory_in_bytes: 0, + stored_fields_memory_in_bytes: 0, + term_vectors_memory_in_bytes: 0, + norms_memory_in_bytes: 0, + points_memory_in_bytes: 0, + doc_values_memory_in_bytes: 0, + index_writer_memory_in_bytes: 0, + version_map_memory_in_bytes: 0, + fixed_bit_set_memory_in_bytes: 320, + max_unsafe_auto_id_timestamp: -1, + file_sizes: {}, + }, + translog: { + operations: 0, + size_in_bytes: 55, + uncommitted_operations: 0, + uncommitted_size_in_bytes: 55, + earliest_last_modified_age: 606298805, + }, + request_cache: { + memory_size_in_bytes: 89320, + evictions: 0, + hit_count: 704, + miss_count: 38, + }, + recovery: { + current_as_source: 0, + current_as_target: 0, + throttle_time_in_millis: 0, + }, + bulk: { + total_operations: 0, + total_time_in_millis: 0, + total_size_in_bytes: 0, + avg_time_in_millis: 0, + avg_size_in_bytes: 0, + }, }, }, }, diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/stats/mock_stats.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/stats/mock_stats.tsx index 0362dcd70a53f..14465e815ad47 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/stats/mock_stats.tsx +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/stats/mock_stats.tsx @@ -558,4 +558,279 @@ export const mockStats: Record = { }, }, }, + 'auditbeat-custom-index-1': { + uuid: 'uyJDDqGrRQqdBTN0mCF-iw', + health: 'yellow', + status: 'open', + primaries: { + docs: { + count: 4, + deleted: 0, + }, + shard_stats: { + total_count: 1, + }, + store: { + size_in_bytes: 28413, + total_data_set_size_in_bytes: 28413, + reserved_in_bytes: 0, + }, + indexing: { + index_total: 0, + index_time_in_millis: 0, + index_current: 0, + index_failed: 0, + delete_total: 0, + delete_time_in_millis: 0, + delete_current: 0, + noop_update_total: 0, + is_throttled: false, + throttle_time_in_millis: 0, + }, + get: { + total: 0, + time_in_millis: 0, + exists_total: 0, + exists_time_in_millis: 0, + missing_total: 0, + missing_time_in_millis: 0, + current: 0, + }, + search: { + open_contexts: 0, + query_total: 24, + query_time_in_millis: 5, + query_current: 0, + fetch_total: 24, + fetch_time_in_millis: 0, + fetch_current: 0, + scroll_total: 0, + scroll_time_in_millis: 0, + scroll_current: 0, + suggest_total: 0, + suggest_time_in_millis: 0, + suggest_current: 0, + }, + merges: { + current: 0, + current_docs: 0, + current_size_in_bytes: 0, + total: 0, + total_time_in_millis: 0, + total_docs: 0, + total_size_in_bytes: 0, + total_stopped_time_in_millis: 0, + total_throttled_time_in_millis: 0, + total_auto_throttle_in_bytes: 20971520, + }, + refresh: { + total: 2, + total_time_in_millis: 0, + external_total: 2, + external_total_time_in_millis: 0, + listeners: 0, + }, + flush: { + total: 1, + periodic: 1, + total_time_in_millis: 0, + }, + warmer: { + current: 0, + total: 1, + total_time_in_millis: 0, + }, + query_cache: { + memory_size_in_bytes: 58, + total_count: 0, + hit_count: 0, + miss_count: 0, + cache_size: 0, + cache_count: 0, + evictions: 0, + }, + fielddata: { + memory_size_in_bytes: 608, + evictions: 0, + }, + completion: { + size_in_bytes: 0, + }, + segments: { + count: 4, + memory_in_bytes: 0, + terms_memory_in_bytes: 0, + stored_fields_memory_in_bytes: 0, + term_vectors_memory_in_bytes: 0, + norms_memory_in_bytes: 0, + points_memory_in_bytes: 0, + doc_values_memory_in_bytes: 0, + index_writer_memory_in_bytes: 0, + version_map_memory_in_bytes: 0, + fixed_bit_set_memory_in_bytes: 0, + max_unsafe_auto_id_timestamp: -1, + file_sizes: {}, + }, + translog: { + operations: 0, + size_in_bytes: 55, + uncommitted_operations: 0, + uncommitted_size_in_bytes: 55, + earliest_last_modified_age: 79289897, + }, + request_cache: { + memory_size_in_bytes: 3760, + evictions: 0, + hit_count: 20, + miss_count: 4, + }, + recovery: { + current_as_source: 0, + current_as_target: 0, + throttle_time_in_millis: 0, + }, + bulk: { + total_operations: 0, + total_time_in_millis: 0, + total_size_in_bytes: 0, + avg_time_in_millis: 0, + avg_size_in_bytes: 0, + }, + }, + total: { + docs: { + count: 4, + deleted: 0, + }, + shard_stats: { + total_count: 1, + }, + store: { + size_in_bytes: 28413, + total_data_set_size_in_bytes: 28413, + reserved_in_bytes: 0, + }, + indexing: { + index_total: 0, + index_time_in_millis: 0, + index_current: 0, + index_failed: 0, + delete_total: 0, + delete_time_in_millis: 0, + delete_current: 0, + noop_update_total: 0, + is_throttled: false, + throttle_time_in_millis: 0, + }, + get: { + total: 0, + time_in_millis: 0, + exists_total: 0, + exists_time_in_millis: 0, + missing_total: 0, + missing_time_in_millis: 0, + current: 0, + }, + search: { + open_contexts: 0, + query_total: 24, + query_time_in_millis: 5, + query_current: 0, + fetch_total: 24, + fetch_time_in_millis: 0, + fetch_current: 0, + scroll_total: 0, + scroll_time_in_millis: 0, + scroll_current: 0, + suggest_total: 0, + suggest_time_in_millis: 0, + suggest_current: 0, + }, + merges: { + current: 0, + current_docs: 0, + current_size_in_bytes: 0, + total: 0, + total_time_in_millis: 0, + total_docs: 0, + total_size_in_bytes: 0, + total_stopped_time_in_millis: 0, + total_throttled_time_in_millis: 0, + total_auto_throttle_in_bytes: 20971520, + }, + refresh: { + total: 2, + total_time_in_millis: 0, + external_total: 2, + external_total_time_in_millis: 0, + listeners: 0, + }, + flush: { + total: 1, + periodic: 1, + total_time_in_millis: 0, + }, + warmer: { + current: 0, + total: 1, + total_time_in_millis: 0, + }, + query_cache: { + memory_size_in_bytes: 58, + total_count: 0, + hit_count: 0, + miss_count: 0, + cache_size: 0, + cache_count: 0, + evictions: 0, + }, + fielddata: { + memory_size_in_bytes: 608, + evictions: 0, + }, + completion: { + size_in_bytes: 0, + }, + segments: { + count: 4, + memory_in_bytes: 0, + terms_memory_in_bytes: 0, + stored_fields_memory_in_bytes: 0, + term_vectors_memory_in_bytes: 0, + norms_memory_in_bytes: 0, + points_memory_in_bytes: 0, + doc_values_memory_in_bytes: 0, + index_writer_memory_in_bytes: 0, + version_map_memory_in_bytes: 0, + fixed_bit_set_memory_in_bytes: 0, + max_unsafe_auto_id_timestamp: -1, + file_sizes: {}, + }, + translog: { + operations: 0, + size_in_bytes: 55, + uncommitted_operations: 0, + uncommitted_size_in_bytes: 55, + earliest_last_modified_age: 79289897, + }, + request_cache: { + memory_size_in_bytes: 3760, + evictions: 0, + hit_count: 20, + miss_count: 4, + }, + recovery: { + current_as_source: 0, + current_as_target: 0, + throttle_time_in_millis: 0, + }, + bulk: { + total_operations: 0, + total_time_in_millis: 0, + total_size_in_bytes: 0, + avg_time_in_millis: 0, + avg_size_in_bytes: 0, + }, + }, + }, }; diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/test_providers/index.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/test_providers/test_providers.tsx similarity index 100% rename from x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/test_providers/index.tsx rename to x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/test_providers/test_providers.tsx diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/unallowed_values/mock_unallowed_values.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/unallowed_values/mock_unallowed_values.ts new file mode 100644 index 0000000000000..393dd15ce9a20 --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/mock/unallowed_values/mock_unallowed_values.ts @@ -0,0 +1,122 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 mockUnallowedValuesResponse = [ + { + took: 1, + timed_out: false, + _shards: { + total: 1, + successful: 1, + skipped: 0, + failed: 0, + }, + hits: { + total: { + value: 3, + relation: 'eq', + }, + max_score: null, + hits: [], + }, + aggregations: { + 'event.category': { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'an_invalid_category', + doc_count: 2, + }, + { + key: 'theory', + doc_count: 1, + }, + ], + }, + }, + status: 200, + }, + { + took: 0, + timed_out: false, + _shards: { + total: 1, + successful: 1, + skipped: 0, + failed: 0, + }, + hits: { + total: { + value: 4, + relation: 'eq', + }, + max_score: null, + hits: [], + }, + aggregations: { + 'event.kind': { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [], + }, + }, + status: 200, + }, + { + took: 0, + timed_out: false, + _shards: { + total: 1, + successful: 1, + skipped: 0, + failed: 0, + }, + hits: { + total: { + value: 4, + relation: 'eq', + }, + max_score: null, + hits: [], + }, + aggregations: { + 'event.outcome': { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [], + }, + }, + status: 200, + }, + { + took: 0, + timed_out: false, + _shards: { + total: 1, + successful: 1, + skipped: 0, + failed: 0, + }, + hits: { + total: { + value: 4, + relation: 'eq', + }, + max_score: null, + hits: [], + }, + aggregations: { + 'event.type': { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [], + }, + }, + status: 200, + }, +]; diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/styles.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/styles.tsx index d54ea9d6316e2..6fbf130d01b8f 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/styles.tsx +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/styles.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiCode } from '@elastic/eui'; +import { EuiCode, EuiText } from '@elastic/eui'; import { euiThemeVars } from '@kbn/ui-theme'; import styled from 'styled-components'; @@ -21,3 +21,10 @@ export const CodeSuccess = styled(EuiCode)` export const CodeWarning = styled(EuiCode)` color: ${euiThemeVars.euiColorWarning}; `; + +export const FixedWidthLegendText = styled(EuiText)<{ + $width: number | undefined; +}>` + text-align: left; + ${({ $width }) => ($width != null ? `width: ${$width}px;` : '')} +`; diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/translations.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/translations.ts index 771faa301cee3..53bedadd9361d 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/translations.ts +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/translations.ts @@ -25,15 +25,6 @@ export const CHECKING = (index: string) => defaultMessage: 'Checking {index}', }); -export const COLLAPSE_BUTTON_LABEL = (collapsed: boolean) => - collapsed - ? i18n.translate('ecsDataQualityDashboard.collapseButtonLabelOpen', { - defaultMessage: 'Open', - }) - : i18n.translate('ecsDataQualityDashboard.collapseButtonLabelClosed', { - defaultMessage: 'Closed', - }); - export const COLD_DESCRIPTION = i18n.translate('ecsDataQualityDashboard.coldDescription', { defaultMessage: 'The index is no longer being updated and is queried infrequently. The information still needs to be searchable, but it’s okay if those queries are slower.', @@ -80,12 +71,6 @@ export const ECS_VERSION = i18n.translate('ecsDataQualityDashboard.ecsVersionSta defaultMessage: 'ECS version', }); -export const ERROR_LOADING_ECS_METADATA = (details: string) => - i18n.translate('ecsDataQualityDashboard.errorLoadingEcsMetadataLabel', { - values: { details }, - defaultMessage: 'Error loading ECS metadata: {details}', - }); - export const ERROR_LOADING_ECS_METADATA_TITLE = i18n.translate( 'ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingEcsMetadataTitle', { @@ -93,12 +78,6 @@ export const ERROR_LOADING_ECS_METADATA_TITLE = i18n.translate( } ); -export const ERROR_LOADING_ECS_VERSION = (details: string) => - i18n.translate('ecsDataQualityDashboard.errorLoadingEcsVersionLabel', { - values: { details }, - defaultMessage: 'Error loading ECS version: {details}', - }); - export const ERROR_LOADING_ECS_VERSION_TITLE = i18n.translate( 'ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingEcsVersionTitle', { @@ -214,6 +193,10 @@ export const SELECT_ONE_OR_MORE_ILM_PHASES: string = i18n.translate( } ); +export const INDEX_SIZE_TOOLTIP = i18n.translate('ecsDataQualityDashboard.indexSizeTooltip', { + defaultMessage: 'The size of the primary index (does not include replicas)', +}); + export const TECHNICAL_PREVIEW = i18n.translate('ecsDataQualityDashboard.technicalPreviewBadge', { defaultMessage: 'Technical preview', }); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/types.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/types.ts index 9edc76bbe6220..d51e908bd7b38 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/types.ts +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/types.ts @@ -10,6 +10,7 @@ import type { IndicesGetMappingIndexMappingRecord, IndicesStatsIndicesStats, } from '@elastic/elasticsearch/lib/api/types'; +import type { Direction } from '@elastic/eui'; export interface Mappings { pattern: string; @@ -113,6 +114,7 @@ export interface PatternRollup { indices: number | undefined; pattern: string; results: Record | undefined; + sizeInBytes: number | undefined; stats: Record | null; } @@ -139,6 +141,7 @@ export interface IndexToCheck { export type OnCheckCompleted = ({ error, + formatBytes, formatNumber, indexName, partitionedFieldMetadata, @@ -146,6 +149,7 @@ export type OnCheckCompleted = ({ version, }: { error: string | null; + formatBytes: (value: number | undefined) => string; formatNumber: (value: number | undefined) => string; indexName: string; partitionedFieldMetadata: PartitionedFieldMetadata | null; @@ -158,3 +162,15 @@ export interface ErrorSummary { indexName: string | null; pattern: string; } + +export interface SortConfig { + sort: { + direction: Direction; + field: string; + }; +} + +export interface SelectedIndex { + indexName: string; + pattern: string; +} diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_mappings/helpers.test.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_mappings/helpers.test.ts new file mode 100644 index 0000000000000..e1865c31c85df --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_mappings/helpers.test.ts @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { fetchMappings } from './helpers'; +import { mockMappingsResponse } from '../mock/mappings_response/mock_mappings_response'; + +describe('helpers', () => { + let originalFetch: typeof global['fetch']; + + beforeAll(() => { + originalFetch = global.fetch; + }); + + afterAll(() => { + global.fetch = originalFetch; + }); + + describe('fetchMappings', () => { + test('it returns the expected mappings', async () => { + const mockFetch = jest.fn().mockResolvedValue({ + ok: true, + json: () => Promise.resolve(mockMappingsResponse), + }); + global.fetch = mockFetch; + + const result = await fetchMappings({ + abortController: new AbortController(), + patternOrIndexName: 'auditbeat-custom-index-1', + }); + + expect(result).toEqual({ + 'auditbeat-custom-index-1': { + mappings: { + properties: { + '@timestamp': { type: 'date' }, + event: { properties: { category: { ignore_above: 1024, type: 'keyword' } } }, + host: { + properties: { + name: { + fields: { keyword: { ignore_above: 256, type: 'keyword' } }, + type: 'text', + }, + }, + }, + some: { + properties: { + field: { + fields: { keyword: { ignore_above: 256, type: 'keyword' } }, + type: 'text', + }, + }, + }, + source: { + properties: { + ip: { fields: { keyword: { ignore_above: 256, type: 'keyword' } }, type: 'text' }, + port: { type: 'long' }, + }, + }, + }, + }, + }, + }); + }); + + test('it throws the expected error when fetch fails', async () => { + const error = 'simulated error'; + const mockFetch = jest.fn().mockResolvedValue({ + ok: false, + statusText: error, + }); + + global.fetch = mockFetch; + + await expect( + fetchMappings({ + abortController: new AbortController(), + patternOrIndexName: 'auditbeat-custom-index-1', + }) + ).rejects.toThrowError( + 'Error loading mappings for auditbeat-custom-index-1: simulated error' + ); + }); + }); +}); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_results_rollup/helpers.test.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_results_rollup/helpers.test.ts new file mode 100644 index 0000000000000..f25903adff823 --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_results_rollup/helpers.test.ts @@ -0,0 +1,511 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 numeral from '@elastic/numeral'; + +import { + getTotalDocsCount, + getTotalIncompatible, + getTotalIndices, + getTotalIndicesChecked, + onPatternRollupUpdated, + updateResultOnCheckCompleted, +} from './helpers'; +import { auditbeatWithAllResults } from '../mock/pattern_rollup/mock_auditbeat_pattern_rollup'; +import { + mockPacketbeatPatternRollup, + packetbeatNoResults, + packetbeatWithSomeErrors, +} from '../mock/pattern_rollup/mock_packetbeat_pattern_rollup'; +import { PatternRollup } from '../types'; +import { EMPTY_STAT } from '../helpers'; +import { IndicesStatsIndicesStats } from '@elastic/elasticsearch/lib/api/types'; +import { mockPartitionedFieldMetadata } from '../mock/partitioned_field_metadata/mock_partitioned_field_metadata'; + +const defaultBytesFormat = '0,0.[0]b'; +const formatBytes = (value: number | undefined) => + value != null ? numeral(value).format(defaultBytesFormat) : EMPTY_STAT; + +const defaultNumberFormat = '0,0.[000]'; +const formatNumber = (value: number | undefined) => + value != null ? numeral(value).format(defaultNumberFormat) : EMPTY_STAT; + +const patternRollups: Record = { + 'auditbeat-*': auditbeatWithAllResults, // indices: 3 + 'packetbeat-*': mockPacketbeatPatternRollup, // indices: 2 +}; + +describe('helpers', () => { + let originalFetch: typeof global['fetch']; + + beforeAll(() => { + originalFetch = global.fetch; + }); + + afterAll(() => { + global.fetch = originalFetch; + }); + + describe('getTotalIndices', () => { + test('it returns the expected total when ALL `PatternRollup`s have an `indices`', () => { + expect(getTotalIndices(patternRollups)).toEqual(5); + }); + + test('it returns undefined when only SOME of the `PatternRollup`s have an `indices`', () => { + const someIndicesAreUndefined: Record = { + 'auditbeat-*': { + ...auditbeatWithAllResults, + indices: undefined, // <-- + }, + 'packetbeat-*': mockPacketbeatPatternRollup, // indices: 2 + }; + + expect(getTotalIndices(someIndicesAreUndefined)).toBeUndefined(); + }); + }); + + describe('getTotalDocsCount', () => { + test('it returns the expected total when ALL `PatternRollup`s have a `docsCount`', () => { + expect(getTotalDocsCount(patternRollups)).toEqual( + Number(auditbeatWithAllResults.docsCount) + Number(mockPacketbeatPatternRollup.docsCount) + ); + }); + + test('it returns undefined when only SOME of the `PatternRollup`s have a `docsCount`', () => { + const someIndicesAreUndefined: Record = { + 'auditbeat-*': { + ...auditbeatWithAllResults, + docsCount: undefined, // <-- + }, + 'packetbeat-*': mockPacketbeatPatternRollup, + }; + + expect(getTotalDocsCount(someIndicesAreUndefined)).toBeUndefined(); + }); + }); + + describe('getTotalIncompatible', () => { + test('it returns the expected total when ALL `PatternRollup`s have `results`', () => { + expect(getTotalIncompatible(patternRollups)).toEqual(4); + }); + + test('it returns the expected total when only SOME of the `PatternRollup`s have `results`', () => { + const someResultsAreUndefined: Record = { + 'auditbeat-*': auditbeatWithAllResults, + 'packetbeat-*': packetbeatNoResults, // <-- results is undefined + }; + + expect(getTotalIncompatible(someResultsAreUndefined)).toEqual(4); + }); + + test('it returns undefined when NONE of the `PatternRollup`s have `results`', () => { + const someResultsAreUndefined: Record = { + 'packetbeat-*': packetbeatNoResults, // <-- results is undefined + }; + + expect(getTotalIncompatible(someResultsAreUndefined)).toBeUndefined(); + }); + }); + + describe('getTotalIndicesChecked', () => { + test('it returns the expected total', () => { + expect(getTotalIndicesChecked(patternRollups)).toEqual(3); + }); + + test('it returns the expected total when errors have occurred', () => { + const someErrors: Record = { + 'auditbeat-*': auditbeatWithAllResults, // indices: 3 + 'packetbeat-*': packetbeatWithSomeErrors, // <-- indices: 2, but one has errors + }; + + expect(getTotalIndicesChecked(someErrors)).toEqual(4); + }); + }); + + describe('onPatternRollupUpdated', () => { + test('it returns a new collection with the updated rollup', () => { + const before: Record = { + 'auditbeat-*': auditbeatWithAllResults, + }; + + expect( + onPatternRollupUpdated({ + patternRollup: mockPacketbeatPatternRollup, + patternRollups: before, + }) + ).toEqual(patternRollups); + }); + }); + + describe('updateResultOnCheckCompleted', () => { + const packetbeatStats861: IndicesStatsIndicesStats = + mockPacketbeatPatternRollup.stats != null + ? mockPacketbeatPatternRollup.stats['.ds-packetbeat-8.6.1-2023.02.04-000001'] + : {}; + const packetbeatStats853: IndicesStatsIndicesStats = + mockPacketbeatPatternRollup.stats != null + ? mockPacketbeatPatternRollup.stats['.ds-packetbeat-8.5.3-2023.02.04-000001'] + : {}; + + test('it returns the updated rollups', () => { + expect( + updateResultOnCheckCompleted({ + error: null, + formatBytes, + formatNumber, + indexName: '.ds-packetbeat-8.6.1-2023.02.04-000001', + partitionedFieldMetadata: mockPartitionedFieldMetadata, + pattern: 'packetbeat-*', + patternRollups: { + 'packetbeat-*': mockPacketbeatPatternRollup, + }, + }) + ).toEqual({ + 'packetbeat-*': { + docsCount: 3258632, + error: null, + ilmExplain: { + '.ds-packetbeat-8.6.1-2023.02.04-000001': { + index: '.ds-packetbeat-8.6.1-2023.02.04-000001', + managed: true, + policy: 'packetbeat', + index_creation_date_millis: 1675536751379, + time_since_index_creation: '25.26d', + lifecycle_date_millis: 1675536751379, + age: '25.26d', + phase: 'hot', + phase_time_millis: 1675536751809, + action: 'rollover', + action_time_millis: 1675536751809, + step: 'check-rollover-ready', + step_time_millis: 1675536751809, + phase_execution: { + policy: 'packetbeat', + version: 1, + modified_date_in_millis: 1675536751205, + }, + }, + '.ds-packetbeat-8.5.3-2023.02.04-000001': { + index: '.ds-packetbeat-8.5.3-2023.02.04-000001', + managed: true, + policy: 'packetbeat', + index_creation_date_millis: 1675536774084, + time_since_index_creation: '25.26d', + lifecycle_date_millis: 1675536774084, + age: '25.26d', + phase: 'hot', + phase_time_millis: 1675536774416, + action: 'rollover', + action_time_millis: 1675536774416, + step: 'check-rollover-ready', + step_time_millis: 1675536774416, + phase_execution: { + policy: 'packetbeat', + version: 1, + modified_date_in_millis: 1675536751205, + }, + }, + }, + ilmExplainPhaseCounts: { + hot: 2, + warm: 0, + cold: 0, + frozen: 0, + unmanaged: 0, + }, + indices: 2, + pattern: 'packetbeat-*', + results: { + '.ds-packetbeat-8.6.1-2023.02.04-000001': { + docsCount: 1628343, + error: null, + ilmPhase: 'hot', + incompatible: 3, + indexName: '.ds-packetbeat-8.6.1-2023.02.04-000001', + markdownComments: [ + '### .ds-packetbeat-8.6.1-2023.02.04-000001\n', + '| Result | Index | Docs | Incompatible fields | ILM Phase | Size |\n|--------|-------|------|---------------------|-----------|------|\n| ❌ | .ds-packetbeat-8.6.1-2023.02.04-000001 | 1,628,343 (50.0%) | 3 | `hot` | 697.7MB |\n\n', + '### **Incompatible fields** `3` **Custom fields** `4` **ECS compliant fields** `2` **All fields** `9`\n', + "#### 3 incompatible fields, 0 fields with mappings in the same family\n\nFields are incompatible with ECS when index mappings, or the values of the fields in the index, don't conform to the Elastic Common Schema (ECS), version 8.6.1.\n\nIncompatible fields with mappings in the same family have exactly the same search behavior but may have different space usage or performance characteristics.\n\nWhen an incompatible field is not in the same family:\n❌ Detection engine rules referencing these fields may not match them correctly\n❌ Pages may not display some events or fields due to unexpected field mappings or values\n❌ Mappings or field values that don't comply with ECS are not supported\n", + '\n#### Incompatible field mappings - .ds-packetbeat-8.6.1-2023.02.04-000001\n\n\n| Field | ECS mapping type (expected) | Index mapping type (actual) | \n|-------|-----------------------------|-----------------------------|\n| host.name | `keyword` | `text` |\n| source.ip | `ip` | `text` |\n\n#### Incompatible field values - .ds-packetbeat-8.6.1-2023.02.04-000001\n\n\n| Field | ECS values (expected) | Document values (actual) | \n|-------|-----------------------|--------------------------|\n| event.category | `authentication`, `configuration`, `database`, `driver`, `email`, `file`, `host`, `iam`, `intrusion_detection`, `malware`, `network`, `package`, `process`, `registry`, `session`, `threat`, `vulnerability`, `web` | `an_invalid_category` (2), `theory` (1) |\n\n', + ], + pattern: 'packetbeat-*', + }, + }, + sizeInBytes: 1464758182, + stats: { + '.ds-packetbeat-8.6.1-2023.02.04-000001': packetbeatStats861, + '.ds-packetbeat-8.5.3-2023.02.04-000001': packetbeatStats853, + }, + }, + }); + }); + + test('it returns the expected results when `patternRollup` does NOT have a `docsCount`', () => { + const noDocsCount = { + ...mockPacketbeatPatternRollup, + docsCount: undefined, // <-- + }; + + expect( + updateResultOnCheckCompleted({ + error: null, + formatBytes, + formatNumber, + indexName: '.ds-packetbeat-8.6.1-2023.02.04-000001', + partitionedFieldMetadata: mockPartitionedFieldMetadata, + pattern: 'packetbeat-*', + patternRollups: { + 'packetbeat-*': noDocsCount, + }, + }) + ).toEqual({ + 'packetbeat-*': { + docsCount: undefined, // <-- + error: null, + ilmExplain: { + '.ds-packetbeat-8.6.1-2023.02.04-000001': { + index: '.ds-packetbeat-8.6.1-2023.02.04-000001', + managed: true, + policy: 'packetbeat', + index_creation_date_millis: 1675536751379, + time_since_index_creation: '25.26d', + lifecycle_date_millis: 1675536751379, + age: '25.26d', + phase: 'hot', + phase_time_millis: 1675536751809, + action: 'rollover', + action_time_millis: 1675536751809, + step: 'check-rollover-ready', + step_time_millis: 1675536751809, + phase_execution: { + policy: 'packetbeat', + version: 1, + modified_date_in_millis: 1675536751205, + }, + }, + '.ds-packetbeat-8.5.3-2023.02.04-000001': { + index: '.ds-packetbeat-8.5.3-2023.02.04-000001', + managed: true, + policy: 'packetbeat', + index_creation_date_millis: 1675536774084, + time_since_index_creation: '25.26d', + lifecycle_date_millis: 1675536774084, + age: '25.26d', + phase: 'hot', + phase_time_millis: 1675536774416, + action: 'rollover', + action_time_millis: 1675536774416, + step: 'check-rollover-ready', + step_time_millis: 1675536774416, + phase_execution: { + policy: 'packetbeat', + version: 1, + modified_date_in_millis: 1675536751205, + }, + }, + }, + ilmExplainPhaseCounts: { + hot: 2, + warm: 0, + cold: 0, + frozen: 0, + unmanaged: 0, + }, + indices: 2, + pattern: 'packetbeat-*', + results: { + '.ds-packetbeat-8.6.1-2023.02.04-000001': { + docsCount: 1628343, + error: null, + ilmPhase: 'hot', + incompatible: 3, + indexName: '.ds-packetbeat-8.6.1-2023.02.04-000001', + markdownComments: [ + '### .ds-packetbeat-8.6.1-2023.02.04-000001\n', + '| Result | Index | Docs | Incompatible fields | ILM Phase | Size |\n|--------|-------|------|---------------------|-----------|------|\n| ❌ | .ds-packetbeat-8.6.1-2023.02.04-000001 | 1,628,343 () | 3 | `hot` | 697.7MB |\n\n', + '### **Incompatible fields** `3` **Custom fields** `4` **ECS compliant fields** `2` **All fields** `9`\n', + "#### 3 incompatible fields, 0 fields with mappings in the same family\n\nFields are incompatible with ECS when index mappings, or the values of the fields in the index, don't conform to the Elastic Common Schema (ECS), version 8.6.1.\n\nIncompatible fields with mappings in the same family have exactly the same search behavior but may have different space usage or performance characteristics.\n\nWhen an incompatible field is not in the same family:\n❌ Detection engine rules referencing these fields may not match them correctly\n❌ Pages may not display some events or fields due to unexpected field mappings or values\n❌ Mappings or field values that don't comply with ECS are not supported\n", + '\n#### Incompatible field mappings - .ds-packetbeat-8.6.1-2023.02.04-000001\n\n\n| Field | ECS mapping type (expected) | Index mapping type (actual) | \n|-------|-----------------------------|-----------------------------|\n| host.name | `keyword` | `text` |\n| source.ip | `ip` | `text` |\n\n#### Incompatible field values - .ds-packetbeat-8.6.1-2023.02.04-000001\n\n\n| Field | ECS values (expected) | Document values (actual) | \n|-------|-----------------------|--------------------------|\n| event.category | `authentication`, `configuration`, `database`, `driver`, `email`, `file`, `host`, `iam`, `intrusion_detection`, `malware`, `network`, `package`, `process`, `registry`, `session`, `threat`, `vulnerability`, `web` | `an_invalid_category` (2), `theory` (1) |\n\n', + ], + pattern: 'packetbeat-*', + }, + }, + sizeInBytes: 1464758182, + stats: { + '.ds-packetbeat-8.6.1-2023.02.04-000001': packetbeatStats861, + '.ds-packetbeat-8.5.3-2023.02.04-000001': packetbeatStats853, + }, + }, + }); + }); + + test('it returns the expected results when `partitionedFieldMetadata` is null', () => { + expect( + updateResultOnCheckCompleted({ + error: null, + formatBytes, + formatNumber, + indexName: '.ds-packetbeat-8.6.1-2023.02.04-000001', + partitionedFieldMetadata: null, // <-- + pattern: 'packetbeat-*', + patternRollups: { + 'packetbeat-*': mockPacketbeatPatternRollup, + }, + }) + ).toEqual({ + 'packetbeat-*': { + docsCount: 3258632, + error: null, + ilmExplain: { + '.ds-packetbeat-8.6.1-2023.02.04-000001': { + index: '.ds-packetbeat-8.6.1-2023.02.04-000001', + managed: true, + policy: 'packetbeat', + index_creation_date_millis: 1675536751379, + time_since_index_creation: '25.26d', + lifecycle_date_millis: 1675536751379, + age: '25.26d', + phase: 'hot', + phase_time_millis: 1675536751809, + action: 'rollover', + action_time_millis: 1675536751809, + step: 'check-rollover-ready', + step_time_millis: 1675536751809, + phase_execution: { + policy: 'packetbeat', + version: 1, + modified_date_in_millis: 1675536751205, + }, + }, + '.ds-packetbeat-8.5.3-2023.02.04-000001': { + index: '.ds-packetbeat-8.5.3-2023.02.04-000001', + managed: true, + policy: 'packetbeat', + index_creation_date_millis: 1675536774084, + time_since_index_creation: '25.26d', + lifecycle_date_millis: 1675536774084, + age: '25.26d', + phase: 'hot', + phase_time_millis: 1675536774416, + action: 'rollover', + action_time_millis: 1675536774416, + step: 'check-rollover-ready', + step_time_millis: 1675536774416, + phase_execution: { + policy: 'packetbeat', + version: 1, + modified_date_in_millis: 1675536751205, + }, + }, + }, + ilmExplainPhaseCounts: { + hot: 2, + warm: 0, + cold: 0, + frozen: 0, + unmanaged: 0, + }, + indices: 2, + pattern: 'packetbeat-*', + results: { + '.ds-packetbeat-8.6.1-2023.02.04-000001': { + docsCount: 1628343, + error: null, + ilmPhase: 'hot', + incompatible: undefined, + indexName: '.ds-packetbeat-8.6.1-2023.02.04-000001', + markdownComments: [], + pattern: 'packetbeat-*', + }, + }, + sizeInBytes: 1464758182, + stats: { + '.ds-packetbeat-8.6.1-2023.02.04-000001': packetbeatStats861, + '.ds-packetbeat-8.5.3-2023.02.04-000001': packetbeatStats853, + }, + }, + }); + }); + + test('it returns the updated rollups when there is no `partitionedFieldMetadata`', () => { + const noIlmExplain = { + ...mockPacketbeatPatternRollup, + ilmExplain: null, + }; + + expect( + updateResultOnCheckCompleted({ + error: null, + formatBytes, + formatNumber, + indexName: '.ds-packetbeat-8.6.1-2023.02.04-000001', + partitionedFieldMetadata: mockPartitionedFieldMetadata, + pattern: 'packetbeat-*', + patternRollups: { + 'packetbeat-*': noIlmExplain, + }, + }) + ).toEqual({ + 'packetbeat-*': { + docsCount: 3258632, + error: null, + ilmExplain: null, + ilmExplainPhaseCounts: { + hot: 2, + warm: 0, + cold: 0, + frozen: 0, + unmanaged: 0, + }, + indices: 2, + pattern: 'packetbeat-*', + results: { + '.ds-packetbeat-8.6.1-2023.02.04-000001': { + docsCount: 1628343, + error: null, + ilmPhase: undefined, + incompatible: 3, + indexName: '.ds-packetbeat-8.6.1-2023.02.04-000001', + markdownComments: [ + '### .ds-packetbeat-8.6.1-2023.02.04-000001\n', + '| Result | Index | Docs | Incompatible fields | ILM Phase | Size |\n|--------|-------|------|---------------------|-----------|------|\n| ❌ | .ds-packetbeat-8.6.1-2023.02.04-000001 | 1,628,343 (50.0%) | 3 | -- | 697.7MB |\n\n', + '### **Incompatible fields** `3` **Custom fields** `4` **ECS compliant fields** `2` **All fields** `9`\n', + "#### 3 incompatible fields, 0 fields with mappings in the same family\n\nFields are incompatible with ECS when index mappings, or the values of the fields in the index, don't conform to the Elastic Common Schema (ECS), version 8.6.1.\n\nIncompatible fields with mappings in the same family have exactly the same search behavior but may have different space usage or performance characteristics.\n\nWhen an incompatible field is not in the same family:\n❌ Detection engine rules referencing these fields may not match them correctly\n❌ Pages may not display some events or fields due to unexpected field mappings or values\n❌ Mappings or field values that don't comply with ECS are not supported\n", + '\n#### Incompatible field mappings - .ds-packetbeat-8.6.1-2023.02.04-000001\n\n\n| Field | ECS mapping type (expected) | Index mapping type (actual) | \n|-------|-----------------------------|-----------------------------|\n| host.name | `keyword` | `text` |\n| source.ip | `ip` | `text` |\n\n#### Incompatible field values - .ds-packetbeat-8.6.1-2023.02.04-000001\n\n\n| Field | ECS values (expected) | Document values (actual) | \n|-------|-----------------------|--------------------------|\n| event.category | `authentication`, `configuration`, `database`, `driver`, `email`, `file`, `host`, `iam`, `intrusion_detection`, `malware`, `network`, `package`, `process`, `registry`, `session`, `threat`, `vulnerability`, `web` | `an_invalid_category` (2), `theory` (1) |\n\n', + ], + pattern: 'packetbeat-*', + }, + }, + sizeInBytes: 1464758182, + stats: { + '.ds-packetbeat-8.6.1-2023.02.04-000001': packetbeatStats861, + '.ds-packetbeat-8.5.3-2023.02.04-000001': packetbeatStats853, + }, + }, + }); + }); + + test('it returns the unmodified rollups when `pattern` is not a member of `patternRollups`', () => { + const shouldNotBeModified: Record = { + 'packetbeat-*': mockPacketbeatPatternRollup, + }; + + expect( + updateResultOnCheckCompleted({ + error: null, + formatBytes, + formatNumber, + indexName: '.ds-packetbeat-8.6.1-2023.02.04-000001', + partitionedFieldMetadata: mockPartitionedFieldMetadata, + pattern: 'this-pattern-is-not-in-pattern-rollups', // <-- + patternRollups: shouldNotBeModified, + }) + ).toEqual(shouldNotBeModified); + }); + }); +}); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_results_rollup/helpers.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_results_rollup/helpers.ts index 995ac8eac86c4..dbad0364904f5 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_results_rollup/helpers.ts +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_results_rollup/helpers.ts @@ -8,7 +8,11 @@ import { getIndexDocsCountFromRollup } from '../data_quality_panel/data_quality_summary/summary_actions/check_all/helpers'; import { getIlmPhase } from '../data_quality_panel/pattern/helpers'; import { getAllIncompatibleMarkdownComments } from '../data_quality_panel/tabs/incompatible_tab/helpers'; -import { getTotalPatternIncompatible, getTotalPatternIndicesChecked } from '../helpers'; +import { + getSizeInBytes, + getTotalPatternIncompatible, + getTotalPatternIndicesChecked, +} from '../helpers'; import type { IlmPhase, PartitionedFieldMetadata, PatternRollup } from '../types'; export const getTotalIndices = ( @@ -19,7 +23,7 @@ export const getTotalIndices = ( // only return the total when all `PatternRollup`s have a `indices`: return allRollupsHaveIndices - ? allRollups.reduce((acc, { indices }) => acc + (indices ?? 0), 0) + ? allRollups.reduce((acc, { indices }) => acc + Number(indices), 0) : undefined; }; @@ -31,7 +35,21 @@ export const getTotalDocsCount = ( // only return the total when all `PatternRollup`s have a `docsCount`: return allRollupsHaveDocsCount - ? allRollups.reduce((acc, { docsCount }) => acc + (docsCount ?? 0), 0) + ? allRollups.reduce((acc, { docsCount }) => acc + Number(docsCount), 0) + : undefined; +}; + +export const getTotalSizeInBytes = ( + patternRollups: Record +): number | undefined => { + const allRollups = Object.values(patternRollups); + const allRollupsHaveSizeInBytes = allRollups.every(({ sizeInBytes }) => + Number.isInteger(sizeInBytes) + ); + + // only return the total when all `PatternRollup`s have a `sizeInBytes`: + return allRollupsHaveSizeInBytes + ? allRollups.reduce((acc, { sizeInBytes }) => acc + Number(sizeInBytes), 0) : undefined; }; @@ -69,6 +87,7 @@ export const onPatternRollupUpdated = ({ export const updateResultOnCheckCompleted = ({ error, + formatBytes, formatNumber, indexName, partitionedFieldMetadata, @@ -76,6 +95,7 @@ export const updateResultOnCheckCompleted = ({ patternRollups, }: { error: string | null; + formatBytes: (value: number | undefined) => string; formatNumber: (value: number | undefined) => string; indexName: string; partitionedFieldMetadata: PartitionedFieldMetadata | null; @@ -85,7 +105,7 @@ export const updateResultOnCheckCompleted = ({ const patternRollup: PatternRollup | undefined = patternRollups[pattern]; if (patternRollup != null) { - const ilmExplain = patternRollup.ilmExplain ?? null; + const ilmExplain = patternRollup.ilmExplain; const ilmPhase: IlmPhase | undefined = ilmExplain != null ? getIlmPhase(ilmExplain[indexName]) : undefined; @@ -97,15 +117,19 @@ export const updateResultOnCheckCompleted = ({ const patternDocsCount = patternRollup.docsCount ?? 0; + const sizeInBytes = getSizeInBytes({ indexName, stats: patternRollup.stats }); + const markdownComments = partitionedFieldMetadata != null ? getAllIncompatibleMarkdownComments({ docsCount, + formatBytes, formatNumber, ilmPhase, indexName, partitionedFieldMetadata, patternDocsCount, + sizeInBytes, }) : []; diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_results_rollup/index.tsx b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_results_rollup/index.tsx index da5745deaa3cd..1976b5e150de3 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_results_rollup/index.tsx +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_results_rollup/index.tsx @@ -17,6 +17,7 @@ import { getTotalIncompatible, getTotalIndices, getTotalIndicesChecked, + getTotalSizeInBytes, onPatternRollupUpdated, updateResultOnCheckCompleted, } from './helpers'; @@ -31,6 +32,7 @@ interface UseResultsRollup { totalIncompatible: number | undefined; totalIndices: number | undefined; totalIndicesChecked: number | undefined; + totalSizeInBytes: number | undefined; updatePatternIndexNames: ({ indexNames, pattern, @@ -58,6 +60,7 @@ export const useResultsRollup = ({ ilmPhases, patterns }: Props): UseResultsRoll () => getTotalIndicesChecked(patternRollups), [patternRollups] ); + const totalSizeInBytes = useMemo(() => getTotalSizeInBytes(patternRollups), [patternRollups]); const updatePatternIndexNames = useCallback( ({ indexNames, pattern }: { indexNames: string[]; pattern: string }) => { @@ -72,12 +75,14 @@ export const useResultsRollup = ({ ilmPhases, patterns }: Props): UseResultsRoll const onCheckCompleted: OnCheckCompleted = useCallback( ({ error, + formatBytes, formatNumber, indexName, partitionedFieldMetadata, pattern, }: { error: string | null; + formatBytes: (value: number | undefined) => string; formatNumber: (value: number | undefined) => string; indexName: string; partitionedFieldMetadata: PartitionedFieldMetadata | null; @@ -86,6 +91,7 @@ export const useResultsRollup = ({ ilmPhases, patterns }: Props): UseResultsRoll setPatternRollups((current) => updateResultOnCheckCompleted({ error, + formatBytes, formatNumber, indexName, partitionedFieldMetadata, @@ -111,6 +117,7 @@ export const useResultsRollup = ({ ilmPhases, patterns }: Props): UseResultsRoll totalIncompatible, totalIndices, totalIndicesChecked, + totalSizeInBytes, updatePatternIndexNames, updatePatternRollup, }; diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_unallowed_values/helpers.test.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_unallowed_values/helpers.test.ts new file mode 100644 index 0000000000000..2f80ba5e2cc7a --- /dev/null +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_unallowed_values/helpers.test.ts @@ -0,0 +1,503 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { omit } from 'lodash/fp'; + +import { + fetchUnallowedValues, + getUnallowedValueCount, + getUnallowedValues, + isBucket, +} from './helpers'; +import { mockUnallowedValuesResponse } from '../mock/unallowed_values/mock_unallowed_values'; +import { UnallowedValueRequestItem, UnallowedValueSearchResult } from '../types'; + +describe('helpers', () => { + let originalFetch: typeof global['fetch']; + + beforeAll(() => { + originalFetch = global.fetch; + }); + + afterAll(() => { + global.fetch = originalFetch; + }); + + describe('isBucket', () => { + test('it returns true when the bucket is valid', () => { + expect( + isBucket({ + key: 'stop', + doc_count: 2, + }) + ).toBe(true); + }); + + test('it returns false when just `key` is missing', () => { + expect( + isBucket({ + doc_count: 2, + }) + ).toBe(false); + }); + + test('it returns false when just `key` has an incorrect type', () => { + expect( + isBucket({ + key: 1234, // <-- should be a string + doc_count: 2, + }) + ).toBe(false); + }); + + test('it returns false when just `doc_count` is missing', () => { + expect( + isBucket({ + key: 'stop', + }) + ).toBe(false); + }); + + test('it returns false when just `doc_count` has an incorrect type', () => { + expect( + isBucket({ + key: 'stop', + doc_count: 'foo', // <-- should be a number + }) + ).toBe(false); + }); + + test('it returns false when both `key` and `doc_count` are missing', () => { + expect(isBucket({})).toBe(false); + }); + + test('it returns false when both `key` and `doc_count` have incorrect types', () => { + expect( + isBucket({ + key: 1234, // <-- should be a string + doc_count: 'foo', // <-- should be a number + }) + ).toBe(false); + }); + + test('it returns false when `maybeBucket` is undefined', () => { + expect(isBucket(undefined)).toBe(false); + }); + }); + + describe('getUnallowedValueCount', () => { + test('it returns the expected count', () => { + expect( + getUnallowedValueCount({ + key: 'stop', + doc_count: 2, + }) + ).toEqual({ count: 2, fieldName: 'stop' }); + }); + }); + + describe('getUnallowedValues', () => { + const requestItems: UnallowedValueRequestItem[] = [ + { + indexName: 'auditbeat-custom-index-1', + indexFieldName: 'event.category', + allowedValues: [ + 'authentication', + 'configuration', + 'database', + 'driver', + 'email', + 'file', + 'host', + 'iam', + 'intrusion_detection', + 'malware', + 'network', + 'package', + 'process', + 'registry', + 'session', + 'threat', + 'vulnerability', + 'web', + ], + }, + { + indexName: 'auditbeat-custom-index-1', + indexFieldName: 'event.kind', + allowedValues: [ + 'alert', + 'enrichment', + 'event', + 'metric', + 'state', + 'pipeline_error', + 'signal', + ], + }, + { + indexName: 'auditbeat-custom-index-1', + indexFieldName: 'event.outcome', + allowedValues: ['failure', 'success', 'unknown'], + }, + { + indexName: 'auditbeat-custom-index-1', + indexFieldName: 'event.type', + allowedValues: [ + 'access', + 'admin', + 'allowed', + 'change', + 'connection', + 'creation', + 'deletion', + 'denied', + 'end', + 'error', + 'group', + 'indicator', + 'info', + 'installation', + 'protocol', + 'start', + 'user', + ], + }, + ]; + + const searchResults: UnallowedValueSearchResult[] = [ + { + aggregations: { + 'event.category': { + buckets: [ + { + key: 'an_invalid_category', + doc_count: 2, + }, + { + key: 'theory', + doc_count: 1, + }, + ], + }, + }, + }, + { + aggregations: { + 'event.kind': { + buckets: [], + }, + }, + }, + { + aggregations: { + 'event.outcome': { + buckets: [], + }, + }, + }, + { + aggregations: { + 'event.type': { + buckets: [], + }, + }, + }, + ]; + + test('it returns the expected unallowed values', () => { + expect( + getUnallowedValues({ + requestItems, + searchResults, + }) + ).toEqual({ + 'event.category': [ + { count: 2, fieldName: 'an_invalid_category' }, + { count: 1, fieldName: 'theory' }, + ], + 'event.kind': [], + 'event.outcome': [], + 'event.type': [], + }); + }); + + test('it returns an empty index when `searchResults` is null', () => { + expect( + getUnallowedValues({ + requestItems, + searchResults: null, + }) + ).toEqual({}); + }); + + test('it returns an empty index when `searchResults` is not an array', () => { + expect( + getUnallowedValues({ + requestItems, + // @ts-expect-error + searchResults: 1234, + }) + ).toEqual({}); + }); + + test('it returns the expected results when `searchResults` does NOT have `aggregations`', () => { + const noAggregations: UnallowedValueSearchResult[] = searchResults.map((x) => + omit('aggregations', x) + ); + + expect( + getUnallowedValues({ + requestItems, + searchResults: noAggregations, + }) + ).toEqual({ + 'event.category': [], + 'event.kind': [], + 'event.outcome': [], + 'event.type': [], + }); + }); + + test('it returns the expected unallowed values when SOME buckets are invalid', () => { + const someInvalid: UnallowedValueSearchResult[] = [ + { + aggregations: { + 'event.category': { + buckets: [ + { + key: 'foo', + // @ts-expect-error + doc_count: 'this-is-an-invalid-bucket', // <-- invalid type, should be number + }, + { + key: 'bar', + doc_count: 1, + }, + ], + }, + }, + }, + { + aggregations: { + 'event.kind': { + buckets: [], + }, + }, + }, + { + aggregations: { + 'event.outcome': { + buckets: [], + }, + }, + }, + { + aggregations: { + 'event.type': { + buckets: [], + }, + }, + }, + ]; + + expect( + getUnallowedValues({ + requestItems, + searchResults: someInvalid, + }) + ).toEqual({ + 'event.category': [{ count: 1, fieldName: 'bar' }], + 'event.kind': [], + 'event.outcome': [], + 'event.type': [], + }); + }); + }); + + describe('fetchUnallowedValues', () => { + const requestItems: UnallowedValueRequestItem[] = [ + { + indexName: 'auditbeat-custom-index-1', + indexFieldName: 'event.category', + allowedValues: [ + 'authentication', + 'configuration', + 'database', + 'driver', + 'email', + 'file', + 'host', + 'iam', + 'intrusion_detection', + 'malware', + 'network', + 'package', + 'process', + 'registry', + 'session', + 'threat', + 'vulnerability', + 'web', + ], + }, + { + indexName: 'auditbeat-custom-index-1', + indexFieldName: 'event.kind', + allowedValues: [ + 'alert', + 'enrichment', + 'event', + 'metric', + 'state', + 'pipeline_error', + 'signal', + ], + }, + { + indexName: 'auditbeat-custom-index-1', + indexFieldName: 'event.outcome', + allowedValues: ['failure', 'success', 'unknown'], + }, + { + indexName: 'auditbeat-custom-index-1', + indexFieldName: 'event.type', + allowedValues: [ + 'access', + 'admin', + 'allowed', + 'change', + 'connection', + 'creation', + 'deletion', + 'denied', + 'end', + 'error', + 'group', + 'indicator', + 'info', + 'installation', + 'protocol', + 'start', + 'user', + ], + }, + ]; + + test('it includes the expected content in the `fetch` request', async () => { + const mockFetch = jest.fn().mockResolvedValue({ + ok: true, + json: () => Promise.resolve(mockUnallowedValuesResponse), + }); + global.fetch = mockFetch; + const abortController = new AbortController(); + + await fetchUnallowedValues({ + abortController, + indexName: 'auditbeat-custom-index-1', + requestItems, + }); + + expect(mockFetch).toBeCalledWith( + '/internal/ecs_data_quality_dashboard/unallowed_field_values', + { + body: JSON.stringify(requestItems), + headers: { 'Content-Type': 'application/json', 'kbn-xsrf': 'xsrf' }, + method: 'POST', + signal: abortController.signal, + } + ); + }); + + test('it returns the expected unallowed values', async () => { + const mockFetch = jest.fn().mockResolvedValue({ + ok: true, + json: () => Promise.resolve(mockUnallowedValuesResponse), + }); + global.fetch = mockFetch; + + const result = await fetchUnallowedValues({ + abortController: new AbortController(), + indexName: 'auditbeat-custom-index-1', + requestItems, + }); + + expect(result).toEqual([ + { + _shards: { failed: 0, skipped: 0, successful: 1, total: 1 }, + aggregations: { + 'event.category': { + buckets: [ + { doc_count: 2, key: 'an_invalid_category' }, + { doc_count: 1, key: 'theory' }, + ], + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + }, + }, + hits: { hits: [], max_score: null, total: { relation: 'eq', value: 3 } }, + status: 200, + timed_out: false, + took: 1, + }, + { + _shards: { failed: 0, skipped: 0, successful: 1, total: 1 }, + aggregations: { + 'event.kind': { buckets: [], doc_count_error_upper_bound: 0, sum_other_doc_count: 0 }, + }, + hits: { hits: [], max_score: null, total: { relation: 'eq', value: 4 } }, + status: 200, + timed_out: false, + took: 0, + }, + { + _shards: { failed: 0, skipped: 0, successful: 1, total: 1 }, + aggregations: { + 'event.outcome': { + buckets: [], + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + }, + }, + hits: { hits: [], max_score: null, total: { relation: 'eq', value: 4 } }, + status: 200, + timed_out: false, + took: 0, + }, + { + _shards: { failed: 0, skipped: 0, successful: 1, total: 1 }, + aggregations: { + 'event.type': { buckets: [], doc_count_error_upper_bound: 0, sum_other_doc_count: 0 }, + }, + hits: { hits: [], max_score: null, total: { relation: 'eq', value: 4 } }, + status: 200, + timed_out: false, + took: 0, + }, + ]); + }); + + test('it throws the expected error when fetch fails', async () => { + const error = 'simulated error'; + const mockFetch = jest.fn().mockResolvedValue({ + ok: false, + statusText: error, + }); + global.fetch = mockFetch; + + await expect( + fetchUnallowedValues({ + abortController: new AbortController(), + indexName: 'auditbeat-custom-index-1', + requestItems, + }) + ).rejects.toThrowError( + 'Error loading unallowed values for index auditbeat-custom-index-1: simulated error' + ); + }); + }); +}); diff --git a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_unallowed_values/helpers.ts b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_unallowed_values/helpers.ts index 1fa8991ce9264..e1ee93b72b283 100644 --- a/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_unallowed_values/helpers.ts +++ b/x-pack/packages/kbn-ecs-data-quality-dashboard/impl/data_quality/use_unallowed_values/helpers.ts @@ -16,6 +16,7 @@ import type { const UNALLOWED_VALUES_API_ROUTE = '/internal/ecs_data_quality_dashboard/unallowed_field_values'; export const isBucket = (maybeBucket: unknown): maybeBucket is Bucket => + maybeBucket != null && typeof (maybeBucket as Bucket).key === 'string' && typeof (maybeBucket as Bucket).doc_count === 'number'; diff --git a/x-pack/plugins/security_solution/public/overview/links.ts b/x-pack/plugins/security_solution/public/overview/links.ts index 2d75eee57bece..07cc2a491cf02 100644 --- a/x-pack/plugins/security_solution/public/overview/links.ts +++ b/x-pack/plugins/security_solution/public/overview/links.ts @@ -103,7 +103,6 @@ export const ecsDataQualityDashboardLinks: LinkItem = { ), path: DATA_QUALITY_PATH, capabilities: [`${SERVER_APP_ID}.show`], - isBeta: true, globalSearchKeywords: [ i18n.translate('xpack.securitySolution.appLinks.ecsDataQualityDashboard', { defaultMessage: 'Data Quality', diff --git a/x-pack/plugins/security_solution/public/overview/pages/data_quality.tsx b/x-pack/plugins/security_solution/public/overview/pages/data_quality.tsx index 35a991e6ae7ad..4f7df91715247 100644 --- a/x-pack/plugins/security_solution/public/overview/pages/data_quality.tsx +++ b/x-pack/plugins/security_solution/public/overview/pages/data_quality.tsx @@ -33,11 +33,10 @@ import { SecurityPageName } from '../../app/types'; import { getGroupByFieldsOnClick } from '../../common/components/alerts_treemap/lib/helpers'; import { useTheme } from '../../common/components/charts/common'; import { HeaderPage } from '../../common/components/header_page'; -import type { BadgeOptions } from '../../common/components/header_page/types'; import { LandingPageComponent } from '../../common/components/landing_page'; import { useLocalStorage } from '../../common/components/local_storage'; import { SecuritySolutionPageWrapper } from '../../common/components/page_wrapper'; -import { DEFAULT_NUMBER_FORMAT } from '../../../common/constants'; +import { DEFAULT_BYTES_FORMAT, DEFAULT_NUMBER_FORMAT } from '../../../common/constants'; import { useSourcererDataView } from '../../common/containers/sourcerer'; import { useGetUserCasesPermissions, @@ -51,11 +50,6 @@ import * as i18n from './translations'; const LOCAL_STORAGE_KEY = 'dataQualityDashboardLastChecked'; -const badgeOptions: BadgeOptions = { - beta: true, - text: i18n.BETA, -}; - const comboBoxStyle: React.CSSProperties = { width: '322px', }; @@ -141,6 +135,7 @@ const DataQualityComponent: React.FC = () => { }, [toasts] ); + const [defaultBytesFormat] = useUiSetting$(DEFAULT_BYTES_FORMAT); const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT); const labelInputId = useGeneratedHtmlId({ prefix: 'labelInput' }); const [selectedOptions, setSelectedOptions] = useState(defaultOptions); @@ -210,11 +205,7 @@ const DataQualityComponent: React.FC = () => { {indicesExist ? ( <> - + { Date: Tue, 25 Apr 2023 00:58:48 -0400 Subject: [PATCH 19/52] [api-docs] 2023-04-25 Daily api_docs build (#155679) Generated by https://buildkite.com/elastic/kibana-api-docs-daily/builds/318 --- api_docs/actions.mdx | 2 +- api_docs/advanced_settings.mdx | 2 +- api_docs/aiops.mdx | 2 +- api_docs/alerting.devdocs.json | 53 + api_docs/alerting.mdx | 4 +- api_docs/apm.mdx | 2 +- api_docs/asset_manager.mdx | 2 +- api_docs/banners.mdx | 2 +- api_docs/bfetch.mdx | 2 +- api_docs/canvas.mdx | 2 +- api_docs/cases.devdocs.json | 2 +- api_docs/cases.mdx | 2 +- api_docs/charts.mdx | 2 +- api_docs/cloud.mdx | 2 +- api_docs/cloud_chat.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.devdocs.json | 11 +- 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.devdocs.json | 4 +- api_docs/data.mdx | 2 +- api_docs/data_query.mdx | 2 +- api_docs/data_search.mdx | 2 +- api_docs/data_view_editor.mdx | 2 +- api_docs/data_view_field_editor.mdx | 2 +- api_docs/data_view_management.mdx | 2 +- api_docs/data_views.devdocs.json | 6 +- api_docs/data_views.mdx | 2 +- api_docs/data_visualizer.mdx | 2 +- api_docs/deprecations_by_api.mdx | 2 +- api_docs/deprecations_by_plugin.mdx | 8 +- api_docs/deprecations_by_team.mdx | 2 +- api_docs/dev_tools.mdx | 2 +- api_docs/discover.mdx | 2 +- api_docs/discover_enhanced.mdx | 2 +- api_docs/ecs_data_quality_dashboard.mdx | 2 +- api_docs/embeddable.devdocs.json | 34 +- api_docs/embeddable.mdx | 4 +- api_docs/embeddable_enhanced.mdx | 2 +- api_docs/encrypted_saved_objects.mdx | 2 +- api_docs/enterprise_search.mdx | 2 +- api_docs/es_ui_shared.mdx | 2 +- api_docs/event_annotation.mdx | 2 +- api_docs/event_log.devdocs.json | 6 +- api_docs/event_log.mdx | 2 +- api_docs/exploratory_view.mdx | 2 +- api_docs/expression_error.mdx | 2 +- api_docs/expression_gauge.mdx | 2 +- api_docs/expression_heatmap.mdx | 2 +- api_docs/expression_image.mdx | 2 +- api_docs/expression_legacy_metric_vis.mdx | 2 +- api_docs/expression_metric.mdx | 2 +- api_docs/expression_metric_vis.mdx | 2 +- api_docs/expression_partition_vis.mdx | 2 +- api_docs/expression_repeat_image.mdx | 2 +- api_docs/expression_reveal_image.mdx | 2 +- api_docs/expression_shape.mdx | 2 +- api_docs/expression_tagcloud.mdx | 2 +- api_docs/expression_x_y.mdx | 2 +- api_docs/expressions.mdx | 2 +- api_docs/features.mdx | 2 +- api_docs/field_formats.mdx | 2 +- api_docs/file_upload.mdx | 2 +- api_docs/files.devdocs.json | 62 +- api_docs/files.mdx | 4 +- api_docs/files_management.mdx | 2 +- api_docs/fleet.mdx | 2 +- api_docs/global_search.mdx | 2 +- api_docs/guided_onboarding.mdx | 2 +- api_docs/home.mdx | 2 +- api_docs/image_embeddable.mdx | 2 +- api_docs/index_lifecycle_management.mdx | 2 +- api_docs/index_management.mdx | 2 +- api_docs/infra.devdocs.json | 14 + api_docs/infra.mdx | 4 +- api_docs/inspector.mdx | 2 +- api_docs/interactive_setup.mdx | 2 +- api_docs/kbn_ace.mdx | 2 +- api_docs/kbn_aiops_components.mdx | 2 +- api_docs/kbn_aiops_utils.mdx | 2 +- api_docs/kbn_alerting_state_types.mdx | 2 +- api_docs/kbn_alerts.mdx | 2 +- api_docs/kbn_alerts_as_data_utils.mdx | 2 +- api_docs/kbn_alerts_ui_shared.mdx | 2 +- api_docs/kbn_analytics.mdx | 2 +- api_docs/kbn_analytics_client.mdx | 2 +- ..._analytics_shippers_elastic_v3_browser.mdx | 2 +- ...n_analytics_shippers_elastic_v3_common.mdx | 2 +- ...n_analytics_shippers_elastic_v3_server.mdx | 2 +- api_docs/kbn_analytics_shippers_fullstory.mdx | 2 +- api_docs/kbn_analytics_shippers_gainsight.mdx | 2 +- api_docs/kbn_apm_config_loader.mdx | 2 +- api_docs/kbn_apm_synthtrace.mdx | 2 +- api_docs/kbn_apm_synthtrace_client.mdx | 2 +- api_docs/kbn_apm_utils.mdx | 2 +- api_docs/kbn_axe_config.mdx | 2 +- api_docs/kbn_cases_components.mdx | 2 +- api_docs/kbn_cell_actions.mdx | 2 +- api_docs/kbn_chart_expressions_common.mdx | 2 +- api_docs/kbn_chart_icons.mdx | 2 +- api_docs/kbn_ci_stats_core.mdx | 2 +- api_docs/kbn_ci_stats_performance_metrics.mdx | 2 +- api_docs/kbn_ci_stats_reporter.mdx | 2 +- api_docs/kbn_cli_dev_mode.mdx | 2 +- api_docs/kbn_code_editor.mdx | 2 +- api_docs/kbn_code_editor_mocks.mdx | 2 +- api_docs/kbn_coloring.mdx | 2 +- api_docs/kbn_config.mdx | 2 +- api_docs/kbn_config_mocks.mdx | 2 +- api_docs/kbn_config_schema.mdx | 2 +- .../kbn_content_management_content_editor.mdx | 2 +- ...content_management_table_list.devdocs.json | 20 +- .../kbn_content_management_table_list.mdx | 7 +- .../kbn_content_management_utils.devdocs.json | 1201 +++++++++++++++++ api_docs/kbn_content_management_utils.mdx | 33 + 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.mdx | 2 +- api_docs/kbn_core_http_server_internal.mdx | 2 +- api_docs/kbn_core_http_server_mocks.mdx | 2 +- api_docs/kbn_core_i18n_browser.mdx | 2 +- api_docs/kbn_core_i18n_browser_mocks.mdx | 2 +- api_docs/kbn_core_i18n_server.mdx | 2 +- api_docs/kbn_core_i18n_server_internal.mdx | 2 +- api_docs/kbn_core_i18n_server_mocks.mdx | 2 +- ...n_core_injected_metadata_browser_mocks.mdx | 2 +- ...kbn_core_integrations_browser_internal.mdx | 2 +- .../kbn_core_integrations_browser_mocks.mdx | 2 +- api_docs/kbn_core_lifecycle_browser.mdx | 2 +- api_docs/kbn_core_lifecycle_browser_mocks.mdx | 2 +- api_docs/kbn_core_lifecycle_server.mdx | 2 +- api_docs/kbn_core_lifecycle_server_mocks.mdx | 2 +- api_docs/kbn_core_logging_browser_mocks.mdx | 2 +- api_docs/kbn_core_logging_common_internal.mdx | 2 +- api_docs/kbn_core_logging_server.mdx | 2 +- api_docs/kbn_core_logging_server_internal.mdx | 2 +- api_docs/kbn_core_logging_server_mocks.mdx | 2 +- ...ore_metrics_collectors_server_internal.mdx | 2 +- ...n_core_metrics_collectors_server_mocks.mdx | 2 +- api_docs/kbn_core_metrics_server.mdx | 2 +- api_docs/kbn_core_metrics_server_internal.mdx | 2 +- api_docs/kbn_core_metrics_server_mocks.mdx | 2 +- api_docs/kbn_core_mount_utils_browser.mdx | 2 +- api_docs/kbn_core_node_server.mdx | 2 +- api_docs/kbn_core_node_server_internal.mdx | 2 +- api_docs/kbn_core_node_server_mocks.mdx | 2 +- api_docs/kbn_core_notifications_browser.mdx | 2 +- ...bn_core_notifications_browser_internal.mdx | 2 +- .../kbn_core_notifications_browser_mocks.mdx | 2 +- api_docs/kbn_core_overlays_browser.mdx | 2 +- .../kbn_core_overlays_browser_internal.mdx | 2 +- api_docs/kbn_core_overlays_browser_mocks.mdx | 2 +- api_docs/kbn_core_plugins_browser.mdx | 2 +- api_docs/kbn_core_plugins_browser_mocks.mdx | 2 +- api_docs/kbn_core_plugins_server.mdx | 2 +- api_docs/kbn_core_plugins_server_mocks.mdx | 2 +- api_docs/kbn_core_preboot_server.mdx | 2 +- api_docs/kbn_core_preboot_server_mocks.mdx | 2 +- api_docs/kbn_core_rendering_browser_mocks.mdx | 2 +- .../kbn_core_rendering_server_internal.mdx | 2 +- api_docs/kbn_core_rendering_server_mocks.mdx | 2 +- api_docs/kbn_core_root_server_internal.mdx | 2 +- .../kbn_core_saved_objects_api_browser.mdx | 2 +- .../kbn_core_saved_objects_api_server.mdx | 2 +- ...core_saved_objects_api_server_internal.mdx | 2 +- ...bn_core_saved_objects_api_server_mocks.mdx | 2 +- ...ore_saved_objects_base_server_internal.mdx | 2 +- ...n_core_saved_objects_base_server_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_browser.mdx | 2 +- ...bn_core_saved_objects_browser_internal.mdx | 2 +- .../kbn_core_saved_objects_browser_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_common.mdx | 2 +- ..._objects_import_export_server_internal.mdx | 2 +- ...ved_objects_import_export_server_mocks.mdx | 2 +- ...aved_objects_migration_server_internal.mdx | 2 +- ...e_saved_objects_migration_server_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_server.mdx | 2 +- ...kbn_core_saved_objects_server_internal.mdx | 2 +- .../kbn_core_saved_objects_server_mocks.mdx | 2 +- .../kbn_core_saved_objects_utils_server.mdx | 2 +- api_docs/kbn_core_status_common.mdx | 2 +- api_docs/kbn_core_status_common_internal.mdx | 2 +- api_docs/kbn_core_status_server.mdx | 2 +- api_docs/kbn_core_status_server_internal.mdx | 2 +- api_docs/kbn_core_status_server_mocks.mdx | 2 +- ...core_test_helpers_deprecations_getters.mdx | 2 +- ...n_core_test_helpers_http_setup_browser.mdx | 2 +- api_docs/kbn_core_test_helpers_kbn_server.mdx | 2 +- ...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_internal.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_crypto.mdx | 2 +- api_docs/kbn_crypto_browser.mdx | 2 +- api_docs/kbn_cypress_config.mdx | 2 +- api_docs/kbn_datemath.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_doc_links.devdocs.json | 2 +- api_docs/kbn_doc_links.mdx | 2 +- api_docs/kbn_docs_utils.mdx | 2 +- api_docs/kbn_dom_drag_drop.mdx | 2 +- api_docs/kbn_ebt_tools.mdx | 2 +- api_docs/kbn_ecs.mdx | 2 +- api_docs/kbn_ecs_data_quality_dashboard.mdx | 2 +- api_docs/kbn_es.mdx | 2 +- api_docs/kbn_es_archiver.mdx | 2 +- api_docs/kbn_es_errors.mdx | 2 +- api_docs/kbn_es_query.mdx | 2 +- api_docs/kbn_es_types.mdx | 2 +- api_docs/kbn_eslint_plugin_imports.mdx | 2 +- api_docs/kbn_expandable_flyout.mdx | 2 +- api_docs/kbn_field_types.mdx | 2 +- api_docs/kbn_find_used_node_modules.mdx | 2 +- .../kbn_ftr_common_functional_services.mdx | 2 +- api_docs/kbn_generate.mdx | 2 +- api_docs/kbn_generate_csv.mdx | 2 +- api_docs/kbn_generate_csv_types.mdx | 2 +- api_docs/kbn_guided_onboarding.mdx | 2 +- api_docs/kbn_handlebars.mdx | 2 +- api_docs/kbn_hapi_mocks.mdx | 2 +- api_docs/kbn_health_gateway_server.mdx | 2 +- api_docs/kbn_home_sample_data_card.mdx | 2 +- api_docs/kbn_home_sample_data_tab.mdx | 2 +- api_docs/kbn_i18n.mdx | 2 +- api_docs/kbn_i18n_react.mdx | 2 +- api_docs/kbn_import_resolver.mdx | 2 +- api_docs/kbn_interpreter.mdx | 2 +- api_docs/kbn_io_ts_utils.devdocs.json | 25 + api_docs/kbn_io_ts_utils.mdx | 4 +- api_docs/kbn_jest_serializers.mdx | 2 +- api_docs/kbn_journeys.mdx | 2 +- api_docs/kbn_json_ast.mdx | 2 +- api_docs/kbn_kibana_manifest_schema.mdx | 2 +- .../kbn_language_documentation_popover.mdx | 2 +- api_docs/kbn_logging.mdx | 2 +- api_docs/kbn_logging_mocks.mdx | 2 +- api_docs/kbn_managed_vscode_config.mdx | 2 +- api_docs/kbn_mapbox_gl.mdx | 2 +- api_docs/kbn_ml_agg_utils.mdx | 2 +- api_docs/kbn_ml_date_picker.mdx | 2 +- api_docs/kbn_ml_error_utils.mdx | 2 +- api_docs/kbn_ml_is_defined.mdx | 2 +- api_docs/kbn_ml_is_populated_object.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_string_hash.mdx | 2 +- api_docs/kbn_ml_trained_models_utils.mdx | 2 +- api_docs/kbn_ml_url_state.mdx | 2 +- api_docs/kbn_monaco.mdx | 2 +- api_docs/kbn_object_versioning.mdx | 2 +- api_docs/kbn_observability_alert_details.mdx | 2 +- api_docs/kbn_optimizer.mdx | 2 +- api_docs/kbn_optimizer_webpack_helpers.mdx | 2 +- api_docs/kbn_osquery_io_ts_types.mdx | 2 +- ..._performance_testing_dataset_extractor.mdx | 2 +- api_docs/kbn_plugin_generator.mdx | 2 +- api_docs/kbn_plugin_helpers.mdx | 2 +- api_docs/kbn_react_field.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_rison.mdx | 2 +- api_docs/kbn_rule_data_utils.mdx | 2 +- api_docs/kbn_saved_objects_settings.mdx | 2 +- api_docs/kbn_security_solution_side_nav.mdx | 2 +- ...kbn_security_solution_storybook_config.mdx | 2 +- .../kbn_securitysolution_autocomplete.mdx | 2 +- api_docs/kbn_securitysolution_data_table.mdx | 2 +- api_docs/kbn_securitysolution_ecs.mdx | 2 +- api_docs/kbn_securitysolution_es_utils.mdx | 2 +- ...ritysolution_exception_list_components.mdx | 2 +- ...kbn_securitysolution_grouping.devdocs.json | 150 +- api_docs/kbn_securitysolution_grouping.mdx | 4 +- api_docs/kbn_securitysolution_hook_utils.mdx | 2 +- ..._securitysolution_io_ts_alerting_types.mdx | 2 +- .../kbn_securitysolution_io_ts_list_types.mdx | 2 +- api_docs/kbn_securitysolution_io_ts_types.mdx | 2 +- api_docs/kbn_securitysolution_io_ts_utils.mdx | 2 +- api_docs/kbn_securitysolution_list_api.mdx | 2 +- .../kbn_securitysolution_list_constants.mdx | 2 +- api_docs/kbn_securitysolution_list_hooks.mdx | 2 +- api_docs/kbn_securitysolution_list_utils.mdx | 2 +- api_docs/kbn_securitysolution_rules.mdx | 2 +- api_docs/kbn_securitysolution_t_grid.mdx | 2 +- api_docs/kbn_securitysolution_utils.mdx | 2 +- api_docs/kbn_server_http_tools.mdx | 2 +- api_docs/kbn_server_route_repository.mdx | 2 +- api_docs/kbn_shared_svg.mdx | 2 +- api_docs/kbn_shared_ux_avatar_solution.mdx | 2 +- ...ared_ux_avatar_user_profile_components.mdx | 2 +- .../kbn_shared_ux_button_exit_full_screen.mdx | 2 +- ...hared_ux_button_exit_full_screen_mocks.mdx | 2 +- api_docs/kbn_shared_ux_button_toolbar.mdx | 2 +- api_docs/kbn_shared_ux_card_no_data.mdx | 2 +- api_docs/kbn_shared_ux_card_no_data_mocks.mdx | 2 +- api_docs/kbn_shared_ux_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 +- .../kbn_shared_ux_file_types.devdocs.json | 20 +- api_docs/kbn_shared_ux_file_types.mdx | 4 +- 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_utility.mdx | 2 +- api_docs/kbn_slo_schema.mdx | 2 +- api_docs/kbn_some_dev_log.mdx | 2 +- api_docs/kbn_std.mdx | 2 +- api_docs/kbn_stdio_dev_helpers.mdx | 2 +- api_docs/kbn_storybook.mdx | 2 +- api_docs/kbn_telemetry_tools.mdx | 2 +- api_docs/kbn_test.mdx | 2 +- api_docs/kbn_test_jest_helpers.mdx | 2 +- api_docs/kbn_test_subj_selector.mdx | 2 +- api_docs/kbn_tooling_log.mdx | 2 +- api_docs/kbn_ts_projects.mdx | 2 +- api_docs/kbn_typed_react_router_config.mdx | 2 +- api_docs/kbn_ui_actions_browser.mdx | 2 +- api_docs/kbn_ui_shared_deps_src.mdx | 2 +- api_docs/kbn_ui_theme.mdx | 2 +- api_docs/kbn_url_state.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_yarn_lock_validator.mdx | 2 +- api_docs/kibana_overview.mdx | 2 +- api_docs/kibana_react.mdx | 2 +- api_docs/kibana_utils.mdx | 2 +- api_docs/kubernetes_security.mdx | 2 +- api_docs/lens.mdx | 2 +- api_docs/license_api_guard.mdx | 2 +- api_docs/license_management.mdx | 2 +- api_docs/licensing.mdx | 2 +- api_docs/lists.mdx | 2 +- api_docs/management.mdx | 2 +- api_docs/maps.mdx | 2 +- api_docs/maps_ems.mdx | 2 +- api_docs/ml.mdx | 2 +- api_docs/monitoring.mdx | 2 +- api_docs/monitoring_collection.mdx | 2 +- api_docs/navigation.mdx | 2 +- api_docs/newsfeed.mdx | 2 +- api_docs/notifications.mdx | 2 +- api_docs/observability.mdx | 2 +- api_docs/observability_shared.mdx | 2 +- api_docs/osquery.mdx | 2 +- api_docs/plugin_directory.mdx | 23 +- api_docs/presentation_util.mdx | 2 +- api_docs/profiling.mdx | 2 +- api_docs/remote_clusters.mdx | 2 +- api_docs/reporting.mdx | 2 +- api_docs/rollup.mdx | 2 +- api_docs/rule_registry.mdx | 2 +- api_docs/runtime_fields.mdx | 2 +- api_docs/saved_objects.mdx | 2 +- api_docs/saved_objects_finder.mdx | 2 +- api_docs/saved_objects_management.mdx | 2 +- api_docs/saved_objects_tagging.mdx | 2 +- api_docs/saved_objects_tagging_oss.mdx | 2 +- api_docs/saved_search.mdx | 2 +- api_docs/screenshot_mode.mdx | 2 +- api_docs/screenshotting.mdx | 2 +- api_docs/security.mdx | 2 +- api_docs/security_solution.mdx | 2 +- api_docs/session_view.mdx | 2 +- api_docs/share.mdx | 2 +- api_docs/snapshot_restore.mdx | 2 +- api_docs/spaces.mdx | 2 +- api_docs/stack_alerts.mdx | 2 +- api_docs/stack_connectors.mdx | 2 +- api_docs/task_manager.mdx | 2 +- api_docs/telemetry.mdx | 2 +- api_docs/telemetry_collection_manager.mdx | 2 +- api_docs/telemetry_collection_xpack.mdx | 2 +- api_docs/telemetry_management_section.mdx | 2 +- api_docs/threat_intelligence.mdx | 2 +- api_docs/timelines.devdocs.json | 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_field_list.mdx | 2 +- api_docs/unified_histogram.mdx | 2 +- api_docs/unified_search.mdx | 2 +- api_docs/unified_search_autocomplete.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.mdx | 2 +- 517 files changed, 2051 insertions(+), 640 deletions(-) create mode 100644 api_docs/kbn_content_management_utils.devdocs.json create mode 100644 api_docs/kbn_content_management_utils.mdx diff --git a/api_docs/actions.mdx b/api_docs/actions.mdx index 9f6783794ad2c..9c6b8a0a1c6a5 100644 --- a/api_docs/actions.mdx +++ b/api_docs/actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/actions title: "actions" image: https://source.unsplash.com/400x175/?github description: API docs for the actions plugin -date: 2023-04-24 +date: 2023-04-25 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 f41313a90546b..bfad5ad4db058 100644 --- a/api_docs/advanced_settings.mdx +++ b/api_docs/advanced_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/advancedSettings title: "advancedSettings" image: https://source.unsplash.com/400x175/?github description: API docs for the advancedSettings plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'advancedSettings'] --- import advancedSettingsObj from './advanced_settings.devdocs.json'; diff --git a/api_docs/aiops.mdx b/api_docs/aiops.mdx index 50cb3ba36668e..e772deb88951a 100644 --- a/api_docs/aiops.mdx +++ b/api_docs/aiops.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/aiops title: "aiops" image: https://source.unsplash.com/400x175/?github description: API docs for the aiops plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'aiops'] --- import aiopsObj from './aiops.devdocs.json'; diff --git a/api_docs/alerting.devdocs.json b/api_docs/alerting.devdocs.json index 264f3c10d43f3..81ad124973bc6 100644 --- a/api_docs/alerting.devdocs.json +++ b/api_docs/alerting.devdocs.json @@ -9313,6 +9313,36 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "alerting", + "id": "def-common.INTERNAL_ALERTING_API_GET_ACTIVE_MAINTENANCE_WINDOWS_PATH", + "type": "string", + "tags": [], + "label": "INTERNAL_ALERTING_API_GET_ACTIVE_MAINTENANCE_WINDOWS_PATH", + "description": [], + "signature": [ + "\"/internal/alerting/rules/maintenance_window/_active\"" + ], + "path": "x-pack/plugins/alerting/common/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "alerting", + "id": "def-common.INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH", + "type": "string", + "tags": [], + "label": "INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH", + "description": [], + "signature": [ + "\"/internal/alerting/rules/maintenance_window\"" + ], + "path": "x-pack/plugins/alerting/common/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "alerting", "id": "def-common.INTERNAL_BASE_ALERTING_API_PATH", @@ -9441,6 +9471,29 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "alerting", + "id": "def-common.MaintenanceWindowCreateBody", + "type": "Type", + "tags": [], + "label": "MaintenanceWindowCreateBody", + "description": [], + "signature": [ + "{ title: string; duration: number; rRule: ", + { + "pluginId": "alerting", + "scope": "common", + "docId": "kibAlertingPluginApi", + "section": "def-common.RRuleParams", + "text": "RRuleParams" + }, + "; }" + ], + "path": "x-pack/plugins/alerting/common/maintenance_window.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "alerting", "id": "def-common.MaintenanceWindowSOAttributes", diff --git a/api_docs/alerting.mdx b/api_docs/alerting.mdx index 05cd2479d10f4..da7722790ce0e 100644 --- a/api_docs/alerting.mdx +++ b/api_docs/alerting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/alerting title: "alerting" image: https://source.unsplash.com/400x175/?github description: API docs for the alerting plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'alerting'] --- import alertingObj from './alerting.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 | |-------------------|-----------|------------------------|-----------------| -| 606 | 1 | 585 | 42 | +| 609 | 1 | 588 | 42 | ## Client diff --git a/api_docs/apm.mdx b/api_docs/apm.mdx index 39984c4c85e92..2bb04a1e4c960 100644 --- a/api_docs/apm.mdx +++ b/api_docs/apm.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/apm title: "apm" image: https://source.unsplash.com/400x175/?github description: API docs for the apm plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apm'] --- import apmObj from './apm.devdocs.json'; diff --git a/api_docs/asset_manager.mdx b/api_docs/asset_manager.mdx index 3fdc2e696e3f0..428201f0e8998 100644 --- a/api_docs/asset_manager.mdx +++ b/api_docs/asset_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/assetManager title: "assetManager" image: https://source.unsplash.com/400x175/?github description: API docs for the assetManager plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'assetManager'] --- import assetManagerObj from './asset_manager.devdocs.json'; diff --git a/api_docs/banners.mdx b/api_docs/banners.mdx index f79d12ad047ef..6ea84c4870d6d 100644 --- a/api_docs/banners.mdx +++ b/api_docs/banners.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/banners title: "banners" image: https://source.unsplash.com/400x175/?github description: API docs for the banners plugin -date: 2023-04-24 +date: 2023-04-25 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 fc6dbe517b369..e597e32169fde 100644 --- a/api_docs/bfetch.mdx +++ b/api_docs/bfetch.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/bfetch title: "bfetch" image: https://source.unsplash.com/400x175/?github description: API docs for the bfetch plugin -date: 2023-04-24 +date: 2023-04-25 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 bbeae3846322f..e4438fdd61abe 100644 --- a/api_docs/canvas.mdx +++ b/api_docs/canvas.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/canvas title: "canvas" image: https://source.unsplash.com/400x175/?github description: API docs for the canvas plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'canvas'] --- import canvasObj from './canvas.devdocs.json'; diff --git a/api_docs/cases.devdocs.json b/api_docs/cases.devdocs.json index f41f5e5efe59a..ce453c5849ecf 100644 --- a/api_docs/cases.devdocs.json +++ b/api_docs/cases.devdocs.json @@ -898,7 +898,7 @@ "CaseSeverity", " | undefined; assignees?: string | string[] | undefined; reporters?: string | string[] | undefined; defaultSearchOperator?: \"AND\" | \"OR\" | undefined; fields?: string | string[] | undefined; from?: string | undefined; page?: number | undefined; perPage?: number | undefined; search?: string | undefined; searchFields?: string | string[] | undefined; rootSearchFields?: string[] | undefined; sortField?: string | undefined; sortOrder?: \"asc\" | \"desc\" | undefined; to?: string | undefined; owner?: string | string[] | undefined; }, signal?: AbortSignal | undefined) => Promise<", "Cases", - ">; getCasesStatus: (query: { from?: string | undefined; to?: string | undefined; owner?: string | string[] | undefined; }, signal?: AbortSignal | undefined) => Promise<{ countOpenCases: number; countInProgressCases: number; countClosedCases: number; }>; getCasesMetrics: (query: { features: string[]; } & { from?: string | undefined; to?: string | undefined; owner?: string | string[] | undefined; }, signal?: AbortSignal | undefined) => Promise<{ mttr?: number | null | undefined; }>; bulkGet: (params: ", + ">; getCasesStatus: (query: { from?: string | undefined; to?: string | undefined; owner?: string | string[] | undefined; }, signal?: AbortSignal | undefined) => Promise<{ countOpenCases: number; countInProgressCases: number; countClosedCases: number; }>; getCasesMetrics: (query: { features: string[]; } & { from?: string | undefined; to?: string | undefined; owner?: string | string[] | undefined; }, signal?: AbortSignal | undefined) => Promise<{ mttr?: number | null | undefined; }>; bulkGet: (params: ", { "pluginId": "cases", "scope": "common", diff --git a/api_docs/cases.mdx b/api_docs/cases.mdx index 15ec086b8eaf2..d5913a5ac4189 100644 --- a/api_docs/cases.mdx +++ b/api_docs/cases.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cases title: "cases" image: https://source.unsplash.com/400x175/?github description: API docs for the cases plugin -date: 2023-04-24 +date: 2023-04-25 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 8a55c2821385f..cc0458a58cf89 100644 --- a/api_docs/charts.mdx +++ b/api_docs/charts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/charts title: "charts" image: https://source.unsplash.com/400x175/?github description: API docs for the charts plugin -date: 2023-04-24 +date: 2023-04-25 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 c09cbda53a15c..c8a0f4283501f 100644 --- a/api_docs/cloud.mdx +++ b/api_docs/cloud.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloud title: "cloud" image: https://source.unsplash.com/400x175/?github description: API docs for the cloud plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloud'] --- import cloudObj from './cloud.devdocs.json'; diff --git a/api_docs/cloud_chat.mdx b/api_docs/cloud_chat.mdx index 53a1a634088eb..8e17fcb273582 100644 --- a/api_docs/cloud_chat.mdx +++ b/api_docs/cloud_chat.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudChat title: "cloudChat" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudChat plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudChat'] --- import cloudChatObj from './cloud_chat.devdocs.json'; diff --git a/api_docs/cloud_data_migration.mdx b/api_docs/cloud_data_migration.mdx index cf1dca265856b..21fc2533c7989 100644 --- a/api_docs/cloud_data_migration.mdx +++ b/api_docs/cloud_data_migration.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudDataMigration title: "cloudDataMigration" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudDataMigration plugin -date: 2023-04-24 +date: 2023-04-25 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 815fae9c2fb42..88b3059b376bf 100644 --- a/api_docs/cloud_defend.mdx +++ b/api_docs/cloud_defend.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudDefend title: "cloudDefend" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudDefend plugin -date: 2023-04-24 +date: 2023-04-25 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 c56ad03988470..eb6d552316f32 100644 --- a/api_docs/cloud_experiments.mdx +++ b/api_docs/cloud_experiments.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudExperiments title: "cloudExperiments" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudExperiments plugin -date: 2023-04-24 +date: 2023-04-25 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 7b95ed904f226..2869a4d8b827d 100644 --- a/api_docs/cloud_security_posture.mdx +++ b/api_docs/cloud_security_posture.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudSecurityPosture title: "cloudSecurityPosture" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudSecurityPosture plugin -date: 2023-04-24 +date: 2023-04-25 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 2f84a61fdae2e..0f4d045b8511e 100644 --- a/api_docs/console.mdx +++ b/api_docs/console.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/console title: "console" image: https://source.unsplash.com/400x175/?github description: API docs for the console plugin -date: 2023-04-24 +date: 2023-04-25 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 21f71777a11e1..c885da1946899 100644 --- a/api_docs/content_management.mdx +++ b/api_docs/content_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/contentManagement title: "contentManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the contentManagement plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'contentManagement'] --- import contentManagementObj from './content_management.devdocs.json'; diff --git a/api_docs/controls.devdocs.json b/api_docs/controls.devdocs.json index 42f533bf0fddc..ce95034b22ea9 100644 --- a/api_docs/controls.devdocs.json +++ b/api_docs/controls.devdocs.json @@ -772,7 +772,7 @@ "section": "def-public.ControlGroupContainer", "text": "ControlGroupContainer" }, - ", controlInputTransform?: ", + ", options?: { controlInputTransform?: ", { "pluginId": "controls", "scope": "common", @@ -780,7 +780,7 @@ "section": "def-common.ControlInputTransform", "text": "ControlInputTransform" }, - " | undefined) => void" + " | undefined; onSave?: ((id: string) => void) | undefined; } | undefined) => void" ], "path": "src/plugins/controls/public/control_group/embeddable/control_group_container.tsx", "deprecated": false, @@ -790,11 +790,12 @@ { "parentPluginId": "controls", "id": "def-public.ControlGroupContainer.openAddDataControlFlyout.$1", - "type": "Function", + "type": "Object", "tags": [], - "label": "controlInputTransform", + "label": "options", "description": [], "signature": [ + "{ controlInputTransform?: ", { "pluginId": "controls", "scope": "common", @@ -802,7 +803,7 @@ "section": "def-common.ControlInputTransform", "text": "ControlInputTransform" }, - " | undefined" + " | undefined; onSave?: ((id: string) => void) | undefined; } | undefined" ], "path": "src/plugins/controls/public/control_group/editor/open_add_data_control_flyout.tsx", "deprecated": false, diff --git a/api_docs/controls.mdx b/api_docs/controls.mdx index c280197a6417a..7ee1e24ae78ab 100644 --- a/api_docs/controls.mdx +++ b/api_docs/controls.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/controls title: "controls" image: https://source.unsplash.com/400x175/?github description: API docs for the controls plugin -date: 2023-04-24 +date: 2023-04-25 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 53316962496a5..ee60cc5194c9a 100644 --- a/api_docs/custom_integrations.mdx +++ b/api_docs/custom_integrations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/customIntegrations title: "customIntegrations" image: https://source.unsplash.com/400x175/?github description: API docs for the customIntegrations plugin -date: 2023-04-24 +date: 2023-04-25 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 ee4d46dc9145f..ff02cd4aa5a3c 100644 --- a/api_docs/dashboard.mdx +++ b/api_docs/dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dashboard title: "dashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the dashboard plugin -date: 2023-04-24 +date: 2023-04-25 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 e79d7c3abd944..35930f4037257 100644 --- a/api_docs/dashboard_enhanced.mdx +++ b/api_docs/dashboard_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dashboardEnhanced title: "dashboardEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the dashboardEnhanced plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboardEnhanced'] --- import dashboardEnhancedObj from './dashboard_enhanced.devdocs.json'; diff --git a/api_docs/data.devdocs.json b/api_docs/data.devdocs.json index a1ab4045a706c..a5f45916bbd6d 100644 --- a/api_docs/data.devdocs.json +++ b/api_docs/data.devdocs.json @@ -13543,7 +13543,7 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.tsx" + "path": "x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_sub_grouping.tsx" }, { "plugin": "securitySolution", @@ -21181,7 +21181,7 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.tsx" + "path": "x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_sub_grouping.tsx" }, { "plugin": "securitySolution", diff --git a/api_docs/data.mdx b/api_docs/data.mdx index f58d4af3b6e61..034d48ef5dd4f 100644 --- a/api_docs/data.mdx +++ b/api_docs/data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data title: "data" image: https://source.unsplash.com/400x175/?github description: API docs for the data plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data'] --- import dataObj from './data.devdocs.json'; diff --git a/api_docs/data_query.mdx b/api_docs/data_query.mdx index 8fd1bc5eca79f..44c7b6a806f88 100644 --- a/api_docs/data_query.mdx +++ b/api_docs/data_query.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data-query title: "data.query" image: https://source.unsplash.com/400x175/?github description: API docs for the data.query plugin -date: 2023-04-24 +date: 2023-04-25 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 f56c297a354ba..5618420b30094 100644 --- a/api_docs/data_search.mdx +++ b/api_docs/data_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data-search title: "data.search" image: https://source.unsplash.com/400x175/?github description: API docs for the data.search plugin -date: 2023-04-24 +date: 2023-04-25 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 776cf231e1e62..1d3df0cb9f4b1 100644 --- a/api_docs/data_view_editor.mdx +++ b/api_docs/data_view_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewEditor title: "dataViewEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewEditor plugin -date: 2023-04-24 +date: 2023-04-25 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 4ac05486048f3..316058cdc98df 100644 --- a/api_docs/data_view_field_editor.mdx +++ b/api_docs/data_view_field_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewFieldEditor title: "dataViewFieldEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewFieldEditor plugin -date: 2023-04-24 +date: 2023-04-25 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 1e27fc11d34a9..742c53d11f637 100644 --- a/api_docs/data_view_management.mdx +++ b/api_docs/data_view_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewManagement title: "dataViewManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewManagement plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewManagement'] --- import dataViewManagementObj from './data_view_management.devdocs.json'; diff --git a/api_docs/data_views.devdocs.json b/api_docs/data_views.devdocs.json index 5dab9a07d047d..dd2c5312568f6 100644 --- a/api_docs/data_views.devdocs.json +++ b/api_docs/data_views.devdocs.json @@ -85,7 +85,7 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.tsx" + "path": "x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_sub_grouping.tsx" }, { "plugin": "securitySolution", @@ -8350,7 +8350,7 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.tsx" + "path": "x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_sub_grouping.tsx" }, { "plugin": "securitySolution", @@ -15672,7 +15672,7 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.tsx" + "path": "x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_sub_grouping.tsx" }, { "plugin": "securitySolution", diff --git a/api_docs/data_views.mdx b/api_docs/data_views.mdx index 57c1558717fa7..1ece2df53bcd5 100644 --- a/api_docs/data_views.mdx +++ b/api_docs/data_views.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViews title: "dataViews" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViews plugin -date: 2023-04-24 +date: 2023-04-25 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 1a34b399f959c..9a015b1c8a057 100644 --- a/api_docs/data_visualizer.mdx +++ b/api_docs/data_visualizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataVisualizer title: "dataVisualizer" image: https://source.unsplash.com/400x175/?github description: API docs for the dataVisualizer plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataVisualizer'] --- import dataVisualizerObj from './data_visualizer.devdocs.json'; diff --git a/api_docs/deprecations_by_api.mdx b/api_docs/deprecations_by_api.mdx index b98c2fea8fc27..5249f3b7fb8aa 100644 --- a/api_docs/deprecations_by_api.mdx +++ b/api_docs/deprecations_by_api.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsByApi slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-api title: Deprecated API usage by API description: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/deprecations_by_plugin.mdx b/api_docs/deprecations_by_plugin.mdx index b49d0197cbea0..06e3460f2f31f 100644 --- a/api_docs/deprecations_by_plugin.mdx +++ b/api_docs/deprecations_by_plugin.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsByPlugin slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-plugin title: Deprecated API usage by plugin description: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -1139,12 +1139,12 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | | [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=SavedObject), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=SavedObject), [user_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/user_risk_score_dashboards.ts#:~:text=SavedObject), [user_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/user_risk_score_dashboards.ts#:~:text=SavedObject) | - | | | [dependencies_start_mock.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/mock/endpoint/dependencies_start_mock.ts#:~:text=indexPatterns) | - | | | [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=SavedObject), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=SavedObject), [user_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/user_risk_score_dashboards.ts#:~:text=SavedObject), [user_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/user_risk_score_dashboards.ts#:~:text=SavedObject), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=SavedObject), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=SavedObject), [user_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/user_risk_score_dashboards.ts#:~:text=SavedObject), [user_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/user_risk_score_dashboards.ts#:~:text=SavedObject), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=SavedObject), [host_risk_score_dashboards.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/saved_object/host_risk_score_dashboards.ts#:~:text=SavedObject)+ 2 more | - | -| | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [use_rule_from_timeline.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.tsx#:~:text=title), [get_es_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts#:~:text=title), [alerts_grouping.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.tsx#:~:text=title), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts#:~:text=title), [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=title), [get_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/get_query_filter.ts#:~:text=title), [index_pattern.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/mock/index_pattern.ts#:~:text=title), [utils.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/alerts_actions/utils.test.ts#:~:text=title)+ 20 more | - | +| | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [use_rule_from_timeline.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.tsx#:~:text=title), [get_es_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts#:~:text=title), [alerts_sub_grouping.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_sub_grouping.tsx#:~:text=title), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts#:~:text=title), [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=title), [get_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/get_query_filter.ts#:~:text=title), [index_pattern.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/mock/index_pattern.ts#:~:text=title), [utils.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/alerts_actions/utils.test.ts#:~:text=title)+ 20 more | - | | | [wrap_search_source_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.ts#:~:text=create) | - | | | [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts#:~:text=fetch) | - | | | [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/hooks/eql/api.ts#:~:text=options) | - | -| | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [use_rule_from_timeline.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.tsx#:~:text=title), [get_es_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts#:~:text=title), [alerts_grouping.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.tsx#:~:text=title), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts#:~:text=title), [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=title), [get_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/get_query_filter.ts#:~:text=title), [index_pattern.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/mock/index_pattern.ts#:~:text=title), [utils.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/alerts_actions/utils.test.ts#:~:text=title)+ 20 more | - | -| | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [use_rule_from_timeline.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.tsx#:~:text=title), [get_es_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts#:~:text=title), [alerts_grouping.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.tsx#:~:text=title), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts#:~:text=title), [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=title), [get_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/get_query_filter.ts#:~:text=title), [index_pattern.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/mock/index_pattern.ts#:~:text=title), [utils.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/alerts_actions/utils.test.ts#:~:text=title)+ 5 more | - | +| | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [use_rule_from_timeline.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.tsx#:~:text=title), [get_es_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts#:~:text=title), [alerts_sub_grouping.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_sub_grouping.tsx#:~:text=title), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts#:~:text=title), [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=title), [get_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/get_query_filter.ts#:~:text=title), [index_pattern.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/mock/index_pattern.ts#:~:text=title), [utils.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/alerts_actions/utils.test.ts#:~:text=title)+ 20 more | - | +| | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [use_rule_from_timeline.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.tsx#:~:text=title), [get_es_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts#:~:text=title), [alerts_sub_grouping.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_sub_grouping.tsx#:~:text=title), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts#:~:text=title), [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=title), [get_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/get_query_filter.ts#:~:text=title), [index_pattern.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/mock/index_pattern.ts#:~:text=title), [utils.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/alerts_actions/utils.test.ts#:~:text=title)+ 5 more | - | | | [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode)+ 7 more | 8.8.0 | | | [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode)+ 7 more | 8.8.0 | | | [query.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/query.ts#:~:text=license%24) | 8.8.0 | diff --git a/api_docs/deprecations_by_team.mdx b/api_docs/deprecations_by_team.mdx index 1a29e749e4dc7..a7dfcd567c0eb 100644 --- a/api_docs/deprecations_by_team.mdx +++ b/api_docs/deprecations_by_team.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsDueByTeam slug: /kibana-dev-docs/api-meta/deprecations-due-by-team title: Deprecated APIs due to be removed, by team description: Lists the teams that are referencing deprecated APIs with a remove by date. -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/dev_tools.mdx b/api_docs/dev_tools.mdx index 73de9827ebc22..e84818ff57329 100644 --- a/api_docs/dev_tools.mdx +++ b/api_docs/dev_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/devTools title: "devTools" image: https://source.unsplash.com/400x175/?github description: API docs for the devTools plugin -date: 2023-04-24 +date: 2023-04-25 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 cde28fb9b20e0..ae5fc044dbe15 100644 --- a/api_docs/discover.mdx +++ b/api_docs/discover.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discover title: "discover" image: https://source.unsplash.com/400x175/?github description: API docs for the discover plugin -date: 2023-04-24 +date: 2023-04-25 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 79aea49ce0239..0348a2e433ad1 100644 --- a/api_docs/discover_enhanced.mdx +++ b/api_docs/discover_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discoverEnhanced title: "discoverEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the discoverEnhanced plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discoverEnhanced'] --- import discoverEnhancedObj from './discover_enhanced.devdocs.json'; diff --git a/api_docs/ecs_data_quality_dashboard.mdx b/api_docs/ecs_data_quality_dashboard.mdx index ec0192478b19a..14d381530a39e 100644 --- a/api_docs/ecs_data_quality_dashboard.mdx +++ b/api_docs/ecs_data_quality_dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ecsDataQualityDashboard title: "ecsDataQualityDashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the ecsDataQualityDashboard plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ecsDataQualityDashboard'] --- import ecsDataQualityDashboardObj from './ecs_data_quality_dashboard.devdocs.json'; diff --git a/api_docs/embeddable.devdocs.json b/api_docs/embeddable.devdocs.json index 59ef54b7c5b64..1d2998c46bd41 100644 --- a/api_docs/embeddable.devdocs.json +++ b/api_docs/embeddable.devdocs.json @@ -5933,7 +5933,7 @@ "section": "def-common.ThemeServiceStart", "text": "ThemeServiceStart" }, - "; }) => ", + "; onAddPanel?: ((id: string) => void) | undefined; }) => ", { "pluginId": "@kbn/core-mount-utils-browser", "scope": "common", @@ -6243,6 +6243,38 @@ "path": "src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/open_add_panel_flyout.tsx", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "embeddable", + "id": "def-public.openAddPanelFlyout.$1.onAddPanel", + "type": "Function", + "tags": [], + "label": "onAddPanel", + "description": [], + "signature": [ + "((id: string) => void) | undefined" + ], + "path": "src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/open_add_panel_flyout.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "embeddable", + "id": "def-public.openAddPanelFlyout.$1.onAddPanel.$1", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "signature": [ + "string" + ], + "path": "src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/open_add_panel_flyout.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] } ] } diff --git a/api_docs/embeddable.mdx b/api_docs/embeddable.mdx index 1de1e6f809d6b..73a43cf574ccf 100644 --- a/api_docs/embeddable.mdx +++ b/api_docs/embeddable.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/embeddable title: "embeddable" image: https://source.unsplash.com/400x175/?github description: API docs for the embeddable plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddable'] --- import embeddableObj from './embeddable.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kib | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 544 | 11 | 442 | 4 | +| 546 | 11 | 444 | 4 | ## Client diff --git a/api_docs/embeddable_enhanced.mdx b/api_docs/embeddable_enhanced.mdx index 24c39169fe91b..251b605711d5b 100644 --- a/api_docs/embeddable_enhanced.mdx +++ b/api_docs/embeddable_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/embeddableEnhanced title: "embeddableEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the embeddableEnhanced plugin -date: 2023-04-24 +date: 2023-04-25 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 6004d2b66f179..a5a1568454305 100644 --- a/api_docs/encrypted_saved_objects.mdx +++ b/api_docs/encrypted_saved_objects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/encryptedSavedObjects title: "encryptedSavedObjects" image: https://source.unsplash.com/400x175/?github description: API docs for the encryptedSavedObjects plugin -date: 2023-04-24 +date: 2023-04-25 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 68f4430bbeaae..6e28518c2984d 100644 --- a/api_docs/enterprise_search.mdx +++ b/api_docs/enterprise_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/enterpriseSearch title: "enterpriseSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the enterpriseSearch plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'enterpriseSearch'] --- import enterpriseSearchObj from './enterprise_search.devdocs.json'; diff --git a/api_docs/es_ui_shared.mdx b/api_docs/es_ui_shared.mdx index 9e32f4ae6e75b..3e5429e314622 100644 --- a/api_docs/es_ui_shared.mdx +++ b/api_docs/es_ui_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/esUiShared title: "esUiShared" image: https://source.unsplash.com/400x175/?github description: API docs for the esUiShared plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'esUiShared'] --- import esUiSharedObj from './es_ui_shared.devdocs.json'; diff --git a/api_docs/event_annotation.mdx b/api_docs/event_annotation.mdx index 77ff102f16bcb..33152035253ec 100644 --- a/api_docs/event_annotation.mdx +++ b/api_docs/event_annotation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventAnnotation title: "eventAnnotation" image: https://source.unsplash.com/400x175/?github description: API docs for the eventAnnotation plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventAnnotation'] --- import eventAnnotationObj from './event_annotation.devdocs.json'; diff --git a/api_docs/event_log.devdocs.json b/api_docs/event_log.devdocs.json index b3075cced41d8..6d0ddc64e7fd3 100644 --- a/api_docs/event_log.devdocs.json +++ b/api_docs/event_log.devdocs.json @@ -1514,7 +1514,7 @@ "label": "data", "description": [], "signature": [ - "(Readonly<{ log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; message?: string | undefined; tags?: string[] | undefined; rule?: Readonly<{ id?: string | undefined; name?: string | undefined; description?: string | undefined; category?: string | undefined; uuid?: string | undefined; version?: string | undefined; license?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; } & {}> | undefined; kibana?: Readonly<{ action?: Readonly<{ id?: string | undefined; name?: string | undefined; execution?: Readonly<{ source?: string | undefined; uuid?: string | undefined; } & {}> | undefined; } & {}> | undefined; alerting?: Readonly<{ outcome?: string | undefined; summary?: Readonly<{ recovered?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; new?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; ongoing?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; } & {}> | undefined; status?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; revision?: string | number | undefined; execution?: Readonly<{ uuid?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ recovered?: string | number | undefined; active?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; total_enrichment_duration_ms?: string | number | undefined; } & {}> | undefined; status?: string | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; uuid?: string | undefined; flapping?: boolean | undefined; maintenance_window_ids?: string[] | undefined; } & {}> | undefined; version?: string | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; namespace?: string | undefined; rel?: string | undefined; type_id?: string | undefined; space_agnostic?: boolean | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; event?: Readonly<{ type?: string[] | undefined; reason?: string | undefined; action?: string | undefined; id?: string | undefined; start?: string | undefined; end?: string | undefined; category?: string[] | undefined; outcome?: string | undefined; code?: string | undefined; url?: string | undefined; severity?: string | number | undefined; duration?: string | number | undefined; created?: string | undefined; dataset?: string | undefined; hash?: string | undefined; ingested?: string | undefined; kind?: string | undefined; module?: string | undefined; original?: string | undefined; provider?: string | undefined; reference?: string | undefined; risk_score?: number | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; timezone?: string | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; } & {}> | undefined)[]" + "(Readonly<{ log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; message?: string | undefined; tags?: string[] | undefined; rule?: Readonly<{ id?: string | undefined; name?: string | undefined; description?: string | undefined; category?: string | undefined; uuid?: string | undefined; version?: string | undefined; license?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; } & {}> | undefined; kibana?: Readonly<{ action?: Readonly<{ id?: string | undefined; name?: string | undefined; execution?: Readonly<{ source?: string | undefined; uuid?: string | undefined; } & {}> | undefined; } & {}> | undefined; alerting?: Readonly<{ outcome?: string | undefined; summary?: Readonly<{ recovered?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; new?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; ongoing?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; } & {}> | undefined; status?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; revision?: string | number | undefined; execution?: Readonly<{ uuid?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ recovered?: string | number | undefined; active?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; total_enrichment_duration_ms?: string | number | undefined; } & {}> | undefined; status?: string | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; uuid?: string | undefined; flapping?: boolean | undefined; maintenance_window_ids?: string[] | undefined; } & {}> | undefined; version?: string | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; namespace?: string | undefined; rel?: string | undefined; type_id?: string | undefined; space_agnostic?: boolean | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; event?: Readonly<{ type?: string[] | undefined; reason?: string | undefined; action?: string | undefined; id?: string | undefined; start?: string | undefined; end?: string | undefined; category?: string[] | undefined; outcome?: string | undefined; duration?: string | number | undefined; code?: string | undefined; url?: string | undefined; severity?: string | number | undefined; created?: string | undefined; dataset?: string | undefined; hash?: string | undefined; ingested?: string | undefined; kind?: string | undefined; module?: string | undefined; original?: string | undefined; provider?: string | undefined; reference?: string | undefined; risk_score?: number | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; timezone?: string | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; } & {}> | undefined)[]" ], "path": "x-pack/plugins/event_log/server/es/cluster_client_adapter.ts", "deprecated": false, @@ -1534,7 +1534,7 @@ "label": "IEvent", "description": [], "signature": [ - "DeepPartial | undefined; error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; message?: string | undefined; tags?: string[] | undefined; rule?: Readonly<{ id?: string | undefined; name?: string | undefined; description?: string | undefined; category?: string | undefined; uuid?: string | undefined; version?: string | undefined; license?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; } & {}> | undefined; kibana?: Readonly<{ action?: Readonly<{ id?: string | undefined; name?: string | undefined; execution?: Readonly<{ source?: string | undefined; uuid?: string | undefined; } & {}> | undefined; } & {}> | undefined; alerting?: Readonly<{ outcome?: string | undefined; summary?: Readonly<{ recovered?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; new?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; ongoing?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; } & {}> | undefined; status?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; revision?: string | number | undefined; execution?: Readonly<{ uuid?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ recovered?: string | number | undefined; active?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; total_enrichment_duration_ms?: string | number | undefined; } & {}> | undefined; status?: string | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; uuid?: string | undefined; flapping?: boolean | undefined; maintenance_window_ids?: string[] | undefined; } & {}> | undefined; version?: string | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; namespace?: string | undefined; rel?: string | undefined; type_id?: string | undefined; space_agnostic?: boolean | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; event?: Readonly<{ type?: string[] | undefined; reason?: string | undefined; action?: string | undefined; id?: string | undefined; start?: string | undefined; end?: string | undefined; category?: string[] | undefined; outcome?: string | undefined; code?: string | undefined; url?: string | undefined; severity?: string | number | undefined; duration?: string | number | undefined; created?: string | undefined; dataset?: string | undefined; hash?: string | undefined; ingested?: string | undefined; kind?: string | undefined; module?: string | undefined; original?: string | undefined; provider?: string | undefined; reference?: string | undefined; risk_score?: number | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; timezone?: string | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; } & {}>>> | undefined" + "DeepPartial | undefined; error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; message?: string | undefined; tags?: string[] | undefined; rule?: Readonly<{ id?: string | undefined; name?: string | undefined; description?: string | undefined; category?: string | undefined; uuid?: string | undefined; version?: string | undefined; license?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; } & {}> | undefined; kibana?: Readonly<{ action?: Readonly<{ id?: string | undefined; name?: string | undefined; execution?: Readonly<{ source?: string | undefined; uuid?: string | undefined; } & {}> | undefined; } & {}> | undefined; alerting?: Readonly<{ outcome?: string | undefined; summary?: Readonly<{ recovered?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; new?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; ongoing?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; } & {}> | undefined; status?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; revision?: string | number | undefined; execution?: Readonly<{ uuid?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ recovered?: string | number | undefined; active?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; total_enrichment_duration_ms?: string | number | undefined; } & {}> | undefined; status?: string | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; uuid?: string | undefined; flapping?: boolean | undefined; maintenance_window_ids?: string[] | undefined; } & {}> | undefined; version?: string | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; namespace?: string | undefined; rel?: string | undefined; type_id?: string | undefined; space_agnostic?: boolean | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; event?: Readonly<{ type?: string[] | undefined; reason?: string | undefined; action?: string | undefined; id?: string | undefined; start?: string | undefined; end?: string | undefined; category?: string[] | undefined; outcome?: string | undefined; duration?: string | number | undefined; code?: string | undefined; url?: string | undefined; severity?: string | number | undefined; created?: string | undefined; dataset?: string | undefined; hash?: string | undefined; ingested?: string | undefined; kind?: string | undefined; module?: string | undefined; original?: string | undefined; provider?: string | undefined; reference?: string | undefined; risk_score?: number | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; timezone?: string | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; } & {}>>> | undefined" ], "path": "x-pack/plugins/event_log/generated/schemas.ts", "deprecated": false, @@ -1549,7 +1549,7 @@ "label": "IValidatedEvent", "description": [], "signature": [ - "Readonly<{ log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; message?: string | undefined; tags?: string[] | undefined; rule?: Readonly<{ id?: string | undefined; name?: string | undefined; description?: string | undefined; category?: string | undefined; uuid?: string | undefined; version?: string | undefined; license?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; } & {}> | undefined; kibana?: Readonly<{ action?: Readonly<{ id?: string | undefined; name?: string | undefined; execution?: Readonly<{ source?: string | undefined; uuid?: string | undefined; } & {}> | undefined; } & {}> | undefined; alerting?: Readonly<{ outcome?: string | undefined; summary?: Readonly<{ recovered?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; new?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; ongoing?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; } & {}> | undefined; status?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; revision?: string | number | undefined; execution?: Readonly<{ uuid?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ recovered?: string | number | undefined; active?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; total_enrichment_duration_ms?: string | number | undefined; } & {}> | undefined; status?: string | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; uuid?: string | undefined; flapping?: boolean | undefined; maintenance_window_ids?: string[] | undefined; } & {}> | undefined; version?: string | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; namespace?: string | undefined; rel?: string | undefined; type_id?: string | undefined; space_agnostic?: boolean | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; event?: Readonly<{ type?: string[] | undefined; reason?: string | undefined; action?: string | undefined; id?: string | undefined; start?: string | undefined; end?: string | undefined; category?: string[] | undefined; outcome?: string | undefined; code?: string | undefined; url?: string | undefined; severity?: string | number | undefined; duration?: string | number | undefined; created?: string | undefined; dataset?: string | undefined; hash?: string | undefined; ingested?: string | undefined; kind?: string | undefined; module?: string | undefined; original?: string | undefined; provider?: string | undefined; reference?: string | undefined; risk_score?: number | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; timezone?: string | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; } & {}> | undefined" + "Readonly<{ log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; message?: string | undefined; tags?: string[] | undefined; rule?: Readonly<{ id?: string | undefined; name?: string | undefined; description?: string | undefined; category?: string | undefined; uuid?: string | undefined; version?: string | undefined; license?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; } & {}> | undefined; kibana?: Readonly<{ action?: Readonly<{ id?: string | undefined; name?: string | undefined; execution?: Readonly<{ source?: string | undefined; uuid?: string | undefined; } & {}> | undefined; } & {}> | undefined; alerting?: Readonly<{ outcome?: string | undefined; summary?: Readonly<{ recovered?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; new?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; ongoing?: Readonly<{ count?: string | number | undefined; } & {}> | undefined; } & {}> | undefined; status?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; revision?: string | number | undefined; execution?: Readonly<{ uuid?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ recovered?: string | number | undefined; active?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; total_enrichment_duration_ms?: string | number | undefined; } & {}> | undefined; status?: string | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; uuid?: string | undefined; flapping?: boolean | undefined; maintenance_window_ids?: string[] | undefined; } & {}> | undefined; version?: string | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; namespace?: string | undefined; rel?: string | undefined; type_id?: string | undefined; space_agnostic?: boolean | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; event?: Readonly<{ type?: string[] | undefined; reason?: string | undefined; action?: string | undefined; id?: string | undefined; start?: string | undefined; end?: string | undefined; category?: string[] | undefined; outcome?: string | undefined; duration?: string | number | undefined; code?: string | undefined; url?: string | undefined; severity?: string | number | undefined; created?: string | undefined; dataset?: string | undefined; hash?: string | undefined; ingested?: string | undefined; kind?: string | undefined; module?: string | undefined; original?: string | undefined; provider?: string | undefined; reference?: string | undefined; risk_score?: number | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; timezone?: string | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; } & {}> | undefined" ], "path": "x-pack/plugins/event_log/generated/schemas.ts", "deprecated": false, diff --git a/api_docs/event_log.mdx b/api_docs/event_log.mdx index 5e2e0f6c10292..5419658ffe67e 100644 --- a/api_docs/event_log.mdx +++ b/api_docs/event_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventLog title: "eventLog" image: https://source.unsplash.com/400x175/?github description: API docs for the eventLog plugin -date: 2023-04-24 +date: 2023-04-25 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 7315c69e13377..f6a72d2496321 100644 --- a/api_docs/exploratory_view.mdx +++ b/api_docs/exploratory_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/exploratoryView title: "exploratoryView" image: https://source.unsplash.com/400x175/?github description: API docs for the exploratoryView plugin -date: 2023-04-24 +date: 2023-04-25 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 4f23479cfbfc6..2c3e1cf2cf74a 100644 --- a/api_docs/expression_error.mdx +++ b/api_docs/expression_error.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionError title: "expressionError" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionError plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionError'] --- import expressionErrorObj from './expression_error.devdocs.json'; diff --git a/api_docs/expression_gauge.mdx b/api_docs/expression_gauge.mdx index 3c92eace2b8e3..6247a438a185e 100644 --- a/api_docs/expression_gauge.mdx +++ b/api_docs/expression_gauge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionGauge title: "expressionGauge" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionGauge plugin -date: 2023-04-24 +date: 2023-04-25 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 0a35f4158d179..fd86849aaaae6 100644 --- a/api_docs/expression_heatmap.mdx +++ b/api_docs/expression_heatmap.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionHeatmap title: "expressionHeatmap" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionHeatmap plugin -date: 2023-04-24 +date: 2023-04-25 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 92cf80d1ab61a..938c8ccfae501 100644 --- a/api_docs/expression_image.mdx +++ b/api_docs/expression_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionImage title: "expressionImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionImage plugin -date: 2023-04-24 +date: 2023-04-25 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 62c32bae5f5f1..a2b8ec78d3043 100644 --- a/api_docs/expression_legacy_metric_vis.mdx +++ b/api_docs/expression_legacy_metric_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionLegacyMetricVis title: "expressionLegacyMetricVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionLegacyMetricVis plugin -date: 2023-04-24 +date: 2023-04-25 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 8ccde120fd543..b40ce4eb2d895 100644 --- a/api_docs/expression_metric.mdx +++ b/api_docs/expression_metric.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionMetric title: "expressionMetric" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionMetric plugin -date: 2023-04-24 +date: 2023-04-25 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 6432d4714f1e3..dad1a3bd2c521 100644 --- a/api_docs/expression_metric_vis.mdx +++ b/api_docs/expression_metric_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionMetricVis title: "expressionMetricVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionMetricVis plugin -date: 2023-04-24 +date: 2023-04-25 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 d8a88c5d95960..355111b7a08f9 100644 --- a/api_docs/expression_partition_vis.mdx +++ b/api_docs/expression_partition_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionPartitionVis title: "expressionPartitionVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionPartitionVis plugin -date: 2023-04-24 +date: 2023-04-25 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 a238dd8036fd1..8dc18439775b0 100644 --- a/api_docs/expression_repeat_image.mdx +++ b/api_docs/expression_repeat_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionRepeatImage title: "expressionRepeatImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionRepeatImage plugin -date: 2023-04-24 +date: 2023-04-25 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 333d1ba0e6264..0679966fa33ac 100644 --- a/api_docs/expression_reveal_image.mdx +++ b/api_docs/expression_reveal_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionRevealImage title: "expressionRevealImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionRevealImage plugin -date: 2023-04-24 +date: 2023-04-25 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 079a9d1bbced9..91a6a255d1056 100644 --- a/api_docs/expression_shape.mdx +++ b/api_docs/expression_shape.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionShape title: "expressionShape" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionShape plugin -date: 2023-04-24 +date: 2023-04-25 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 8af091771bd3e..151a78bae5094 100644 --- a/api_docs/expression_tagcloud.mdx +++ b/api_docs/expression_tagcloud.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionTagcloud title: "expressionTagcloud" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionTagcloud plugin -date: 2023-04-24 +date: 2023-04-25 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 1f6d6a9ba1c68..fe5a5e96b411b 100644 --- a/api_docs/expression_x_y.mdx +++ b/api_docs/expression_x_y.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionXY title: "expressionXY" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionXY plugin -date: 2023-04-24 +date: 2023-04-25 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 631a20f0bcdf6..b6c561254afa3 100644 --- a/api_docs/expressions.mdx +++ b/api_docs/expressions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressions title: "expressions" image: https://source.unsplash.com/400x175/?github description: API docs for the expressions plugin -date: 2023-04-24 +date: 2023-04-25 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 6dedd249971e1..2da84edae66dc 100644 --- a/api_docs/features.mdx +++ b/api_docs/features.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/features title: "features" image: https://source.unsplash.com/400x175/?github description: API docs for the features plugin -date: 2023-04-24 +date: 2023-04-25 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 fc537a030e52b..9fb8bb5129081 100644 --- a/api_docs/field_formats.mdx +++ b/api_docs/field_formats.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fieldFormats title: "fieldFormats" image: https://source.unsplash.com/400x175/?github description: API docs for the fieldFormats plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fieldFormats'] --- import fieldFormatsObj from './field_formats.devdocs.json'; diff --git a/api_docs/file_upload.mdx b/api_docs/file_upload.mdx index 624bbf01a981d..8f9d667848990 100644 --- a/api_docs/file_upload.mdx +++ b/api_docs/file_upload.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fileUpload title: "fileUpload" image: https://source.unsplash.com/400x175/?github description: API docs for the fileUpload plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fileUpload'] --- import fileUploadObj from './file_upload.devdocs.json'; diff --git a/api_docs/files.devdocs.json b/api_docs/files.devdocs.json index a5d56dd4feea1..5ae8b6fe06dc3 100644 --- a/api_docs/files.devdocs.json +++ b/api_docs/files.devdocs.json @@ -305,7 +305,7 @@ "section": "def-common.FilesMetrics", "text": "FilesMetrics" }, - ">; publicDownload: (arg: Omit<{ token: string; fileName?: string | undefined; }, \"kind\">) => any; find: (arg: Omit<{ kind?: string | string[] | undefined; status?: string | string[] | undefined; extension?: string | string[] | undefined; name?: string | string[] | undefined; meta?: M | undefined; } & ", + ">; publicDownload: (arg: Omit<{ token: string; fileName?: string | undefined; }, \"kind\">) => any; find: (arg: Omit<{ kind?: string | string[] | undefined; kindToExclude?: string | string[] | undefined; status?: string | string[] | undefined; extension?: string | string[] | undefined; name?: string | string[] | undefined; meta?: M | undefined; } & ", { "pluginId": "@kbn/shared-ux-file-types", "scope": "common", @@ -649,9 +649,31 @@ "label": "FilesStart", "description": [], "signature": [ - "{ filesClientFactory: ", - "FilesClientFactory", - "; }" + "Pick<", + { + "pluginId": "files", + "scope": "public", + "docId": "kibFilesPluginApi", + "section": "def-public.FilesSetup", + "text": "FilesSetup" + }, + ", \"filesClientFactory\"> & { getFileKindDefinition: (id: string) => ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileKindBrowser", + "text": "FileKindBrowser" + }, + "; getAllFindKindDefinitions: () => ", + { + "pluginId": "@kbn/shared-ux-file-types", + "scope": "common", + "docId": "kibKbnSharedUxFileTypesPluginApi", + "section": "def-common.FileKindBrowser", + "text": "FileKindBrowser" + }, + "[]; }" ], "path": "src/plugins/files/public/plugin.ts", "deprecated": false, @@ -4145,6 +4167,22 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "files", + "id": "def-server.FindFileArgs.kindToExclude", + "type": "Array", + "tags": [], + "label": "kindToExclude", + "description": [ + "\nFile kind(s) to exclude from search, see {@link FileKind}." + ], + "signature": [ + "string[] | undefined" + ], + "path": "src/plugins/files/server/file_service/file_action_types.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "files", "id": "def-server.FindFileArgs.name", @@ -5513,6 +5551,22 @@ "path": "packages/shared-ux/file/types/index.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "files", + "id": "def-common.FileKindBrowser.managementUiActions", + "type": "Object", + "tags": [], + "label": "managementUiActions", + "description": [ + "\nAllowed actions that can be done in the File Management UI. If not provided, all actions are allowed\n" + ], + "signature": [ + "{ list?: { enabled: boolean; } | undefined; delete?: { enabled: boolean; reason?: string | undefined; } | undefined; } | undefined" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/files.mdx b/api_docs/files.mdx index 2e7af1d73120f..a5b8ee2210ad0 100644 --- a/api_docs/files.mdx +++ b/api_docs/files.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/files title: "files" image: https://source.unsplash.com/400x175/?github description: API docs for the files plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'files'] --- import filesObj from './files.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sh | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 215 | 0 | 10 | 5 | +| 217 | 0 | 10 | 5 | ## Client diff --git a/api_docs/files_management.mdx b/api_docs/files_management.mdx index 49c9a40ff8946..c09c2b6c46ab0 100644 --- a/api_docs/files_management.mdx +++ b/api_docs/files_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/filesManagement title: "filesManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the filesManagement plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'filesManagement'] --- import filesManagementObj from './files_management.devdocs.json'; diff --git a/api_docs/fleet.mdx b/api_docs/fleet.mdx index fe95c91406e4c..5ce7f17fbba77 100644 --- a/api_docs/fleet.mdx +++ b/api_docs/fleet.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fleet title: "fleet" image: https://source.unsplash.com/400x175/?github description: API docs for the fleet plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fleet'] --- import fleetObj from './fleet.devdocs.json'; diff --git a/api_docs/global_search.mdx b/api_docs/global_search.mdx index 37e44d7a88a33..cadcb64046cb3 100644 --- a/api_docs/global_search.mdx +++ b/api_docs/global_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/globalSearch title: "globalSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the globalSearch plugin -date: 2023-04-24 +date: 2023-04-25 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 475a8a2c05da3..6b231e46f696e 100644 --- a/api_docs/guided_onboarding.mdx +++ b/api_docs/guided_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/guidedOnboarding title: "guidedOnboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the guidedOnboarding plugin -date: 2023-04-24 +date: 2023-04-25 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 f62e10db50461..9c3bd452e4b3e 100644 --- a/api_docs/home.mdx +++ b/api_docs/home.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/home title: "home" image: https://source.unsplash.com/400x175/?github description: API docs for the home plugin -date: 2023-04-24 +date: 2023-04-25 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 3477ece3819e0..0c1734a4fc1bc 100644 --- a/api_docs/image_embeddable.mdx +++ b/api_docs/image_embeddable.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/imageEmbeddable title: "imageEmbeddable" image: https://source.unsplash.com/400x175/?github description: API docs for the imageEmbeddable plugin -date: 2023-04-24 +date: 2023-04-25 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 51b3707ff4cda..923ea0cf3aa3e 100644 --- a/api_docs/index_lifecycle_management.mdx +++ b/api_docs/index_lifecycle_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/indexLifecycleManagement title: "indexLifecycleManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the indexLifecycleManagement plugin -date: 2023-04-24 +date: 2023-04-25 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 7e62154cabf29..a05fd39524e3f 100644 --- a/api_docs/index_management.mdx +++ b/api_docs/index_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/indexManagement title: "indexManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the indexManagement plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexManagement'] --- import indexManagementObj from './index_management.devdocs.json'; diff --git a/api_docs/infra.devdocs.json b/api_docs/infra.devdocs.json index c9e30b7cf0fed..9807a95bd3e38 100644 --- a/api_docs/infra.devdocs.json +++ b/api_docs/infra.devdocs.json @@ -658,6 +658,20 @@ "deprecated": false, "trackAdoption": false, "children": [ + { + "parentPluginId": "infra", + "id": "def-server.InfraPluginStart.inventoryViews", + "type": "Object", + "tags": [], + "label": "inventoryViews", + "description": [], + "signature": [ + "InventoryViewsServiceStart" + ], + "path": "x-pack/plugins/infra/server/types.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "infra", "id": "def-server.InfraPluginStart.logViews", diff --git a/api_docs/infra.mdx b/api_docs/infra.mdx index f95130c7fda77..01d3e95ad371c 100644 --- a/api_docs/infra.mdx +++ b/api_docs/infra.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/infra title: "infra" image: https://source.unsplash.com/400x175/?github description: API docs for the infra plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'infra'] --- import infraObj from './infra.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/inf | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 44 | 0 | 41 | 9 | +| 45 | 0 | 42 | 10 | ## Client diff --git a/api_docs/inspector.mdx b/api_docs/inspector.mdx index 627ca307b241f..7d973531defa7 100644 --- a/api_docs/inspector.mdx +++ b/api_docs/inspector.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/inspector title: "inspector" image: https://source.unsplash.com/400x175/?github description: API docs for the inspector plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'inspector'] --- import inspectorObj from './inspector.devdocs.json'; diff --git a/api_docs/interactive_setup.mdx b/api_docs/interactive_setup.mdx index 5794f30eaf867..133c39b185221 100644 --- a/api_docs/interactive_setup.mdx +++ b/api_docs/interactive_setup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/interactiveSetup title: "interactiveSetup" image: https://source.unsplash.com/400x175/?github description: API docs for the interactiveSetup plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'interactiveSetup'] --- import interactiveSetupObj from './interactive_setup.devdocs.json'; diff --git a/api_docs/kbn_ace.mdx b/api_docs/kbn_ace.mdx index ec79dddfe1fdb..52ec43f9cd7a6 100644 --- a/api_docs/kbn_ace.mdx +++ b/api_docs/kbn_ace.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ace title: "@kbn/ace" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ace plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ace'] --- import kbnAceObj from './kbn_ace.devdocs.json'; diff --git a/api_docs/kbn_aiops_components.mdx b/api_docs/kbn_aiops_components.mdx index 027622873bb5d..922819bab9d75 100644 --- a/api_docs/kbn_aiops_components.mdx +++ b/api_docs/kbn_aiops_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-components title: "@kbn/aiops-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-components plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-components'] --- import kbnAiopsComponentsObj from './kbn_aiops_components.devdocs.json'; diff --git a/api_docs/kbn_aiops_utils.mdx b/api_docs/kbn_aiops_utils.mdx index 3ba110b4d641e..f54e0a324c770 100644 --- a/api_docs/kbn_aiops_utils.mdx +++ b/api_docs/kbn_aiops_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-utils title: "@kbn/aiops-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-utils plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-utils'] --- import kbnAiopsUtilsObj from './kbn_aiops_utils.devdocs.json'; diff --git a/api_docs/kbn_alerting_state_types.mdx b/api_docs/kbn_alerting_state_types.mdx index 9209559d4c511..1f9b0096c77da 100644 --- a/api_docs/kbn_alerting_state_types.mdx +++ b/api_docs/kbn_alerting_state_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerting-state-types title: "@kbn/alerting-state-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerting-state-types plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerting-state-types'] --- import kbnAlertingStateTypesObj from './kbn_alerting_state_types.devdocs.json'; diff --git a/api_docs/kbn_alerts.mdx b/api_docs/kbn_alerts.mdx index fd625f04df24b..c0711e797420b 100644 --- a/api_docs/kbn_alerts.mdx +++ b/api_docs/kbn_alerts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts title: "@kbn/alerts" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts'] --- import kbnAlertsObj from './kbn_alerts.devdocs.json'; diff --git a/api_docs/kbn_alerts_as_data_utils.mdx b/api_docs/kbn_alerts_as_data_utils.mdx index 3529d77abdd16..aa8bf3cf410c9 100644 --- a/api_docs/kbn_alerts_as_data_utils.mdx +++ b/api_docs/kbn_alerts_as_data_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts-as-data-utils title: "@kbn/alerts-as-data-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts-as-data-utils plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts-as-data-utils'] --- import kbnAlertsAsDataUtilsObj from './kbn_alerts_as_data_utils.devdocs.json'; diff --git a/api_docs/kbn_alerts_ui_shared.mdx b/api_docs/kbn_alerts_ui_shared.mdx index c39f9c81be753..4ebd0dabd5566 100644 --- a/api_docs/kbn_alerts_ui_shared.mdx +++ b/api_docs/kbn_alerts_ui_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts-ui-shared title: "@kbn/alerts-ui-shared" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts-ui-shared plugin -date: 2023-04-24 +date: 2023-04-25 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 90704b67958d8..b85a463e73ea7 100644 --- a/api_docs/kbn_analytics.mdx +++ b/api_docs/kbn_analytics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics title: "@kbn/analytics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics'] --- import kbnAnalyticsObj from './kbn_analytics.devdocs.json'; diff --git a/api_docs/kbn_analytics_client.mdx b/api_docs/kbn_analytics_client.mdx index 5bdc58d85dc6f..8e1d4258bfb99 100644 --- a/api_docs/kbn_analytics_client.mdx +++ b/api_docs/kbn_analytics_client.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-client title: "@kbn/analytics-client" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-client plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-client'] --- import kbnAnalyticsClientObj from './kbn_analytics_client.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx index 98e6154aa8a60..e759f05ccf0a7 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-browser title: "@kbn/analytics-shippers-elastic-v3-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-browser plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-browser'] --- import kbnAnalyticsShippersElasticV3BrowserObj from './kbn_analytics_shippers_elastic_v3_browser.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx index 5cedf9017c0c3..d5cdbeb3d218c 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-common title: "@kbn/analytics-shippers-elastic-v3-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-common plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-common'] --- import kbnAnalyticsShippersElasticV3CommonObj from './kbn_analytics_shippers_elastic_v3_common.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx index 1900aafa94eb7..33b5b599ff9e5 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-server title: "@kbn/analytics-shippers-elastic-v3-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-server plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-server'] --- import kbnAnalyticsShippersElasticV3ServerObj from './kbn_analytics_shippers_elastic_v3_server.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_fullstory.mdx b/api_docs/kbn_analytics_shippers_fullstory.mdx index 506c2ef17b8a8..a4322612120fe 100644 --- a/api_docs/kbn_analytics_shippers_fullstory.mdx +++ b/api_docs/kbn_analytics_shippers_fullstory.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-fullstory title: "@kbn/analytics-shippers-fullstory" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-fullstory plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-fullstory'] --- import kbnAnalyticsShippersFullstoryObj from './kbn_analytics_shippers_fullstory.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_gainsight.mdx b/api_docs/kbn_analytics_shippers_gainsight.mdx index 51e1daf9c0a4c..9c2065eac13c2 100644 --- a/api_docs/kbn_analytics_shippers_gainsight.mdx +++ b/api_docs/kbn_analytics_shippers_gainsight.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-gainsight title: "@kbn/analytics-shippers-gainsight" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-gainsight plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-gainsight'] --- import kbnAnalyticsShippersGainsightObj from './kbn_analytics_shippers_gainsight.devdocs.json'; diff --git a/api_docs/kbn_apm_config_loader.mdx b/api_docs/kbn_apm_config_loader.mdx index 28663f15a680c..119d56dbb3003 100644 --- a/api_docs/kbn_apm_config_loader.mdx +++ b/api_docs/kbn_apm_config_loader.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-config-loader title: "@kbn/apm-config-loader" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-config-loader plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-config-loader'] --- import kbnApmConfigLoaderObj from './kbn_apm_config_loader.devdocs.json'; diff --git a/api_docs/kbn_apm_synthtrace.mdx b/api_docs/kbn_apm_synthtrace.mdx index 1b0583f4267b7..984ef45d23120 100644 --- a/api_docs/kbn_apm_synthtrace.mdx +++ b/api_docs/kbn_apm_synthtrace.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-synthtrace title: "@kbn/apm-synthtrace" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-synthtrace plugin -date: 2023-04-24 +date: 2023-04-25 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 080fc9faa8d69..d4f621b3f9380 100644 --- a/api_docs/kbn_apm_synthtrace_client.mdx +++ b/api_docs/kbn_apm_synthtrace_client.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-synthtrace-client title: "@kbn/apm-synthtrace-client" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-synthtrace-client plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-synthtrace-client'] --- import kbnApmSynthtraceClientObj from './kbn_apm_synthtrace_client.devdocs.json'; diff --git a/api_docs/kbn_apm_utils.mdx b/api_docs/kbn_apm_utils.mdx index 9509d2d37a5f2..53c237da8263f 100644 --- a/api_docs/kbn_apm_utils.mdx +++ b/api_docs/kbn_apm_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-utils title: "@kbn/apm-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-utils plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-utils'] --- import kbnApmUtilsObj from './kbn_apm_utils.devdocs.json'; diff --git a/api_docs/kbn_axe_config.mdx b/api_docs/kbn_axe_config.mdx index c9bac589f98e1..32b32fc6447e6 100644 --- a/api_docs/kbn_axe_config.mdx +++ b/api_docs/kbn_axe_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-axe-config title: "@kbn/axe-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/axe-config plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/axe-config'] --- import kbnAxeConfigObj from './kbn_axe_config.devdocs.json'; diff --git a/api_docs/kbn_cases_components.mdx b/api_docs/kbn_cases_components.mdx index 9609f525002a3..9b53e44e3c6a2 100644 --- a/api_docs/kbn_cases_components.mdx +++ b/api_docs/kbn_cases_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cases-components title: "@kbn/cases-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cases-components plugin -date: 2023-04-24 +date: 2023-04-25 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 936150c592f65..06a1af606f4dd 100644 --- a/api_docs/kbn_cell_actions.mdx +++ b/api_docs/kbn_cell_actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cell-actions title: "@kbn/cell-actions" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cell-actions plugin -date: 2023-04-24 +date: 2023-04-25 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 55c5bc95f3389..11a7574a92b3f 100644 --- a/api_docs/kbn_chart_expressions_common.mdx +++ b/api_docs/kbn_chart_expressions_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-chart-expressions-common title: "@kbn/chart-expressions-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/chart-expressions-common plugin -date: 2023-04-24 +date: 2023-04-25 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 81991f4c3596a..c45f16e2e80f5 100644 --- a/api_docs/kbn_chart_icons.mdx +++ b/api_docs/kbn_chart_icons.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-chart-icons title: "@kbn/chart-icons" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/chart-icons plugin -date: 2023-04-24 +date: 2023-04-25 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 3eeac77d30d96..60bd4e161066f 100644 --- a/api_docs/kbn_ci_stats_core.mdx +++ b/api_docs/kbn_ci_stats_core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-core title: "@kbn/ci-stats-core" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-core plugin -date: 2023-04-24 +date: 2023-04-25 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 4210e48cd996c..33fc563195c53 100644 --- a/api_docs/kbn_ci_stats_performance_metrics.mdx +++ b/api_docs/kbn_ci_stats_performance_metrics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-performance-metrics title: "@kbn/ci-stats-performance-metrics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-performance-metrics plugin -date: 2023-04-24 +date: 2023-04-25 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 dd1eeae4185e8..14c447938b011 100644 --- a/api_docs/kbn_ci_stats_reporter.mdx +++ b/api_docs/kbn_ci_stats_reporter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-reporter title: "@kbn/ci-stats-reporter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-reporter plugin -date: 2023-04-24 +date: 2023-04-25 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 674d7849ea278..6799c9eb75262 100644 --- a/api_docs/kbn_cli_dev_mode.mdx +++ b/api_docs/kbn_cli_dev_mode.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cli-dev-mode title: "@kbn/cli-dev-mode" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cli-dev-mode plugin -date: 2023-04-24 +date: 2023-04-25 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 2352502cb6168..e8336376665f3 100644 --- a/api_docs/kbn_code_editor.mdx +++ b/api_docs/kbn_code_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-code-editor title: "@kbn/code-editor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/code-editor plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/code-editor'] --- import kbnCodeEditorObj from './kbn_code_editor.devdocs.json'; diff --git a/api_docs/kbn_code_editor_mocks.mdx b/api_docs/kbn_code_editor_mocks.mdx index 299b419bc62ee..b0313bea59b09 100644 --- a/api_docs/kbn_code_editor_mocks.mdx +++ b/api_docs/kbn_code_editor_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-code-editor-mocks title: "@kbn/code-editor-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/code-editor-mocks plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/code-editor-mocks'] --- import kbnCodeEditorMocksObj from './kbn_code_editor_mocks.devdocs.json'; diff --git a/api_docs/kbn_coloring.mdx b/api_docs/kbn_coloring.mdx index d0b05022cb683..9dd7c36be8f17 100644 --- a/api_docs/kbn_coloring.mdx +++ b/api_docs/kbn_coloring.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-coloring title: "@kbn/coloring" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/coloring plugin -date: 2023-04-24 +date: 2023-04-25 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 25ba7ec1f2e1f..f410ac6a9047f 100644 --- a/api_docs/kbn_config.mdx +++ b/api_docs/kbn_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config title: "@kbn/config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config plugin -date: 2023-04-24 +date: 2023-04-25 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 c9cb8f54bf0b4..783c496cbd07b 100644 --- a/api_docs/kbn_config_mocks.mdx +++ b/api_docs/kbn_config_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config-mocks title: "@kbn/config-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 11c17046984bd..0b2e28d2fcc01 100644 --- a/api_docs/kbn_config_schema.mdx +++ b/api_docs/kbn_config_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config-schema title: "@kbn/config-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config-schema plugin -date: 2023-04-24 +date: 2023-04-25 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 54ab8505a8c85..f86f0d415b6d4 100644 --- a/api_docs/kbn_content_management_content_editor.mdx +++ b/api_docs/kbn_content_management_content_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-content-editor title: "@kbn/content-management-content-editor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-content-editor plugin -date: 2023-04-24 +date: 2023-04-25 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_table_list.devdocs.json b/api_docs/kbn_content_management_table_list.devdocs.json index 8f1e0a68c22ae..cfd72c12a42e5 100644 --- a/api_docs/kbn_content_management_table_list.devdocs.json +++ b/api_docs/kbn_content_management_table_list.devdocs.json @@ -35,7 +35,7 @@ "section": "def-common.UserContentCommonSchema", "text": "UserContentCommonSchema" }, - ">({ tableListTitle, tableListDescription, entityName, entityNamePlural, initialFilter: initialQuery, headingId, initialPageSize, listingLimit, urlStateEnabled, customTableColumn, emptyPrompt, findItems, createItem, editItem, deleteItems, getDetailViewLink, onClickTitle, id: listingId, contentEditor, children, titleColumnName, additionalRightSideActions, withoutPageTemplateWrapper, }: ", + ">({ tableListTitle, tableListDescription, entityName, entityNamePlural, initialFilter: initialQuery, headingId, initialPageSize, listingLimit, urlStateEnabled, customTableColumn, emptyPrompt, rowItemActions, findItems, createItem, editItem, deleteItems, getDetailViewLink, onClickTitle, id: listingId, contentEditor, children, titleColumnName, additionalRightSideActions, withoutPageTemplateWrapper, }: ", "Props", ") => JSX.Element | null" ], @@ -416,7 +416,23 @@ } ], "enums": [], - "misc": [], + "misc": [ + { + "parentPluginId": "@kbn/content-management-table-list", + "id": "def-common.RowActions", + "type": "Type", + "tags": [], + "label": "RowActions", + "description": [], + "signature": [ + "{ delete?: { enabled: boolean; reason?: string | undefined; } | undefined; }" + ], + "path": "packages/content-management/table_list/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], "objects": [] } } \ No newline at end of file diff --git a/api_docs/kbn_content_management_table_list.mdx b/api_docs/kbn_content_management_table_list.mdx index 4c8960148995d..73765de18a394 100644 --- a/api_docs/kbn_content_management_table_list.mdx +++ b/api_docs/kbn_content_management_table_list.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list title: "@kbn/content-management-table-list" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list'] --- import kbnContentManagementTableListObj from './kbn_content_management_table_list.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sh | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 20 | 0 | 13 | 5 | +| 21 | 0 | 14 | 5 | ## Common @@ -31,3 +31,6 @@ Contact [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sh ### Interfaces +### Consts, variables and types + + diff --git a/api_docs/kbn_content_management_utils.devdocs.json b/api_docs/kbn_content_management_utils.devdocs.json new file mode 100644 index 0000000000000..9f1ddd3e9ad60 --- /dev/null +++ b/api_docs/kbn_content_management_utils.devdocs.json @@ -0,0 +1,1201 @@ +{ + "id": "@kbn/content-management-utils", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [ + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.ContentManagementCrudTypes", + "type": "Interface", + "tags": [ + "argument", + "argument" + ], + "label": "ContentManagementCrudTypes", + "description": [ + "\nTypes used by content management storage" + ], + "signature": [ + { + "pluginId": "@kbn/content-management-utils", + "scope": "common", + "docId": "kibKbnContentManagementUtilsPluginApi", + "section": "def-common.ContentManagementCrudTypes", + "text": "ContentManagementCrudTypes" + }, + "" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.ContentManagementCrudTypes.Item", + "type": "Object", + "tags": [], + "label": "Item", + "description": [ + "\nComplete saved object" + ], + "signature": [ + { + "pluginId": "@kbn/content-management-utils", + "scope": "common", + "docId": "kibKbnContentManagementUtilsPluginApi", + "section": "def-common.SOWithMetadata", + "text": "SOWithMetadata" + }, + "" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.ContentManagementCrudTypes.PartialItem", + "type": "CompoundType", + "tags": [], + "label": "PartialItem", + "description": [ + "\nPartial saved object, used as output for update" + ], + "signature": [ + "Omit<", + { + "pluginId": "@kbn/content-management-utils", + "scope": "common", + "docId": "kibKbnContentManagementUtilsPluginApi", + "section": "def-common.SOWithMetadata", + "text": "SOWithMetadata" + }, + ", \"references\" | \"attributes\"> & { attributes: Partial; references: ", + { + "pluginId": "@kbn/content-management-utils", + "scope": "common", + "docId": "kibKbnContentManagementUtilsPluginApi", + "section": "def-common.Reference", + "text": "Reference" + }, + "[] | undefined; }" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.ContentManagementCrudTypes.CreateOptions", + "type": "Uncategorized", + "tags": [], + "label": "CreateOptions", + "description": [ + "\nCreate options" + ], + "signature": [ + "CreateOptions" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.ContentManagementCrudTypes.UpdateOptions", + "type": "Uncategorized", + "tags": [], + "label": "UpdateOptions", + "description": [ + "\nUpdate options" + ], + "signature": [ + "UpdateOptions" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.ContentManagementCrudTypes.SearchOptions", + "type": "Uncategorized", + "tags": [], + "label": "SearchOptions", + "description": [ + "\nSearch options" + ], + "signature": [ + "SearchOptions" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.ContentManagementCrudTypes.GetIn", + "type": "Object", + "tags": [], + "label": "GetIn", + "description": [ + "\nGet item params" + ], + "signature": [ + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.GetIn", + "text": "GetIn" + }, + "" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.ContentManagementCrudTypes.GetOut", + "type": "Object", + "tags": [], + "label": "GetOut", + "description": [ + "\nGet item result" + ], + "signature": [ + "{ item: ", + { + "pluginId": "@kbn/content-management-utils", + "scope": "common", + "docId": "kibKbnContentManagementUtilsPluginApi", + "section": "def-common.SOWithMetadata", + "text": "SOWithMetadata" + }, + "; meta: { outcome: \"conflict\" | \"exactMatch\" | \"aliasMatch\"; aliasTargetId?: string | undefined; aliasPurpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; }; }" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.ContentManagementCrudTypes.CreateIn", + "type": "Object", + "tags": [], + "label": "CreateIn", + "description": [ + "\nCreate item params" + ], + "signature": [ + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.CreateIn", + "text": "CreateIn" + }, + "" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.ContentManagementCrudTypes.CreateOut", + "type": "Object", + "tags": [], + "label": "CreateOut", + "description": [ + "\nCreate item result" + ], + "signature": [ + "{ item: ", + { + "pluginId": "@kbn/content-management-utils", + "scope": "common", + "docId": "kibKbnContentManagementUtilsPluginApi", + "section": "def-common.SOWithMetadata", + "text": "SOWithMetadata" + }, + "; }" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.ContentManagementCrudTypes.SearchIn", + "type": "Object", + "tags": [], + "label": "SearchIn", + "description": [ + "\nSearch item params" + ], + "signature": [ + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.SearchIn", + "text": "SearchIn" + }, + "" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.ContentManagementCrudTypes.SearchOut", + "type": "Object", + "tags": [], + "label": "SearchOut", + "description": [ + "\nSearch item result" + ], + "signature": [ + "{ hits: ", + { + "pluginId": "@kbn/content-management-utils", + "scope": "common", + "docId": "kibKbnContentManagementUtilsPluginApi", + "section": "def-common.SOWithMetadata", + "text": "SOWithMetadata" + }, + "[]; pagination: { total: number; cursor?: string | undefined; }; }" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.ContentManagementCrudTypes.UpdateIn", + "type": "Object", + "tags": [], + "label": "UpdateIn", + "description": [ + "\nUpdate item params" + ], + "signature": [ + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.UpdateIn", + "text": "UpdateIn" + }, + "" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.ContentManagementCrudTypes.UpdateOut", + "type": "Object", + "tags": [], + "label": "UpdateOut", + "description": [ + "\nUpdate item result" + ], + "signature": [ + "{ item: PartialItem; }" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.ContentManagementCrudTypes.DeleteIn", + "type": "Object", + "tags": [], + "label": "DeleteIn", + "description": [ + "\nDelete item params" + ], + "signature": [ + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.DeleteIn", + "text": "DeleteIn" + }, + "" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.ContentManagementCrudTypes.DeleteOut", + "type": "Object", + "tags": [], + "label": "DeleteOut", + "description": [ + "\nDelete item result" + ], + "signature": [ + { + "pluginId": "contentManagement", + "scope": "common", + "docId": "kibContentManagementPluginApi", + "section": "def-common.DeleteResult", + "text": "DeleteResult" + } + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.Reference", + "type": "Interface", + "tags": [], + "label": "Reference", + "description": [], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.Reference.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.Reference.id", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.Reference.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.SavedObjectCreateOptions", + "type": "Interface", + "tags": [], + "label": "SavedObjectCreateOptions", + "description": [ + "Saved Object create options - Pick and Omit to customize" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.SavedObjectCreateOptions.id", + "type": "string", + "tags": [], + "label": "id", + "description": [ + "(not recommended) Specify an id for the document" + ], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.SavedObjectCreateOptions.overwrite", + "type": "CompoundType", + "tags": [], + "label": "overwrite", + "description": [ + "Overwrite existing documents (defaults to false)" + ], + "signature": [ + "boolean | undefined" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.SavedObjectCreateOptions.version", + "type": "string", + "tags": [], + "label": "version", + "description": [ + "\nAn opaque version number which changes on each successful write operation.\nCan be used in conjunction with `overwrite` for implementing optimistic concurrency control." + ], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.SavedObjectCreateOptions.references", + "type": "Array", + "tags": [], + "label": "references", + "description": [ + "Array of referenced saved objects." + ], + "signature": [ + { + "pluginId": "@kbn/content-management-utils", + "scope": "common", + "docId": "kibKbnContentManagementUtilsPluginApi", + "section": "def-common.Reference", + "text": "Reference" + }, + "[] | undefined" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.SavedObjectCreateOptions.refresh", + "type": "CompoundType", + "tags": [], + "label": "refresh", + "description": [ + "The Elasticsearch Refresh setting for this operation" + ], + "signature": [ + "boolean | undefined" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.SavedObjectCreateOptions.initialNamespaces", + "type": "Array", + "tags": [], + "label": "initialNamespaces", + "description": [ + "\nOptional initial namespaces for the object to be created in. If this is defined, it will supersede the namespace ID that is in\n{@link SavedObjectsCreateOptions}.\n\n* For shareable object types (registered with `namespaceType: 'multiple'`): this option can be used to specify one or more spaces,\n including the \"All spaces\" identifier (`'*'`).\n* For isolated object types (registered with `namespaceType: 'single'` or `namespaceType: 'multiple-isolated'`): this option can only\n be used to specify a single space, and the \"All spaces\" identifier (`'*'`) is not allowed.\n* For global object types (registered with `namespaceType: 'agnostic'`): this option cannot be used." + ], + "signature": [ + "string[] | undefined" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.SavedObjectSearchOptions", + "type": "Interface", + "tags": [], + "label": "SavedObjectSearchOptions", + "description": [ + "Saved Object search options - Pick and Omit to customize" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.SavedObjectSearchOptions.page", + "type": "number", + "tags": [], + "label": "page", + "description": [ + "the page of results to return" + ], + "signature": [ + "number | undefined" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.SavedObjectSearchOptions.perPage", + "type": "number", + "tags": [], + "label": "perPage", + "description": [ + "the number of objects per page" + ], + "signature": [ + "number | undefined" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.SavedObjectSearchOptions.sortField", + "type": "string", + "tags": [], + "label": "sortField", + "description": [ + "which field to sort by" + ], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.SavedObjectSearchOptions.sortOrder", + "type": "CompoundType", + "tags": [], + "label": "sortOrder", + "description": [ + "sort order, ascending or descending" + ], + "signature": [ + "SortOrder", + " | undefined" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.SavedObjectSearchOptions.fields", + "type": "Array", + "tags": [], + "label": "fields", + "description": [ + "\nAn array of fields to include in the results" + ], + "signature": [ + "string[] | undefined" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.SavedObjectSearchOptions.search", + "type": "string", + "tags": [], + "label": "search", + "description": [ + "Search documents using the Elasticsearch Simple Query String syntax. See Elasticsearch Simple Query String `query` argument for more information" + ], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.SavedObjectSearchOptions.searchFields", + "type": "Array", + "tags": [], + "label": "searchFields", + "description": [ + "The fields to perform the parsed query against. See Elasticsearch Simple Query String `fields` argument for more information" + ], + "signature": [ + "string[] | undefined" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.SavedObjectSearchOptions.searchAfter", + "type": "Array", + "tags": [], + "label": "searchAfter", + "description": [ + "\nUse the sort values from the previous page to retrieve the next page of results." + ], + "signature": [ + "SortResults", + " | undefined" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.SavedObjectSearchOptions.rootSearchFields", + "type": "Array", + "tags": [], + "label": "rootSearchFields", + "description": [ + "\nThe fields to perform the parsed query against. Unlike the `searchFields` argument, these are expected to be root fields and will not\nbe modified. If used in conjunction with `searchFields`, both are concatenated together." + ], + "signature": [ + "string[] | undefined" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.SavedObjectSearchOptions.hasReference", + "type": "CompoundType", + "tags": [], + "label": "hasReference", + "description": [ + "\nSearch for documents having a reference to the specified objects.\nUse `hasReferenceOperator` to specify the operator to use when searching for multiple references." + ], + "signature": [ + { + "pluginId": "@kbn/core-saved-objects-api-server", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-common.SavedObjectsFindOptionsReference", + "text": "SavedObjectsFindOptionsReference" + }, + " | ", + { + "pluginId": "@kbn/core-saved-objects-api-server", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-common.SavedObjectsFindOptionsReference", + "text": "SavedObjectsFindOptionsReference" + }, + "[] | undefined" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.SavedObjectSearchOptions.hasReferenceOperator", + "type": "CompoundType", + "tags": [], + "label": "hasReferenceOperator", + "description": [ + "\nThe operator to use when searching by multiple references using the `hasReference` option. Defaults to `OR`" + ], + "signature": [ + "\"AND\" | \"OR\" | undefined" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.SavedObjectSearchOptions.hasNoReference", + "type": "CompoundType", + "tags": [], + "label": "hasNoReference", + "description": [ + "\nSearch for documents *not* having a reference to the specified objects.\nUse `hasNoReferenceOperator` to specify the operator to use when searching for multiple references." + ], + "signature": [ + { + "pluginId": "@kbn/core-saved-objects-api-server", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-common.SavedObjectsFindOptionsReference", + "text": "SavedObjectsFindOptionsReference" + }, + " | ", + { + "pluginId": "@kbn/core-saved-objects-api-server", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-common.SavedObjectsFindOptionsReference", + "text": "SavedObjectsFindOptionsReference" + }, + "[] | undefined" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.SavedObjectSearchOptions.hasNoReferenceOperator", + "type": "CompoundType", + "tags": [], + "label": "hasNoReferenceOperator", + "description": [ + "\nThe operator to use when searching by multiple references using the `hasNoReference` option. Defaults to `OR`" + ], + "signature": [ + "\"AND\" | \"OR\" | undefined" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.SavedObjectSearchOptions.defaultSearchOperator", + "type": "CompoundType", + "tags": [], + "label": "defaultSearchOperator", + "description": [ + "\nThe search operator to use with the provided filter. Defaults to `OR`" + ], + "signature": [ + "\"AND\" | \"OR\" | undefined" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.SavedObjectSearchOptions.filter", + "type": "Any", + "tags": [], + "label": "filter", + "description": [ + "filter string for the search query" + ], + "signature": [ + "any" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.SavedObjectSearchOptions.aggs", + "type": "Object", + "tags": [ + "alpha" + ], + "label": "aggs", + "description": [ + "\nA record of aggregations to perform.\nThe API currently only supports a limited set of metrics and bucket aggregation types.\nAdditional aggregation types can be contributed to Core.\n" + ], + "signature": [ + "Record | undefined" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.SavedObjectSearchOptions.namespaces", + "type": "Array", + "tags": [], + "label": "namespaces", + "description": [ + "array of namespaces to search" + ], + "signature": [ + "string[] | undefined" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.SavedObjectSearchOptions.pit", + "type": "Object", + "tags": [], + "label": "pit", + "description": [ + "\nSearch against a specific Point In Time (PIT) that you've opened with {@link SavedObjectsClient.openPointInTimeForType}." + ], + "signature": [ + { + "pluginId": "@kbn/core-saved-objects-api-server", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-common.SavedObjectsPitParams", + "text": "SavedObjectsPitParams" + }, + " | undefined" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.SavedObjectUpdateOptions", + "type": "Interface", + "tags": [], + "label": "SavedObjectUpdateOptions", + "description": [ + "Saved Object update options - Pick and Omit to customize" + ], + "signature": [ + { + "pluginId": "@kbn/content-management-utils", + "scope": "common", + "docId": "kibKbnContentManagementUtilsPluginApi", + "section": "def-common.SavedObjectUpdateOptions", + "text": "SavedObjectUpdateOptions" + }, + "" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.SavedObjectUpdateOptions.references", + "type": "Array", + "tags": [], + "label": "references", + "description": [ + "Array of referenced saved objects." + ], + "signature": [ + { + "pluginId": "@kbn/content-management-utils", + "scope": "common", + "docId": "kibKbnContentManagementUtilsPluginApi", + "section": "def-common.Reference", + "text": "Reference" + }, + "[] | undefined" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.SavedObjectUpdateOptions.version", + "type": "string", + "tags": [], + "label": "version", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.SavedObjectUpdateOptions.refresh", + "type": "CompoundType", + "tags": [], + "label": "refresh", + "description": [ + "The Elasticsearch Refresh setting for this operation" + ], + "signature": [ + { + "pluginId": "@kbn/core-saved-objects-api-server", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-common.MutatingOperationRefreshSetting", + "text": "MutatingOperationRefreshSetting" + }, + " | undefined" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.SavedObjectUpdateOptions.upsert", + "type": "Uncategorized", + "tags": [], + "label": "upsert", + "description": [ + "If specified, will be used to perform an upsert if the object doesn't exist" + ], + "signature": [ + "Attributes | undefined" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.SavedObjectUpdateOptions.retryOnConflict", + "type": "number", + "tags": [], + "label": "retryOnConflict", + "description": [ + "\nThe Elasticsearch `retry_on_conflict` setting for this operation.\nDefaults to `0` when `version` is provided, `3` otherwise." + ], + "signature": [ + "number | undefined" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.SOWithMetadata", + "type": "Interface", + "tags": [], + "label": "SOWithMetadata", + "description": [ + "\nSaved object with metadata" + ], + "signature": [ + { + "pluginId": "@kbn/content-management-utils", + "scope": "common", + "docId": "kibKbnContentManagementUtilsPluginApi", + "section": "def-common.SOWithMetadata", + "text": "SOWithMetadata" + }, + "" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.SOWithMetadata.id", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.SOWithMetadata.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.SOWithMetadata.version", + "type": "string", + "tags": [], + "label": "version", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.SOWithMetadata.createdAt", + "type": "string", + "tags": [], + "label": "createdAt", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.SOWithMetadata.updatedAt", + "type": "string", + "tags": [], + "label": "updatedAt", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.SOWithMetadata.error", + "type": "Object", + "tags": [], + "label": "error", + "description": [], + "signature": [ + "{ error: string; message: string; statusCode: number; metadata?: Record | undefined; } | undefined" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.SOWithMetadata.attributes", + "type": "Uncategorized", + "tags": [], + "label": "attributes", + "description": [], + "signature": [ + "Attributes" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.SOWithMetadata.references", + "type": "Array", + "tags": [], + "label": "references", + "description": [], + "signature": [ + { + "pluginId": "@kbn/content-management-utils", + "scope": "common", + "docId": "kibKbnContentManagementUtilsPluginApi", + "section": "def-common.Reference", + "text": "Reference" + }, + "[]" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.SOWithMetadata.namespaces", + "type": "Array", + "tags": [], + "label": "namespaces", + "description": [], + "signature": [ + "string[] | undefined" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.SOWithMetadata.originId", + "type": "string", + "tags": [], + "label": "originId", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], + "enums": [], + "misc": [ + { + "parentPluginId": "@kbn/content-management-utils", + "id": "def-common.GetResultSO", + "type": "Type", + "tags": [], + "label": "GetResultSO", + "description": [ + "Return value for Saved Object get, T is item returned" + ], + "signature": [ + "{ item: T; meta: { outcome: \"conflict\" | \"exactMatch\" | \"aliasMatch\"; aliasTargetId?: string | undefined; aliasPurpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; }; }" + ], + "path": "packages/kbn-content-management-utils/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_content_management_utils.mdx b/api_docs/kbn_content_management_utils.mdx new file mode 100644 index 0000000000000..311ffb835ca3d --- /dev/null +++ b/api_docs/kbn_content_management_utils.mdx @@ -0,0 +1,33 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibKbnContentManagementUtilsPluginApi +slug: /kibana-dev-docs/api/kbn-content-management-utils +title: "@kbn/content-management-utils" +image: https://source.unsplash.com/400x175/?github +description: API docs for the @kbn/content-management-utils plugin +date: 2023-04-25 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-utils'] +--- +import kbnContentManagementUtilsObj from './kbn_content_management_utils.devdocs.json'; + + + +Contact [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 64 | 1 | 15 | 0 | + +## Common + +### Interfaces + + +### Consts, variables and types + + diff --git a/api_docs/kbn_core_analytics_browser.mdx b/api_docs/kbn_core_analytics_browser.mdx index 61fd9c2863537..b64655db6f085 100644 --- a/api_docs/kbn_core_analytics_browser.mdx +++ b/api_docs/kbn_core_analytics_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser title: "@kbn/core-analytics-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser plugin -date: 2023-04-24 +date: 2023-04-25 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 8a3895b535171..f269813712914 100644 --- a/api_docs/kbn_core_analytics_browser_internal.mdx +++ b/api_docs/kbn_core_analytics_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-internal title: "@kbn/core-analytics-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser-internal plugin -date: 2023-04-24 +date: 2023-04-25 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 685a28bea6242..99a930f9d224f 100644 --- a/api_docs/kbn_core_analytics_browser_mocks.mdx +++ b/api_docs/kbn_core_analytics_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-mocks title: "@kbn/core-analytics-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 5db1b0e6e16a6..a1ed9cf575661 100644 --- a/api_docs/kbn_core_analytics_server.mdx +++ b/api_docs/kbn_core_analytics_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server title: "@kbn/core-analytics-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server plugin -date: 2023-04-24 +date: 2023-04-25 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 4272953c4d763..bfa8aecc9291f 100644 --- a/api_docs/kbn_core_analytics_server_internal.mdx +++ b/api_docs/kbn_core_analytics_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-internal title: "@kbn/core-analytics-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server-internal plugin -date: 2023-04-24 +date: 2023-04-25 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 e0082181ed949..e66607e2cde5a 100644 --- a/api_docs/kbn_core_analytics_server_mocks.mdx +++ b/api_docs/kbn_core_analytics_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-mocks title: "@kbn/core-analytics-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 d3f1f8cfa0925..d7b3c1d1d5d2f 100644 --- a/api_docs/kbn_core_application_browser.mdx +++ b/api_docs/kbn_core_application_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser title: "@kbn/core-application-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser plugin -date: 2023-04-24 +date: 2023-04-25 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 6ff37b3089b42..a1e664d7b5389 100644 --- a/api_docs/kbn_core_application_browser_internal.mdx +++ b/api_docs/kbn_core_application_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser-internal title: "@kbn/core-application-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser-internal plugin -date: 2023-04-24 +date: 2023-04-25 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 02a3fd158ec19..a745b6da5f6a0 100644 --- a/api_docs/kbn_core_application_browser_mocks.mdx +++ b/api_docs/kbn_core_application_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser-mocks title: "@kbn/core-application-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 d6a70b2b0157f..518ab4af770c2 100644 --- a/api_docs/kbn_core_application_common.mdx +++ b/api_docs/kbn_core_application_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-common title: "@kbn/core-application-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-common plugin -date: 2023-04-24 +date: 2023-04-25 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 d7c038b7a711f..de4d727c14fab 100644 --- a/api_docs/kbn_core_apps_browser_internal.mdx +++ b/api_docs/kbn_core_apps_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-browser-internal title: "@kbn/core-apps-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-browser-internal plugin -date: 2023-04-24 +date: 2023-04-25 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 eb6b4e1963417..b87d5048a6905 100644 --- a/api_docs/kbn_core_apps_browser_mocks.mdx +++ b/api_docs/kbn_core_apps_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-browser-mocks title: "@kbn/core-apps-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-browser-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 1fcfd9f49e0fe..a3c8d5603427e 100644 --- a/api_docs/kbn_core_apps_server_internal.mdx +++ b/api_docs/kbn_core_apps_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-server-internal title: "@kbn/core-apps-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-server-internal plugin -date: 2023-04-24 +date: 2023-04-25 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 7d8bb7252a866..4914748e901c1 100644 --- a/api_docs/kbn_core_base_browser_mocks.mdx +++ b/api_docs/kbn_core_base_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-browser-mocks title: "@kbn/core-base-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-browser-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 69cc08dec9190..40d46a7687fdd 100644 --- a/api_docs/kbn_core_base_common.mdx +++ b/api_docs/kbn_core_base_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-common title: "@kbn/core-base-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-common plugin -date: 2023-04-24 +date: 2023-04-25 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 eef24003ab660..1463fb87cb8fb 100644 --- a/api_docs/kbn_core_base_server_internal.mdx +++ b/api_docs/kbn_core_base_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-internal title: "@kbn/core-base-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-server-internal plugin -date: 2023-04-24 +date: 2023-04-25 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 58aff21c3f633..54c64f8b8fda9 100644 --- a/api_docs/kbn_core_base_server_mocks.mdx +++ b/api_docs/kbn_core_base_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-mocks title: "@kbn/core-base-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-server-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 14f3d8712b6f3..0965192dcfdbe 100644 --- a/api_docs/kbn_core_capabilities_browser_mocks.mdx +++ b/api_docs/kbn_core_capabilities_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-browser-mocks title: "@kbn/core-capabilities-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-browser-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 d0b5db8e3f5b1..ad84e496b2079 100644 --- a/api_docs/kbn_core_capabilities_common.mdx +++ b/api_docs/kbn_core_capabilities_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-common title: "@kbn/core-capabilities-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-common plugin -date: 2023-04-24 +date: 2023-04-25 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 ced47f174e1bf..8f38c2b6bdad6 100644 --- a/api_docs/kbn_core_capabilities_server.mdx +++ b/api_docs/kbn_core_capabilities_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-server title: "@kbn/core-capabilities-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-server plugin -date: 2023-04-24 +date: 2023-04-25 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 236d416f8d3f5..11d9eb0463909 100644 --- a/api_docs/kbn_core_capabilities_server_mocks.mdx +++ b/api_docs/kbn_core_capabilities_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-server-mocks title: "@kbn/core-capabilities-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-server-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 c286a69723583..6b89a10f6ec77 100644 --- a/api_docs/kbn_core_chrome_browser.mdx +++ b/api_docs/kbn_core_chrome_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-chrome-browser title: "@kbn/core-chrome-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-chrome-browser plugin -date: 2023-04-24 +date: 2023-04-25 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 c4201d23a52f5..22da384234ced 100644 --- a/api_docs/kbn_core_chrome_browser_mocks.mdx +++ b/api_docs/kbn_core_chrome_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-chrome-browser-mocks title: "@kbn/core-chrome-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-chrome-browser-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 5524f5d6c7f3a..70fcaf9182e51 100644 --- a/api_docs/kbn_core_config_server_internal.mdx +++ b/api_docs/kbn_core_config_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-config-server-internal title: "@kbn/core-config-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-config-server-internal plugin -date: 2023-04-24 +date: 2023-04-25 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 c06544f094756..5031c4b861035 100644 --- a/api_docs/kbn_core_custom_branding_browser.mdx +++ b/api_docs/kbn_core_custom_branding_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-browser title: "@kbn/core-custom-branding-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-browser plugin -date: 2023-04-24 +date: 2023-04-25 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 47db10d316e4b..1de9b8ddba154 100644 --- a/api_docs/kbn_core_custom_branding_browser_internal.mdx +++ b/api_docs/kbn_core_custom_branding_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-browser-internal title: "@kbn/core-custom-branding-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-browser-internal plugin -date: 2023-04-24 +date: 2023-04-25 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 74eb25b3e0989..087844e3c88bf 100644 --- a/api_docs/kbn_core_custom_branding_browser_mocks.mdx +++ b/api_docs/kbn_core_custom_branding_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-browser-mocks title: "@kbn/core-custom-branding-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-browser-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 2852e74f1d272..80375fc935809 100644 --- a/api_docs/kbn_core_custom_branding_common.mdx +++ b/api_docs/kbn_core_custom_branding_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-common title: "@kbn/core-custom-branding-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-common plugin -date: 2023-04-24 +date: 2023-04-25 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 6b39d83563325..8f06e9041f173 100644 --- a/api_docs/kbn_core_custom_branding_server.mdx +++ b/api_docs/kbn_core_custom_branding_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-server title: "@kbn/core-custom-branding-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-server plugin -date: 2023-04-24 +date: 2023-04-25 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 efcb0ef0d682e..b6d11a52ac6a1 100644 --- a/api_docs/kbn_core_custom_branding_server_internal.mdx +++ b/api_docs/kbn_core_custom_branding_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-server-internal title: "@kbn/core-custom-branding-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-server-internal plugin -date: 2023-04-24 +date: 2023-04-25 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 1b84c3249cedb..ccf31dd049ecc 100644 --- a/api_docs/kbn_core_custom_branding_server_mocks.mdx +++ b/api_docs/kbn_core_custom_branding_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-server-mocks title: "@kbn/core-custom-branding-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-server-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 d9183c76749eb..26a111f2dbf6b 100644 --- a/api_docs/kbn_core_deprecations_browser.mdx +++ b/api_docs/kbn_core_deprecations_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser title: "@kbn/core-deprecations-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser plugin -date: 2023-04-24 +date: 2023-04-25 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 2dabde1158feb..d06dc3b228af6 100644 --- a/api_docs/kbn_core_deprecations_browser_internal.mdx +++ b/api_docs/kbn_core_deprecations_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser-internal title: "@kbn/core-deprecations-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser-internal plugin -date: 2023-04-24 +date: 2023-04-25 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 1f150624000c0..a885d35bc4a9c 100644 --- a/api_docs/kbn_core_deprecations_browser_mocks.mdx +++ b/api_docs/kbn_core_deprecations_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser-mocks title: "@kbn/core-deprecations-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 f6098418f65b8..e7d74aed91261 100644 --- a/api_docs/kbn_core_deprecations_common.mdx +++ b/api_docs/kbn_core_deprecations_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-common title: "@kbn/core-deprecations-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-common plugin -date: 2023-04-24 +date: 2023-04-25 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 18f41498f2022..35fa1b024d886 100644 --- a/api_docs/kbn_core_deprecations_server.mdx +++ b/api_docs/kbn_core_deprecations_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server title: "@kbn/core-deprecations-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server plugin -date: 2023-04-24 +date: 2023-04-25 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 4da7cae29afc1..0ba73bbd364fa 100644 --- a/api_docs/kbn_core_deprecations_server_internal.mdx +++ b/api_docs/kbn_core_deprecations_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server-internal title: "@kbn/core-deprecations-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server-internal plugin -date: 2023-04-24 +date: 2023-04-25 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 7efaa1d260ede..1c2e9f62733f0 100644 --- a/api_docs/kbn_core_deprecations_server_mocks.mdx +++ b/api_docs/kbn_core_deprecations_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server-mocks title: "@kbn/core-deprecations-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 3a8b4f7733b89..db91ea5304499 100644 --- a/api_docs/kbn_core_doc_links_browser.mdx +++ b/api_docs/kbn_core_doc_links_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser title: "@kbn/core-doc-links-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-browser plugin -date: 2023-04-24 +date: 2023-04-25 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 6d30b7f184a66..cfe58fb7263b4 100644 --- a/api_docs/kbn_core_doc_links_browser_mocks.mdx +++ b/api_docs/kbn_core_doc_links_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser-mocks title: "@kbn/core-doc-links-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-browser-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 7e04c291cf2e5..7bdf93b209089 100644 --- a/api_docs/kbn_core_doc_links_server.mdx +++ b/api_docs/kbn_core_doc_links_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server title: "@kbn/core-doc-links-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-server plugin -date: 2023-04-24 +date: 2023-04-25 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 bd6d7e5360355..794d174977ede 100644 --- a/api_docs/kbn_core_doc_links_server_mocks.mdx +++ b/api_docs/kbn_core_doc_links_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server-mocks title: "@kbn/core-doc-links-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-server-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 108a226e77328..a85edbed68ebb 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_internal.mdx +++ b/api_docs/kbn_core_elasticsearch_client_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-client-server-internal title: "@kbn/core-elasticsearch-client-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-client-server-internal plugin -date: 2023-04-24 +date: 2023-04-25 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 36da5cccdf92f..d3fd0ffaa55a9 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx +++ b/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-client-server-mocks title: "@kbn/core-elasticsearch-client-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-client-server-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 a54b680f5d90d..f07a80ead0b91 100644 --- a/api_docs/kbn_core_elasticsearch_server.mdx +++ b/api_docs/kbn_core_elasticsearch_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server title: "@kbn/core-elasticsearch-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server plugin -date: 2023-04-24 +date: 2023-04-25 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 22c91cf01d375..bb44b77ef391a 100644 --- a/api_docs/kbn_core_elasticsearch_server_internal.mdx +++ b/api_docs/kbn_core_elasticsearch_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server-internal title: "@kbn/core-elasticsearch-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server-internal plugin -date: 2023-04-24 +date: 2023-04-25 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 50e70544b210b..be34c24b4d520 100644 --- a/api_docs/kbn_core_elasticsearch_server_mocks.mdx +++ b/api_docs/kbn_core_elasticsearch_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server-mocks title: "@kbn/core-elasticsearch-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 f538a5f8e0b4e..e31b3a81bdabc 100644 --- a/api_docs/kbn_core_environment_server_internal.mdx +++ b/api_docs/kbn_core_environment_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-internal title: "@kbn/core-environment-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-environment-server-internal plugin -date: 2023-04-24 +date: 2023-04-25 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 8f1461db2e66f..d2e61d23962c0 100644 --- a/api_docs/kbn_core_environment_server_mocks.mdx +++ b/api_docs/kbn_core_environment_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-mocks title: "@kbn/core-environment-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-environment-server-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 77a377a7db9c4..6df94b463ca90 100644 --- a/api_docs/kbn_core_execution_context_browser.mdx +++ b/api_docs/kbn_core_execution_context_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser title: "@kbn/core-execution-context-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser plugin -date: 2023-04-24 +date: 2023-04-25 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 d7dcc38efe471..497fddcf7e231 100644 --- a/api_docs/kbn_core_execution_context_browser_internal.mdx +++ b/api_docs/kbn_core_execution_context_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser-internal title: "@kbn/core-execution-context-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser-internal plugin -date: 2023-04-24 +date: 2023-04-25 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 d1ac9f6597bd6..856e7f9d76fb4 100644 --- a/api_docs/kbn_core_execution_context_browser_mocks.mdx +++ b/api_docs/kbn_core_execution_context_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser-mocks title: "@kbn/core-execution-context-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 4d4c0cdc35fd7..e06e18473ee93 100644 --- a/api_docs/kbn_core_execution_context_common.mdx +++ b/api_docs/kbn_core_execution_context_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-common title: "@kbn/core-execution-context-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-common plugin -date: 2023-04-24 +date: 2023-04-25 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 d81bd38aa1916..a7aaa90cb5c41 100644 --- a/api_docs/kbn_core_execution_context_server.mdx +++ b/api_docs/kbn_core_execution_context_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server title: "@kbn/core-execution-context-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server plugin -date: 2023-04-24 +date: 2023-04-25 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 05d5f2ae35765..dbf268c236e98 100644 --- a/api_docs/kbn_core_execution_context_server_internal.mdx +++ b/api_docs/kbn_core_execution_context_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server-internal title: "@kbn/core-execution-context-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server-internal plugin -date: 2023-04-24 +date: 2023-04-25 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 68eb012274a21..13fccc87faa20 100644 --- a/api_docs/kbn_core_execution_context_server_mocks.mdx +++ b/api_docs/kbn_core_execution_context_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server-mocks title: "@kbn/core-execution-context-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 4bdeef98bbfde..48cac2e369e1d 100644 --- a/api_docs/kbn_core_fatal_errors_browser.mdx +++ b/api_docs/kbn_core_fatal_errors_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-fatal-errors-browser title: "@kbn/core-fatal-errors-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-fatal-errors-browser plugin -date: 2023-04-24 +date: 2023-04-25 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 d94568e3ed0e6..2b5d84f23701a 100644 --- a/api_docs/kbn_core_fatal_errors_browser_mocks.mdx +++ b/api_docs/kbn_core_fatal_errors_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-fatal-errors-browser-mocks title: "@kbn/core-fatal-errors-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-fatal-errors-browser-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 306f52219527d..acf212d2c6846 100644 --- a/api_docs/kbn_core_http_browser.mdx +++ b/api_docs/kbn_core_http_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser title: "@kbn/core-http-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser plugin -date: 2023-04-24 +date: 2023-04-25 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 ab7a2167ee8d3..8e3cf5be8daf6 100644 --- a/api_docs/kbn_core_http_browser_internal.mdx +++ b/api_docs/kbn_core_http_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser-internal title: "@kbn/core-http-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser-internal plugin -date: 2023-04-24 +date: 2023-04-25 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 3a484f5c5bb94..ebb1175bcb40a 100644 --- a/api_docs/kbn_core_http_browser_mocks.mdx +++ b/api_docs/kbn_core_http_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser-mocks title: "@kbn/core-http-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 4c6f719e62bb6..4728e428fba30 100644 --- a/api_docs/kbn_core_http_common.mdx +++ b/api_docs/kbn_core_http_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-common title: "@kbn/core-http-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-common plugin -date: 2023-04-24 +date: 2023-04-25 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 867ab6116747a..6fd18f3be652a 100644 --- a/api_docs/kbn_core_http_context_server_mocks.mdx +++ b/api_docs/kbn_core_http_context_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-context-server-mocks title: "@kbn/core-http-context-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-context-server-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 ad72f4f7242bc..8bd37f962ce10 100644 --- a/api_docs/kbn_core_http_request_handler_context_server.mdx +++ b/api_docs/kbn_core_http_request_handler_context_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-request-handler-context-server title: "@kbn/core-http-request-handler-context-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-request-handler-context-server plugin -date: 2023-04-24 +date: 2023-04-25 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 8bca969b22064..24d10c696553f 100644 --- a/api_docs/kbn_core_http_resources_server.mdx +++ b/api_docs/kbn_core_http_resources_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server title: "@kbn/core-http-resources-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server plugin -date: 2023-04-24 +date: 2023-04-25 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 408b53256f7db..bed254fe88fcd 100644 --- a/api_docs/kbn_core_http_resources_server_internal.mdx +++ b/api_docs/kbn_core_http_resources_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server-internal title: "@kbn/core-http-resources-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server-internal plugin -date: 2023-04-24 +date: 2023-04-25 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 c87dfa9f9fccf..a23262b66cac9 100644 --- a/api_docs/kbn_core_http_resources_server_mocks.mdx +++ b/api_docs/kbn_core_http_resources_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server-mocks title: "@kbn/core-http-resources-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 f165267f56e33..a56c06d0404df 100644 --- a/api_docs/kbn_core_http_router_server_internal.mdx +++ b/api_docs/kbn_core_http_router_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-router-server-internal title: "@kbn/core-http-router-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-router-server-internal plugin -date: 2023-04-24 +date: 2023-04-25 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 e9e7dc64f0942..79d55f9e4eeb8 100644 --- a/api_docs/kbn_core_http_router_server_mocks.mdx +++ b/api_docs/kbn_core_http_router_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-router-server-mocks title: "@kbn/core-http-router-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-router-server-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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.mdx b/api_docs/kbn_core_http_server.mdx index f0cdaa85851dc..067b9306999e9 100644 --- a/api_docs/kbn_core_http_server.mdx +++ b/api_docs/kbn_core_http_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server title: "@kbn/core-http-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server plugin -date: 2023-04-24 +date: 2023-04-25 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 f38f74d167a98..3914ccd95f841 100644 --- a/api_docs/kbn_core_http_server_internal.mdx +++ b/api_docs/kbn_core_http_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server-internal title: "@kbn/core-http-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server-internal plugin -date: 2023-04-24 +date: 2023-04-25 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 c59347545a328..df11606798683 100644 --- a/api_docs/kbn_core_http_server_mocks.mdx +++ b/api_docs/kbn_core_http_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server-mocks title: "@kbn/core-http-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 c38585619f6c0..a3ce906c0279d 100644 --- a/api_docs/kbn_core_i18n_browser.mdx +++ b/api_docs/kbn_core_i18n_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser title: "@kbn/core-i18n-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-browser plugin -date: 2023-04-24 +date: 2023-04-25 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 0fe31a6d87fa1..45a7e9042ce1b 100644 --- a/api_docs/kbn_core_i18n_browser_mocks.mdx +++ b/api_docs/kbn_core_i18n_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser-mocks title: "@kbn/core-i18n-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-browser-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 a9a56e81b982d..609a0a4818ab3 100644 --- a/api_docs/kbn_core_i18n_server.mdx +++ b/api_docs/kbn_core_i18n_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server title: "@kbn/core-i18n-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server plugin -date: 2023-04-24 +date: 2023-04-25 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 ec02bcea6b7f7..37c4c3ee83c08 100644 --- a/api_docs/kbn_core_i18n_server_internal.mdx +++ b/api_docs/kbn_core_i18n_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server-internal title: "@kbn/core-i18n-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server-internal plugin -date: 2023-04-24 +date: 2023-04-25 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 1a2c897736245..d84bd90a40383 100644 --- a/api_docs/kbn_core_i18n_server_mocks.mdx +++ b/api_docs/kbn_core_i18n_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server-mocks title: "@kbn/core-i18n-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 3c162c9189752..530e498c01a25 100644 --- a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx +++ b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-injected-metadata-browser-mocks title: "@kbn/core-injected-metadata-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-injected-metadata-browser-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 494208c4c5914..591163fb8790d 100644 --- a/api_docs/kbn_core_integrations_browser_internal.mdx +++ b/api_docs/kbn_core_integrations_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-integrations-browser-internal title: "@kbn/core-integrations-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-integrations-browser-internal plugin -date: 2023-04-24 +date: 2023-04-25 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 a037d65732f72..d4657d880f3f5 100644 --- a/api_docs/kbn_core_integrations_browser_mocks.mdx +++ b/api_docs/kbn_core_integrations_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-integrations-browser-mocks title: "@kbn/core-integrations-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-integrations-browser-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 99067d79f0a9f..171b4c8439822 100644 --- a/api_docs/kbn_core_lifecycle_browser.mdx +++ b/api_docs/kbn_core_lifecycle_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser title: "@kbn/core-lifecycle-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser plugin -date: 2023-04-24 +date: 2023-04-25 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 534bb78d4eaba..aa224ae9c82c7 100644 --- a/api_docs/kbn_core_lifecycle_browser_mocks.mdx +++ b/api_docs/kbn_core_lifecycle_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser-mocks title: "@kbn/core-lifecycle-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 81ac149482e3c..5f88d588307f3 100644 --- a/api_docs/kbn_core_lifecycle_server.mdx +++ b/api_docs/kbn_core_lifecycle_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-server title: "@kbn/core-lifecycle-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-server plugin -date: 2023-04-24 +date: 2023-04-25 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 26ba420c08d5c..b5fb67855d772 100644 --- a/api_docs/kbn_core_lifecycle_server_mocks.mdx +++ b/api_docs/kbn_core_lifecycle_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-server-mocks title: "@kbn/core-lifecycle-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-server-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 9ba0f825772fa..42cd892938479 100644 --- a/api_docs/kbn_core_logging_browser_mocks.mdx +++ b/api_docs/kbn_core_logging_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-browser-mocks title: "@kbn/core-logging-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-browser-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 a960a65461e1d..5cbeb9a6b2d77 100644 --- a/api_docs/kbn_core_logging_common_internal.mdx +++ b/api_docs/kbn_core_logging_common_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-common-internal title: "@kbn/core-logging-common-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-common-internal plugin -date: 2023-04-24 +date: 2023-04-25 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 3fd09f6263c27..8f2e8da57a9d0 100644 --- a/api_docs/kbn_core_logging_server.mdx +++ b/api_docs/kbn_core_logging_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server title: "@kbn/core-logging-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server plugin -date: 2023-04-24 +date: 2023-04-25 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 f3acbc7fc8045..203f90c1d41ee 100644 --- a/api_docs/kbn_core_logging_server_internal.mdx +++ b/api_docs/kbn_core_logging_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-internal title: "@kbn/core-logging-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server-internal plugin -date: 2023-04-24 +date: 2023-04-25 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 c3ea743d26d84..b0fcac62cba04 100644 --- a/api_docs/kbn_core_logging_server_mocks.mdx +++ b/api_docs/kbn_core_logging_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-mocks title: "@kbn/core-logging-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 71e8faae9eed4..95725dc4f0853 100644 --- a/api_docs/kbn_core_metrics_collectors_server_internal.mdx +++ b/api_docs/kbn_core_metrics_collectors_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-collectors-server-internal title: "@kbn/core-metrics-collectors-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-collectors-server-internal plugin -date: 2023-04-24 +date: 2023-04-25 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 2eabbc2bacc62..8601cebc03b08 100644 --- a/api_docs/kbn_core_metrics_collectors_server_mocks.mdx +++ b/api_docs/kbn_core_metrics_collectors_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-collectors-server-mocks title: "@kbn/core-metrics-collectors-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-collectors-server-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 f37d52033dd7a..2b6f90835423c 100644 --- a/api_docs/kbn_core_metrics_server.mdx +++ b/api_docs/kbn_core_metrics_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server title: "@kbn/core-metrics-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server plugin -date: 2023-04-24 +date: 2023-04-25 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 a7217eaf16db7..fd0f9f47b6c60 100644 --- a/api_docs/kbn_core_metrics_server_internal.mdx +++ b/api_docs/kbn_core_metrics_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server-internal title: "@kbn/core-metrics-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server-internal plugin -date: 2023-04-24 +date: 2023-04-25 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 59ea42f319e9d..38722e9154916 100644 --- a/api_docs/kbn_core_metrics_server_mocks.mdx +++ b/api_docs/kbn_core_metrics_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server-mocks title: "@kbn/core-metrics-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 466745e2ce499..7e520e3b42b45 100644 --- a/api_docs/kbn_core_mount_utils_browser.mdx +++ b/api_docs/kbn_core_mount_utils_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-mount-utils-browser title: "@kbn/core-mount-utils-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-mount-utils-browser plugin -date: 2023-04-24 +date: 2023-04-25 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 98986e42b5bb0..b6faa54b4347e 100644 --- a/api_docs/kbn_core_node_server.mdx +++ b/api_docs/kbn_core_node_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server title: "@kbn/core-node-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server plugin -date: 2023-04-24 +date: 2023-04-25 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 d8b7087087930..42104c5c02645 100644 --- a/api_docs/kbn_core_node_server_internal.mdx +++ b/api_docs/kbn_core_node_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-internal title: "@kbn/core-node-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server-internal plugin -date: 2023-04-24 +date: 2023-04-25 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 b45a40d461b52..f2069ec00fad8 100644 --- a/api_docs/kbn_core_node_server_mocks.mdx +++ b/api_docs/kbn_core_node_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-mocks title: "@kbn/core-node-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 c8536a941c46f..afa264cdbe4b2 100644 --- a/api_docs/kbn_core_notifications_browser.mdx +++ b/api_docs/kbn_core_notifications_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser title: "@kbn/core-notifications-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser plugin -date: 2023-04-24 +date: 2023-04-25 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 7146b20a88240..847bf67c4aa8d 100644 --- a/api_docs/kbn_core_notifications_browser_internal.mdx +++ b/api_docs/kbn_core_notifications_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser-internal title: "@kbn/core-notifications-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser-internal plugin -date: 2023-04-24 +date: 2023-04-25 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 2518700bd2a9d..0d34a5cd902a3 100644 --- a/api_docs/kbn_core_notifications_browser_mocks.mdx +++ b/api_docs/kbn_core_notifications_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser-mocks title: "@kbn/core-notifications-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 38f32ad1bd2c1..3fd6130cdb8d8 100644 --- a/api_docs/kbn_core_overlays_browser.mdx +++ b/api_docs/kbn_core_overlays_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser title: "@kbn/core-overlays-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser plugin -date: 2023-04-24 +date: 2023-04-25 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 d158e2d4ff953..8ecd79bb3a3e8 100644 --- a/api_docs/kbn_core_overlays_browser_internal.mdx +++ b/api_docs/kbn_core_overlays_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser-internal title: "@kbn/core-overlays-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser-internal plugin -date: 2023-04-24 +date: 2023-04-25 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 e2c79208c0674..2fc8b367d6fdd 100644 --- a/api_docs/kbn_core_overlays_browser_mocks.mdx +++ b/api_docs/kbn_core_overlays_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser-mocks title: "@kbn/core-overlays-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 8d655eb267f5a..6fb4b8ec4c90e 100644 --- a/api_docs/kbn_core_plugins_browser.mdx +++ b/api_docs/kbn_core_plugins_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser title: "@kbn/core-plugins-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser plugin -date: 2023-04-24 +date: 2023-04-25 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 77ea3d979a1fa..4705b9ed25bf7 100644 --- a/api_docs/kbn_core_plugins_browser_mocks.mdx +++ b/api_docs/kbn_core_plugins_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser-mocks title: "@kbn/core-plugins-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser-mocks plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-browser-mocks'] --- import kbnCorePluginsBrowserMocksObj from './kbn_core_plugins_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_server.mdx b/api_docs/kbn_core_plugins_server.mdx index 44b22b20623c4..8b10022d6b74e 100644 --- a/api_docs/kbn_core_plugins_server.mdx +++ b/api_docs/kbn_core_plugins_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-server title: "@kbn/core-plugins-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-server plugin -date: 2023-04-24 +date: 2023-04-25 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 c032049f2dd4f..3404d9772c1ad 100644 --- a/api_docs/kbn_core_plugins_server_mocks.mdx +++ b/api_docs/kbn_core_plugins_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-server-mocks title: "@kbn/core-plugins-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-server-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 08eca2b502206..787d6e83c77ae 100644 --- a/api_docs/kbn_core_preboot_server.mdx +++ b/api_docs/kbn_core_preboot_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-preboot-server title: "@kbn/core-preboot-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-preboot-server plugin -date: 2023-04-24 +date: 2023-04-25 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 971dc60fbd402..f5261db81d189 100644 --- a/api_docs/kbn_core_preboot_server_mocks.mdx +++ b/api_docs/kbn_core_preboot_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-preboot-server-mocks title: "@kbn/core-preboot-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-preboot-server-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 f7cb4f618bbf9..af9157cecdeaf 100644 --- a/api_docs/kbn_core_rendering_browser_mocks.mdx +++ b/api_docs/kbn_core_rendering_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-browser-mocks title: "@kbn/core-rendering-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-browser-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 91d2f9cd5b3a7..0dd9d376a4e67 100644 --- a/api_docs/kbn_core_rendering_server_internal.mdx +++ b/api_docs/kbn_core_rendering_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-server-internal title: "@kbn/core-rendering-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-server-internal plugin -date: 2023-04-24 +date: 2023-04-25 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 e114fbfb6c870..142f2fa27e62a 100644 --- a/api_docs/kbn_core_rendering_server_mocks.mdx +++ b/api_docs/kbn_core_rendering_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-server-mocks title: "@kbn/core-rendering-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-server-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 12234cdb3c57c..eee680ca1b5e1 100644 --- a/api_docs/kbn_core_root_server_internal.mdx +++ b/api_docs/kbn_core_root_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-root-server-internal title: "@kbn/core-root-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-root-server-internal plugin -date: 2023-04-24 +date: 2023-04-25 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 2ae4ebd0c693b..dfaeecf2543c4 100644 --- a/api_docs/kbn_core_saved_objects_api_browser.mdx +++ b/api_docs/kbn_core_saved_objects_api_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-browser title: "@kbn/core-saved-objects-api-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-browser plugin -date: 2023-04-24 +date: 2023-04-25 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 55646f1f382b6..d04aab2eb21b9 100644 --- a/api_docs/kbn_core_saved_objects_api_server.mdx +++ b/api_docs/kbn_core_saved_objects_api_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server title: "@kbn/core-saved-objects-api-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server plugin -date: 2023-04-24 +date: 2023-04-25 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_internal.mdx b/api_docs/kbn_core_saved_objects_api_server_internal.mdx index 165d452af821e..7cbd01cb6659d 100644 --- a/api_docs/kbn_core_saved_objects_api_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_api_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server-internal title: "@kbn/core-saved-objects-api-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server-internal plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server-internal'] --- import kbnCoreSavedObjectsApiServerInternalObj from './kbn_core_saved_objects_api_server_internal.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 99c35f7983158..ccbf3695bf3d2 100644 --- a/api_docs/kbn_core_saved_objects_api_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_api_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server-mocks title: "@kbn/core-saved-objects-api-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 1978b36015f7e..128e9e835c029 100644 --- a/api_docs/kbn_core_saved_objects_base_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_base_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-base-server-internal title: "@kbn/core-saved-objects-base-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-base-server-internal plugin -date: 2023-04-24 +date: 2023-04-25 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 79c73b9220d0c..d0775b712d605 100644 --- a/api_docs/kbn_core_saved_objects_base_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_base_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-base-server-mocks title: "@kbn/core-saved-objects-base-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-base-server-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 10eb5868ecf47..6f1f46aa38e73 100644 --- a/api_docs/kbn_core_saved_objects_browser.mdx +++ b/api_docs/kbn_core_saved_objects_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser title: "@kbn/core-saved-objects-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser plugin -date: 2023-04-24 +date: 2023-04-25 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 4615be9c11ce1..86d42b6b8c608 100644 --- a/api_docs/kbn_core_saved_objects_browser_internal.mdx +++ b/api_docs/kbn_core_saved_objects_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser-internal title: "@kbn/core-saved-objects-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser-internal plugin -date: 2023-04-24 +date: 2023-04-25 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 a37c978cfb343..d65aee63687a2 100644 --- a/api_docs/kbn_core_saved_objects_browser_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser-mocks title: "@kbn/core-saved-objects-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 706710e2cb824..f676d4fca4630 100644 --- a/api_docs/kbn_core_saved_objects_common.mdx +++ b/api_docs/kbn_core_saved_objects_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-common title: "@kbn/core-saved-objects-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-common plugin -date: 2023-04-24 +date: 2023-04-25 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 e0122dffcacb0..98be6ceff5983 100644 --- a/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-import-export-server-internal title: "@kbn/core-saved-objects-import-export-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-import-export-server-internal plugin -date: 2023-04-24 +date: 2023-04-25 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 08cc43ae6a54d..4460fd0b02f89 100644 --- a/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-import-export-server-mocks title: "@kbn/core-saved-objects-import-export-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-import-export-server-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 49dcee745ce38..38281e01c8bbe 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_migration_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-migration-server-internal title: "@kbn/core-saved-objects-migration-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-migration-server-internal plugin -date: 2023-04-24 +date: 2023-04-25 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 612f215e8d922..47e427a65c326 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-migration-server-mocks title: "@kbn/core-saved-objects-migration-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-migration-server-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 391665fd14f8c..60e7cc8bfb088 100644 --- a/api_docs/kbn_core_saved_objects_server.mdx +++ b/api_docs/kbn_core_saved_objects_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server title: "@kbn/core-saved-objects-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server plugin -date: 2023-04-24 +date: 2023-04-25 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 8f7af4ecd077d..63c8b4908cd45 100644 --- a/api_docs/kbn_core_saved_objects_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server-internal title: "@kbn/core-saved-objects-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server-internal plugin -date: 2023-04-24 +date: 2023-04-25 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 0a1e5d1292b0a..3fef61e10359b 100644 --- a/api_docs/kbn_core_saved_objects_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server-mocks title: "@kbn/core-saved-objects-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 b99ce47f08e8a..9fa7764ac307f 100644 --- a/api_docs/kbn_core_saved_objects_utils_server.mdx +++ b/api_docs/kbn_core_saved_objects_utils_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-utils-server title: "@kbn/core-saved-objects-utils-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-utils-server plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-utils-server'] --- import kbnCoreSavedObjectsUtilsServerObj from './kbn_core_saved_objects_utils_server.devdocs.json'; diff --git a/api_docs/kbn_core_status_common.mdx b/api_docs/kbn_core_status_common.mdx index de9b1e18cae5d..9bbd560f514cd 100644 --- a/api_docs/kbn_core_status_common.mdx +++ b/api_docs/kbn_core_status_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-common title: "@kbn/core-status-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-common plugin -date: 2023-04-24 +date: 2023-04-25 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 301494e7b2388..1fcb770a7f836 100644 --- a/api_docs/kbn_core_status_common_internal.mdx +++ b/api_docs/kbn_core_status_common_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-common-internal title: "@kbn/core-status-common-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-common-internal plugin -date: 2023-04-24 +date: 2023-04-25 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 df15953abc315..ac91c3bbbe491 100644 --- a/api_docs/kbn_core_status_server.mdx +++ b/api_docs/kbn_core_status_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server title: "@kbn/core-status-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server plugin -date: 2023-04-24 +date: 2023-04-25 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 075a12e1c6f24..3b84bab2c12fa 100644 --- a/api_docs/kbn_core_status_server_internal.mdx +++ b/api_docs/kbn_core_status_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server-internal title: "@kbn/core-status-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server-internal plugin -date: 2023-04-24 +date: 2023-04-25 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 d47e6f46490f1..545b8c3c51a64 100644 --- a/api_docs/kbn_core_status_server_mocks.mdx +++ b/api_docs/kbn_core_status_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server-mocks title: "@kbn/core-status-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 a38afc5d4cc28..f3fc43285ef75 100644 --- a/api_docs/kbn_core_test_helpers_deprecations_getters.mdx +++ b/api_docs/kbn_core_test_helpers_deprecations_getters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-deprecations-getters title: "@kbn/core-test-helpers-deprecations-getters" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-deprecations-getters plugin -date: 2023-04-24 +date: 2023-04-25 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 ef13c55e07c61..fb6c5ca01698c 100644 --- a/api_docs/kbn_core_test_helpers_http_setup_browser.mdx +++ b/api_docs/kbn_core_test_helpers_http_setup_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-http-setup-browser title: "@kbn/core-test-helpers-http-setup-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-http-setup-browser plugin -date: 2023-04-24 +date: 2023-04-25 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 cf75dcf5478b4..aef8e4d42c71c 100644 --- a/api_docs/kbn_core_test_helpers_kbn_server.mdx +++ b/api_docs/kbn_core_test_helpers_kbn_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-kbn-server title: "@kbn/core-test-helpers-kbn-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-kbn-server plugin -date: 2023-04-24 +date: 2023-04-25 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_so_type_serializer.mdx b/api_docs/kbn_core_test_helpers_so_type_serializer.mdx index f11a869c92ee1..4374ec559a5d6 100644 --- a/api_docs/kbn_core_test_helpers_so_type_serializer.mdx +++ b/api_docs/kbn_core_test_helpers_so_type_serializer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-so-type-serializer title: "@kbn/core-test-helpers-so-type-serializer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-so-type-serializer plugin -date: 2023-04-24 +date: 2023-04-25 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 484dd22728fbc..e6b16af9bcb5d 100644 --- a/api_docs/kbn_core_test_helpers_test_utils.mdx +++ b/api_docs/kbn_core_test_helpers_test_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-test-utils title: "@kbn/core-test-helpers-test-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-test-utils plugin -date: 2023-04-24 +date: 2023-04-25 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 63cd2de86d6e4..7383ce4111dae 100644 --- a/api_docs/kbn_core_theme_browser.mdx +++ b/api_docs/kbn_core_theme_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser title: "@kbn/core-theme-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser plugin -date: 2023-04-24 +date: 2023-04-25 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_internal.mdx b/api_docs/kbn_core_theme_browser_internal.mdx index 54a121b8df78a..1235740a86f62 100644 --- a/api_docs/kbn_core_theme_browser_internal.mdx +++ b/api_docs/kbn_core_theme_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser-internal title: "@kbn/core-theme-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser-internal plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser-internal'] --- import kbnCoreThemeBrowserInternalObj from './kbn_core_theme_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser_mocks.mdx b/api_docs/kbn_core_theme_browser_mocks.mdx index a21b5fa4ab454..23b5c986ca522 100644 --- a/api_docs/kbn_core_theme_browser_mocks.mdx +++ b/api_docs/kbn_core_theme_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser-mocks title: "@kbn/core-theme-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 e741c75cc66b0..2931039207d99 100644 --- a/api_docs/kbn_core_ui_settings_browser.mdx +++ b/api_docs/kbn_core_ui_settings_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser title: "@kbn/core-ui-settings-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser plugin -date: 2023-04-24 +date: 2023-04-25 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 92c21046ca2bb..1889f6b78437a 100644 --- a/api_docs/kbn_core_ui_settings_browser_internal.mdx +++ b/api_docs/kbn_core_ui_settings_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser-internal title: "@kbn/core-ui-settings-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser-internal plugin -date: 2023-04-24 +date: 2023-04-25 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 9b8babfa1fcf3..7eb24fbc7fb7b 100644 --- a/api_docs/kbn_core_ui_settings_browser_mocks.mdx +++ b/api_docs/kbn_core_ui_settings_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser-mocks title: "@kbn/core-ui-settings-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 b95f699d6d050..2450d22c66fb1 100644 --- a/api_docs/kbn_core_ui_settings_common.mdx +++ b/api_docs/kbn_core_ui_settings_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-common title: "@kbn/core-ui-settings-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-common plugin -date: 2023-04-24 +date: 2023-04-25 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 a88c23c489891..eaeb3e4a7e921 100644 --- a/api_docs/kbn_core_ui_settings_server.mdx +++ b/api_docs/kbn_core_ui_settings_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server title: "@kbn/core-ui-settings-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server plugin -date: 2023-04-24 +date: 2023-04-25 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 06432816c1788..631a6b4af48d1 100644 --- a/api_docs/kbn_core_ui_settings_server_internal.mdx +++ b/api_docs/kbn_core_ui_settings_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server-internal title: "@kbn/core-ui-settings-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server-internal plugin -date: 2023-04-24 +date: 2023-04-25 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 e1477ab3eeb87..9be618bb23931 100644 --- a/api_docs/kbn_core_ui_settings_server_mocks.mdx +++ b/api_docs/kbn_core_ui_settings_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server-mocks title: "@kbn/core-ui-settings-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 914a5468667b5..336935840823f 100644 --- a/api_docs/kbn_core_usage_data_server.mdx +++ b/api_docs/kbn_core_usage_data_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server title: "@kbn/core-usage-data-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server plugin -date: 2023-04-24 +date: 2023-04-25 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 b5596d8ce5bc7..5bff292e0ba66 100644 --- a/api_docs/kbn_core_usage_data_server_internal.mdx +++ b/api_docs/kbn_core_usage_data_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server-internal title: "@kbn/core-usage-data-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server-internal plugin -date: 2023-04-24 +date: 2023-04-25 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 c5fc08f39d31e..59d9348dc7110 100644 --- a/api_docs/kbn_core_usage_data_server_mocks.mdx +++ b/api_docs/kbn_core_usage_data_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server-mocks title: "@kbn/core-usage-data-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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_crypto.mdx b/api_docs/kbn_crypto.mdx index ff42ee5669ec3..382f353135287 100644 --- a/api_docs/kbn_crypto.mdx +++ b/api_docs/kbn_crypto.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-crypto title: "@kbn/crypto" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/crypto plugin -date: 2023-04-24 +date: 2023-04-25 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 6af342c88de2c..d0bd92809e486 100644 --- a/api_docs/kbn_crypto_browser.mdx +++ b/api_docs/kbn_crypto_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-crypto-browser title: "@kbn/crypto-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/crypto-browser plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto-browser'] --- import kbnCryptoBrowserObj from './kbn_crypto_browser.devdocs.json'; diff --git a/api_docs/kbn_cypress_config.mdx b/api_docs/kbn_cypress_config.mdx index 845a8d13f6268..ba85e5db86b15 100644 --- a/api_docs/kbn_cypress_config.mdx +++ b/api_docs/kbn_cypress_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cypress-config title: "@kbn/cypress-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cypress-config plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cypress-config'] --- import kbnCypressConfigObj from './kbn_cypress_config.devdocs.json'; diff --git a/api_docs/kbn_datemath.mdx b/api_docs/kbn_datemath.mdx index 3b0b33c77b227..d846010affe30 100644 --- a/api_docs/kbn_datemath.mdx +++ b/api_docs/kbn_datemath.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-datemath title: "@kbn/datemath" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/datemath plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/datemath'] --- import kbnDatemathObj from './kbn_datemath.devdocs.json'; diff --git a/api_docs/kbn_dev_cli_errors.mdx b/api_docs/kbn_dev_cli_errors.mdx index afafe20cb2e1e..dde7c69400f0c 100644 --- a/api_docs/kbn_dev_cli_errors.mdx +++ b/api_docs/kbn_dev_cli_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-errors title: "@kbn/dev-cli-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-cli-errors plugin -date: 2023-04-24 +date: 2023-04-25 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 4de5774e12693..266c8b2554645 100644 --- a/api_docs/kbn_dev_cli_runner.mdx +++ b/api_docs/kbn_dev_cli_runner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-runner title: "@kbn/dev-cli-runner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-cli-runner plugin -date: 2023-04-24 +date: 2023-04-25 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 683f4f5f7821b..e4d487eed68f8 100644 --- a/api_docs/kbn_dev_proc_runner.mdx +++ b/api_docs/kbn_dev_proc_runner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-proc-runner title: "@kbn/dev-proc-runner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-proc-runner plugin -date: 2023-04-24 +date: 2023-04-25 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 36602e1f31d55..9e553a8d2b1f2 100644 --- a/api_docs/kbn_dev_utils.mdx +++ b/api_docs/kbn_dev_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-utils title: "@kbn/dev-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-utils plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-utils'] --- import kbnDevUtilsObj from './kbn_dev_utils.devdocs.json'; diff --git a/api_docs/kbn_doc_links.devdocs.json b/api_docs/kbn_doc_links.devdocs.json index b01beac37dc53..496205404ca2d 100644 --- a/api_docs/kbn_doc_links.devdocs.json +++ b/api_docs/kbn_doc_links.devdocs.json @@ -300,7 +300,7 @@ "label": "enterpriseSearch", "description": [], "signature": [ - "{ readonly apiKeys: string; readonly behavioralAnalytics: string; readonly behavioralAnalyticsEvents: string; readonly bulkApi: string; readonly configuration: string; readonly connectors: string; readonly connectorsAzureBlobStorage: string; readonly connectorsGoogleCloudStorage: string; readonly connectorsMicrosoftSQL: string; readonly connectorsMongoDB: string; readonly connectorsMySQL: string; readonly connectorsNetworkDrive: string; readonly connectorsOracle: string; readonly connectorsPostgreSQL: string; readonly connectorsS3: string; readonly connectorsWorkplaceSearch: string; readonly crawlerExtractionRules: string; readonly crawlerManaging: string; readonly crawlerOverview: string; readonly deployTrainedModels: string; readonly documentLevelSecurity: string; readonly elser: string; readonly engines: string; readonly ingestionApis: string; readonly ingestPipelines: string; readonly languageAnalyzers: string; readonly languageClients: string; readonly licenseManagement: string; readonly machineLearningStart: string; readonly mailService: string; readonly start: string; readonly syncRules: string; readonly troubleshootSetup: string; readonly usersAccess: string; }" + "{ readonly apiKeys: string; readonly behavioralAnalytics: string; readonly behavioralAnalyticsEvents: string; readonly bulkApi: string; readonly configuration: string; readonly connectors: string; readonly connectorsAzureBlobStorage: string; readonly connectorsGoogleCloudStorage: string; readonly connectorsMicrosoftSQL: string; readonly connectorsMongoDB: string; readonly connectorsMySQL: string; readonly connectorsNetworkDrive: string; readonly connectorsOracle: string; readonly connectorsPostgreSQL: string; readonly connectorsS3: string; readonly connectorsWorkplaceSearch: string; readonly crawlerExtractionRules: string; readonly crawlerManaging: string; readonly crawlerOverview: string; readonly deployTrainedModels: string; readonly documentLevelSecurity: string; readonly elser: string; readonly engines: string; readonly ingestionApis: string; readonly ingestPipelines: string; readonly languageAnalyzers: string; readonly languageClients: string; readonly licenseManagement: string; readonly machineLearningStart: string; readonly mailService: string; readonly mlDocumentEnrichment: string; readonly start: string; readonly syncRules: string; readonly troubleshootSetup: string; readonly usersAccess: string; }" ], "path": "packages/kbn-doc-links/src/types.ts", "deprecated": false, diff --git a/api_docs/kbn_doc_links.mdx b/api_docs/kbn_doc_links.mdx index 3aaf17560db47..3764d3e6efd5b 100644 --- a/api_docs/kbn_doc_links.mdx +++ b/api_docs/kbn_doc_links.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-doc-links title: "@kbn/doc-links" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/doc-links plugin -date: 2023-04-24 +date: 2023-04-25 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 b1465811e2295..69318531901df 100644 --- a/api_docs/kbn_docs_utils.mdx +++ b/api_docs/kbn_docs_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-docs-utils title: "@kbn/docs-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/docs-utils plugin -date: 2023-04-24 +date: 2023-04-25 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 ead5751fa35c3..3b0041e92a94f 100644 --- a/api_docs/kbn_dom_drag_drop.mdx +++ b/api_docs/kbn_dom_drag_drop.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dom-drag-drop title: "@kbn/dom-drag-drop" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dom-drag-drop plugin -date: 2023-04-24 +date: 2023-04-25 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 17263518fb4d1..6c7650530715b 100644 --- a/api_docs/kbn_ebt_tools.mdx +++ b/api_docs/kbn_ebt_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ebt-tools title: "@kbn/ebt-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ebt-tools plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ebt-tools'] --- import kbnEbtToolsObj from './kbn_ebt_tools.devdocs.json'; diff --git a/api_docs/kbn_ecs.mdx b/api_docs/kbn_ecs.mdx index 6b880006797c0..ba5be3d9b2750 100644 --- a/api_docs/kbn_ecs.mdx +++ b/api_docs/kbn_ecs.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ecs title: "@kbn/ecs" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ecs plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ecs'] --- import kbnEcsObj from './kbn_ecs.devdocs.json'; diff --git a/api_docs/kbn_ecs_data_quality_dashboard.mdx b/api_docs/kbn_ecs_data_quality_dashboard.mdx index 0b8d0d939e4ae..c29d5b8dd2907 100644 --- a/api_docs/kbn_ecs_data_quality_dashboard.mdx +++ b/api_docs/kbn_ecs_data_quality_dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ecs-data-quality-dashboard title: "@kbn/ecs-data-quality-dashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ecs-data-quality-dashboard plugin -date: 2023-04-24 +date: 2023-04-25 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_es.mdx b/api_docs/kbn_es.mdx index 2969a379e7d3c..5e7e203e9099c 100644 --- a/api_docs/kbn_es.mdx +++ b/api_docs/kbn_es.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es title: "@kbn/es" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es'] --- import kbnEsObj from './kbn_es.devdocs.json'; diff --git a/api_docs/kbn_es_archiver.mdx b/api_docs/kbn_es_archiver.mdx index eaf29c3077a8b..1b4b48beebca7 100644 --- a/api_docs/kbn_es_archiver.mdx +++ b/api_docs/kbn_es_archiver.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-archiver title: "@kbn/es-archiver" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-archiver plugin -date: 2023-04-24 +date: 2023-04-25 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 ab41505be21e9..58bb73e76d2ec 100644 --- a/api_docs/kbn_es_errors.mdx +++ b/api_docs/kbn_es_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-errors title: "@kbn/es-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-errors plugin -date: 2023-04-24 +date: 2023-04-25 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 9dac7b564cfc4..1e36c23a4ae00 100644 --- a/api_docs/kbn_es_query.mdx +++ b/api_docs/kbn_es_query.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-query title: "@kbn/es-query" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-query plugin -date: 2023-04-24 +date: 2023-04-25 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 9d1a75a856f82..958d349ebcbf8 100644 --- a/api_docs/kbn_es_types.mdx +++ b/api_docs/kbn_es_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-types title: "@kbn/es-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-types plugin -date: 2023-04-24 +date: 2023-04-25 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 cd2e3feaf7128..6a3f9acac5186 100644 --- a/api_docs/kbn_eslint_plugin_imports.mdx +++ b/api_docs/kbn_eslint_plugin_imports.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-eslint-plugin-imports title: "@kbn/eslint-plugin-imports" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/eslint-plugin-imports plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/eslint-plugin-imports'] --- import kbnEslintPluginImportsObj from './kbn_eslint_plugin_imports.devdocs.json'; diff --git a/api_docs/kbn_expandable_flyout.mdx b/api_docs/kbn_expandable_flyout.mdx index d6f3aa5be6f6b..bbe08f3124717 100644 --- a/api_docs/kbn_expandable_flyout.mdx +++ b/api_docs/kbn_expandable_flyout.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-expandable-flyout title: "@kbn/expandable-flyout" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/expandable-flyout plugin -date: 2023-04-24 +date: 2023-04-25 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 4d30f7635f93f..7535845c13c1f 100644 --- a/api_docs/kbn_field_types.mdx +++ b/api_docs/kbn_field_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-field-types title: "@kbn/field-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/field-types plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/field-types'] --- import kbnFieldTypesObj from './kbn_field_types.devdocs.json'; diff --git a/api_docs/kbn_find_used_node_modules.mdx b/api_docs/kbn_find_used_node_modules.mdx index 602d4b76e4b69..a9f638b69ffe3 100644 --- a/api_docs/kbn_find_used_node_modules.mdx +++ b/api_docs/kbn_find_used_node_modules.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-find-used-node-modules title: "@kbn/find-used-node-modules" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/find-used-node-modules plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/find-used-node-modules'] --- import kbnFindUsedNodeModulesObj from './kbn_find_used_node_modules.devdocs.json'; diff --git a/api_docs/kbn_ftr_common_functional_services.mdx b/api_docs/kbn_ftr_common_functional_services.mdx index 512b1b3bf1c1c..e89bcb91ea00b 100644 --- a/api_docs/kbn_ftr_common_functional_services.mdx +++ b/api_docs/kbn_ftr_common_functional_services.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ftr-common-functional-services title: "@kbn/ftr-common-functional-services" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ftr-common-functional-services plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ftr-common-functional-services'] --- import kbnFtrCommonFunctionalServicesObj from './kbn_ftr_common_functional_services.devdocs.json'; diff --git a/api_docs/kbn_generate.mdx b/api_docs/kbn_generate.mdx index c0b9ff1ab00b0..27613f3c7e4b8 100644 --- a/api_docs/kbn_generate.mdx +++ b/api_docs/kbn_generate.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate title: "@kbn/generate" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate'] --- import kbnGenerateObj from './kbn_generate.devdocs.json'; diff --git a/api_docs/kbn_generate_csv.mdx b/api_docs/kbn_generate_csv.mdx index 3927471f08514..3969bb18a85a9 100644 --- a/api_docs/kbn_generate_csv.mdx +++ b/api_docs/kbn_generate_csv.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate-csv title: "@kbn/generate-csv" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate-csv plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate-csv'] --- import kbnGenerateCsvObj from './kbn_generate_csv.devdocs.json'; diff --git a/api_docs/kbn_generate_csv_types.mdx b/api_docs/kbn_generate_csv_types.mdx index 0e35a01c9f842..296b6f32ef717 100644 --- a/api_docs/kbn_generate_csv_types.mdx +++ b/api_docs/kbn_generate_csv_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate-csv-types title: "@kbn/generate-csv-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate-csv-types plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate-csv-types'] --- import kbnGenerateCsvTypesObj from './kbn_generate_csv_types.devdocs.json'; diff --git a/api_docs/kbn_guided_onboarding.mdx b/api_docs/kbn_guided_onboarding.mdx index fa3960f635cfa..5b5f25b8f9af4 100644 --- a/api_docs/kbn_guided_onboarding.mdx +++ b/api_docs/kbn_guided_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-guided-onboarding title: "@kbn/guided-onboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/guided-onboarding plugin -date: 2023-04-24 +date: 2023-04-25 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 52466ad20426f..b3a5386218da5 100644 --- a/api_docs/kbn_handlebars.mdx +++ b/api_docs/kbn_handlebars.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-handlebars title: "@kbn/handlebars" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/handlebars plugin -date: 2023-04-24 +date: 2023-04-25 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 cd8245459b82a..312114890ac9a 100644 --- a/api_docs/kbn_hapi_mocks.mdx +++ b/api_docs/kbn_hapi_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-hapi-mocks title: "@kbn/hapi-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/hapi-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 f6df58ece9340..a37d1ecb7a39e 100644 --- a/api_docs/kbn_health_gateway_server.mdx +++ b/api_docs/kbn_health_gateway_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-health-gateway-server title: "@kbn/health-gateway-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/health-gateway-server plugin -date: 2023-04-24 +date: 2023-04-25 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 65d82c8aa5eb7..51e5023b97924 100644 --- a/api_docs/kbn_home_sample_data_card.mdx +++ b/api_docs/kbn_home_sample_data_card.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-home-sample-data-card title: "@kbn/home-sample-data-card" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/home-sample-data-card plugin -date: 2023-04-24 +date: 2023-04-25 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 139ab20896b0d..6cfc3ecb35995 100644 --- a/api_docs/kbn_home_sample_data_tab.mdx +++ b/api_docs/kbn_home_sample_data_tab.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-home-sample-data-tab title: "@kbn/home-sample-data-tab" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/home-sample-data-tab plugin -date: 2023-04-24 +date: 2023-04-25 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 c5d491f697f42..74290f7b26982 100644 --- a/api_docs/kbn_i18n.mdx +++ b/api_docs/kbn_i18n.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-i18n title: "@kbn/i18n" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/i18n plugin -date: 2023-04-24 +date: 2023-04-25 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 50c599f41ff0c..0ccb8e7051a6d 100644 --- a/api_docs/kbn_i18n_react.mdx +++ b/api_docs/kbn_i18n_react.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-i18n-react title: "@kbn/i18n-react" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/i18n-react plugin -date: 2023-04-24 +date: 2023-04-25 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 d6377597c1189..066367700c6c0 100644 --- a/api_docs/kbn_import_resolver.mdx +++ b/api_docs/kbn_import_resolver.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-import-resolver title: "@kbn/import-resolver" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/import-resolver plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/import-resolver'] --- import kbnImportResolverObj from './kbn_import_resolver.devdocs.json'; diff --git a/api_docs/kbn_interpreter.mdx b/api_docs/kbn_interpreter.mdx index 4d82a84859ef6..cd0d3ea9a262d 100644 --- a/api_docs/kbn_interpreter.mdx +++ b/api_docs/kbn_interpreter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-interpreter title: "@kbn/interpreter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/interpreter plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/interpreter'] --- import kbnInterpreterObj from './kbn_interpreter.devdocs.json'; diff --git a/api_docs/kbn_io_ts_utils.devdocs.json b/api_docs/kbn_io_ts_utils.devdocs.json index 9b5b0e6802254..40cd0884ecb9d 100644 --- a/api_docs/kbn_io_ts_utils.devdocs.json +++ b/api_docs/kbn_io_ts_utils.devdocs.json @@ -650,6 +650,31 @@ "deprecated": false, "trackAdoption": false, "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/io-ts-utils", + "id": "def-common.NonEmptyString", + "type": "Type", + "tags": [], + "label": "NonEmptyString", + "description": [], + "signature": [ + "string & ", + "Brand", + "<", + { + "pluginId": "@kbn/io-ts-utils", + "scope": "common", + "docId": "kibKbnIoTsUtilsPluginApi", + "section": "def-common.NonEmptyStringBrand", + "text": "NonEmptyStringBrand" + }, + ">" + ], + "path": "packages/kbn-io-ts-utils/src/non_empty_string_rt/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false } ], "objects": [ diff --git a/api_docs/kbn_io_ts_utils.mdx b/api_docs/kbn_io_ts_utils.mdx index d739403a3224c..eed88f2ab3ade 100644 --- a/api_docs/kbn_io_ts_utils.mdx +++ b/api_docs/kbn_io_ts_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-io-ts-utils title: "@kbn/io-ts-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/io-ts-utils plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/io-ts-utils'] --- import kbnIoTsUtilsObj from './kbn_io_ts_utils.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/apm-ui](https://github.com/orgs/elastic/teams/apm-ui) for ques | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 38 | 0 | 38 | 4 | +| 39 | 0 | 39 | 4 | ## Common diff --git a/api_docs/kbn_jest_serializers.mdx b/api_docs/kbn_jest_serializers.mdx index 479812a7adc8e..d4acef9dbda43 100644 --- a/api_docs/kbn_jest_serializers.mdx +++ b/api_docs/kbn_jest_serializers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-jest-serializers title: "@kbn/jest-serializers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/jest-serializers plugin -date: 2023-04-24 +date: 2023-04-25 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 66f07ded66198..be4e1dfa7edf1 100644 --- a/api_docs/kbn_journeys.mdx +++ b/api_docs/kbn_journeys.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-journeys title: "@kbn/journeys" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/journeys plugin -date: 2023-04-24 +date: 2023-04-25 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 9ba905d696133..04f04d6bc73d3 100644 --- a/api_docs/kbn_json_ast.mdx +++ b/api_docs/kbn_json_ast.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-json-ast title: "@kbn/json-ast" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/json-ast plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/json-ast'] --- import kbnJsonAstObj from './kbn_json_ast.devdocs.json'; diff --git a/api_docs/kbn_kibana_manifest_schema.mdx b/api_docs/kbn_kibana_manifest_schema.mdx index 9b3835108da87..60bbd043f4e99 100644 --- a/api_docs/kbn_kibana_manifest_schema.mdx +++ b/api_docs/kbn_kibana_manifest_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-kibana-manifest-schema title: "@kbn/kibana-manifest-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/kibana-manifest-schema plugin -date: 2023-04-24 +date: 2023-04-25 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 34e8d2903cb26..9ac9f4294c031 100644 --- a/api_docs/kbn_language_documentation_popover.mdx +++ b/api_docs/kbn_language_documentation_popover.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-language-documentation-popover title: "@kbn/language-documentation-popover" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/language-documentation-popover plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/language-documentation-popover'] --- import kbnLanguageDocumentationPopoverObj from './kbn_language_documentation_popover.devdocs.json'; diff --git a/api_docs/kbn_logging.mdx b/api_docs/kbn_logging.mdx index 6c2e32868b8c7..2a8855f2702e0 100644 --- a/api_docs/kbn_logging.mdx +++ b/api_docs/kbn_logging.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-logging title: "@kbn/logging" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/logging plugin -date: 2023-04-24 +date: 2023-04-25 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 c5fa54588432d..2a1b7e6dfd5d8 100644 --- a/api_docs/kbn_logging_mocks.mdx +++ b/api_docs/kbn_logging_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-logging-mocks title: "@kbn/logging-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/logging-mocks plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging-mocks'] --- import kbnLoggingMocksObj from './kbn_logging_mocks.devdocs.json'; diff --git a/api_docs/kbn_managed_vscode_config.mdx b/api_docs/kbn_managed_vscode_config.mdx index a0492d6bad71a..35fce2db8fb5d 100644 --- a/api_docs/kbn_managed_vscode_config.mdx +++ b/api_docs/kbn_managed_vscode_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-managed-vscode-config title: "@kbn/managed-vscode-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/managed-vscode-config plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/managed-vscode-config'] --- import kbnManagedVscodeConfigObj from './kbn_managed_vscode_config.devdocs.json'; diff --git a/api_docs/kbn_mapbox_gl.mdx b/api_docs/kbn_mapbox_gl.mdx index 2cfeb2a59556c..fd94f66416a73 100644 --- a/api_docs/kbn_mapbox_gl.mdx +++ b/api_docs/kbn_mapbox_gl.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-mapbox-gl title: "@kbn/mapbox-gl" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/mapbox-gl plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/mapbox-gl'] --- import kbnMapboxGlObj from './kbn_mapbox_gl.devdocs.json'; diff --git a/api_docs/kbn_ml_agg_utils.mdx b/api_docs/kbn_ml_agg_utils.mdx index 0faf79405aec0..bbb48df7e90d2 100644 --- a/api_docs/kbn_ml_agg_utils.mdx +++ b/api_docs/kbn_ml_agg_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-agg-utils title: "@kbn/ml-agg-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-agg-utils plugin -date: 2023-04-24 +date: 2023-04-25 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_date_picker.mdx b/api_docs/kbn_ml_date_picker.mdx index 6d638b542f37f..3664a673b4ccb 100644 --- a/api_docs/kbn_ml_date_picker.mdx +++ b/api_docs/kbn_ml_date_picker.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-date-picker title: "@kbn/ml-date-picker" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-date-picker plugin -date: 2023-04-24 +date: 2023-04-25 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_error_utils.mdx b/api_docs/kbn_ml_error_utils.mdx index 39fbc8c7d9cc2..af897555cb6a4 100644 --- a/api_docs/kbn_ml_error_utils.mdx +++ b/api_docs/kbn_ml_error_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-error-utils title: "@kbn/ml-error-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-error-utils plugin -date: 2023-04-24 +date: 2023-04-25 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_is_defined.mdx b/api_docs/kbn_ml_is_defined.mdx index 22473afe43b10..0d5eec908ea3f 100644 --- a/api_docs/kbn_ml_is_defined.mdx +++ b/api_docs/kbn_ml_is_defined.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-is-defined title: "@kbn/ml-is-defined" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-is-defined plugin -date: 2023-04-24 +date: 2023-04-25 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 6418b8eaa65f6..15741af3c7fd7 100644 --- a/api_docs/kbn_ml_is_populated_object.mdx +++ b/api_docs/kbn_ml_is_populated_object.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-is-populated-object title: "@kbn/ml-is-populated-object" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-is-populated-object plugin -date: 2023-04-24 +date: 2023-04-25 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_local_storage.mdx b/api_docs/kbn_ml_local_storage.mdx index b6ab8338b1be1..64961e33e9aab 100644 --- a/api_docs/kbn_ml_local_storage.mdx +++ b/api_docs/kbn_ml_local_storage.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-local-storage title: "@kbn/ml-local-storage" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-local-storage plugin -date: 2023-04-24 +date: 2023-04-25 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 20e06ed847ebb..a73f49add8a1c 100644 --- a/api_docs/kbn_ml_nested_property.mdx +++ b/api_docs/kbn_ml_nested_property.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-nested-property title: "@kbn/ml-nested-property" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-nested-property plugin -date: 2023-04-24 +date: 2023-04-25 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 e17712d9bddd9..0b45c5cd26d28 100644 --- a/api_docs/kbn_ml_number_utils.mdx +++ b/api_docs/kbn_ml_number_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-number-utils title: "@kbn/ml-number-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-number-utils plugin -date: 2023-04-24 +date: 2023-04-25 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 ceef878888760..35f0a8b02a6b6 100644 --- a/api_docs/kbn_ml_query_utils.mdx +++ b/api_docs/kbn_ml_query_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-query-utils title: "@kbn/ml-query-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-query-utils plugin -date: 2023-04-24 +date: 2023-04-25 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 e065a2d921874..587c8453128d1 100644 --- a/api_docs/kbn_ml_random_sampler_utils.mdx +++ b/api_docs/kbn_ml_random_sampler_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-random-sampler-utils title: "@kbn/ml-random-sampler-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-random-sampler-utils plugin -date: 2023-04-24 +date: 2023-04-25 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 619f44aa95904..e1c844420142c 100644 --- a/api_docs/kbn_ml_route_utils.mdx +++ b/api_docs/kbn_ml_route_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-route-utils title: "@kbn/ml-route-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-route-utils plugin -date: 2023-04-24 +date: 2023-04-25 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_string_hash.mdx b/api_docs/kbn_ml_string_hash.mdx index c338805fe37b6..51adc66a13288 100644 --- a/api_docs/kbn_ml_string_hash.mdx +++ b/api_docs/kbn_ml_string_hash.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-string-hash title: "@kbn/ml-string-hash" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-string-hash plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-string-hash'] --- import kbnMlStringHashObj from './kbn_ml_string_hash.devdocs.json'; diff --git a/api_docs/kbn_ml_trained_models_utils.mdx b/api_docs/kbn_ml_trained_models_utils.mdx index 77df8b9fc96a9..f1eb955d2fb50 100644 --- a/api_docs/kbn_ml_trained_models_utils.mdx +++ b/api_docs/kbn_ml_trained_models_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-trained-models-utils title: "@kbn/ml-trained-models-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-trained-models-utils plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-trained-models-utils'] --- import kbnMlTrainedModelsUtilsObj from './kbn_ml_trained_models_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_url_state.mdx b/api_docs/kbn_ml_url_state.mdx index 271d8d798bb8b..21a68c43a88e9 100644 --- a/api_docs/kbn_ml_url_state.mdx +++ b/api_docs/kbn_ml_url_state.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-url-state title: "@kbn/ml-url-state" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-url-state plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-url-state'] --- import kbnMlUrlStateObj from './kbn_ml_url_state.devdocs.json'; diff --git a/api_docs/kbn_monaco.mdx b/api_docs/kbn_monaco.mdx index 0d286dc40dd46..dd405dac0663b 100644 --- a/api_docs/kbn_monaco.mdx +++ b/api_docs/kbn_monaco.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-monaco title: "@kbn/monaco" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/monaco plugin -date: 2023-04-24 +date: 2023-04-25 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 bf8dc9c9b2edb..f608b578c43e8 100644 --- a/api_docs/kbn_object_versioning.mdx +++ b/api_docs/kbn_object_versioning.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-object-versioning title: "@kbn/object-versioning" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/object-versioning plugin -date: 2023-04-24 +date: 2023-04-25 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 109f0b15690e7..3497a15c009c8 100644 --- a/api_docs/kbn_observability_alert_details.mdx +++ b/api_docs/kbn_observability_alert_details.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-observability-alert-details title: "@kbn/observability-alert-details" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/observability-alert-details plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/observability-alert-details'] --- import kbnObservabilityAlertDetailsObj from './kbn_observability_alert_details.devdocs.json'; diff --git a/api_docs/kbn_optimizer.mdx b/api_docs/kbn_optimizer.mdx index 2be790b6675bf..9d6ec4585eae9 100644 --- a/api_docs/kbn_optimizer.mdx +++ b/api_docs/kbn_optimizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer title: "@kbn/optimizer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/optimizer plugin -date: 2023-04-24 +date: 2023-04-25 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 c03883615b27e..ae408b9a0c7eb 100644 --- a/api_docs/kbn_optimizer_webpack_helpers.mdx +++ b/api_docs/kbn_optimizer_webpack_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer-webpack-helpers title: "@kbn/optimizer-webpack-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/optimizer-webpack-helpers plugin -date: 2023-04-24 +date: 2023-04-25 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 39eafb2553e2a..50d4446c8492d 100644 --- a/api_docs/kbn_osquery_io_ts_types.mdx +++ b/api_docs/kbn_osquery_io_ts_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-osquery-io-ts-types title: "@kbn/osquery-io-ts-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/osquery-io-ts-types plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/osquery-io-ts-types'] --- import kbnOsqueryIoTsTypesObj from './kbn_osquery_io_ts_types.devdocs.json'; diff --git a/api_docs/kbn_performance_testing_dataset_extractor.mdx b/api_docs/kbn_performance_testing_dataset_extractor.mdx index fe2a28edfb759..766ec15375419 100644 --- a/api_docs/kbn_performance_testing_dataset_extractor.mdx +++ b/api_docs/kbn_performance_testing_dataset_extractor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-performance-testing-dataset-extractor title: "@kbn/performance-testing-dataset-extractor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/performance-testing-dataset-extractor plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/performance-testing-dataset-extractor'] --- import kbnPerformanceTestingDatasetExtractorObj from './kbn_performance_testing_dataset_extractor.devdocs.json'; diff --git a/api_docs/kbn_plugin_generator.mdx b/api_docs/kbn_plugin_generator.mdx index dfafc2995046a..8ea785d2a6232 100644 --- a/api_docs/kbn_plugin_generator.mdx +++ b/api_docs/kbn_plugin_generator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-generator title: "@kbn/plugin-generator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-generator plugin -date: 2023-04-24 +date: 2023-04-25 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 90bfd0727dc86..91024484857b2 100644 --- a/api_docs/kbn_plugin_helpers.mdx +++ b/api_docs/kbn_plugin_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-helpers title: "@kbn/plugin-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-helpers plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-helpers'] --- import kbnPluginHelpersObj from './kbn_plugin_helpers.devdocs.json'; diff --git a/api_docs/kbn_react_field.mdx b/api_docs/kbn_react_field.mdx index 894f6e1897cb7..2090a6ff74782 100644 --- a/api_docs/kbn_react_field.mdx +++ b/api_docs/kbn_react_field.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-field title: "@kbn/react-field" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-field plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-field'] --- import kbnReactFieldObj from './kbn_react_field.devdocs.json'; diff --git a/api_docs/kbn_repo_file_maps.mdx b/api_docs/kbn_repo_file_maps.mdx index 9baafe7d47198..9898246c243f3 100644 --- a/api_docs/kbn_repo_file_maps.mdx +++ b/api_docs/kbn_repo_file_maps.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-file-maps title: "@kbn/repo-file-maps" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-file-maps plugin -date: 2023-04-24 +date: 2023-04-25 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 0dcb15d07b2b0..f2350045bb743 100644 --- a/api_docs/kbn_repo_linter.mdx +++ b/api_docs/kbn_repo_linter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-linter title: "@kbn/repo-linter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-linter plugin -date: 2023-04-24 +date: 2023-04-25 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 44df1cb8c7ba8..609ccdea58475 100644 --- a/api_docs/kbn_repo_path.mdx +++ b/api_docs/kbn_repo_path.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-path title: "@kbn/repo-path" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-path plugin -date: 2023-04-24 +date: 2023-04-25 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 3725fe5986da2..309b78834d066 100644 --- a/api_docs/kbn_repo_source_classifier.mdx +++ b/api_docs/kbn_repo_source_classifier.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-source-classifier title: "@kbn/repo-source-classifier" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-source-classifier plugin -date: 2023-04-24 +date: 2023-04-25 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 660528756fcdf..934a85e2f0a20 100644 --- a/api_docs/kbn_reporting_common.mdx +++ b/api_docs/kbn_reporting_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-common title: "@kbn/reporting-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-common plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-common'] --- import kbnReportingCommonObj from './kbn_reporting_common.devdocs.json'; diff --git a/api_docs/kbn_rison.mdx b/api_docs/kbn_rison.mdx index ee1fd9df0086e..991b131f66034 100644 --- a/api_docs/kbn_rison.mdx +++ b/api_docs/kbn_rison.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rison title: "@kbn/rison" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rison plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rison'] --- import kbnRisonObj from './kbn_rison.devdocs.json'; diff --git a/api_docs/kbn_rule_data_utils.mdx b/api_docs/kbn_rule_data_utils.mdx index 56b40b98c6111..db06808e4b24b 100644 --- a/api_docs/kbn_rule_data_utils.mdx +++ b/api_docs/kbn_rule_data_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rule-data-utils title: "@kbn/rule-data-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rule-data-utils plugin -date: 2023-04-24 +date: 2023-04-25 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 ac3326c810bdf..1f5facec95281 100644 --- a/api_docs/kbn_saved_objects_settings.mdx +++ b/api_docs/kbn_saved_objects_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-saved-objects-settings title: "@kbn/saved-objects-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/saved-objects-settings plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/saved-objects-settings'] --- import kbnSavedObjectsSettingsObj from './kbn_saved_objects_settings.devdocs.json'; diff --git a/api_docs/kbn_security_solution_side_nav.mdx b/api_docs/kbn_security_solution_side_nav.mdx index dbd27615a2886..8d753b443430d 100644 --- a/api_docs/kbn_security_solution_side_nav.mdx +++ b/api_docs/kbn_security_solution_side_nav.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-side-nav title: "@kbn/security-solution-side-nav" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-side-nav plugin -date: 2023-04-24 +date: 2023-04-25 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 31bc6c9d9cbc9..6a7febcc12770 100644 --- a/api_docs/kbn_security_solution_storybook_config.mdx +++ b/api_docs/kbn_security_solution_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-storybook-config title: "@kbn/security-solution-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-storybook-config plugin -date: 2023-04-24 +date: 2023-04-25 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 f51ac2903abc5..40bf71465317b 100644 --- a/api_docs/kbn_securitysolution_autocomplete.mdx +++ b/api_docs/kbn_securitysolution_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-autocomplete title: "@kbn/securitysolution-autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-autocomplete plugin -date: 2023-04-24 +date: 2023-04-25 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 cbb7736cb66be..ae067bd381300 100644 --- a/api_docs/kbn_securitysolution_data_table.mdx +++ b/api_docs/kbn_securitysolution_data_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-data-table title: "@kbn/securitysolution-data-table" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-data-table plugin -date: 2023-04-24 +date: 2023-04-25 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 2d549e62753a9..226588554d715 100644 --- a/api_docs/kbn_securitysolution_ecs.mdx +++ b/api_docs/kbn_securitysolution_ecs.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-ecs title: "@kbn/securitysolution-ecs" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-ecs plugin -date: 2023-04-24 +date: 2023-04-25 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 966df1d43affc..5659087472cfd 100644 --- a/api_docs/kbn_securitysolution_es_utils.mdx +++ b/api_docs/kbn_securitysolution_es_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-es-utils title: "@kbn/securitysolution-es-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-es-utils plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-es-utils'] --- import kbnSecuritysolutionEsUtilsObj from './kbn_securitysolution_es_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_exception_list_components.mdx b/api_docs/kbn_securitysolution_exception_list_components.mdx index ef163cd54d784..8825da7e86191 100644 --- a/api_docs/kbn_securitysolution_exception_list_components.mdx +++ b/api_docs/kbn_securitysolution_exception_list_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-exception-list-components title: "@kbn/securitysolution-exception-list-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-exception-list-components plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-exception-list-components'] --- import kbnSecuritysolutionExceptionListComponentsObj from './kbn_securitysolution_exception_list_components.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_grouping.devdocs.json b/api_docs/kbn_securitysolution_grouping.devdocs.json index 710ef66fad8aa..d3c5052958e2d 100644 --- a/api_docs/kbn_securitysolution_grouping.devdocs.json +++ b/api_docs/kbn_securitysolution_grouping.devdocs.json @@ -29,7 +29,7 @@ "\nComposes grouping query and aggregations" ], "signature": [ - "({ additionalFilters, from, groupByFields, pageNumber, rootAggregations, runtimeMappings, size, sort, statsAggregations, to, }: ", + "({ additionalFilters, from, groupByFields, rootAggregations, runtimeMappings, size, pageNumber, sort, statsAggregations, to, }: ", "GroupingQueryArgs", ") => ", "GroupingQuery" @@ -43,7 +43,7 @@ "id": "def-common.getGroupingQuery.$1", "type": "Object", "tags": [], - "label": "{\n additionalFilters = [],\n from,\n groupByFields,\n pageNumber,\n rootAggregations,\n runtimeMappings,\n size = DEFAULT_GROUP_BY_FIELD_SIZE,\n sort,\n statsAggregations,\n to,\n}", + "label": "{\n additionalFilters = [],\n from,\n groupByFields,\n rootAggregations,\n runtimeMappings,\n size = DEFAULT_GROUP_BY_FIELD_SIZE,\n pageNumber,\n sort,\n statsAggregations,\n to,\n}", "description": [], "signature": [ "GroupingQueryArgs" @@ -69,7 +69,7 @@ "\nChecks if no group is selected" ], "signature": [ - "(groupKey: string | null) => boolean" + "(groupKeys: string[]) => boolean" ], "path": "packages/kbn-securitysolution-grouping/src/components/index.tsx", "deprecated": false, @@ -78,19 +78,19 @@ { "parentPluginId": "@kbn/securitysolution-grouping", "id": "def-common.isNoneGroup.$1", - "type": "CompoundType", + "type": "Array", "tags": [], - "label": "groupKey", + "label": "groupKeys", "description": [ - "selected group field value" + "selected group field values" ], "signature": [ - "string | null" + "string[]" ], "path": "packages/kbn-securitysolution-grouping/src/components/index.tsx", "deprecated": false, "trackAdoption": false, - "isRequired": false + "isRequired": true } ], "returnComment": [ @@ -108,7 +108,7 @@ "\nHook to configure grouping component" ], "signature": [ - "({ componentProps, defaultGroupingOptions, fields, groupingId, onGroupChange, tracker, }: GroupingArgs) => Grouping" + "({ componentProps, defaultGroupingOptions, fields, groupingId, maxGroupingLevels, onGroupChange, tracker, }: GroupingArgs) => Grouping" ], "path": "packages/kbn-securitysolution-grouping/src/hooks/use_grouping.tsx", "deprecated": false, @@ -119,7 +119,7 @@ "id": "def-common.useGrouping.$1", "type": "Object", "tags": [], - "label": "{\n componentProps,\n defaultGroupingOptions,\n fields,\n groupingId,\n onGroupChange,\n tracker,\n}", + "label": "{\n componentProps,\n defaultGroupingOptions,\n fields,\n groupingId,\n maxGroupingLevels,\n onGroupChange,\n tracker,\n}", "description": [], "signature": [ "GroupingArgs" @@ -135,82 +135,6 @@ } ], "interfaces": [ - { - "parentPluginId": "@kbn/securitysolution-grouping", - "id": "def-common.GroupingAggregation", - "type": "Interface", - "tags": [], - "label": "GroupingAggregation", - "description": [ - "Defines the shape of the aggregation returned by Elasticsearch" - ], - "signature": [ - { - "pluginId": "@kbn/securitysolution-grouping", - "scope": "common", - "docId": "kibKbnSecuritysolutionGroupingPluginApi", - "section": "def-common.GroupingAggregation", - "text": "GroupingAggregation" - }, - "" - ], - "path": "packages/kbn-securitysolution-grouping/src/components/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/securitysolution-grouping", - "id": "def-common.GroupingAggregation.groupByFields", - "type": "Object", - "tags": [], - "label": "groupByFields", - "description": [], - "signature": [ - "{ buckets?: ", - { - "pluginId": "@kbn/securitysolution-grouping", - "scope": "common", - "docId": "kibKbnSecuritysolutionGroupingPluginApi", - "section": "def-common.RawBucket", - "text": "RawBucket" - }, - "[] | undefined; } | undefined" - ], - "path": "packages/kbn-securitysolution-grouping/src/components/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "@kbn/securitysolution-grouping", - "id": "def-common.GroupingAggregation.groupsCount", - "type": "Object", - "tags": [], - "label": "groupsCount", - "description": [], - "signature": [ - "{ value?: number | null | undefined; } | undefined" - ], - "path": "packages/kbn-securitysolution-grouping/src/components/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "@kbn/securitysolution-grouping", - "id": "def-common.GroupingAggregation.unitsCount", - "type": "Object", - "tags": [], - "label": "unitsCount", - "description": [], - "signature": [ - "{ value?: number | null | undefined; } | undefined" - ], - "path": "packages/kbn-securitysolution-grouping/src/components/types.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, { "parentPluginId": "@kbn/securitysolution-grouping", "id": "def-common.GroupOption", @@ -306,21 +230,59 @@ "misc": [ { "parentPluginId": "@kbn/securitysolution-grouping", - "id": "def-common.GroupingFieldTotalAggregation", + "id": "def-common.DynamicGroupingProps", "type": "Type", - "tags": [], - "label": "GroupingFieldTotalAggregation", - "description": [], + "tags": [ + "interface" + ], + "label": "DynamicGroupingProps", + "description": [ + "Type for dynamic grouping component props where T is the consumer `GroupingAggregation`" + ], "signature": [ - "{ [x: string]: { value?: number | null | undefined; buckets?: ", + "{ isLoading: boolean; data?: ", { "pluginId": "@kbn/securitysolution-grouping", "scope": "common", "docId": "kibKbnSecuritysolutionGroupingPluginApi", - "section": "def-common.RawBucket", - "text": "RawBucket" + "section": "def-common.GroupingAggregation", + "text": "GroupingAggregation" }, - "[] | undefined; }; }" + " | undefined; activePage: number; itemsPerPage: number; groupingLevel?: number | undefined; inspectButton?: JSX.Element | undefined; onChangeGroupsItemsPerPage?: ((size: number) => void) | undefined; onChangeGroupsPage?: ((index: number) => void) | undefined; renderChildComponent: (groupFilter: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[]) => React.ReactElement>; onGroupClose: () => void; selectedGroup: string; takeActionItems: (groupFilters: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[], groupNumber: number) => JSX.Element[]; }" + ], + "path": "packages/kbn-securitysolution-grouping/src/hooks/use_grouping.tsx", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/securitysolution-grouping", + "id": "def-common.GroupingAggregation", + "type": "Type", + "tags": [], + "label": "GroupingAggregation", + "description": [], + "signature": [ + "RootAggregation", + " & ", + "GroupingFieldTotalAggregation", + "" ], "path": "packages/kbn-securitysolution-grouping/src/components/types.ts", "deprecated": false, diff --git a/api_docs/kbn_securitysolution_grouping.mdx b/api_docs/kbn_securitysolution_grouping.mdx index 548914f17feb8..2b716679e092a 100644 --- a/api_docs/kbn_securitysolution_grouping.mdx +++ b/api_docs/kbn_securitysolution_grouping.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-grouping title: "@kbn/securitysolution-grouping" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-grouping plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-grouping'] --- import kbnSecuritysolutionGroupingObj from './kbn_securitysolution_grouping.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/security-threat-hunting-explore](https://github.com/orgs/elast | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 20 | 0 | 15 | 4 | +| 17 | 0 | 12 | 6 | ## Common diff --git a/api_docs/kbn_securitysolution_hook_utils.mdx b/api_docs/kbn_securitysolution_hook_utils.mdx index 012e37a5de10c..e2bbd21ba5d00 100644 --- a/api_docs/kbn_securitysolution_hook_utils.mdx +++ b/api_docs/kbn_securitysolution_hook_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-hook-utils title: "@kbn/securitysolution-hook-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-hook-utils plugin -date: 2023-04-24 +date: 2023-04-25 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 c19977b427358..b16aed40d059c 100644 --- a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-alerting-types title: "@kbn/securitysolution-io-ts-alerting-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-alerting-types plugin -date: 2023-04-24 +date: 2023-04-25 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 61c51d7af0af1..16ac8b89a1297 100644 --- a/api_docs/kbn_securitysolution_io_ts_list_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_list_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-list-types title: "@kbn/securitysolution-io-ts-list-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-list-types plugin -date: 2023-04-24 +date: 2023-04-25 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 97859ef4b0ff6..4ad236560201d 100644 --- a/api_docs/kbn_securitysolution_io_ts_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-types title: "@kbn/securitysolution-io-ts-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-types plugin -date: 2023-04-24 +date: 2023-04-25 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 087b27374d68b..0a2855949754a 100644 --- a/api_docs/kbn_securitysolution_io_ts_utils.mdx +++ b/api_docs/kbn_securitysolution_io_ts_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-utils title: "@kbn/securitysolution-io-ts-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-utils plugin -date: 2023-04-24 +date: 2023-04-25 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 aadca05334b92..652e9b2b4eee7 100644 --- a/api_docs/kbn_securitysolution_list_api.mdx +++ b/api_docs/kbn_securitysolution_list_api.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-api title: "@kbn/securitysolution-list-api" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-api plugin -date: 2023-04-24 +date: 2023-04-25 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 2d19d6111ef53..b9f07add0070b 100644 --- a/api_docs/kbn_securitysolution_list_constants.mdx +++ b/api_docs/kbn_securitysolution_list_constants.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-constants title: "@kbn/securitysolution-list-constants" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-constants plugin -date: 2023-04-24 +date: 2023-04-25 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 e5f63ce69a791..8f978e102091b 100644 --- a/api_docs/kbn_securitysolution_list_hooks.mdx +++ b/api_docs/kbn_securitysolution_list_hooks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-hooks title: "@kbn/securitysolution-list-hooks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-hooks plugin -date: 2023-04-24 +date: 2023-04-25 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 9fbc74f2281fa..6dae9754b092b 100644 --- a/api_docs/kbn_securitysolution_list_utils.mdx +++ b/api_docs/kbn_securitysolution_list_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-utils title: "@kbn/securitysolution-list-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-utils plugin -date: 2023-04-24 +date: 2023-04-25 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 d99c895cfbb07..0c8dfd59d0756 100644 --- a/api_docs/kbn_securitysolution_rules.mdx +++ b/api_docs/kbn_securitysolution_rules.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-rules title: "@kbn/securitysolution-rules" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-rules plugin -date: 2023-04-24 +date: 2023-04-25 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 fc1a7126299da..4040df55dfbb5 100644 --- a/api_docs/kbn_securitysolution_t_grid.mdx +++ b/api_docs/kbn_securitysolution_t_grid.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-t-grid title: "@kbn/securitysolution-t-grid" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-t-grid plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-t-grid'] --- import kbnSecuritysolutionTGridObj from './kbn_securitysolution_t_grid.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_utils.mdx b/api_docs/kbn_securitysolution_utils.mdx index 5b42ba79461cd..7e0d6a471876b 100644 --- a/api_docs/kbn_securitysolution_utils.mdx +++ b/api_docs/kbn_securitysolution_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-utils title: "@kbn/securitysolution-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-utils plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-utils'] --- import kbnSecuritysolutionUtilsObj from './kbn_securitysolution_utils.devdocs.json'; diff --git a/api_docs/kbn_server_http_tools.mdx b/api_docs/kbn_server_http_tools.mdx index c538168844e59..32506057fb398 100644 --- a/api_docs/kbn_server_http_tools.mdx +++ b/api_docs/kbn_server_http_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-http-tools title: "@kbn/server-http-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-http-tools plugin -date: 2023-04-24 +date: 2023-04-25 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 7c066060f57b3..b046e0bf3a0b1 100644 --- a/api_docs/kbn_server_route_repository.mdx +++ b/api_docs/kbn_server_route_repository.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-route-repository title: "@kbn/server-route-repository" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-route-repository plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-route-repository'] --- import kbnServerRouteRepositoryObj from './kbn_server_route_repository.devdocs.json'; diff --git a/api_docs/kbn_shared_svg.mdx b/api_docs/kbn_shared_svg.mdx index 57b4b0757c666..72d7f5a5a936b 100644 --- a/api_docs/kbn_shared_svg.mdx +++ b/api_docs/kbn_shared_svg.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-svg title: "@kbn/shared-svg" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-svg plugin -date: 2023-04-24 +date: 2023-04-25 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 0e0a7de059854..027126e63d38b 100644 --- a/api_docs/kbn_shared_ux_avatar_solution.mdx +++ b/api_docs/kbn_shared_ux_avatar_solution.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-avatar-solution title: "@kbn/shared-ux-avatar-solution" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-avatar-solution plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-avatar-solution'] --- import kbnSharedUxAvatarSolutionObj from './kbn_shared_ux_avatar_solution.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx b/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx index cbe1bbc592105..1f698d252027e 100644 --- a/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx +++ b/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-avatar-user-profile-components title: "@kbn/shared-ux-avatar-user-profile-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-avatar-user-profile-components plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-avatar-user-profile-components'] --- import kbnSharedUxAvatarUserProfileComponentsObj from './kbn_shared_ux_avatar_user_profile_components.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_exit_full_screen.mdx b/api_docs/kbn_shared_ux_button_exit_full_screen.mdx index fd754396fe38d..0cb0c6e120097 100644 --- a/api_docs/kbn_shared_ux_button_exit_full_screen.mdx +++ b/api_docs/kbn_shared_ux_button_exit_full_screen.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-exit-full-screen title: "@kbn/shared-ux-button-exit-full-screen" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-exit-full-screen plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-exit-full-screen'] --- import kbnSharedUxButtonExitFullScreenObj from './kbn_shared_ux_button_exit_full_screen.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx b/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx index 704bf0d55a588..4db2e15234b2c 100644 --- a/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx +++ b/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-exit-full-screen-mocks title: "@kbn/shared-ux-button-exit-full-screen-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-exit-full-screen-mocks plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-exit-full-screen-mocks'] --- import kbnSharedUxButtonExitFullScreenMocksObj from './kbn_shared_ux_button_exit_full_screen_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_toolbar.mdx b/api_docs/kbn_shared_ux_button_toolbar.mdx index d5cbbf1440008..dcf9d9bec380e 100644 --- a/api_docs/kbn_shared_ux_button_toolbar.mdx +++ b/api_docs/kbn_shared_ux_button_toolbar.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-toolbar title: "@kbn/shared-ux-button-toolbar" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-toolbar plugin -date: 2023-04-24 +date: 2023-04-25 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 6e187d6a67d6d..f36cb7d427e59 100644 --- a/api_docs/kbn_shared_ux_card_no_data.mdx +++ b/api_docs/kbn_shared_ux_card_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-card-no-data title: "@kbn/shared-ux-card-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-card-no-data plugin -date: 2023-04-24 +date: 2023-04-25 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 f1eafa0073b75..463a82c4199c4 100644 --- a/api_docs/kbn_shared_ux_card_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_card_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-card-no-data-mocks title: "@kbn/shared-ux-card-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-card-no-data-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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_file_context.mdx b/api_docs/kbn_shared_ux_file_context.mdx index 345346e831748..a497f9b7b3b39 100644 --- a/api_docs/kbn_shared_ux_file_context.mdx +++ b/api_docs/kbn_shared_ux_file_context.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-context title: "@kbn/shared-ux-file-context" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-context plugin -date: 2023-04-24 +date: 2023-04-25 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 e242bd651fa0d..5a10a184e8612 100644 --- a/api_docs/kbn_shared_ux_file_image.mdx +++ b/api_docs/kbn_shared_ux_file_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-image title: "@kbn/shared-ux-file-image" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-image plugin -date: 2023-04-24 +date: 2023-04-25 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 4e391b9d8ab27..487bdb38fb383 100644 --- a/api_docs/kbn_shared_ux_file_image_mocks.mdx +++ b/api_docs/kbn_shared_ux_file_image_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-image-mocks title: "@kbn/shared-ux-file-image-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-image-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 662c634769dd1..03da8f2f7777b 100644 --- a/api_docs/kbn_shared_ux_file_mocks.mdx +++ b/api_docs/kbn_shared_ux_file_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-mocks title: "@kbn/shared-ux-file-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 1e22cbdd34b00..ab7439a55c766 100644 --- a/api_docs/kbn_shared_ux_file_picker.mdx +++ b/api_docs/kbn_shared_ux_file_picker.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-picker title: "@kbn/shared-ux-file-picker" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-picker plugin -date: 2023-04-24 +date: 2023-04-25 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.devdocs.json b/api_docs/kbn_shared_ux_file_types.devdocs.json index de6c162159d55..bd0ba52bf411a 100644 --- a/api_docs/kbn_shared_ux_file_types.devdocs.json +++ b/api_docs/kbn_shared_ux_file_types.devdocs.json @@ -79,7 +79,7 @@ "\nFind a set of files given some filters.\n" ], "signature": [ - "(args: { kind?: string | string[] | undefined; status?: string | string[] | undefined; extension?: string | string[] | undefined; name?: string | string[] | undefined; meta?: M | undefined; } & ", + "(args: { kind?: string | string[] | undefined; kindToExclude?: string | string[] | undefined; status?: string | string[] | undefined; extension?: string | string[] | undefined; name?: string | string[] | undefined; meta?: M | undefined; } & ", { "pluginId": "@kbn/shared-ux-file-types", "scope": "common", @@ -119,7 +119,7 @@ "- File filters" ], "signature": [ - "{ kind?: string | string[] | undefined; status?: string | string[] | undefined; extension?: string | string[] | undefined; name?: string | string[] | undefined; meta?: M | undefined; } & ", + "{ kind?: string | string[] | undefined; kindToExclude?: string | string[] | undefined; status?: string | string[] | undefined; extension?: string | string[] | undefined; name?: string | string[] | undefined; meta?: M | undefined; } & ", { "pluginId": "@kbn/shared-ux-file-types", "scope": "common", @@ -1301,6 +1301,22 @@ "path": "packages/shared-ux/file/types/index.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-file-types", + "id": "def-common.FileKindBrowser.managementUiActions", + "type": "Object", + "tags": [], + "label": "managementUiActions", + "description": [ + "\nAllowed actions that can be done in the File Management UI. If not provided, all actions are allowed\n" + ], + "signature": [ + "{ list?: { enabled: boolean; } | undefined; delete?: { enabled: boolean; reason?: string | undefined; } | undefined; } | undefined" + ], + "path": "packages/shared-ux/file/types/index.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/kbn_shared_ux_file_types.mdx b/api_docs/kbn_shared_ux_file_types.mdx index 43c10d7423ef7..426d6f89d33ea 100644 --- a/api_docs/kbn_shared_ux_file_types.mdx +++ b/api_docs/kbn_shared_ux_file_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-types title: "@kbn/shared-ux-file-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-types plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-types'] --- import kbnSharedUxFileTypesObj from './kbn_shared_ux_file_types.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sh | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 70 | 0 | 9 | 0 | +| 71 | 0 | 9 | 0 | ## Common diff --git a/api_docs/kbn_shared_ux_file_upload.mdx b/api_docs/kbn_shared_ux_file_upload.mdx index 7a684a3ddf9b6..17278d3391172 100644 --- a/api_docs/kbn_shared_ux_file_upload.mdx +++ b/api_docs/kbn_shared_ux_file_upload.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-upload title: "@kbn/shared-ux-file-upload" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-upload plugin -date: 2023-04-24 +date: 2023-04-25 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 7a12cb245836a..90687689300cf 100644 --- a/api_docs/kbn_shared_ux_file_util.mdx +++ b/api_docs/kbn_shared_ux_file_util.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-util title: "@kbn/shared-ux-file-util" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-util plugin -date: 2023-04-24 +date: 2023-04-25 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 dc82b967602d2..45abcd789302e 100644 --- a/api_docs/kbn_shared_ux_link_redirect_app.mdx +++ b/api_docs/kbn_shared_ux_link_redirect_app.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-link-redirect-app title: "@kbn/shared-ux-link-redirect-app" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-link-redirect-app plugin -date: 2023-04-24 +date: 2023-04-25 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 18b157f050418..abad65bf4bc03 100644 --- a/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx +++ b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-link-redirect-app-mocks title: "@kbn/shared-ux-link-redirect-app-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-link-redirect-app-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 8d43b2bc2609d..582712bf883ad 100644 --- a/api_docs/kbn_shared_ux_markdown.mdx +++ b/api_docs/kbn_shared_ux_markdown.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-markdown title: "@kbn/shared-ux-markdown" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-markdown plugin -date: 2023-04-24 +date: 2023-04-25 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 6ad8500d4ffa1..3f11bf724f8ff 100644 --- a/api_docs/kbn_shared_ux_markdown_mocks.mdx +++ b/api_docs/kbn_shared_ux_markdown_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-markdown-mocks title: "@kbn/shared-ux-markdown-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-markdown-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 da16a3294719e..a73b2130810da 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-analytics-no-data title: "@kbn/shared-ux-page-analytics-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-analytics-no-data plugin -date: 2023-04-24 +date: 2023-04-25 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 a79ab025d8f39..be46cd48c00e3 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-analytics-no-data-mocks title: "@kbn/shared-ux-page-analytics-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-analytics-no-data-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 675d8ca547baa..9f218fb8b7711 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-no-data title: "@kbn/shared-ux-page-kibana-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-no-data plugin -date: 2023-04-24 +date: 2023-04-25 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 87063d4968ca9..d1fb5d6a6f424 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-no-data-mocks title: "@kbn/shared-ux-page-kibana-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-no-data-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 d83b54deb377f..6a03b3207fd5a 100644 --- a/api_docs/kbn_shared_ux_page_kibana_template.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_template.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-template title: "@kbn/shared-ux-page-kibana-template" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-template plugin -date: 2023-04-24 +date: 2023-04-25 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 a915fe7ce8d2f..b0a172e51dcd4 100644 --- a/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-template-mocks title: "@kbn/shared-ux-page-kibana-template-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-template-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 d386725c0f96e..0f4655e1c7b2c 100644 --- a/api_docs/kbn_shared_ux_page_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data title: "@kbn/shared-ux-page-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data plugin -date: 2023-04-24 +date: 2023-04-25 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 b609f61949e3a..5b97d695996a6 100644 --- a/api_docs/kbn_shared_ux_page_no_data_config.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-config title: "@kbn/shared-ux-page-no-data-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-config plugin -date: 2023-04-24 +date: 2023-04-25 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 3b8a6aea768c4..d0c153561221c 100644 --- a/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-config-mocks title: "@kbn/shared-ux-page-no-data-config-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-config-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 b549d3be8ce48..20d822602caac 100644 --- a/api_docs/kbn_shared_ux_page_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-mocks title: "@kbn/shared-ux-page-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 57b7f77917dbf..f59ea8eee9b01 100644 --- a/api_docs/kbn_shared_ux_page_solution_nav.mdx +++ b/api_docs/kbn_shared_ux_page_solution_nav.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-solution-nav title: "@kbn/shared-ux-page-solution-nav" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-solution-nav plugin -date: 2023-04-24 +date: 2023-04-25 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 cde75adfcbd03..7c1a23d42acbb 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-no-data-views title: "@kbn/shared-ux-prompt-no-data-views" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-no-data-views plugin -date: 2023-04-24 +date: 2023-04-25 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 449ec14d0e89b..a3b987ec20f3f 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-no-data-views-mocks title: "@kbn/shared-ux-prompt-no-data-views-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-no-data-views-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 54e8a60fb3a71..1ead3393a1d1e 100644 --- a/api_docs/kbn_shared_ux_prompt_not_found.mdx +++ b/api_docs/kbn_shared_ux_prompt_not_found.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-not-found title: "@kbn/shared-ux-prompt-not-found" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-not-found plugin -date: 2023-04-24 +date: 2023-04-25 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 64431ebf0a9f6..b85098cc1faf9 100644 --- a/api_docs/kbn_shared_ux_router.mdx +++ b/api_docs/kbn_shared_ux_router.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-router title: "@kbn/shared-ux-router" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-router plugin -date: 2023-04-24 +date: 2023-04-25 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 8f903a6a4599b..1a2dfe73599e1 100644 --- a/api_docs/kbn_shared_ux_router_mocks.mdx +++ b/api_docs/kbn_shared_ux_router_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-router-mocks title: "@kbn/shared-ux-router-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-router-mocks plugin -date: 2023-04-24 +date: 2023-04-25 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 aa8aeb5ed628b..cd277b50d00af 100644 --- a/api_docs/kbn_shared_ux_storybook_config.mdx +++ b/api_docs/kbn_shared_ux_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook-config title: "@kbn/shared-ux-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-storybook-config plugin -date: 2023-04-24 +date: 2023-04-25 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 1f594a6b27503..12a97778fad87 100644 --- a/api_docs/kbn_shared_ux_storybook_mock.mdx +++ b/api_docs/kbn_shared_ux_storybook_mock.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook-mock title: "@kbn/shared-ux-storybook-mock" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-storybook-mock plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-storybook-mock'] --- import kbnSharedUxStorybookMockObj from './kbn_shared_ux_storybook_mock.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_utility.mdx b/api_docs/kbn_shared_ux_utility.mdx index 35030e8f65b69..0c09f2472b024 100644 --- a/api_docs/kbn_shared_ux_utility.mdx +++ b/api_docs/kbn_shared_ux_utility.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-utility title: "@kbn/shared-ux-utility" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-utility plugin -date: 2023-04-24 +date: 2023-04-25 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 b86313f3a585f..b14e5c2841704 100644 --- a/api_docs/kbn_slo_schema.mdx +++ b/api_docs/kbn_slo_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-slo-schema title: "@kbn/slo-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/slo-schema plugin -date: 2023-04-24 +date: 2023-04-25 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 8a4c14deb442e..714f8f1f61bda 100644 --- a/api_docs/kbn_some_dev_log.mdx +++ b/api_docs/kbn_some_dev_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-some-dev-log title: "@kbn/some-dev-log" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/some-dev-log plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/some-dev-log'] --- import kbnSomeDevLogObj from './kbn_some_dev_log.devdocs.json'; diff --git a/api_docs/kbn_std.mdx b/api_docs/kbn_std.mdx index 8a33d6033f83b..be0949d0ede01 100644 --- a/api_docs/kbn_std.mdx +++ b/api_docs/kbn_std.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-std title: "@kbn/std" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/std plugin -date: 2023-04-24 +date: 2023-04-25 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 8bb33b6f9fc6c..3bc8c63d56fa1 100644 --- a/api_docs/kbn_stdio_dev_helpers.mdx +++ b/api_docs/kbn_stdio_dev_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-stdio-dev-helpers title: "@kbn/stdio-dev-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/stdio-dev-helpers plugin -date: 2023-04-24 +date: 2023-04-25 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 0dcbe8b65314a..576f6d4cd96da 100644 --- a/api_docs/kbn_storybook.mdx +++ b/api_docs/kbn_storybook.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-storybook title: "@kbn/storybook" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/storybook plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/storybook'] --- import kbnStorybookObj from './kbn_storybook.devdocs.json'; diff --git a/api_docs/kbn_telemetry_tools.mdx b/api_docs/kbn_telemetry_tools.mdx index 009ae2d00a260..1b27ea9251410 100644 --- a/api_docs/kbn_telemetry_tools.mdx +++ b/api_docs/kbn_telemetry_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-telemetry-tools title: "@kbn/telemetry-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/telemetry-tools plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/telemetry-tools'] --- import kbnTelemetryToolsObj from './kbn_telemetry_tools.devdocs.json'; diff --git a/api_docs/kbn_test.mdx b/api_docs/kbn_test.mdx index 077c288dbbd06..69b0b63437380 100644 --- a/api_docs/kbn_test.mdx +++ b/api_docs/kbn_test.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test title: "@kbn/test" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test'] --- import kbnTestObj from './kbn_test.devdocs.json'; diff --git a/api_docs/kbn_test_jest_helpers.mdx b/api_docs/kbn_test_jest_helpers.mdx index ac6e29488cd2d..503316ebfede3 100644 --- a/api_docs/kbn_test_jest_helpers.mdx +++ b/api_docs/kbn_test_jest_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-jest-helpers title: "@kbn/test-jest-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-jest-helpers plugin -date: 2023-04-24 +date: 2023-04-25 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 4b38e19d2c144..5c043b0c4a6c1 100644 --- a/api_docs/kbn_test_subj_selector.mdx +++ b/api_docs/kbn_test_subj_selector.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-subj-selector title: "@kbn/test-subj-selector" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-subj-selector plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-subj-selector'] --- import kbnTestSubjSelectorObj from './kbn_test_subj_selector.devdocs.json'; diff --git a/api_docs/kbn_tooling_log.mdx b/api_docs/kbn_tooling_log.mdx index 1e093e1c87a67..493ea7c87e5ed 100644 --- a/api_docs/kbn_tooling_log.mdx +++ b/api_docs/kbn_tooling_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-tooling-log title: "@kbn/tooling-log" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/tooling-log plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/tooling-log'] --- import kbnToolingLogObj from './kbn_tooling_log.devdocs.json'; diff --git a/api_docs/kbn_ts_projects.mdx b/api_docs/kbn_ts_projects.mdx index ec139fac154dc..9f58d89939bd4 100644 --- a/api_docs/kbn_ts_projects.mdx +++ b/api_docs/kbn_ts_projects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ts-projects title: "@kbn/ts-projects" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ts-projects plugin -date: 2023-04-24 +date: 2023-04-25 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 c335bc8053866..9cdd2a92c31ff 100644 --- a/api_docs/kbn_typed_react_router_config.mdx +++ b/api_docs/kbn_typed_react_router_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-typed-react-router-config title: "@kbn/typed-react-router-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/typed-react-router-config plugin -date: 2023-04-24 +date: 2023-04-25 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 e4f4818385008..06135cf53284d 100644 --- a/api_docs/kbn_ui_actions_browser.mdx +++ b/api_docs/kbn_ui_actions_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-actions-browser title: "@kbn/ui-actions-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-actions-browser plugin -date: 2023-04-24 +date: 2023-04-25 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 cfc3a2c905dbb..e4fcda7eaf227 100644 --- a/api_docs/kbn_ui_shared_deps_src.mdx +++ b/api_docs/kbn_ui_shared_deps_src.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-shared-deps-src title: "@kbn/ui-shared-deps-src" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-shared-deps-src plugin -date: 2023-04-24 +date: 2023-04-25 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 2306fe2c1523e..8918f46eeb6f0 100644 --- a/api_docs/kbn_ui_theme.mdx +++ b/api_docs/kbn_ui_theme.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-theme title: "@kbn/ui-theme" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-theme plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-theme'] --- import kbnUiThemeObj from './kbn_ui_theme.devdocs.json'; diff --git a/api_docs/kbn_url_state.mdx b/api_docs/kbn_url_state.mdx index 1ecdc4cf5f6b8..57ddb467440df 100644 --- a/api_docs/kbn_url_state.mdx +++ b/api_docs/kbn_url_state.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-url-state title: "@kbn/url-state" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/url-state plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/url-state'] --- import kbnUrlStateObj from './kbn_url_state.devdocs.json'; diff --git a/api_docs/kbn_user_profile_components.mdx b/api_docs/kbn_user_profile_components.mdx index 9678a8f3d941e..05e8e2b467830 100644 --- a/api_docs/kbn_user_profile_components.mdx +++ b/api_docs/kbn_user_profile_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-user-profile-components title: "@kbn/user-profile-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/user-profile-components plugin -date: 2023-04-24 +date: 2023-04-25 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 0847fe990eba3..e9bda657218a8 100644 --- a/api_docs/kbn_utility_types.mdx +++ b/api_docs/kbn_utility_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types title: "@kbn/utility-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utility-types plugin -date: 2023-04-24 +date: 2023-04-25 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 bd7b6dc7e7ca0..1aec92203b008 100644 --- a/api_docs/kbn_utility_types_jest.mdx +++ b/api_docs/kbn_utility_types_jest.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types-jest title: "@kbn/utility-types-jest" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utility-types-jest plugin -date: 2023-04-24 +date: 2023-04-25 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 3bb37ce9fb475..31a898abdcd9c 100644 --- a/api_docs/kbn_utils.mdx +++ b/api_docs/kbn_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utils title: "@kbn/utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utils plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utils'] --- import kbnUtilsObj from './kbn_utils.devdocs.json'; diff --git a/api_docs/kbn_yarn_lock_validator.mdx b/api_docs/kbn_yarn_lock_validator.mdx index 89bec78fbe1d0..2f26f43d82140 100644 --- a/api_docs/kbn_yarn_lock_validator.mdx +++ b/api_docs/kbn_yarn_lock_validator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-yarn-lock-validator title: "@kbn/yarn-lock-validator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/yarn-lock-validator plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/yarn-lock-validator'] --- import kbnYarnLockValidatorObj from './kbn_yarn_lock_validator.devdocs.json'; diff --git a/api_docs/kibana_overview.mdx b/api_docs/kibana_overview.mdx index 35b6ab39c77a0..ed7132ef96976 100644 --- a/api_docs/kibana_overview.mdx +++ b/api_docs/kibana_overview.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaOverview title: "kibanaOverview" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaOverview plugin -date: 2023-04-24 +date: 2023-04-25 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 277ae838dc455..2f572069ba16c 100644 --- a/api_docs/kibana_react.mdx +++ b/api_docs/kibana_react.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaReact title: "kibanaReact" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaReact plugin -date: 2023-04-24 +date: 2023-04-25 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 9710a9b016f88..e7081fb0b123e 100644 --- a/api_docs/kibana_utils.mdx +++ b/api_docs/kibana_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaUtils title: "kibanaUtils" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaUtils plugin -date: 2023-04-24 +date: 2023-04-25 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 e9b23fb4642c8..3a47dfb8f8190 100644 --- a/api_docs/kubernetes_security.mdx +++ b/api_docs/kubernetes_security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kubernetesSecurity title: "kubernetesSecurity" image: https://source.unsplash.com/400x175/?github description: API docs for the kubernetesSecurity plugin -date: 2023-04-24 +date: 2023-04-25 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 7e35673a3ef08..d5ba1063609b4 100644 --- a/api_docs/lens.mdx +++ b/api_docs/lens.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lens title: "lens" image: https://source.unsplash.com/400x175/?github description: API docs for the lens plugin -date: 2023-04-24 +date: 2023-04-25 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 18e54115992df..3fb2d265e634f 100644 --- a/api_docs/license_api_guard.mdx +++ b/api_docs/license_api_guard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseApiGuard title: "licenseApiGuard" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseApiGuard plugin -date: 2023-04-24 +date: 2023-04-25 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 f791025b3e353..04136e7075bf2 100644 --- a/api_docs/license_management.mdx +++ b/api_docs/license_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseManagement title: "licenseManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseManagement plugin -date: 2023-04-24 +date: 2023-04-25 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 348e30ab776d8..67c1ef29b5996 100644 --- a/api_docs/licensing.mdx +++ b/api_docs/licensing.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licensing title: "licensing" image: https://source.unsplash.com/400x175/?github description: API docs for the licensing plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licensing'] --- import licensingObj from './licensing.devdocs.json'; diff --git a/api_docs/lists.mdx b/api_docs/lists.mdx index 7a4f5df515fb3..580fb0856897a 100644 --- a/api_docs/lists.mdx +++ b/api_docs/lists.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lists title: "lists" image: https://source.unsplash.com/400x175/?github description: API docs for the lists plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lists'] --- import listsObj from './lists.devdocs.json'; diff --git a/api_docs/management.mdx b/api_docs/management.mdx index e86efc700e7ea..927666bf772e0 100644 --- a/api_docs/management.mdx +++ b/api_docs/management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/management title: "management" image: https://source.unsplash.com/400x175/?github description: API docs for the management plugin -date: 2023-04-24 +date: 2023-04-25 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 d5ef557d05116..ba6ae74332111 100644 --- a/api_docs/maps.mdx +++ b/api_docs/maps.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/maps title: "maps" image: https://source.unsplash.com/400x175/?github description: API docs for the maps plugin -date: 2023-04-24 +date: 2023-04-25 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 97034dddf8ac6..bd11ed47d4e37 100644 --- a/api_docs/maps_ems.mdx +++ b/api_docs/maps_ems.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/mapsEms title: "mapsEms" image: https://source.unsplash.com/400x175/?github description: API docs for the mapsEms plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'mapsEms'] --- import mapsEmsObj from './maps_ems.devdocs.json'; diff --git a/api_docs/ml.mdx b/api_docs/ml.mdx index d977d47492384..bd43f592ea080 100644 --- a/api_docs/ml.mdx +++ b/api_docs/ml.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ml title: "ml" image: https://source.unsplash.com/400x175/?github description: API docs for the ml plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ml'] --- import mlObj from './ml.devdocs.json'; diff --git a/api_docs/monitoring.mdx b/api_docs/monitoring.mdx index 90c474ac78f1a..4ccdc03719357 100644 --- a/api_docs/monitoring.mdx +++ b/api_docs/monitoring.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/monitoring title: "monitoring" image: https://source.unsplash.com/400x175/?github description: API docs for the monitoring plugin -date: 2023-04-24 +date: 2023-04-25 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 8d7ae64cb77ba..55dddb743db0d 100644 --- a/api_docs/monitoring_collection.mdx +++ b/api_docs/monitoring_collection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/monitoringCollection title: "monitoringCollection" image: https://source.unsplash.com/400x175/?github description: API docs for the monitoringCollection plugin -date: 2023-04-24 +date: 2023-04-25 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 59a2eae887fb5..4c01c76df4884 100644 --- a/api_docs/navigation.mdx +++ b/api_docs/navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/navigation title: "navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the navigation plugin -date: 2023-04-24 +date: 2023-04-25 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 f1ec49c987768..9eb555cf9ed2a 100644 --- a/api_docs/newsfeed.mdx +++ b/api_docs/newsfeed.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/newsfeed title: "newsfeed" image: https://source.unsplash.com/400x175/?github description: API docs for the newsfeed plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'newsfeed'] --- import newsfeedObj from './newsfeed.devdocs.json'; diff --git a/api_docs/notifications.mdx b/api_docs/notifications.mdx index bf6aade55bc14..ea77584e99213 100644 --- a/api_docs/notifications.mdx +++ b/api_docs/notifications.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/notifications title: "notifications" image: https://source.unsplash.com/400x175/?github description: API docs for the notifications plugin -date: 2023-04-24 +date: 2023-04-25 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 01cac0e1d423a..666a83cdf4e15 100644 --- a/api_docs/observability.mdx +++ b/api_docs/observability.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observability title: "observability" image: https://source.unsplash.com/400x175/?github description: API docs for the observability plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observability'] --- import observabilityObj from './observability.devdocs.json'; diff --git a/api_docs/observability_shared.mdx b/api_docs/observability_shared.mdx index 321906e5ba6ff..fccba2b5c522f 100644 --- a/api_docs/observability_shared.mdx +++ b/api_docs/observability_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityShared title: "observabilityShared" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityShared plugin -date: 2023-04-24 +date: 2023-04-25 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 171effe7c5467..365e4ce0d265e 100644 --- a/api_docs/osquery.mdx +++ b/api_docs/osquery.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/osquery title: "osquery" image: https://source.unsplash.com/400x175/?github description: API docs for the osquery plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'osquery'] --- import osqueryObj from './osquery.devdocs.json'; diff --git a/api_docs/plugin_directory.mdx b/api_docs/plugin_directory.mdx index e0ee93073348b..95a66f4fb67fa 100644 --- a/api_docs/plugin_directory.mdx +++ b/api_docs/plugin_directory.mdx @@ -7,7 +7,7 @@ id: kibDevDocsPluginDirectory slug: /kibana-dev-docs/api-meta/plugin-api-directory title: Directory description: Directory of public APIs available through plugins or packages. -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -15,13 +15,13 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Count | Plugins or Packages with a
public API | Number of teams | |--------------|----------|------------------------| -| 597 | 493 | 37 | +| 598 | 494 | 37 | ### Public API health stats | API Count | Any Count | Missing comments | Missing exports | |--------------|----------|-----------------|--------| -| 69151 | 525 | 59683 | 1333 | +| 69223 | 526 | 59703 | 1336 | ## Plugin Directory @@ -30,7 +30,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 259 | 8 | 254 | 26 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 36 | 1 | 32 | 2 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | AIOps plugin maintained by ML team. | 39 | 0 | 24 | 0 | -| | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 606 | 1 | 585 | 42 | +| | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 609 | 1 | 588 | 42 | | | [@elastic/apm-ui](https://github.com/orgs/elastic/teams/apm-ui) | The user interface for Elastic APM | 43 | 0 | 43 | 110 | | | [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) | Asset manager plugin for entity assets (inventory, topology, etc) | 3 | 0 | 3 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 9 | 0 | 9 | 0 | @@ -65,7 +65,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | This plugin contains the Discover application and the saved search embeddable. | 97 | 0 | 78 | 7 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 37 | 0 | 35 | 2 | | | [@elastic/security-threat-hunting-investigations](https://github.com/orgs/elastic/teams/security-threat-hunting-investigations) | APIs used to assess the quality of data in Elasticsearch indexes | 2 | 0 | 0 | 0 | -| | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds embeddables service to Kibana | 544 | 11 | 442 | 4 | +| | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds embeddables service to Kibana | 546 | 11 | 444 | 4 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Extends embeddable plugin with more functionality | 14 | 0 | 14 | 0 | | | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides encryption and decryption utilities for saved objects containing sensitive information. | 51 | 0 | 44 | 0 | | | [@elastic/enterprise-search-frontend](https://github.com/orgs/elastic/teams/enterprise-search-frontend) | Adds dashboards for discovering and managing Enterprise Search products. | 7 | 0 | 7 | 0 | @@ -90,7 +90,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 235 | 0 | 99 | 2 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Index pattern fields and ambiguous values formatters | 288 | 26 | 249 | 3 | | | [@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. | 62 | 0 | 62 | 2 | -| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | File upload, download, sharing, and serving over HTTP implementation in Kibana. | 215 | 0 | 10 | 5 | +| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | File upload, download, sharing, and serving over HTTP implementation in Kibana. | 217 | 0 | 10 | 5 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Simple UI for managing files in Kibana | 2 | 1 | 2 | 0 | | | [@elastic/fleet](https://github.com/orgs/elastic/teams/fleet) | - | 1109 | 3 | 1004 | 28 | | ftrApis | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 0 | 0 | 0 | 0 | @@ -104,7 +104,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Image embeddable | 3 | 0 | 3 | 1 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 4 | 0 | 4 | 0 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 177 | 0 | 172 | 3 | -| | [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) | This plugin visualizes data from Filebeat and Metricbeat, and integrates with other Observability solutions | 44 | 0 | 41 | 9 | +| | [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) | This plugin visualizes data from Filebeat and Metricbeat, and integrates with other Observability solutions | 45 | 0 | 42 | 10 | | ingestPipelines | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 0 | 0 | 0 | 0 | | inputControlVis | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds Input Control visualization to Kibana | 0 | 0 | 0 | 0 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | - | 123 | 2 | 96 | 4 | @@ -232,7 +232,8 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 24 | 0 | 24 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 129 | 3 | 127 | 17 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 6 | 0 | 4 | 4 | -| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 20 | 0 | 13 | 5 | +| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 21 | 0 | 14 | 5 | +| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 64 | 1 | 15 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 4 | 0 | 0 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 7 | 0 | 7 | 1 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 4 | 0 | 4 | 0 | @@ -425,7 +426,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 61 | 0 | 1 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 47 | 0 | 40 | 0 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 52 | 12 | 43 | 0 | -| | [@elastic/apm-ui](https://github.com/orgs/elastic/teams/apm-ui) | - | 38 | 0 | 38 | 4 | +| | [@elastic/apm-ui](https://github.com/orgs/elastic/teams/apm-ui) | - | 39 | 0 | 39 | 4 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 13 | 0 | 13 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 85 | 0 | 77 | 5 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 41 | 2 | 35 | 0 | @@ -474,7 +475,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/security-threat-hunting-explore](https://github.com/orgs/elastic/teams/security-threat-hunting-explore) | - | 341 | 1 | 337 | 32 | | | [@elastic/security-solution-platform](https://github.com/orgs/elastic/teams/security-solution-platform) | - | 68 | 0 | 62 | 1 | | | [@elastic/security-solution-platform](https://github.com/orgs/elastic/teams/security-solution-platform) | - | 104 | 0 | 93 | 1 | -| | [@elastic/security-threat-hunting-explore](https://github.com/orgs/elastic/teams/security-threat-hunting-explore) | - | 20 | 0 | 15 | 4 | +| | [@elastic/security-threat-hunting-explore](https://github.com/orgs/elastic/teams/security-threat-hunting-explore) | - | 17 | 0 | 12 | 6 | | | [@elastic/security-solution-platform](https://github.com/orgs/elastic/teams/security-solution-platform) | - | 15 | 0 | 7 | 0 | | | [@elastic/security-solution-platform](https://github.com/orgs/elastic/teams/security-solution-platform) | - | 147 | 0 | 125 | 0 | | | [@elastic/security-solution-platform](https://github.com/orgs/elastic/teams/security-solution-platform) | - | 528 | 0 | 515 | 0 | @@ -502,7 +503,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 2 | 0 | 2 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 1 | 0 | 1 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 14 | 0 | 6 | 0 | -| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 70 | 0 | 9 | 0 | +| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 71 | 0 | 9 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 7 | 0 | 7 | 1 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 17 | 0 | 15 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 17 | 0 | 9 | 0 | diff --git a/api_docs/presentation_util.mdx b/api_docs/presentation_util.mdx index bb5dd22da4a5d..d0383bd897a40 100644 --- a/api_docs/presentation_util.mdx +++ b/api_docs/presentation_util.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/presentationUtil title: "presentationUtil" image: https://source.unsplash.com/400x175/?github description: API docs for the presentationUtil plugin -date: 2023-04-24 +date: 2023-04-25 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 fa7c514839f1d..5fe59c8d1a5c8 100644 --- a/api_docs/profiling.mdx +++ b/api_docs/profiling.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/profiling title: "profiling" image: https://source.unsplash.com/400x175/?github description: API docs for the profiling plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'profiling'] --- import profilingObj from './profiling.devdocs.json'; diff --git a/api_docs/remote_clusters.mdx b/api_docs/remote_clusters.mdx index 052f465958e43..e611da2cbb249 100644 --- a/api_docs/remote_clusters.mdx +++ b/api_docs/remote_clusters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/remoteClusters title: "remoteClusters" image: https://source.unsplash.com/400x175/?github description: API docs for the remoteClusters plugin -date: 2023-04-24 +date: 2023-04-25 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 b3e643f23c824..488ef2fa16eea 100644 --- a/api_docs/reporting.mdx +++ b/api_docs/reporting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/reporting title: "reporting" image: https://source.unsplash.com/400x175/?github description: API docs for the reporting plugin -date: 2023-04-24 +date: 2023-04-25 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 3ad92d06484ef..8a51e3435ed0c 100644 --- a/api_docs/rollup.mdx +++ b/api_docs/rollup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/rollup title: "rollup" image: https://source.unsplash.com/400x175/?github description: API docs for the rollup plugin -date: 2023-04-24 +date: 2023-04-25 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 2230c7e475fbf..0281bc7f316c3 100644 --- a/api_docs/rule_registry.mdx +++ b/api_docs/rule_registry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ruleRegistry title: "ruleRegistry" image: https://source.unsplash.com/400x175/?github description: API docs for the ruleRegistry plugin -date: 2023-04-24 +date: 2023-04-25 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 fbb641e9ec07b..3922bd0239938 100644 --- a/api_docs/runtime_fields.mdx +++ b/api_docs/runtime_fields.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/runtimeFields title: "runtimeFields" image: https://source.unsplash.com/400x175/?github description: API docs for the runtimeFields plugin -date: 2023-04-24 +date: 2023-04-25 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 4775f7bab3e25..14d55ddb58f46 100644 --- a/api_docs/saved_objects.mdx +++ b/api_docs/saved_objects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjects title: "savedObjects" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjects plugin -date: 2023-04-24 +date: 2023-04-25 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 c5cc15bc31edd..2fcbcc7d88c0b 100644 --- a/api_docs/saved_objects_finder.mdx +++ b/api_docs/saved_objects_finder.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsFinder title: "savedObjectsFinder" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsFinder plugin -date: 2023-04-24 +date: 2023-04-25 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 8ed4d9ce7ea8b..e5e861b210704 100644 --- a/api_docs/saved_objects_management.mdx +++ b/api_docs/saved_objects_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsManagement title: "savedObjectsManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsManagement plugin -date: 2023-04-24 +date: 2023-04-25 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 28d4dfa912c94..f89a314475878 100644 --- a/api_docs/saved_objects_tagging.mdx +++ b/api_docs/saved_objects_tagging.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsTagging title: "savedObjectsTagging" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsTagging plugin -date: 2023-04-24 +date: 2023-04-25 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 c4e422b6d4bf5..56588f7a39e29 100644 --- a/api_docs/saved_objects_tagging_oss.mdx +++ b/api_docs/saved_objects_tagging_oss.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsTaggingOss title: "savedObjectsTaggingOss" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsTaggingOss plugin -date: 2023-04-24 +date: 2023-04-25 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 b41919c5f029f..ba64470873a2d 100644 --- a/api_docs/saved_search.mdx +++ b/api_docs/saved_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedSearch title: "savedSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the savedSearch plugin -date: 2023-04-24 +date: 2023-04-25 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 eee83610aebe7..2032cc95f5e46 100644 --- a/api_docs/screenshot_mode.mdx +++ b/api_docs/screenshot_mode.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/screenshotMode title: "screenshotMode" image: https://source.unsplash.com/400x175/?github description: API docs for the screenshotMode plugin -date: 2023-04-24 +date: 2023-04-25 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 8b105e90157ca..a0d34f1ce02df 100644 --- a/api_docs/screenshotting.mdx +++ b/api_docs/screenshotting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/screenshotting title: "screenshotting" image: https://source.unsplash.com/400x175/?github description: API docs for the screenshotting plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotting'] --- import screenshottingObj from './screenshotting.devdocs.json'; diff --git a/api_docs/security.mdx b/api_docs/security.mdx index 6a3ef50e5d725..8f05b6b6c7022 100644 --- a/api_docs/security.mdx +++ b/api_docs/security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/security title: "security" image: https://source.unsplash.com/400x175/?github description: API docs for the security plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'security'] --- import securityObj from './security.devdocs.json'; diff --git a/api_docs/security_solution.mdx b/api_docs/security_solution.mdx index 49cd868cf6aee..ff027a894fe00 100644 --- a/api_docs/security_solution.mdx +++ b/api_docs/security_solution.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/securitySolution title: "securitySolution" image: https://source.unsplash.com/400x175/?github description: API docs for the securitySolution plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolution'] --- import securitySolutionObj from './security_solution.devdocs.json'; diff --git a/api_docs/session_view.mdx b/api_docs/session_view.mdx index 89f21abe997f4..2f539f8601a3f 100644 --- a/api_docs/session_view.mdx +++ b/api_docs/session_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/sessionView title: "sessionView" image: https://source.unsplash.com/400x175/?github description: API docs for the sessionView plugin -date: 2023-04-24 +date: 2023-04-25 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 41725af018c9e..8a5c1e3d3b3ee 100644 --- a/api_docs/share.mdx +++ b/api_docs/share.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/share title: "share" image: https://source.unsplash.com/400x175/?github description: API docs for the share plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'share'] --- import shareObj from './share.devdocs.json'; diff --git a/api_docs/snapshot_restore.mdx b/api_docs/snapshot_restore.mdx index 582bfce7f3253..c4b821d22e374 100644 --- a/api_docs/snapshot_restore.mdx +++ b/api_docs/snapshot_restore.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/snapshotRestore title: "snapshotRestore" image: https://source.unsplash.com/400x175/?github description: API docs for the snapshotRestore plugin -date: 2023-04-24 +date: 2023-04-25 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 2467dff1cd464..4b5964d1329b2 100644 --- a/api_docs/spaces.mdx +++ b/api_docs/spaces.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/spaces title: "spaces" image: https://source.unsplash.com/400x175/?github description: API docs for the spaces plugin -date: 2023-04-24 +date: 2023-04-25 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 f2fd4e7fff1d3..b5a783c0352c6 100644 --- a/api_docs/stack_alerts.mdx +++ b/api_docs/stack_alerts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/stackAlerts title: "stackAlerts" image: https://source.unsplash.com/400x175/?github description: API docs for the stackAlerts plugin -date: 2023-04-24 +date: 2023-04-25 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 12ffc30623e60..76676c70e8167 100644 --- a/api_docs/stack_connectors.mdx +++ b/api_docs/stack_connectors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/stackConnectors title: "stackConnectors" image: https://source.unsplash.com/400x175/?github description: API docs for the stackConnectors plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackConnectors'] --- import stackConnectorsObj from './stack_connectors.devdocs.json'; diff --git a/api_docs/task_manager.mdx b/api_docs/task_manager.mdx index ec588948cbe18..48a65ec9218bc 100644 --- a/api_docs/task_manager.mdx +++ b/api_docs/task_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/taskManager title: "taskManager" image: https://source.unsplash.com/400x175/?github description: API docs for the taskManager plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'taskManager'] --- import taskManagerObj from './task_manager.devdocs.json'; diff --git a/api_docs/telemetry.mdx b/api_docs/telemetry.mdx index 439a2431b4376..dbc433c4a6906 100644 --- a/api_docs/telemetry.mdx +++ b/api_docs/telemetry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetry title: "telemetry" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetry plugin -date: 2023-04-24 +date: 2023-04-25 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 a445edd5a138f..4a2d5ddcb3340 100644 --- a/api_docs/telemetry_collection_manager.mdx +++ b/api_docs/telemetry_collection_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionManager title: "telemetryCollectionManager" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryCollectionManager plugin -date: 2023-04-24 +date: 2023-04-25 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 29ee2d6178438..206cd595f282b 100644 --- a/api_docs/telemetry_collection_xpack.mdx +++ b/api_docs/telemetry_collection_xpack.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionXpack title: "telemetryCollectionXpack" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryCollectionXpack plugin -date: 2023-04-24 +date: 2023-04-25 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 fbe7face22441..7d83295bd815b 100644 --- a/api_docs/telemetry_management_section.mdx +++ b/api_docs/telemetry_management_section.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryManagementSection title: "telemetryManagementSection" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryManagementSection plugin -date: 2023-04-24 +date: 2023-04-25 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 5ca7b1d27e7d4..6e08d1f6c67f8 100644 --- a/api_docs/threat_intelligence.mdx +++ b/api_docs/threat_intelligence.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/threatIntelligence title: "threatIntelligence" image: https://source.unsplash.com/400x175/?github description: API docs for the threatIntelligence plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'threatIntelligence'] --- import threatIntelligenceObj from './threat_intelligence.devdocs.json'; diff --git a/api_docs/timelines.devdocs.json b/api_docs/timelines.devdocs.json index 2cc9a92f16706..4d5c557d27e35 100644 --- a/api_docs/timelines.devdocs.json +++ b/api_docs/timelines.devdocs.json @@ -4155,7 +4155,7 @@ "label": "EntityType", "description": [], "signature": [ - "\"alerts\" | \"sessions\" | \"events\"" + "\"alerts\" | \"events\" | \"sessions\"" ], "path": "x-pack/plugins/timelines/common/search_strategy/timeline/events/index.ts", "deprecated": false, diff --git a/api_docs/timelines.mdx b/api_docs/timelines.mdx index 41df96ec361dd..13a6ab02db045 100644 --- a/api_docs/timelines.mdx +++ b/api_docs/timelines.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/timelines title: "timelines" image: https://source.unsplash.com/400x175/?github description: API docs for the timelines plugin -date: 2023-04-24 +date: 2023-04-25 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 e0a7c8eca1a70..195ea7e628c28 100644 --- a/api_docs/transform.mdx +++ b/api_docs/transform.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/transform title: "transform" image: https://source.unsplash.com/400x175/?github description: API docs for the transform plugin -date: 2023-04-24 +date: 2023-04-25 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 0dfa7fa50555b..c6ada20ba1469 100644 --- a/api_docs/triggers_actions_ui.mdx +++ b/api_docs/triggers_actions_ui.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/triggersActionsUi title: "triggersActionsUi" image: https://source.unsplash.com/400x175/?github description: API docs for the triggersActionsUi plugin -date: 2023-04-24 +date: 2023-04-25 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 ba54dfe3328c8..124cea9d3ee49 100644 --- a/api_docs/ui_actions.mdx +++ b/api_docs/ui_actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uiActions title: "uiActions" image: https://source.unsplash.com/400x175/?github description: API docs for the uiActions plugin -date: 2023-04-24 +date: 2023-04-25 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 0111328be34a0..4c7f1c13c386b 100644 --- a/api_docs/ui_actions_enhanced.mdx +++ b/api_docs/ui_actions_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uiActionsEnhanced title: "uiActionsEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the uiActionsEnhanced plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActionsEnhanced'] --- import uiActionsEnhancedObj from './ui_actions_enhanced.devdocs.json'; diff --git a/api_docs/unified_field_list.mdx b/api_docs/unified_field_list.mdx index 97fceac316e4d..b54f077ec640f 100644 --- a/api_docs/unified_field_list.mdx +++ b/api_docs/unified_field_list.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedFieldList title: "unifiedFieldList" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedFieldList plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedFieldList'] --- import unifiedFieldListObj from './unified_field_list.devdocs.json'; diff --git a/api_docs/unified_histogram.mdx b/api_docs/unified_histogram.mdx index e0acc17411f17..3f72b6536df3b 100644 --- a/api_docs/unified_histogram.mdx +++ b/api_docs/unified_histogram.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedHistogram title: "unifiedHistogram" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedHistogram plugin -date: 2023-04-24 +date: 2023-04-25 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 9655a20a1dda9..dc4452def7050 100644 --- a/api_docs/unified_search.mdx +++ b/api_docs/unified_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedSearch title: "unifiedSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedSearch plugin -date: 2023-04-24 +date: 2023-04-25 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 f70714d1e5938..65aca0296752f 100644 --- a/api_docs/unified_search_autocomplete.mdx +++ b/api_docs/unified_search_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedSearch-autocomplete title: "unifiedSearch.autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedSearch.autocomplete plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch.autocomplete'] --- import unifiedSearchAutocompleteObj from './unified_search_autocomplete.devdocs.json'; diff --git a/api_docs/url_forwarding.mdx b/api_docs/url_forwarding.mdx index fb8870f2325d2..b6383facf7fcd 100644 --- a/api_docs/url_forwarding.mdx +++ b/api_docs/url_forwarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/urlForwarding title: "urlForwarding" image: https://source.unsplash.com/400x175/?github description: API docs for the urlForwarding plugin -date: 2023-04-24 +date: 2023-04-25 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 569cd4f8baf09..24515c95a5c7a 100644 --- a/api_docs/usage_collection.mdx +++ b/api_docs/usage_collection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/usageCollection title: "usageCollection" image: https://source.unsplash.com/400x175/?github description: API docs for the usageCollection plugin -date: 2023-04-24 +date: 2023-04-25 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 71a2488190eb8..53918746384ba 100644 --- a/api_docs/ux.mdx +++ b/api_docs/ux.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ux title: "ux" image: https://source.unsplash.com/400x175/?github description: API docs for the ux plugin -date: 2023-04-24 +date: 2023-04-25 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 aa3747f8476f0..81ac0a71e3af8 100644 --- a/api_docs/vis_default_editor.mdx +++ b/api_docs/vis_default_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visDefaultEditor title: "visDefaultEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the visDefaultEditor plugin -date: 2023-04-24 +date: 2023-04-25 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 24c99b2aa19f8..2ee372bc06670 100644 --- a/api_docs/vis_type_gauge.mdx +++ b/api_docs/vis_type_gauge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeGauge title: "visTypeGauge" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeGauge plugin -date: 2023-04-24 +date: 2023-04-25 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 d7649559452e8..4d368c89743f0 100644 --- a/api_docs/vis_type_heatmap.mdx +++ b/api_docs/vis_type_heatmap.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeHeatmap title: "visTypeHeatmap" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeHeatmap plugin -date: 2023-04-24 +date: 2023-04-25 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 c493a75a57076..94e0c5d1dcb8b 100644 --- a/api_docs/vis_type_pie.mdx +++ b/api_docs/vis_type_pie.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypePie title: "visTypePie" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypePie plugin -date: 2023-04-24 +date: 2023-04-25 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 b954ce0b719d2..17258d78b7ad4 100644 --- a/api_docs/vis_type_table.mdx +++ b/api_docs/vis_type_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTable title: "visTypeTable" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTable plugin -date: 2023-04-24 +date: 2023-04-25 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 b98b43d726106..2cbe6e2b666d4 100644 --- a/api_docs/vis_type_timelion.mdx +++ b/api_docs/vis_type_timelion.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTimelion title: "visTypeTimelion" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTimelion plugin -date: 2023-04-24 +date: 2023-04-25 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 7a80a887ba186..517213167e2fd 100644 --- a/api_docs/vis_type_timeseries.mdx +++ b/api_docs/vis_type_timeseries.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTimeseries title: "visTypeTimeseries" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTimeseries plugin -date: 2023-04-24 +date: 2023-04-25 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 b760568fac4e3..fad9653e3d3ea 100644 --- a/api_docs/vis_type_vega.mdx +++ b/api_docs/vis_type_vega.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeVega title: "visTypeVega" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVega plugin -date: 2023-04-24 +date: 2023-04-25 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 27174661c8845..dfe878c269ab1 100644 --- a/api_docs/vis_type_vislib.mdx +++ b/api_docs/vis_type_vislib.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeVislib title: "visTypeVislib" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVislib plugin -date: 2023-04-24 +date: 2023-04-25 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 f64e0c6adbde7..e774540c6c793 100644 --- a/api_docs/vis_type_xy.mdx +++ b/api_docs/vis_type_xy.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeXy title: "visTypeXy" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeXy plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeXy'] --- import visTypeXyObj from './vis_type_xy.devdocs.json'; diff --git a/api_docs/visualizations.mdx b/api_docs/visualizations.mdx index a8c2abb735484..ea85d8d3f48ce 100644 --- a/api_docs/visualizations.mdx +++ b/api_docs/visualizations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visualizations title: "visualizations" image: https://source.unsplash.com/400x175/?github description: API docs for the visualizations plugin -date: 2023-04-24 +date: 2023-04-25 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visualizations'] --- import visualizationsObj from './visualizations.devdocs.json'; From 8d8c2aab81e0d97e7c0382118dcf30e9059fe48a Mon Sep 17 00:00:00 2001 From: Shahzad Date: Tue, 25 Apr 2023 08:08:53 +0200 Subject: [PATCH 20/52] [Synthetics] Push cloud and deployment id to service (#155660) --- .../server/synthetics_service/service_api_client.test.ts | 8 +++++++- .../server/synthetics_service/service_api_client.ts | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/synthetics/server/synthetics_service/service_api_client.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/service_api_client.test.ts index 2ed86eb91ae52..700f339516215 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/service_api_client.test.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/service_api_client.test.ts @@ -441,7 +441,11 @@ describe('callAPI', () => { manifestUrl: 'http://localhost:8080/api/manifest', tls: { certificate: 'test-certificate', key: 'test-key' } as any, }, - { isDev: true, stackVersion: '8.7.0' } as UptimeServerSetup + { + isDev: true, + stackVersion: '8.7.0', + cloud: { cloudId: 'test-id', deploymentId: 'deployment-id' }, + } as UptimeServerSetup ); apiClient.locations = testLocations; @@ -462,6 +466,8 @@ describe('callAPI', () => { stack_version: '8.7.0', license_level: 'trial', license_issued_to: '2c515bd215ce444441f83ffd36a9d3d2546', + cloud_id: 'test-id', + deployment_id: 'deployment-id', }, headers: { 'x-kibana-version': '8.7.0', diff --git a/x-pack/plugins/synthetics/server/synthetics_service/service_api_client.ts b/x-pack/plugins/synthetics/server/synthetics_service/service_api_client.ts index 4fea1d04b64e4..876f0270c20ce 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/service_api_client.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/service_api_client.ts @@ -236,6 +236,8 @@ export class ServiceAPIClient { is_edit: isEdit, license_level: license.type, license_issued_to: license.issued_to, + deployment_id: this.server.cloud?.deploymentId, + cloud_id: this.server.cloud?.cloudId, }, headers: authHeader, httpsAgent: this.getHttpsAgent(baseUrl), From 1ae38df15dc564c00916bcd1171c8c696b813613 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Tue, 25 Apr 2023 08:09:22 +0200 Subject: [PATCH 21/52] [Synthetics] Fix default date range on errors page (#155661) --- .../synthetics_date_picker.test.tsx | 20 ------------------- .../date_picker/synthetics_date_picker.tsx | 12 +---------- 2 files changed, 1 insertion(+), 31 deletions(-) diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/date_picker/synthetics_date_picker.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/date_picker/synthetics_date_picker.test.tsx index 6fae43af920e5..a78710dd9994e 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/date_picker/synthetics_date_picker.test.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/date_picker/synthetics_date_picker.test.tsx @@ -25,26 +25,6 @@ describe('SyntheticsDatePicker component', () => { expect(await findByText('Refresh')).toBeInTheDocument(); }); - it('uses shared date range state when there is no url date range state', async () => { - const customHistory = createMemoryHistory({ - initialEntries: ['/?dateRangeStart=now-24h&dateRangeEnd=now'], - }); - - jest.spyOn(customHistory, 'push'); - - const { findByText } = render(, { - history: customHistory, - core: startPlugins, - }); - - expect(await findByText('~ 30 minutes ago')).toBeInTheDocument(); - - expect(customHistory.push).toHaveBeenCalledWith({ - pathname: '/', - search: 'dateRangeEnd=now-15m&dateRangeStart=now-30m', - }); - }); - it('should use url date range even if shared date range is present', async () => { const customHistory = createMemoryHistory({ initialEntries: ['/?g=%22%22&dateRangeStart=now-10m&dateRangeEnd=now'], diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/date_picker/synthetics_date_picker.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/date_picker/synthetics_date_picker.tsx index 82f2874f9ffa2..a5eaeacf6c7ed 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/date_picker/synthetics_date_picker.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/date_picker/synthetics_date_picker.tsx @@ -7,7 +7,6 @@ import React, { useContext, useEffect } from 'react'; import { EuiSuperDatePicker } from '@elastic/eui'; -import { CLIENT_DEFAULTS_SYNTHETICS } from '../../../../../../common/constants/synthetics/client_defaults'; import { useUrlParams } from '../../../hooks'; import { CLIENT_DEFAULTS } from '../../../../../../common/constants'; import { @@ -16,12 +15,6 @@ import { SyntheticsRefreshContext, } from '../../../contexts'; -const isSyntheticsDefaultDateRange = (dateRangeStart: string, dateRangeEnd: string) => { - const { DATE_RANGE_START, DATE_RANGE_END } = CLIENT_DEFAULTS_SYNTHETICS; - - return dateRangeStart === DATE_RANGE_START && dateRangeEnd === DATE_RANGE_END; -}; - export const SyntheticsDatePicker = ({ fullWidth }: { fullWidth?: boolean }) => { const [getUrlParams, updateUrl] = useUrlParams(); const { commonlyUsedRanges } = useContext(SyntheticsSettingsContext); @@ -36,10 +29,7 @@ export const SyntheticsDatePicker = ({ fullWidth }: { fullWidth?: boolean }) => useEffect(() => { const { from, to } = sharedTimeState ?? {}; - // if it's synthetics default range, and we have shared state from kibana, let's use that - if (isSyntheticsDefaultDateRange(start, end) && (from !== start || to !== end)) { - updateUrl({ dateRangeStart: from, dateRangeEnd: to }); - } else if (from !== start || to !== end) { + if (from !== start || to !== end) { // if it's coming url. let's update shared state data?.query.timefilter.timefilter.setTime({ from: start, to: end }); } From 4e4f408a34f418053d5433eec8d59250816b0ba8 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Tue, 25 Apr 2023 08:11:19 +0200 Subject: [PATCH 22/52] [Synthetics] Fix next / prev test navigation (#155624) --- .../step_details_page/step_page_nav.tsx | 30 ++++++++------- .../server/queries/get_journey_details.ts | 38 +++++++++++-------- 2 files changed, 39 insertions(+), 29 deletions(-) diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_page_nav.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_page_nav.tsx index 528e3c9d6aa1b..9bd25a715dff1 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_page_nav.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_page_nav.tsx @@ -57,19 +57,23 @@ export const StepPageNavigation = ({ testRunPage }: { testRunPage?: boolean }) = checkGroupId: data?.details?.next?.checkGroup, }); - if (testRunPage && data?.details?.previous?.checkGroup && data?.details?.next?.checkGroup) { - prevHref = getTestRunDetailLink({ - basePath, - monitorId, - locationId: selectedLocation?.id, - checkGroup: data?.details?.previous?.checkGroup, - }); - nextHref = getTestRunDetailLink({ - basePath, - monitorId, - locationId: selectedLocation?.id, - checkGroup: data?.details?.next?.checkGroup, - }); + if (testRunPage) { + if (data?.details?.previous?.checkGroup) { + prevHref = getTestRunDetailLink({ + basePath, + monitorId, + locationId: selectedLocation?.id, + checkGroup: data?.details?.previous?.checkGroup, + }); + } + if (data?.details?.next?.checkGroup) { + nextHref = getTestRunDetailLink({ + basePath, + monitorId, + locationId: selectedLocation?.id, + checkGroup: data?.details?.next?.checkGroup, + }); + } } if (!startedAt) { diff --git a/x-pack/plugins/synthetics/server/queries/get_journey_details.ts b/x-pack/plugins/synthetics/server/queries/get_journey_details.ts index 025688af45242..551696614d749 100644 --- a/x-pack/plugins/synthetics/server/queries/get_journey_details.ts +++ b/x-pack/plugins/synthetics/server/queries/get_journey_details.ts @@ -64,6 +64,15 @@ export const getJourneyDetails: UMElasticsearchQueryFn< body: { query: { bool: { + must_not: [ + { + term: { + 'monitor.check_group': { + value: journeySource.monitor.check_group, + }, + }, + }, + ], filter: [ { term: { @@ -93,6 +102,7 @@ export const getJourneyDetails: UMElasticsearchQueryFn< ...baseSiblingParams.body, query: { bool: { + must_not: baseSiblingParams.body.query.bool.must_not, filter: [ ...baseSiblingParams.body.query.bool.filter, { @@ -114,6 +124,7 @@ export const getJourneyDetails: UMElasticsearchQueryFn< ...baseSiblingParams.body, query: { bool: { + must_not: baseSiblingParams.body.query.bool.must_not, filter: [ ...baseSiblingParams.body.query.bool.filter, { @@ -151,20 +162,6 @@ export const getJourneyDetails: UMElasticsearchQueryFn< ({ _source: summarySource }) => summarySource.synthetics?.type === 'heartbeat/summary' )?._source; - const previousInfo = previousJourney - ? { - checkGroup: previousJourney._source.monitor.check_group, - timestamp: previousJourney._source['@timestamp'], - } - : undefined; - - const nextInfo = nextJourney - ? { - checkGroup: nextJourney._source.monitor.check_group, - timestamp: nextJourney._source['@timestamp'], - } - : undefined; - return { timestamp: journeySource['@timestamp'], journey: { ...journeySource, _id: foundJourney._id }, @@ -175,10 +172,19 @@ export const getJourneyDetails: UMElasticsearchQueryFn< }, } : {}), - previous: previousInfo, - next: nextInfo, + previous: filterNextPrevJourney(journeySource.monitor.check_group, previousJourney?._source), + next: filterNextPrevJourney(journeySource.monitor.check_group, nextJourney?._source), }; } else { return null; } }; + +const filterNextPrevJourney = (checkGroup: string, pingSource: DocumentSource) => { + return pingSource && pingSource.monitor.check_group !== checkGroup + ? { + checkGroup: pingSource.monitor.check_group, + timestamp: pingSource['@timestamp'], + } + : undefined; +}; From 21351df953a0564024eb12393d840a1d1c084265 Mon Sep 17 00:00:00 2001 From: Gerard Soldevila Date: Tue, 25 Apr 2023 09:43:42 +0200 Subject: [PATCH 23/52] Split the .kibana saved objects index into multiple indices (#154888) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description Fix https://github.com/elastic/kibana/issues/104081 This PR move some of the SO types from the `.kibana` index into the following ones: - `.kibana_alerting_cases` - `.kibana_analytics` - `.kibana_security_solution` - `.kibana_ingest` This split/reallocation will occur during the `8.8.0` Kibana upgrade (*meaning: from any version older than `8.8.0` to any version greater or equal to `8.8.0`*) **This PR main changes are:** - implement the changes required in the SO migration algorithm to support this reallocation - update the FTR tools (looking at you esArchiver) to support these new indices - update hardcoded references to `.kibana` and usage of the `core.savedObjects.getKibanaIndex()` to use new APIs to target the correct index/indices - update FTR datasets, tests and utility accordingly ## To reviewers **Overall estimated risk of regressions: low** But, still, please take the time to review changes in your code. The parts of the production code that were the most impacted are the telemetry collectors, as most of them were performing direct requests against the `.kibana` index, so we had to adapt them. Most other contributor-owned changes are in FTR tests and datasets. If you think a type is misplaced (either we missed some types that should be moved to a specific index, or some types were moved and shouldn't have been) please tell us, and we'll fix the reallocation either in this PR or in a follow-up. ## .Kibana split The following new indices are introduced by this PR, with the following SO types being moved to it. (any SO type not listed here will be staying in its current index) Note: The complete **_type => index_** breakdown is available in [this spreadsheet](https://docs.google.com/spreadsheets/d/1b_MG_E_aBksZ4Vkd9cVayij1oBpdhvH4XC8NVlChiio/edit#gid=145920788). #### `.kibana_alerting_cases` - action - action_task_params - alert - api_key_pending_invalidation - cases - cases-comments - cases-configure - cases-connector-mappings - cases-telemetry - cases-user-actions - connector_token - rules-settings - maintenance-window #### `.kibana_security_solution` - csp-rule-template - endpoint:user-artifact - endpoint:user-artifact-manifest - exception-list - exception-list-agnostic - osquery-manager-usage-metric - osquery-pack - osquery-pack-asset - osquery-saved-query - security-rule - security-solution-signals-migration - siem-detection-engine-rule-actions - siem-ui-timeline - siem-ui-timeline-note - siem-ui-timeline-pinned-event #### `.kibana_analytics` - canvas-element - canvas-workpad-template - canvas-workpad - dashboard - graph-workspace - index-pattern - kql-telemetry - lens - lens-ui-telemetry - map - search - search-session - search-telemetry - visualization #### `.kibana_ingest` - epm-packages - epm-packages-assets - fleet-fleet-server-host - fleet-message-signing-keys - fleet-preconfiguration-deletion-record - fleet-proxy - ingest_manager_settings - ingest-agent-policies - ingest-download-sources - ingest-outputs - ingest-package-policies ## Tasks / PRs ### Sub-PRs **Implementation** - 🟣 https://github.com/elastic/kibana/pull/154846 - 🟣 https://github.com/elastic/kibana/pull/154892 - 🟣 https://github.com/elastic/kibana/pull/154882 - 🟣 https://github.com/elastic/kibana/pull/154884 - 🟣 https://github.com/elastic/kibana/pull/155155 **Individual index split** - 🟣 https://github.com/elastic/kibana/pull/154897 - 🟣 https://github.com/elastic/kibana/pull/155129 - 🟣 https://github.com/elastic/kibana/pull/155140 - 🟣 https://github.com/elastic/kibana/pull/155130 ### Improvements / follow-ups - 👷🏼 Extract logic into [runV2Migration](https://github.com/elastic/kibana/pull/154151#discussion_r1158470566) @gsoldevila - Make `getCurrentIndexTypesMap` resillient to intermittent failures https://github.com/elastic/kibana/pull/154151#discussion_r1169289717 - 🚧 Build a more structured [MigratorSynchronizer](https://github.com/elastic/kibana/pull/154151#discussion_r1158469918) - 🟣 https://github.com/elastic/kibana/pull/155035 - 🟣 https://github.com/elastic/kibana/pull/155116 - 🟣 https://github.com/elastic/kibana/pull/155366 ## Reallocation tweaks Tweaks to the reallocation can be done after the initial merge, as long as it's done before the public release of 8.8 - `url` should get back to `.kibana` (see [comment](https://github.com/elastic/kibana/pull/154888#discussion_r1172317133)) ## Release Note For performance purposes, Kibana is now using more system indices to store its internal data. The following system indices will be created when upgrading to `8.8.0`: - `.kibana_alerting_cases` - `.kibana_analytics` - `.kibana_security_solution` - `.kibana_ingest` --------- Co-authored-by: pgayvallet Co-authored-by: Christos Nasikas Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Georgii Gorbachev --- .../src/plugin_context.ts | 7 +- .../repository.encryption_extension.test.ts | 11 +- .../src/lib/repository.test.ts | 19 +- .../test_helpers/repository.test.common.ts | 43 +- .../index.ts | 1 + .../src/mappings/index.ts | 1 + .../src/mappings/types.ts | 9 + .../src/migration/kibana_migrator.ts | 6 +- .../src/utils/get_index_for_type.test.ts | 7 +- ...grations_state_action_machine.test.ts.snap | 186 + .../src/actions/index.ts | 3 + .../src/actions/synchronize_migrators.test.ts | 156 + .../src/actions/synchronize_migrators.ts | 27 + .../src/initial_state.test.ts | 235 +- .../src/initial_state.ts | 46 +- .../src/kibana_migrator.test.ts | 288 +- .../src/kibana_migrator.ts | 80 +- .../src/kibana_migrator_constants.ts | 131 + .../src/kibana_migrator_utils.fixtures.ts | 3413 +++++++++++++++++ .../src/kibana_migrator_utils.test.ts | 257 ++ .../src/kibana_migrator_utils.ts | 146 + .../migrations_state_action_machine.test.ts | 44 +- .../src/migrations_state_action_machine.ts | 31 +- .../src/model/create_batches.test.ts | 189 +- .../src/model/create_batches.ts | 42 +- .../src/model/helpers.test.ts | 48 + .../src/model/helpers.ts | 16 +- .../src/model/model.test.ts | 182 +- .../src/model/model.ts | 138 +- .../src/next.test.ts | 5 +- .../src/next.ts | 34 +- .../src/run_resilient_migrator.ts | 22 +- .../src/state.ts | 39 +- .../tsconfig.json | 1 - .../deprecations/unknown_object_types.test.ts | 4 +- .../src/saved_objects_service.test.ts | 89 + .../src/saved_objects_service.ts | 29 +- .../src/status.test.ts | 8 +- .../src/saved_objects_service.mock.ts | 15 +- .../core-saved-objects-server/index.ts | 9 + .../src/contracts.ts | 30 +- .../src/saved_objects_index_pattern.ts | 29 + .../src/core_usage_data_service.ts | 31 +- .../src/actions/empty_kibana_index.ts | 9 +- packages/kbn-es-archiver/src/actions/load.ts | 14 +- .../lib/docs/generate_doc_records_stream.ts | 5 +- packages/kbn-es-archiver/src/lib/index.ts | 6 +- .../indices/create_index_stream.test.mock.ts | 8 +- .../lib/indices/create_index_stream.test.ts | 22 +- .../src/lib/indices/create_index_stream.ts | 14 +- .../src/lib/indices/delete_index_stream.ts | 7 +- .../indices/generate_index_records_stream.ts | 6 +- .../kbn-es-archiver/src/lib/indices/index.ts | 6 +- .../src/lib/indices/kibana_index.ts | 54 +- packages/kbn-es-archiver/tsconfig.json | 1 + .../kibana_server/extend_es_archiver.ts | 4 +- .../tsconfig.json | 1 + .../group1/7.7.2_xpack_100k.test.ts | 9 +- .../group1/7_13_0_transform_failures.test.ts | 26 +- .../group2/batch_size_bytes.test.ts | 4 +- .../migrations/group3/actions/actions.test.ts | 18 +- .../group3/split_kibana_index.test.ts | 386 ++ .../migrations/kibana_migrator_test_kit.ts | 62 +- .../service/lib/repository_with_proxy.test.ts | 27 +- .../lib/repository_with_proxy_utils.ts | 67 +- .../docs/content_onboarding.md | 10 +- .../dashboard_saved_object.ts | 2 + .../dashboard_telemetry_collection_task.ts | 6 +- src/plugins/dashboard/tsconfig.json | 1 + .../kql_telemetry/kql_telemetry_service.ts | 4 +- .../usage_collector/fetch.test.ts | 2 +- .../kql_telemetry/usage_collector/fetch.ts | 6 +- .../make_kql_usage_collector.test.ts | 6 +- .../make_kql_usage_collector.ts | 7 +- .../server/saved_objects/kql_telemetry.ts | 2 + .../data/server/saved_objects/query.ts | 2 + .../server/saved_objects/search_telemetry.ts | 2 + .../server/search/collectors/search/fetch.ts | 5 +- .../search/collectors/search/register.ts | 7 +- .../collectors/search_session/fetch.test.ts | 3 +- .../search/collectors/search_session/fetch.ts | 5 +- .../collectors/search_session/register.ts | 4 +- .../search/saved_objects/search_session.ts | 2 + .../data/server/search/search_service.ts | 10 +- src/plugins/data/tsconfig.json | 1 + .../server/saved_objects/data_views.ts | 2 + src/plugins/data_views/tsconfig.json | 1 + .../sample_data/sample_data_registry.ts | 7 +- .../services/sample_data/usage/collector.ts | 4 +- .../sample_data/usage/collector_fetch.test.ts | 12 +- .../sample_data/usage/collector_fetch.ts | 3 +- .../kibana_usage_collector.test.ts | 3 +- .../kibana_usage_collector.ts | 5 +- .../kibana_usage_collection/server/plugin.ts | 8 +- .../server/saved_objects/search.ts | 2 + src/plugins/saved_search/tsconfig.json | 1 + src/plugins/usage_collection/server/plugin.ts | 2 +- .../server/saved_objects/visualization.ts | 2 + src/plugins/visualizations/tsconfig.json | 1 + .../apis/kql_telemetry/kql_telemetry.ts | 9 +- .../saved_objects/delete_unknown_types.ts | 6 +- .../delete_unknown_types/data.json | 8 +- .../delete_unknown_types/mappings.json | 764 +++- .../management/_handle_version_conflict.ts | 7 +- .../deprecations_service/mappings.json | 118 +- .../es_archiver/huge_fields/data.json.gz | Bin 49227 -> 49240 bytes .../export_transform/mappings.json | 118 +- .../hidden_saved_objects/mappings.json | 118 +- .../nested_export_transform/mappings.json | 118 +- .../visible_in_management/data.json | 1 - .../visible_in_management/mappings.json | 711 +++- test/tsconfig.json | 3 +- .../actions/server/actions_client.test.ts | 28 +- .../plugins/actions/server/actions_client.ts | 18 +- x-pack/plugins/actions/server/plugin.ts | 18 +- .../actions/server/saved_objects/index.ts | 4 + x-pack/plugins/actions/server/usage/task.ts | 29 +- x-pack/plugins/alerting/server/plugin.ts | 10 +- .../alerting/server/saved_objects/index.ts | 5 + .../lib/get_telemetry_from_kibana.test.ts | 8 +- .../usage/lib/get_telemetry_from_kibana.ts | 10 +- x-pack/plugins/alerting/server/usage/task.ts | 22 +- .../canvas/server/collectors/collector.ts | 4 +- .../collectors/custom_element_collector.ts | 5 +- .../server/collectors/workpad_collector.ts | 5 +- x-pack/plugins/canvas/server/plugin.ts | 8 +- .../server/saved_objects/custom_element.ts | 2 + .../canvas/server/saved_objects/workpad.ts | 2 + .../server/saved_objects/workpad_template.ts | 2 + x-pack/plugins/canvas/tsconfig.json | 1 + x-pack/plugins/canvas/types/telemetry.ts | 5 +- .../cases/server/saved_object_types/cases.ts | 2 + .../server/saved_object_types/comments.ts | 2 + .../server/saved_object_types/configure.ts | 2 + .../saved_object_types/connector_mappings.ts | 2 + .../server/saved_object_types/telemetry.ts | 2 + .../server/saved_object_types/user_actions.ts | 2 + .../server/saved_objects/saved_objects.ts | 2 + .../cloud_security_posture/tsconfig.json | 1 + x-pack/plugins/event_log/server/plugin.ts | 4 +- .../plugins/fleet/common/constants/index.ts | 1 + .../fleet/common/constants/saved_objects.ts | 8 + .../plugins/fleet/public/constants/index.ts | 4 +- .../plugins/fleet/server/constants/index.ts | 2 + .../fleet/server/saved_objects/index.ts | 12 + .../server/saved_objects/graph_workspace.ts | 2 + x-pack/plugins/graph/tsconfig.json | 1 + x-pack/plugins/lens/server/saved_objects.ts | 3 + .../server/saved_objects/exception_list.ts | 3 + x-pack/plugins/lists/tsconfig.json | 1 + .../saved_objects/setup_saved_objects.ts | 4 +- x-pack/plugins/maps/tsconfig.json | 1 + x-pack/plugins/ml/server/plugin.ts | 6 +- x-pack/plugins/ml/server/usage/collector.ts | 12 +- x-pack/plugins/monitoring/server/plugin.ts | 2 +- .../monitoring_collection/server/plugin.ts | 2 +- .../lib/saved_query/saved_object_mappings.ts | 5 + x-pack/plugins/osquery/tsconfig.json | 3 +- .../rollup/server/collectors/register.test.ts | 7 +- .../rollup/server/collectors/register.ts | 11 +- x-pack/plugins/rollup/server/plugin.ts | 4 +- .../saved_objects_tagging/server/plugin.ts | 5 +- .../server/usage/fetch_tag_usage_data.ts | 6 +- .../server/usage/tag_usage_collector.ts | 8 +- x-pack/plugins/security/server/plugin.ts | 2 +- .../lib/artifacts/saved_object_mappings.ts | 3 + .../migrations/saved_objects.ts | 2 + .../rule_assets/prebuilt_rule_assets_type.ts | 2 + .../legacy_saved_object_mappings.ts | 2 + .../server/lib/telemetry/receiver.ts | 14 +- .../timeline/saved_object_mappings/notes.ts | 2 + .../saved_object_mappings/pinned_events.ts | 2 + .../saved_object_mappings/timelines.ts | 2 + .../security_solution/server/plugin.ts | 5 +- x-pack/plugins/spaces/server/plugin.ts | 4 +- .../spaces_usage_collector.test.ts | 9 +- .../spaces_usage_collector.ts | 7 +- x-pack/plugins/task_manager/server/plugin.ts | 4 +- .../common/lib/task_manager_utils.ts | 3 +- .../tests/action_task_params/migrations.ts | 5 +- .../tests/alerting/group1/create.ts | 3 +- .../tests/alerting/group4/migrations.ts | 47 +- .../tests/alerting/group4/migrations/8_2_0.ts | 5 +- .../apis/security/index_fields.ts | 3 +- .../apis/security_solution/utils.ts | 3 +- .../common/lib/api/index.ts | 77 +- .../security_and_spaces/tests/basic/index.ts | 3 + .../tests/common/cases/find_cases.ts | 3 +- .../common/kibana_alerting_cases_index.ts | 204 + .../security_and_spaces/tests/trial/index.ts | 3 + x-pack/test/common/lib/test_data_loader.ts | 3 +- .../group1/fleet_integration.ts | 24 + .../security_and_spaces/group10/migrations.ts | 5 +- .../group10/resolve_read_rules.ts | 3 +- .../utils/delete_all_rule_execution_info.ts | 5 +- .../utils/delete_all_timelines.ts | 5 +- .../utils/downgrade_immutable_rule.ts | 3 +- .../get_legacy_action_notification_so.ts | 5 +- ...et_legacy_action_notifications_so_by_id.ts | 5 +- .../utils/get_legacy_action_so.ts | 5 +- .../utils/get_legacy_actions_so_by_id.ts | 5 +- .../utils/get_rule_so_by_id.ts | 5 +- .../create_prebuilt_rule_saved_objects.ts | 10 +- .../delete_all_prebuilt_rule_assets.ts | 5 +- .../apis/agents/status.ts | 5 +- .../apis/epm/install_by_upload.ts | 5 +- .../install_with_signature_verification.ts | 3 +- .../apis/package_policy/create.ts | 3 +- .../apis/package_policy/get.ts | 3 +- .../cases/migrations/8.8.0/data.json.gz | Bin 0 -> 1911509 bytes .../cases/migrations/8.8.0/mappings.json | 3049 +++++++++++++++ .../pre_calculated_histogram/data.json | 2 +- .../legacy_actions/data.json | 1372 ++++--- .../saved_objects/spaces/data.json | 18 +- .../saved_objects/spaces/mappings.json | 275 +- .../saved_objects/spaces/data.json | 20 +- .../saved_objects/spaces/mappings.json | 260 +- .../common/lib/space_test_utils.ts | 3 +- .../common/suites/delete.ts | 3 +- .../suites/disable_legacy_url_aliases.ts | 8 +- .../common/suites/update_objects_spaces.ts | 5 +- 221 files changed, 13291 insertions(+), 1552 deletions(-) create mode 100644 packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/synchronize_migrators.test.ts create mode 100644 packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/synchronize_migrators.ts create mode 100644 packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator_constants.ts create mode 100644 packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator_utils.fixtures.ts create mode 100644 packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator_utils.test.ts create mode 100644 packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator_utils.ts create mode 100644 packages/core/saved-objects/core-saved-objects-server/src/saved_objects_index_pattern.ts create mode 100644 src/core/server/integration_tests/saved_objects/migrations/group3/split_kibana_index.test.ts create mode 100644 x-pack/plugins/fleet/common/constants/saved_objects.ts create mode 100644 x-pack/test/cases_api_integration/security_and_spaces/tests/common/kibana_alerting_cases_index.ts create mode 100644 x-pack/test/functional/es_archives/cases/migrations/8.8.0/data.json.gz create mode 100644 x-pack/test/functional/es_archives/cases/migrations/8.8.0/mappings.json diff --git a/packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts b/packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts index 80caa2e9f2336..191fb3a729135 100644 --- a/packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts +++ b/packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts @@ -245,7 +245,8 @@ export function createPluginSetupContext( setSecurityExtension: deps.savedObjects.setSecurityExtension, setSpacesExtension: deps.savedObjects.setSpacesExtension, registerType: deps.savedObjects.registerType, - getKibanaIndex: deps.savedObjects.getKibanaIndex, + getDefaultIndex: deps.savedObjects.getDefaultIndex, + getAllIndices: deps.savedObjects.getAllIndices, }, status: { core$: deps.status.core$, @@ -313,6 +314,10 @@ export function createPluginStartContext( createExporter: deps.savedObjects.createExporter, createImporter: deps.savedObjects.createImporter, getTypeRegistry: deps.savedObjects.getTypeRegistry, + getDefaultIndex: deps.savedObjects.getDefaultIndex, + getIndexForType: deps.savedObjects.getIndexForType, + getIndicesForTypes: deps.savedObjects.getIndicesForTypes, + getAllIndices: deps.savedObjects.getAllIndices, }, metrics: { collectionInterval: deps.metrics.collectionInterval, diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.encryption_extension.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.encryption_extension.test.ts index a297d6b5a2b7e..56b9f433a9593 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.encryption_extension.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.encryption_extension.test.ts @@ -22,8 +22,9 @@ import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-m import { kibanaMigratorMock } from '../mocks'; import { SavedObjectsSerializer } from '@kbn/core-saved-objects-base-server-internal'; import { - ISavedObjectsEncryptionExtension, - SavedObjectsRawDocSource, + MAIN_SAVED_OBJECT_INDEX, + type ISavedObjectsEncryptionExtension, + type SavedObjectsRawDocSource, } from '@kbn/core-saved-objects-server'; import { bulkCreateSuccess, @@ -41,8 +42,8 @@ import { mockVersion, mockVersionProps, MULTI_NAMESPACE_ENCRYPTED_TYPE, - TypeIdTuple, updateSuccess, + type TypeIdTuple, } from '../test_helpers/repository.test.common'; import { savedObjectsExtensionsMock } from '../mocks/saved_objects_extensions.mock'; @@ -633,7 +634,7 @@ describe('SavedObjectsRepository Encryption Extension', () => { total: 2, hits: [ { - _index: '.kibana', + _index: MAIN_SAVED_OBJECT_INDEX, _id: `${space ? `${space}:` : ''}${encryptedSO.type}:${encryptedSO.id}`, _score: 1, ...mockVersionProps, @@ -643,7 +644,7 @@ describe('SavedObjectsRepository Encryption Extension', () => { }, }, { - _index: '.kibana', + _index: MAIN_SAVED_OBJECT_INDEX, _id: `${space ? `${space}:` : ''}index-pattern:logstash-*`, _score: 2, ...mockVersionProps, diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts index f0aea1afaf5fd..372489c379a4b 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts @@ -47,13 +47,14 @@ import type { SavedObjectsBulkDeleteObject, SavedObjectsBulkDeleteOptions, } from '@kbn/core-saved-objects-api-server'; -import type { - SavedObjectsRawDoc, - SavedObjectsRawDocSource, - SavedObjectUnsanitizedDoc, - SavedObject, - SavedObjectReference, - BulkResolveError, +import { + type SavedObjectsRawDoc, + type SavedObjectsRawDocSource, + type SavedObjectUnsanitizedDoc, + type SavedObject, + type SavedObjectReference, + type BulkResolveError, + MAIN_SAVED_OBJECT_INDEX, } from '@kbn/core-saved-objects-server'; import { ALL_NAMESPACES_STRING } from '@kbn/core-saved-objects-utils-server'; import { SavedObjectsErrorHelpers } from '@kbn/core-saved-objects-server'; @@ -4364,7 +4365,7 @@ describe('SavedObjectsRepository', () => { body: { _id: params.id, ...mockVersionProps, - _index: '.kibana', + _index: MAIN_SAVED_OBJECT_INDEX, get: { found: true, _source: { @@ -4668,7 +4669,7 @@ describe('SavedObjectsRepository', () => { body: { _id: params.id, ...mockVersionProps, - _index: '.kibana', + _index: MAIN_SAVED_OBJECT_INDEX, get: { found: true, _source: { diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/test_helpers/repository.test.common.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/test_helpers/repository.test.common.ts index 43892f6719c19..abeab2a8f2e7c 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/test_helpers/repository.test.common.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/test_helpers/repository.test.common.ts @@ -9,19 +9,20 @@ import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { schema } from '@kbn/config-schema'; import { loggerMock } from '@kbn/logging-mocks'; -import { Payload } from 'elastic-apm-node'; -import type { - AuthorizationTypeEntry, - AuthorizeAndRedactMultiNamespaceReferencesParams, - CheckAuthorizationResult, - ISavedObjectsSecurityExtension, - SavedObjectsMappingProperties, - SavedObjectsRawDocSource, - SavedObjectsType, - SavedObjectsTypeMappingDefinition, - SavedObject, - SavedObjectReference, - AuthorizeFindParams, +import type { Payload } from 'elastic-apm-node'; +import { + type AuthorizationTypeEntry, + type AuthorizeAndRedactMultiNamespaceReferencesParams, + type CheckAuthorizationResult, + type ISavedObjectsSecurityExtension, + type SavedObjectsMappingProperties, + type SavedObjectsRawDocSource, + type SavedObjectsType, + type SavedObjectsTypeMappingDefinition, + type SavedObject, + type SavedObjectReference, + type AuthorizeFindParams, + MAIN_SAVED_OBJECT_INDEX, } from '@kbn/core-saved-objects-server'; import type { SavedObjectsBaseOptions, @@ -47,9 +48,9 @@ import { } from '@kbn/core-elasticsearch-client-server-mocks'; import { DocumentMigrator } from '@kbn/core-saved-objects-migration-server-internal'; import { - AuthorizeAndRedactInternalBulkResolveParams, - GetFindRedactTypeMapParams, - AuthorizationTypeMap, + type AuthorizeAndRedactInternalBulkResolveParams, + type GetFindRedactTypeMapParams, + type AuthorizationTypeMap, SavedObjectsErrorHelpers, } from '@kbn/core-saved-objects-server'; import { mockGetSearchDsl } from '../lib/repository.test.mock'; @@ -601,8 +602,6 @@ export const getMockBulkCreateResponse = ( managed: docManaged, }) => ({ create: { - // status: 1, - // _index: '.kibana', _id: `${namespace ? `${namespace}:` : ''}${type}:${id}`, _source: { [type]: attributes, @@ -726,7 +725,7 @@ export const generateIndexPatternSearchResults = (namespace?: string) => { total: 4, hits: [ { - _index: '.kibana', + _index: MAIN_SAVED_OBJECT_INDEX, _id: `${namespace ? `${namespace}:` : ''}index-pattern:logstash-*`, _score: 1, ...mockVersionProps, @@ -743,7 +742,7 @@ export const generateIndexPatternSearchResults = (namespace?: string) => { }, }, { - _index: '.kibana', + _index: MAIN_SAVED_OBJECT_INDEX, _id: `${namespace ? `${namespace}:` : ''}config:6.0.0-alpha1`, _score: 2, ...mockVersionProps, @@ -758,7 +757,7 @@ export const generateIndexPatternSearchResults = (namespace?: string) => { }, }, { - _index: '.kibana', + _index: MAIN_SAVED_OBJECT_INDEX, _id: `${namespace ? `${namespace}:` : ''}index-pattern:stocks-*`, _score: 3, ...mockVersionProps, @@ -774,7 +773,7 @@ export const generateIndexPatternSearchResults = (namespace?: string) => { }, }, { - _index: '.kibana', + _index: MAIN_SAVED_OBJECT_INDEX, _id: `${NAMESPACE_AGNOSTIC_TYPE}:something`, _score: 4, ...mockVersionProps, diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/index.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/index.ts index bfe48e43491a0..981783bb05fd5 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/index.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/index.ts @@ -14,6 +14,7 @@ export { getTypes, type IndexMapping, type IndexMappingMeta, + type IndexTypesMap, type SavedObjectsTypeMappingDefinitions, type IndexMappingMigrationStateMeta, } from './src/mappings'; diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/index.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/index.ts index 7b2bb933fab3f..ee51fa9aaf4bb 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/index.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/index.ts @@ -11,5 +11,6 @@ export type { SavedObjectsTypeMappingDefinitions, IndexMappingMeta, IndexMapping, + IndexTypesMap, IndexMappingMigrationStateMeta, } from './types'; diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/types.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/types.ts index 10faa1b03d31d..c756f0534db67 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/types.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/types.ts @@ -55,6 +55,9 @@ export interface IndexMapping { _meta?: IndexMappingMeta; } +/** @internal */ +export type IndexTypesMap = Record; + /** @internal */ export interface IndexMappingMeta { /** @@ -65,6 +68,12 @@ export interface IndexMappingMeta { * @remark: Only defined for indices using the v2 migration algorithm. */ migrationMappingPropertyHashes?: { [k: string]: string }; + /** + * A map that tells what are the SO types stored in each index + * + * @remark: Only defined for indices using the v2 migration algorithm. + */ + indexTypesMap?: IndexTypesMap; /** * The current model versions of the mapping of the index. * diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/migration/kibana_migrator.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/migration/kibana_migrator.ts index bb078135c8bcc..de569332ff9ce 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/migration/kibana_migrator.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/migration/kibana_migrator.ts @@ -69,7 +69,11 @@ export type MigrationStatus = /** @internal */ export type MigrationResult = | { status: 'skipped' } - | { status: 'patched' } + | { + status: 'patched'; + destIndex: string; + elapsedMs: number; + } | { status: 'migrated'; destIndex: string; diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/utils/get_index_for_type.test.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/utils/get_index_for_type.test.ts index 551c9dc1187eb..d5bc19a11d17e 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/utils/get_index_for_type.test.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/utils/get_index_for_type.test.ts @@ -6,7 +6,10 @@ * Side Public License, v 1. */ -import { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; +import { + type ISavedObjectTypeRegistry, + MAIN_SAVED_OBJECT_INDEX, +} from '@kbn/core-saved-objects-server'; import { getIndexForType } from './get_index_for_type'; const createTypeRegistry = () => { @@ -17,7 +20,7 @@ const createTypeRegistry = () => { describe('getIndexForType', () => { const kibanaVersion = '8.0.0'; - const defaultIndex = '.kibana'; + const defaultIndex = MAIN_SAVED_OBJECT_INDEX; let typeRegistry: ReturnType; beforeEach(() => { diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/__snapshots__/migrations_state_action_machine.test.ts.snap b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/__snapshots__/migrations_state_action_machine.test.ts.snap index cd2b218d58dd4..387dbd87bbafe 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/__snapshots__/migrations_state_action_machine.test.ts.snap +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/__snapshots__/migrations_state_action_machine.test.ts.snap @@ -138,6 +138,20 @@ Object { }, }, "indexPrefix": ".my-so-index", + "indexTypesMap": Object { + ".kibana": Array [ + "typeA", + "typeB", + "typeC", + ], + ".kibana_cases": Array [ + "typeD", + "typeE", + ], + ".kibana_task_manager": Array [ + "task", + ], + }, "kibanaVersion": "7.11.0", "knownTypes": Array [], "legacyIndex": ".my-so-index", @@ -154,6 +168,7 @@ Object { "resolveMigrationFailures": "https://www.elastic.co/guide/en/kibana/test-branch/resolve-migrations-failures.html", "routingAllocationDisabled": "https://www.elastic.co/guide/en/kibana/test-branch/resolve-migrations-failures.html#routing-allocation-disabled", }, + "mustRelocateDocuments": true, "outdatedDocuments": Array [], "outdatedDocumentsQuery": Object { "bool": Object { @@ -167,6 +182,22 @@ Object { "retryCount": 0, "retryDelay": 0, "targetIndexMappings": Object { + "_meta": Object { + "indexTypesMap": Object { + ".kibana": Array [ + "typeA", + "typeB", + "typeC", + ], + ".kibana_cases": Array [ + "typeD", + "typeE", + ], + ".kibana_task_manager": Array [ + "task", + ], + }, + }, "properties": Object {}, }, "tempIndex": ".my-so-index_7.11.0_reindex_temp", @@ -325,6 +356,20 @@ Object { }, }, "indexPrefix": ".my-so-index", + "indexTypesMap": Object { + ".kibana": Array [ + "typeA", + "typeB", + "typeC", + ], + ".kibana_cases": Array [ + "typeD", + "typeE", + ], + ".kibana_task_manager": Array [ + "task", + ], + }, "kibanaVersion": "7.11.0", "knownTypes": Array [], "legacyIndex": ".my-so-index", @@ -345,6 +390,7 @@ Object { "resolveMigrationFailures": "https://www.elastic.co/guide/en/kibana/test-branch/resolve-migrations-failures.html", "routingAllocationDisabled": "https://www.elastic.co/guide/en/kibana/test-branch/resolve-migrations-failures.html#routing-allocation-disabled", }, + "mustRelocateDocuments": true, "outdatedDocuments": Array [], "outdatedDocumentsQuery": Object { "bool": Object { @@ -358,6 +404,22 @@ Object { "retryCount": 0, "retryDelay": 0, "targetIndexMappings": Object { + "_meta": Object { + "indexTypesMap": Object { + ".kibana": Array [ + "typeA", + "typeB", + "typeC", + ], + ".kibana_cases": Array [ + "typeD", + "typeE", + ], + ".kibana_task_manager": Array [ + "task", + ], + }, + }, "properties": Object {}, }, "tempIndex": ".my-so-index_7.11.0_reindex_temp", @@ -516,6 +578,20 @@ Object { }, }, "indexPrefix": ".my-so-index", + "indexTypesMap": Object { + ".kibana": Array [ + "typeA", + "typeB", + "typeC", + ], + ".kibana_cases": Array [ + "typeD", + "typeE", + ], + ".kibana_task_manager": Array [ + "task", + ], + }, "kibanaVersion": "7.11.0", "knownTypes": Array [], "legacyIndex": ".my-so-index", @@ -540,6 +616,7 @@ Object { "resolveMigrationFailures": "https://www.elastic.co/guide/en/kibana/test-branch/resolve-migrations-failures.html", "routingAllocationDisabled": "https://www.elastic.co/guide/en/kibana/test-branch/resolve-migrations-failures.html#routing-allocation-disabled", }, + "mustRelocateDocuments": true, "outdatedDocuments": Array [], "outdatedDocumentsQuery": Object { "bool": Object { @@ -553,6 +630,22 @@ Object { "retryCount": 0, "retryDelay": 0, "targetIndexMappings": Object { + "_meta": Object { + "indexTypesMap": Object { + ".kibana": Array [ + "typeA", + "typeB", + "typeC", + ], + ".kibana_cases": Array [ + "typeD", + "typeE", + ], + ".kibana_task_manager": Array [ + "task", + ], + }, + }, "properties": Object {}, }, "tempIndex": ".my-so-index_7.11.0_reindex_temp", @@ -711,6 +804,20 @@ Object { }, }, "indexPrefix": ".my-so-index", + "indexTypesMap": Object { + ".kibana": Array [ + "typeA", + "typeB", + "typeC", + ], + ".kibana_cases": Array [ + "typeD", + "typeE", + ], + ".kibana_task_manager": Array [ + "task", + ], + }, "kibanaVersion": "7.11.0", "knownTypes": Array [], "legacyIndex": ".my-so-index", @@ -739,6 +846,7 @@ Object { "resolveMigrationFailures": "https://www.elastic.co/guide/en/kibana/test-branch/resolve-migrations-failures.html", "routingAllocationDisabled": "https://www.elastic.co/guide/en/kibana/test-branch/resolve-migrations-failures.html#routing-allocation-disabled", }, + "mustRelocateDocuments": true, "outdatedDocuments": Array [], "outdatedDocumentsQuery": Object { "bool": Object { @@ -752,6 +860,22 @@ Object { "retryCount": 0, "retryDelay": 0, "targetIndexMappings": Object { + "_meta": Object { + "indexTypesMap": Object { + ".kibana": Array [ + "typeA", + "typeB", + "typeC", + ], + ".kibana_cases": Array [ + "typeD", + "typeE", + ], + ".kibana_task_manager": Array [ + "task", + ], + }, + }, "properties": Object {}, }, "tempIndex": ".my-so-index_7.11.0_reindex_temp", @@ -954,6 +1078,20 @@ Object { }, }, "indexPrefix": ".my-so-index", + "indexTypesMap": Object { + ".kibana": Array [ + "typeA", + "typeB", + "typeC", + ], + ".kibana_cases": Array [ + "typeD", + "typeE", + ], + ".kibana_task_manager": Array [ + "task", + ], + }, "kibanaVersion": "7.11.0", "knownTypes": Array [], "legacyIndex": ".my-so-index", @@ -970,6 +1108,7 @@ Object { "resolveMigrationFailures": "https://www.elastic.co/guide/en/kibana/test-branch/resolve-migrations-failures.html", "routingAllocationDisabled": "https://www.elastic.co/guide/en/kibana/test-branch/resolve-migrations-failures.html#routing-allocation-disabled", }, + "mustRelocateDocuments": true, "outdatedDocuments": Array [ Object { "_id": "1234", @@ -988,6 +1127,22 @@ Object { "retryCount": 0, "retryDelay": 0, "targetIndexMappings": Object { + "_meta": Object { + "indexTypesMap": Object { + ".kibana": Array [ + "typeA", + "typeB", + "typeC", + ], + ".kibana_cases": Array [ + "typeD", + "typeE", + ], + ".kibana_task_manager": Array [ + "task", + ], + }, + }, "properties": Object {}, }, "tempIndex": ".my-so-index_7.11.0_reindex_temp", @@ -1152,6 +1307,20 @@ Object { }, }, "indexPrefix": ".my-so-index", + "indexTypesMap": Object { + ".kibana": Array [ + "typeA", + "typeB", + "typeC", + ], + ".kibana_cases": Array [ + "typeD", + "typeE", + ], + ".kibana_task_manager": Array [ + "task", + ], + }, "kibanaVersion": "7.11.0", "knownTypes": Array [], "legacyIndex": ".my-so-index", @@ -1172,6 +1341,7 @@ Object { "resolveMigrationFailures": "https://www.elastic.co/guide/en/kibana/test-branch/resolve-migrations-failures.html", "routingAllocationDisabled": "https://www.elastic.co/guide/en/kibana/test-branch/resolve-migrations-failures.html#routing-allocation-disabled", }, + "mustRelocateDocuments": true, "outdatedDocuments": Array [ Object { "_id": "1234", @@ -1190,6 +1360,22 @@ Object { "retryCount": 0, "retryDelay": 0, "targetIndexMappings": Object { + "_meta": Object { + "indexTypesMap": Object { + ".kibana": Array [ + "typeA", + "typeB", + "typeC", + ], + ".kibana_cases": Array [ + "typeD", + "typeE", + ], + ".kibana_task_manager": Array [ + "task", + ], + }, + }, "properties": Object {}, }, "tempIndex": ".my-so-index_7.11.0_reindex_temp", diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/index.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/index.ts index 652178fd25919..9080e2ce93dbe 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/index.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/index.ts @@ -83,6 +83,9 @@ export { cleanupUnknownAndExcluded } from './cleanup_unknown_and_excluded'; export { waitForDeleteByQueryTask } from './wait_for_delete_by_query_task'; export type { CreateIndexParams, ClusterShardLimitExceeded } from './create_index'; + +export { synchronizeMigrators } from './synchronize_migrators'; + export { createIndex } from './create_index'; export { checkTargetMappings } from './check_target_mappings'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/synchronize_migrators.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/synchronize_migrators.test.ts new file mode 100644 index 0000000000000..a5a8e9c25f929 --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/synchronize_migrators.test.ts @@ -0,0 +1,156 @@ +/* + * Copyright 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 { synchronizeMigrators } from './synchronize_migrators'; +import { type Defer, defer } from '../kibana_migrator_utils'; + +describe('synchronizeMigrators', () => { + let defers: Array>; + let allDefersPromise: Promise; + let migratorsDefers: Array>; + + beforeEach(() => { + jest.clearAllMocks(); + + defers = ['.kibana_cases', '.kibana_task_manager', '.kibana'].map(defer); + allDefersPromise = Promise.all(defers.map(({ promise }) => promise)); + + migratorsDefers = defers.map(({ resolve, reject }) => ({ + resolve: jest.fn(resolve), + reject: jest.fn(reject), + promise: allDefersPromise, + })); + }); + + describe('when all migrators reach the synchronization point with a correct state', () => { + it('unblocks all migrators and resolves Right', async () => { + const tasks = migratorsDefers.map((migratorDefer) => synchronizeMigrators(migratorDefer)); + + const res = await Promise.all(tasks.map((task) => task())); + + migratorsDefers.forEach((migratorDefer) => + expect(migratorDefer.resolve).toHaveBeenCalledTimes(1) + ); + migratorsDefers.forEach((migratorDefer) => + expect(migratorDefer.reject).not.toHaveBeenCalled() + ); + + expect(res).toEqual([ + { _tag: 'Right', right: 'synchronized_successfully' }, + { _tag: 'Right', right: 'synchronized_successfully' }, + { _tag: 'Right', right: 'synchronized_successfully' }, + ]); + }); + + it('migrators are not unblocked until the last one reaches the synchronization point', async () => { + let resolved: number = 0; + migratorsDefers.forEach((migratorDefer) => migratorDefer.promise.then(() => ++resolved)); + const [casesDefer, ...otherMigratorsDefers] = migratorsDefers; + + // we simulate that only kibana_task_manager and kibana migrators get to the sync point + const tasks = otherMigratorsDefers.map((migratorDefer) => + synchronizeMigrators(migratorDefer) + ); + // we don't await for them, or we would be locked forever + Promise.all(tasks.map((task) => task())); + + const [taskManagerDefer, kibanaDefer] = otherMigratorsDefers; + expect(taskManagerDefer.resolve).toHaveBeenCalledTimes(1); + expect(kibanaDefer.resolve).toHaveBeenCalledTimes(1); + expect(casesDefer.resolve).not.toHaveBeenCalled(); + expect(resolved).toEqual(0); + + // finally, the last migrator gets to the synchronization point + await synchronizeMigrators(casesDefer)(); + expect(resolved).toEqual(3); + }); + }); + + describe('when one migrator fails and rejects the synchronization defer', () => { + describe('before the rest of the migrators reach the synchronization point', () => { + it('synchronizedMigrators resolves Left for the rest of migrators', async () => { + let resolved: number = 0; + let errors: number = 0; + migratorsDefers.forEach((migratorDefer) => + migratorDefer.promise.then(() => ++resolved).catch(() => ++errors) + ); + const [casesDefer, ...otherMigratorsDefers] = migratorsDefers; + + // we first make one random migrator fail and not reach the sync point + casesDefer.reject('Oops. The cases migrator failed unexpectedly.'); + + // the other migrators then try to synchronize + const tasks = otherMigratorsDefers.map((migratorDefer) => + synchronizeMigrators(migratorDefer) + ); + + expect(Promise.all(tasks.map((task) => task()))).resolves.toEqual([ + { + _tag: 'Left', + left: { + type: 'sync_failed', + error: 'Oops. The cases migrator failed unexpectedly.', + }, + }, + { + _tag: 'Left', + left: { + type: 'sync_failed', + error: 'Oops. The cases migrator failed unexpectedly.', + }, + }, + ]); + + // force next tick (as we did not await for Promises) + await new Promise((resolve) => setImmediate(resolve)); + expect(resolved).toEqual(0); + expect(errors).toEqual(3); + }); + }); + + describe('after the rest of the migrators reach the synchronization point', () => { + it('synchronizedMigrators resolves Left for the rest of migrators', async () => { + let resolved: number = 0; + let errors: number = 0; + migratorsDefers.forEach((migratorDefer) => + migratorDefer.promise.then(() => ++resolved).catch(() => ++errors) + ); + const [casesDefer, ...otherMigratorsDefers] = migratorsDefers; + + // some migrators try to synchronize + const tasks = otherMigratorsDefers.map((migratorDefer) => + synchronizeMigrators(migratorDefer) + ); + + // we then make one random migrator fail and not reach the sync point + casesDefer.reject('Oops. The cases migrator failed unexpectedly.'); + + expect(Promise.all(tasks.map((task) => task()))).resolves.toEqual([ + { + _tag: 'Left', + left: { + type: 'sync_failed', + error: 'Oops. The cases migrator failed unexpectedly.', + }, + }, + { + _tag: 'Left', + left: { + type: 'sync_failed', + error: 'Oops. The cases migrator failed unexpectedly.', + }, + }, + ]); + + // force next tick (as we did not await for Promises) + await new Promise((resolve) => setImmediate(resolve)); + expect(resolved).toEqual(0); + expect(errors).toEqual(3); + }); + }); + }); +}); diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/synchronize_migrators.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/synchronize_migrators.ts new file mode 100644 index 0000000000000..26763f1f51ae2 --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/synchronize_migrators.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import * as Either from 'fp-ts/lib/Either'; +import * as TaskEither from 'fp-ts/lib/TaskEither'; +import type { Defer } from '../kibana_migrator_utils'; + +export interface SyncFailed { + type: 'sync_failed'; + error: Error; +} + +export function synchronizeMigrators( + defer: Defer +): TaskEither.TaskEither { + return () => { + defer.resolve(); + return defer.promise + .then(() => Either.right('synchronized_successfully' as const)) + .catch((error) => Either.left({ type: 'sync_failed' as const, error })); + }; +} diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.test.ts index 2dcadc9ab0210..d49e784f77633 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.test.ts @@ -13,44 +13,63 @@ import { docLinksServiceMock } from '@kbn/core-doc-links-server-mocks'; import { type SavedObjectsMigrationConfigType, SavedObjectTypeRegistry, + type IndexMapping, } from '@kbn/core-saved-objects-base-server-internal'; +import type { Logger } from '@kbn/logging'; import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; -import { createInitialState } from './initial_state'; +import { createInitialState, type CreateInitialStateParams } from './initial_state'; const mockLogger = loggingSystemMock.create(); +const migrationsConfig = { + retryAttempts: 15, + batchSize: 1000, + maxBatchSizeBytes: ByteSizeValue.parse('100mb'), +} as unknown as SavedObjectsMigrationConfigType; + +const createInitialStateCommonParams = { + kibanaVersion: '8.1.0', + waitForMigrationCompletion: false, + mustRelocateDocuments: true, + indexTypesMap: { + '.kibana': ['typeA', 'typeB', 'typeC'], + '.kibana_task_manager': ['task'], + '.kibana_cases': ['typeD', 'typeE'], + }, + targetMappings: { + dynamic: 'strict', + properties: { my_type: { properties: { title: { type: 'text' } } } }, + } as IndexMapping, + migrationVersionPerType: {}, + indexPrefix: '.kibana_task_manager', + migrationsConfig, +}; + describe('createInitialState', () => { let typeRegistry: SavedObjectTypeRegistry; let docLinks: DocLinksServiceSetup; + let logger: Logger; + let createInitialStateParams: CreateInitialStateParams; beforeEach(() => { typeRegistry = new SavedObjectTypeRegistry(); docLinks = docLinksServiceMock.createSetupContract(); + logger = mockLogger.get(); + createInitialStateParams = { + ...createInitialStateCommonParams, + typeRegistry, + docLinks, + logger, + }; }); afterEach(() => jest.clearAllMocks()); - const migrationsConfig = { - retryAttempts: 15, - batchSize: 1000, - maxBatchSizeBytes: ByteSizeValue.parse('100mb'), - } as unknown as SavedObjectsMigrationConfigType; - it('creates the initial state for the model based on the passed in parameters', () => { expect( createInitialState({ - kibanaVersion: '8.1.0', + ...createInitialStateParams, waitForMigrationCompletion: true, - targetMappings: { - dynamic: 'strict', - properties: { my_type: { properties: { title: { type: 'text' } } } }, - }, - migrationVersionPerType: {}, - indexPrefix: '.kibana_task_manager', - migrationsConfig, - typeRegistry, - docLinks, - logger: mockLogger.get(), }) ).toMatchInlineSnapshot(` Object { @@ -172,6 +191,20 @@ describe('createInitialState', () => { }, }, "indexPrefix": ".kibana_task_manager", + "indexTypesMap": Object { + ".kibana": Array [ + "typeA", + "typeB", + "typeC", + ], + ".kibana_cases": Array [ + "typeD", + "typeE", + ], + ".kibana_task_manager": Array [ + "task", + ], + }, "kibanaVersion": "8.1.0", "knownTypes": Array [], "legacyIndex": ".kibana_task_manager", @@ -183,6 +216,7 @@ describe('createInitialState', () => { "resolveMigrationFailures": "https://www.elastic.co/guide/en/kibana/test-branch/resolve-migrations-failures.html", "routingAllocationDisabled": "https://www.elastic.co/guide/en/kibana/test-branch/resolve-migrations-failures.html#routing-allocation-disabled", }, + "mustRelocateDocuments": true, "outdatedDocumentsQuery": Object { "bool": Object { "should": Array [], @@ -195,6 +229,22 @@ describe('createInitialState', () => { "retryCount": 0, "retryDelay": 0, "targetIndexMappings": Object { + "_meta": Object { + "indexTypesMap": Object { + ".kibana": Array [ + "typeA", + "typeB", + "typeC", + ], + ".kibana_cases": Array [ + "typeD", + "typeE", + ], + ".kibana_task_manager": Array [ + "task", + ], + }, + }, "dynamic": "strict", "properties": Object { "my_type": Object { @@ -227,22 +277,7 @@ describe('createInitialState', () => { }); it('creates the initial state for the model with waitForMigrationCompletion false,', () => { - expect( - createInitialState({ - kibanaVersion: '8.1.0', - waitForMigrationCompletion: false, - targetMappings: { - dynamic: 'strict', - properties: { my_type: { properties: { title: { type: 'text' } } } }, - }, - migrationVersionPerType: {}, - indexPrefix: '.kibana_task_manager', - migrationsConfig, - typeRegistry, - docLinks, - logger: mockLogger.get(), - }) - ).toMatchObject({ + expect(createInitialState(createInitialStateParams)).toMatchObject({ waitForMigrationCompletion: false, }); }); @@ -262,18 +297,10 @@ describe('createInitialState', () => { }); const initialState = createInitialState({ - kibanaVersion: '8.1.0', - waitForMigrationCompletion: false, - targetMappings: { - dynamic: 'strict', - properties: { my_type: { properties: { title: { type: 'text' } } } }, - }, - migrationVersionPerType: {}, - indexPrefix: '.kibana_task_manager', - migrationsConfig, + ...createInitialStateParams, typeRegistry, docLinks, - logger: mockLogger.get(), + logger, }); expect(initialState.knownTypes).toEqual(['foo', 'bar']); @@ -289,40 +316,15 @@ describe('createInitialState', () => { excludeOnUpgrade: fooExcludeOnUpgradeHook, }); - const initialState = createInitialState({ - kibanaVersion: '8.1.0', - waitForMigrationCompletion: false, - targetMappings: { - dynamic: 'strict', - properties: { my_type: { properties: { title: { type: 'text' } } } }, - }, - migrationVersionPerType: {}, - indexPrefix: '.kibana_task_manager', - migrationsConfig, - typeRegistry, - docLinks, - logger: mockLogger.get(), - }); - + const initialState = createInitialState(createInitialStateParams); expect(initialState.excludeFromUpgradeFilterHooks).toEqual({ foo: fooExcludeOnUpgradeHook }); }); it('returns state with a preMigration script', () => { const preMigrationScript = "ctx._id = ctx._source.type + ':' + ctx._id"; const initialState = createInitialState({ - kibanaVersion: '8.1.0', - waitForMigrationCompletion: false, - targetMappings: { - dynamic: 'strict', - properties: { my_type: { properties: { title: { type: 'text' } } } }, - }, + ...createInitialStateParams, preMigrationScript, - migrationVersionPerType: {}, - indexPrefix: '.kibana_task_manager', - migrationsConfig, - typeRegistry, - docLinks, - logger: mockLogger.get(), }); expect(Option.isSome(initialState.preMigrationScript)).toEqual(true); @@ -334,19 +336,8 @@ describe('createInitialState', () => { expect( Option.isNone( createInitialState({ - kibanaVersion: '8.1.0', - waitForMigrationCompletion: false, - targetMappings: { - dynamic: 'strict', - properties: { my_type: { properties: { title: { type: 'text' } } } }, - }, + ...createInitialStateParams, preMigrationScript: undefined, - migrationVersionPerType: {}, - indexPrefix: '.kibana_task_manager', - migrationsConfig, - typeRegistry, - docLinks, - logger: mockLogger.get(), }).preMigrationScript ) ).toEqual(true); @@ -354,19 +345,9 @@ describe('createInitialState', () => { it('returns state with an outdatedDocumentsQuery', () => { expect( createInitialState({ - kibanaVersion: '8.1.0', - waitForMigrationCompletion: false, - targetMappings: { - dynamic: 'strict', - properties: { my_type: { properties: { title: { type: 'text' } } } }, - }, + ...createInitialStateParams, preMigrationScript: "ctx._id = ctx._source.type + ':' + ctx._id", migrationVersionPerType: { my_dashboard: '7.10.1', my_viz: '8.0.0' }, - indexPrefix: '.kibana_task_manager', - migrationsConfig, - typeRegistry, - docLinks, - logger: mockLogger.get(), }).outdatedDocumentsQuery ).toMatchInlineSnapshot(` Object { @@ -473,44 +454,19 @@ describe('createInitialState', () => { }); it('initializes the `discardUnknownObjects` flag to false if the flag is not provided in the config', () => { - const logger = mockLogger.get(); - const initialState = createInitialState({ - kibanaVersion: '8.1.0', - waitForMigrationCompletion: false, - targetMappings: { - dynamic: 'strict', - properties: { my_type: { properties: { title: { type: 'text' } } } }, - }, - migrationVersionPerType: {}, - indexPrefix: '.kibana_task_manager', - migrationsConfig, - typeRegistry, - docLinks, - logger, - }); + const initialState = createInitialState(createInitialStateParams); expect(logger.warn).not.toBeCalled(); expect(initialState.discardUnknownObjects).toEqual(false); }); it('initializes the `discardUnknownObjects` flag to false if the value provided in the config does not match the current kibana version', () => { - const logger = mockLogger.get(); const initialState = createInitialState({ - kibanaVersion: '8.1.0', - waitForMigrationCompletion: false, - targetMappings: { - dynamic: 'strict', - properties: { my_type: { properties: { title: { type: 'text' } } } }, - }, - migrationVersionPerType: {}, - indexPrefix: '.kibana_task_manager', + ...createInitialStateParams, migrationsConfig: { ...migrationsConfig, discardUnknownObjects: '8.0.0', }, - typeRegistry, - docLinks, - logger, }); expect(initialState.discardUnknownObjects).toEqual(false); @@ -522,44 +478,23 @@ describe('createInitialState', () => { it('initializes the `discardUnknownObjects` flag to true if the value provided in the config matches the current kibana version', () => { const initialState = createInitialState({ - kibanaVersion: '8.1.0', - waitForMigrationCompletion: false, - targetMappings: { - dynamic: 'strict', - properties: { my_type: { properties: { title: { type: 'text' } } } }, - }, - migrationVersionPerType: {}, - indexPrefix: '.kibana_task_manager', + ...createInitialStateParams, migrationsConfig: { ...migrationsConfig, discardUnknownObjects: '8.1.0', }, - typeRegistry, - docLinks, - logger: mockLogger.get(), }); expect(initialState.discardUnknownObjects).toEqual(true); }); it('initializes the `discardCorruptObjects` flag to false if the value provided in the config does not match the current kibana version', () => { - const logger = mockLogger.get(); const initialState = createInitialState({ - kibanaVersion: '8.1.0', - waitForMigrationCompletion: false, - targetMappings: { - dynamic: 'strict', - properties: { my_type: { properties: { title: { type: 'text' } } } }, - }, - migrationVersionPerType: {}, - indexPrefix: '.kibana_task_manager', + ...createInitialStateParams, migrationsConfig: { ...migrationsConfig, discardCorruptObjects: '8.0.0', }, - typeRegistry, - docLinks, - logger, }); expect(initialState.discardCorruptObjects).toEqual(false); @@ -571,21 +506,11 @@ describe('createInitialState', () => { it('initializes the `discardCorruptObjects` flag to true if the value provided in the config matches the current kibana version', () => { const initialState = createInitialState({ - kibanaVersion: '8.1.0', - waitForMigrationCompletion: false, - targetMappings: { - dynamic: 'strict', - properties: { my_type: { properties: { title: { type: 'text' } } } }, - }, - migrationVersionPerType: {}, - indexPrefix: '.kibana_task_manager', + ...createInitialStateParams, migrationsConfig: { ...migrationsConfig, discardCorruptObjects: '8.1.0', }, - typeRegistry, - docLinks, - logger: mockLogger.get(), }); expect(initialState.discardCorruptObjects).toEqual(true); diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.ts index ff15afa7c691a..6daa8887d5b72 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.ts @@ -14,10 +14,27 @@ import type { SavedObjectsMigrationVersion } from '@kbn/core-saved-objects-commo import type { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; import type { IndexMapping, + IndexTypesMap, SavedObjectsMigrationConfigType, } from '@kbn/core-saved-objects-base-server-internal'; import type { InitState } from './state'; import { excludeUnusedTypesQuery } from './core'; +import { getTempIndexName } from './model/helpers'; + +export interface CreateInitialStateParams { + kibanaVersion: string; + waitForMigrationCompletion: boolean; + mustRelocateDocuments: boolean; + indexTypesMap: IndexTypesMap; + targetMappings: IndexMapping; + preMigrationScript?: string; + migrationVersionPerType: SavedObjectsMigrationVersion; + indexPrefix: string; + migrationsConfig: SavedObjectsMigrationConfigType; + typeRegistry: ISavedObjectTypeRegistry; + docLinks: DocLinksServiceStart; + logger: Logger; +} /** * Construct the initial state for the model @@ -25,6 +42,8 @@ import { excludeUnusedTypesQuery } from './core'; export const createInitialState = ({ kibanaVersion, waitForMigrationCompletion, + mustRelocateDocuments, + indexTypesMap, targetMappings, preMigrationScript, migrationVersionPerType, @@ -33,18 +52,7 @@ export const createInitialState = ({ typeRegistry, docLinks, logger, -}: { - kibanaVersion: string; - waitForMigrationCompletion: boolean; - targetMappings: IndexMapping; - preMigrationScript?: string; - migrationVersionPerType: SavedObjectsMigrationVersion; - indexPrefix: string; - migrationsConfig: SavedObjectsMigrationConfigType; - typeRegistry: ISavedObjectTypeRegistry; - docLinks: DocLinksServiceStart; - logger: Logger; -}): InitState => { +}: CreateInitialStateParams): InitState => { const outdatedDocumentsQuery: QueryDslQueryContainer = { bool: { should: Object.entries(migrationVersionPerType).map(([type, latestVersion]) => ({ @@ -117,18 +125,28 @@ export const createInitialState = ({ ); } + const targetIndexMappings: IndexMapping = { + ...targetMappings, + _meta: { + ...targetMappings._meta, + indexTypesMap, + }, + }; + return { controlState: 'INIT', waitForMigrationCompletion, + mustRelocateDocuments, + indexTypesMap, indexPrefix, legacyIndex: indexPrefix, currentAlias: indexPrefix, versionAlias: `${indexPrefix}_${kibanaVersion}`, versionIndex: `${indexPrefix}_${kibanaVersion}_001`, - tempIndex: `${indexPrefix}_${kibanaVersion}_reindex_temp`, + tempIndex: getTempIndexName(indexPrefix, kibanaVersion), kibanaVersion, preMigrationScript: Option.fromNullable(preMigrationScript), - targetIndexMappings: targetMappings, + targetIndexMappings, tempIndexMappings: reindexTargetMappings, outdatedDocumentsQuery, retryCount: 0, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.test.ts index e6855a1256b54..f726141c7294d 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.test.ts @@ -18,6 +18,15 @@ import { DocumentMigrator } from './document_migrator'; import { ByteSizeValue } from '@kbn/config-schema'; import { docLinksServiceMock } from '@kbn/core-doc-links-server-mocks'; import { lastValueFrom } from 'rxjs'; +import { runResilientMigrator } from './run_resilient_migrator'; + +jest.mock('./run_resilient_migrator', () => { + const actual = jest.requireActual('./run_resilient_migrator'); + + return { + runResilientMigrator: jest.fn(actual.runResilientMigrator), + }; +}); jest.mock('./document_migrator', () => { return { @@ -29,6 +38,21 @@ jest.mock('./document_migrator', () => { }; }); +const mappingsResponseWithoutIndexTypesMap: estypes.IndicesGetMappingResponse = { + '.kibana_8.7.0_001': { + mappings: { + _meta: { + migrationMappingPropertyHashes: { + references: '7997cf5a56cc02bdc9c93361bde732b0', + // ... + }, + // we do not add a `indexTypesMap` + // simulating a Kibana < 8.8.0 that does not have one yet + }, + }, + }, +}; + const createRegistry = (types: Array>) => { const registry = new SavedObjectTypeRegistry(); types.forEach((type) => @@ -47,6 +71,7 @@ const createRegistry = (types: Array>) => { describe('KibanaMigrator', () => { beforeEach(() => { (DocumentMigrator as jest.Mock).mockClear(); + (runResilientMigrator as jest.MockedFunction).mockClear(); }); describe('getActiveMappings', () => { it('returns full index mappings w/ core properties', () => { @@ -60,7 +85,7 @@ describe('KibanaMigrator', () => { }, { name: 'bmap', - indexPattern: 'other-index', + indexPattern: '.other-index', mappings: { properties: { field: { type: 'text' } }, }, @@ -98,19 +123,34 @@ describe('KibanaMigrator', () => { describe('runMigrations', () => { it('throws if prepareMigrations is not called first', async () => { const options = mockOptions(); - - options.client.indices.get.mockResponse({}, { statusCode: 200 }); - const migrator = new KibanaMigrator(options); - await expect(() => migrator.runMigrations()).toThrowErrorMatchingInlineSnapshot( - `"Migrations are not ready. Make sure prepareMigrations is called first."` + await expect(migrator.runMigrations()).rejects.toThrowError( + 'Migrations are not ready. Make sure prepareMigrations is called first.' ); }); it('only runs migrations once if called multiple times', async () => { + const successfulRun: typeof runResilientMigrator = ({ indexPrefix }) => + Promise.resolve({ + sourceIndex: indexPrefix, + destIndex: indexPrefix, + elapsedMs: 28, + status: 'migrated', + }); + const mockRunResilientMigrator = runResilientMigrator as jest.MockedFunction< + typeof runResilientMigrator + >; + + mockRunResilientMigrator.mockImplementationOnce(successfulRun); + mockRunResilientMigrator.mockImplementationOnce(successfulRun); + mockRunResilientMigrator.mockImplementationOnce(successfulRun); + mockRunResilientMigrator.mockImplementationOnce(successfulRun); const options = mockOptions(); options.client.indices.get.mockResponse({}, { statusCode: 200 }); + options.client.indices.getMapping.mockResponse(mappingsResponseWithoutIndexTypesMap, { + statusCode: 200, + }); options.client.cluster.getSettings.mockResponse( { @@ -127,11 +167,42 @@ describe('KibanaMigrator', () => { await migrator.runMigrations(); // indices.get is called twice during a single migration - expect(options.client.indices.get).toHaveBeenCalledTimes(2); + expect(runResilientMigrator).toHaveBeenCalledTimes(4); + expect(runResilientMigrator).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + indexPrefix: '.my-index', + mustRelocateDocuments: true, + }) + ); + expect(runResilientMigrator).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ + indexPrefix: '.other-index', + mustRelocateDocuments: true, + }) + ); + expect(runResilientMigrator).toHaveBeenNthCalledWith( + 3, + expect.objectContaining({ + indexPrefix: '.my-task-index', + mustRelocateDocuments: false, + }) + ); + expect(runResilientMigrator).toHaveBeenNthCalledWith( + 4, + expect.objectContaining({ + indexPrefix: '.my-complementary-index', + mustRelocateDocuments: true, + }) + ); }); it('emits results on getMigratorResult$()', async () => { const options = mockV2MigrationOptions(); + options.client.indices.getMapping.mockResponse(mappingsResponseWithoutIndexTypesMap, { + statusCode: 200, + }); const migrator = new KibanaMigrator(options); const migratorStatus = lastValueFrom(migrator.getStatus$().pipe(take(3))); migrator.prepareMigrations(); @@ -146,12 +217,12 @@ describe('KibanaMigrator', () => { status: 'migrated', }); expect(result![1]).toMatchObject({ - destIndex: 'other-index_8.2.3_001', + destIndex: '.other-index_8.2.3_001', elapsedMs: expect.any(Number), status: 'patched', }); }); - it('rejects when the migration state machine terminates in a FATAL state', () => { + it('rejects when the migration state machine terminates in a FATAL state', async () => { const options = mockV2MigrationOptions(); options.client.indices.get.mockResponse( { @@ -166,6 +237,9 @@ describe('KibanaMigrator', () => { }, { statusCode: 200 } ); + options.client.indices.getMapping.mockResponse(mappingsResponseWithoutIndexTypesMap, { + statusCode: 200, + }); const migrator = new KibanaMigrator(options); migrator.prepareMigrations(); @@ -181,6 +255,9 @@ describe('KibanaMigrator', () => { error: { type: 'elasticsearch_exception', reason: 'task failed with an error' }, task: { description: 'task description' } as any, }); + options.client.indices.getMapping.mockResponse(mappingsResponseWithoutIndexTypesMap, { + statusCode: 200, + }); const migrator = new KibanaMigrator(options); migrator.prepareMigrations(); @@ -193,6 +270,160 @@ describe('KibanaMigrator', () => { {"_tag":"Some","value":{"type":"elasticsearch_exception","reason":"task failed with an error"}}] `); }); + + describe('for V2 migrations', () => { + describe('where some SO types must be relocated', () => { + it('runs successfully', async () => { + const options = mockV2MigrationOptions(); + options.client.indices.getMapping.mockResponse(mappingsResponseWithoutIndexTypesMap, { + statusCode: 200, + }); + + const migrator = new KibanaMigrator(options); + migrator.prepareMigrations(); + const results = await migrator.runMigrations(); + + expect(results.length).toEqual(4); + expect(results[0]).toEqual( + expect.objectContaining({ + sourceIndex: '.my-index_pre8.2.3_001', + destIndex: '.my-index_8.2.3_001', + elapsedMs: expect.any(Number), + status: 'migrated', + }) + ); + expect(results[1]).toEqual( + expect.objectContaining({ + destIndex: '.other-index_8.2.3_001', + elapsedMs: expect.any(Number), + status: 'patched', + }) + ); + expect(results[2]).toEqual( + expect.objectContaining({ + destIndex: '.my-task-index_8.2.3_001', + elapsedMs: expect.any(Number), + status: 'patched', + }) + ); + expect(results[3]).toEqual( + expect.objectContaining({ + destIndex: '.my-complementary-index_8.2.3_001', + elapsedMs: expect.any(Number), + status: 'patched', + }) + ); + + expect(runResilientMigrator).toHaveBeenCalledTimes(4); + expect(runResilientMigrator).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + kibanaVersion: '8.2.3', + indexPrefix: '.my-index', + indexTypesMap: { + '.my-index': ['testtype', 'testtype3'], + '.other-index': ['testtype2'], + '.my-task-index': ['testtasktype'], + }, + targetMappings: expect.objectContaining({ + properties: expect.objectContaining({ + testtype: expect.anything(), + testtype3: expect.anything(), + }), + }), + readyToReindex: expect.objectContaining({ + promise: expect.anything(), + resolve: expect.anything(), + reject: expect.anything(), + }), + mustRelocateDocuments: true, + doneReindexing: expect.objectContaining({ + promise: expect.anything(), + resolve: expect.anything(), + reject: expect.anything(), + }), + }) + ); + expect(runResilientMigrator).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ + kibanaVersion: '8.2.3', + indexPrefix: '.other-index', + indexTypesMap: { + '.my-index': ['testtype', 'testtype3'], + '.other-index': ['testtype2'], + '.my-task-index': ['testtasktype'], + }, + targetMappings: expect.objectContaining({ + properties: expect.objectContaining({ + testtype2: expect.anything(), + }), + }), + readyToReindex: expect.objectContaining({ + promise: expect.anything(), + resolve: expect.anything(), + reject: expect.anything(), + }), + mustRelocateDocuments: true, + doneReindexing: expect.objectContaining({ + promise: expect.anything(), + resolve: expect.anything(), + reject: expect.anything(), + }), + }) + ); + expect(runResilientMigrator).toHaveBeenNthCalledWith( + 3, + expect.objectContaining({ + kibanaVersion: '8.2.3', + indexPrefix: '.my-task-index', + indexTypesMap: { + '.my-index': ['testtype', 'testtype3'], + '.other-index': ['testtype2'], + '.my-task-index': ['testtasktype'], + }, + targetMappings: expect.objectContaining({ + properties: expect.objectContaining({ + testtasktype: expect.anything(), + }), + }), + // this migrator is NOT involved in any relocation, + // thus, it must not synchronize with other migrators + mustRelocateDocuments: false, + readyToReindex: undefined, + doneReindexing: undefined, + }) + ); + expect(runResilientMigrator).toHaveBeenNthCalledWith( + 4, + expect.objectContaining({ + kibanaVersion: '8.2.3', + indexPrefix: '.my-complementary-index', + indexTypesMap: { + '.my-index': ['testtype', 'testtype3'], + '.other-index': ['testtype2'], + '.my-task-index': ['testtasktype'], + }, + targetMappings: expect.objectContaining({ + properties: expect.not.objectContaining({ + // this index does no longer have any types associated to it + testtype: expect.anything(), + testtype2: expect.anything(), + testtype3: expect.anything(), + testtasktype: expect.anything(), + }), + }), + mustRelocateDocuments: true, + doneReindexing: expect.objectContaining({ + promise: expect.anything(), + resolve: expect.anything(), + reject: expect.anything(), + }), + }) + ); + }); + }); + }); }); }); @@ -254,7 +485,19 @@ const mockOptions = () => { logger: loggingSystemMock.create().get(), kibanaVersion: '8.2.3', waitForMigrationCompletion: false, + defaultIndexTypesMap: { + '.my-index': ['testtype', 'testtype2'], + '.my-task-index': ['testtasktype'], + // this index no longer has any types registered in typeRegistry + // but we still need a migrator for it, so that 'testtype3' documents + // are moved over to their new index (.my_index) + '.my-complementary-index': ['testtype3'], + }, typeRegistry: createRegistry([ + // typeRegistry depicts an updated index map: + // .my-index: ['testtype', 'testtype3'], + // .my-other-index: ['testtype2'], + // .my-task-index': ['testtasktype'], { name: 'testtype', hidden: false, @@ -270,7 +513,32 @@ const mockOptions = () => { name: 'testtype2', hidden: false, namespaceType: 'single', - indexPattern: 'other-index', + // We are moving 'testtype2' from '.my-index' to '.other-index' + indexPattern: '.other-index', + mappings: { + properties: { + name: { type: 'keyword' }, + }, + }, + migrations: {}, + }, + { + name: 'testtasktype', + hidden: false, + namespaceType: 'single', + indexPattern: '.my-task-index', + mappings: { + properties: { + name: { type: 'keyword' }, + }, + }, + migrations: {}, + }, + { + // We are moving 'testtype3' from '.my-complementary-index' to '.my-index' + name: 'testtype3', + hidden: false, + namespaceType: 'single', mappings: { properties: { name: { type: 'keyword' }, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.ts index ef5166f8528fc..d9b9f19b785e4 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.ts @@ -16,10 +16,11 @@ import Semver from 'semver'; import type { Logger } from '@kbn/logging'; import type { DocLinksServiceStart } from '@kbn/core-doc-links-server'; import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import type { - SavedObjectUnsanitizedDoc, - SavedObjectsRawDoc, - ISavedObjectTypeRegistry, +import { + MAIN_SAVED_OBJECT_INDEX, + type SavedObjectUnsanitizedDoc, + type SavedObjectsRawDoc, + type ISavedObjectTypeRegistry, } from '@kbn/core-saved-objects-server'; import { SavedObjectsSerializer, @@ -29,17 +30,17 @@ import { type IKibanaMigrator, type KibanaMigratorStatus, type MigrationResult, + type IndexTypesMap, } from '@kbn/core-saved-objects-base-server-internal'; +import { getIndicesInvolvedInRelocation } from './kibana_migrator_utils'; import { buildActiveMappings, buildTypesMappings } from './core'; import { DocumentMigrator } from './document_migrator'; import { createIndexMap } from './core/build_index_map'; import { runResilientMigrator } from './run_resilient_migrator'; import { migrateRawDocsSafely } from './core/migrate_raw_docs'; import { runZeroDowntimeMigration } from './zdt'; - -// ensure plugins don't try to convert SO namespaceTypes after 8.0.0 -// see https://github.com/elastic/kibana/issues/147344 -const ALLOWED_CONVERT_VERSION = '8.0.0'; +import { createMultiPromiseDefer, indexMapToIndexTypesMap } from './kibana_migrator_utils'; +import { ALLOWED_CONVERT_VERSION, DEFAULT_INDEX_TYPES_MAP } from './kibana_migrator_constants'; export interface KibanaMigratorOptions { client: ElasticsearchClient; @@ -50,6 +51,7 @@ export interface KibanaMigratorOptions { logger: Logger; docLinks: DocLinksServiceStart; waitForMigrationCompletion: boolean; + defaultIndexTypesMap?: IndexTypesMap; } /** @@ -71,6 +73,7 @@ export class KibanaMigrator implements IKibanaMigrator { private readonly soMigrationsConfig: SavedObjectsMigrationConfigType; private readonly docLinks: DocLinksServiceStart; private readonly waitForMigrationCompletion: boolean; + private readonly defaultIndexTypesMap: IndexTypesMap; public readonly kibanaVersion: string; /** @@ -84,6 +87,7 @@ export class KibanaMigrator implements IKibanaMigrator { kibanaVersion, logger, docLinks, + defaultIndexTypesMap = DEFAULT_INDEX_TYPES_MAP, waitForMigrationCompletion, }: KibanaMigratorOptions) { this.client = client; @@ -105,6 +109,7 @@ export class KibanaMigrator implements IKibanaMigrator { // operation so we cache the result this.activeMappings = buildActiveMappings(this.mappingProperties); this.docLinks = docLinks; + this.defaultIndexTypesMap = defaultIndexTypesMap; } public runMigrations({ rerun = false }: { rerun?: boolean } = {}): Promise { @@ -134,12 +139,12 @@ export class KibanaMigrator implements IKibanaMigrator { return this.status$.asObservable(); } - private runMigrationsInternal(): Promise { + private async runMigrationsInternal(): Promise { const migrationAlgorithm = this.soMigrationsConfig.algorithm; if (migrationAlgorithm === 'zdt') { - return this.runMigrationZdt(); + return await this.runMigrationZdt(); } else { - return this.runMigrationV2(); + return await this.runMigrationV2(); } } @@ -157,7 +162,7 @@ export class KibanaMigrator implements IKibanaMigrator { }); } - private runMigrationV2(): Promise { + private async runMigrationV2(): Promise { const indexMap = createIndexMap({ kibanaIndexName: this.kibanaIndex, indexMap: this.mappingProperties, @@ -173,16 +178,59 @@ export class KibanaMigrator implements IKibanaMigrator { this.log.debug(`migrationVersion: ${migrationVersion} saved object type: ${type}`); }); - const migrators = Object.keys(indexMap).map((index) => { + // build a indexTypesMap from the info present in tye typeRegistry, e.g.: + // { + // '.kibana': ['typeA', 'typeB', ...] + // '.kibana_task_manager': ['task', ...] + // '.kibana_cases': ['typeC', 'typeD', ...] + // ... + // } + const indexTypesMap = indexMapToIndexTypesMap(indexMap); + + // compare indexTypesMap with the one present (or not) in the .kibana index meta + // and check if some SO types have been moved to different indices + const indicesWithMovingTypes = await getIndicesInvolvedInRelocation({ + mainIndex: MAIN_SAVED_OBJECT_INDEX, + client: this.client, + indexTypesMap, + logger: this.log, + defaultIndexTypesMap: this.defaultIndexTypesMap, + }); + + // we create 2 synchronization objects (2 synchronization points) for each of the + // migrators involved in relocations, aka each of the migrators that will: + // A) reindex some documents TO other indices + // B) receive some documents FROM other indices + // C) both + const readyToReindexDefers = createMultiPromiseDefer(indicesWithMovingTypes); + const doneReindexingDefers = createMultiPromiseDefer(indicesWithMovingTypes); + + // build a list of all migrators that must be started + const migratorIndices = new Set(Object.keys(indexMap)); + // indices involved in a relocation might no longer be present in current mappings + // but if their SOs must be relocated to another index, we still need a migrator to do the job + indicesWithMovingTypes.forEach((index) => migratorIndices.add(index)); + + const migrators = Array.from(migratorIndices).map((indexName, i) => { return { migrate: (): Promise => { + const readyToReindex = readyToReindexDefers[indexName]; + const doneReindexing = doneReindexingDefers[indexName]; + // check if this migrator's index is involved in some document redistribution + const mustRelocateDocuments = !!readyToReindex; + return runResilientMigrator({ client: this.client, kibanaVersion: this.kibanaVersion, + mustRelocateDocuments, + indexTypesMap, waitForMigrationCompletion: this.waitForMigrationCompletion, - targetMappings: buildActiveMappings(indexMap[index].typeMappings), + // a migrator's index might no longer have any associated types to it + targetMappings: buildActiveMappings(indexMap[indexName]?.typeMappings ?? {}), logger: this.log, - preMigrationScript: indexMap[index].script, + preMigrationScript: indexMap[indexName]?.script, + readyToReindex, + doneReindexing, transformRawDocs: (rawDocs: SavedObjectsRawDoc[]) => migrateRawDocsSafely({ serializer: this.serializer, @@ -190,7 +238,7 @@ export class KibanaMigrator implements IKibanaMigrator { rawDocs, }), migrationVersionPerType: this.documentMigrator.migrationVersion, - indexPrefix: index, + indexPrefix: indexName, migrationsConfig: this.soMigrationsConfig, typeRegistry: this.typeRegistry, docLinks: this.docLinks, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator_constants.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator_constants.ts new file mode 100644 index 0000000000000..e18abed5caca7 --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator_constants.ts @@ -0,0 +1,131 @@ +/* + * Copyright 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 { IndexTypesMap } from '@kbn/core-saved-objects-base-server-internal'; + +export enum TypeStatus { + Added = 'added', + Removed = 'removed', + Moved = 'moved', + Untouched = 'untouched', +} + +export interface TypeStatusDetails { + currentIndex?: string; + targetIndex?: string; + status: TypeStatus; +} + +// ensure plugins don't try to convert SO namespaceTypes after 8.0.0 +// see https://github.com/elastic/kibana/issues/147344 +export const ALLOWED_CONVERT_VERSION = '8.0.0'; + +export const DEFAULT_INDEX_TYPES_MAP: IndexTypesMap = { + '.kibana_task_manager': ['task'], + '.kibana': [ + 'action', + 'action_task_params', + 'alert', + 'api_key_pending_invalidation', + 'apm-indices', + 'apm-server-schema', + 'apm-service-group', + 'apm-telemetry', + 'app_search_telemetry', + 'application_usage_daily', + 'application_usage_totals', + 'book', + 'canvas-element', + 'canvas-workpad', + 'canvas-workpad-template', + 'cases', + 'cases-comments', + 'cases-configure', + 'cases-connector-mappings', + 'cases-telemetry', + 'cases-user-actions', + 'config', + 'config-global', + 'connector_token', + 'core-usage-stats', + 'csp-rule-template', + 'dashboard', + 'endpoint:user-artifact', + 'endpoint:user-artifact-manifest', + 'enterprise_search_telemetry', + 'epm-packages', + 'epm-packages-assets', + 'event_loop_delays_daily', + 'exception-list', + 'exception-list-agnostic', + 'file', + 'file-upload-usage-collection-telemetry', + 'fileShare', + 'fleet-fleet-server-host', + 'fleet-message-signing-keys', + 'fleet-preconfiguration-deletion-record', + 'fleet-proxy', + 'graph-workspace', + 'guided-onboarding-guide-state', + 'guided-onboarding-plugin-state', + 'index-pattern', + 'infrastructure-monitoring-log-view', + 'infrastructure-ui-source', + 'ingest-agent-policies', + 'ingest-download-sources', + 'ingest-outputs', + 'ingest-package-policies', + 'ingest_manager_settings', + 'inventory-view', + 'kql-telemetry', + 'legacy-url-alias', + 'lens', + 'lens-ui-telemetry', + 'map', + 'metrics-explorer-view', + 'ml-job', + 'ml-module', + 'ml-trained-model', + 'monitoring-telemetry', + 'osquery-manager-usage-metric', + 'osquery-pack', + 'osquery-pack-asset', + 'osquery-saved-query', + 'query', + 'rules-settings', + 'sample-data-telemetry', + 'search', + 'search-session', + 'search-telemetry', + 'searchableList', + 'security-rule', + 'security-solution-signals-migration', + 'siem-detection-engine-rule-actions', + 'siem-ui-timeline', + 'siem-ui-timeline-note', + 'siem-ui-timeline-pinned-event', + 'slo', + 'space', + 'spaces-usage-stats', + 'synthetics-monitor', + 'synthetics-param', + 'synthetics-privates-locations', + 'tag', + 'telemetry', + 'todo', + 'ui-metric', + 'upgrade-assistant-ml-upgrade-operation', + 'upgrade-assistant-reindex-operation', + 'uptime-dynamic-settings', + 'uptime-synthetics-api-key', + 'url', + 'usage-counters', + 'visualization', + 'workplace_search_telemetry', + ], +}; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator_utils.fixtures.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator_utils.fixtures.ts new file mode 100644 index 0000000000000..802167d733fb5 --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator_utils.fixtures.ts @@ -0,0 +1,3413 @@ +/* + * Copyright 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 { IndexMap } from './core'; + +export const INDEX_MAP_BEFORE_SPLIT: IndexMap = { + '.kibana': { + typeMappings: { + 'core-usage-stats': { + dynamic: false, + properties: {}, + }, + 'legacy-url-alias': { + dynamic: false, + properties: { + sourceId: { + type: 'keyword', + }, + targetNamespace: { + type: 'keyword', + }, + targetType: { + type: 'keyword', + }, + targetId: { + type: 'keyword', + }, + resolveCounter: { + type: 'long', + }, + disabled: { + type: 'boolean', + }, + }, + }, + config: { + dynamic: false, + properties: { + buildNum: { + type: 'keyword', + }, + }, + }, + 'config-global': { + dynamic: false, + properties: { + buildNum: { + type: 'keyword', + }, + }, + }, + 'usage-counters': { + dynamic: false, + properties: { + domainId: { + type: 'keyword', + }, + }, + }, + 'guided-onboarding-guide-state': { + dynamic: false, + properties: { + guideId: { + type: 'keyword', + }, + isActive: { + type: 'boolean', + }, + }, + }, + 'guided-onboarding-plugin-state': { + dynamic: false, + properties: {}, + }, + 'ui-metric': { + properties: { + count: { + type: 'integer', + }, + }, + }, + application_usage_totals: { + dynamic: false, + properties: {}, + }, + application_usage_daily: { + dynamic: false, + properties: { + timestamp: { + type: 'date', + }, + }, + }, + event_loop_delays_daily: { + dynamic: false, + properties: { + lastUpdatedAt: { + type: 'date', + }, + }, + }, + url: { + properties: { + slug: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + }, + }, + }, + accessCount: { + type: 'long', + }, + accessDate: { + type: 'date', + }, + createDate: { + type: 'date', + }, + url: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 2048, + }, + }, + }, + locatorJSON: { + type: 'text', + index: false, + }, + }, + }, + 'index-pattern': { + dynamic: false, + properties: { + title: { + type: 'text', + }, + type: { + type: 'keyword', + }, + name: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + }, + }, + }, + }, + }, + 'sample-data-telemetry': { + properties: { + installCount: { + type: 'long', + }, + unInstallCount: { + type: 'long', + }, + }, + }, + space: { + properties: { + name: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 2048, + }, + }, + }, + description: { + type: 'text', + }, + initials: { + type: 'keyword', + }, + color: { + type: 'keyword', + }, + disabledFeatures: { + type: 'keyword', + }, + imageUrl: { + type: 'text', + index: false, + }, + _reserved: { + type: 'boolean', + }, + }, + }, + 'spaces-usage-stats': { + dynamic: false, + properties: {}, + }, + 'exception-list-agnostic': { + properties: { + _tags: { + type: 'keyword', + }, + created_at: { + type: 'keyword', + }, + created_by: { + type: 'keyword', + }, + description: { + type: 'keyword', + }, + immutable: { + type: 'boolean', + }, + list_id: { + type: 'keyword', + }, + list_type: { + type: 'keyword', + }, + meta: { + type: 'keyword', + }, + name: { + fields: { + text: { + type: 'text', + }, + }, + type: 'keyword', + }, + tags: { + fields: { + text: { + type: 'text', + }, + }, + type: 'keyword', + }, + tie_breaker_id: { + type: 'keyword', + }, + type: { + type: 'keyword', + }, + updated_by: { + type: 'keyword', + }, + version: { + type: 'keyword', + }, + comments: { + properties: { + comment: { + type: 'keyword', + }, + created_at: { + type: 'keyword', + }, + created_by: { + type: 'keyword', + }, + id: { + type: 'keyword', + }, + updated_at: { + type: 'keyword', + }, + updated_by: { + type: 'keyword', + }, + }, + }, + entries: { + properties: { + entries: { + properties: { + field: { + type: 'keyword', + }, + operator: { + type: 'keyword', + }, + type: { + type: 'keyword', + }, + value: { + fields: { + text: { + type: 'text', + }, + }, + type: 'keyword', + }, + }, + }, + field: { + type: 'keyword', + }, + list: { + properties: { + id: { + type: 'keyword', + }, + type: { + type: 'keyword', + }, + }, + }, + operator: { + type: 'keyword', + }, + type: { + type: 'keyword', + }, + value: { + fields: { + text: { + type: 'text', + }, + }, + type: 'keyword', + }, + }, + }, + expire_time: { + type: 'date', + }, + item_id: { + type: 'keyword', + }, + os_types: { + type: 'keyword', + }, + }, + }, + 'exception-list': { + properties: { + _tags: { + type: 'keyword', + }, + created_at: { + type: 'keyword', + }, + created_by: { + type: 'keyword', + }, + description: { + type: 'keyword', + }, + immutable: { + type: 'boolean', + }, + list_id: { + type: 'keyword', + }, + list_type: { + type: 'keyword', + }, + meta: { + type: 'keyword', + }, + name: { + fields: { + text: { + type: 'text', + }, + }, + type: 'keyword', + }, + tags: { + fields: { + text: { + type: 'text', + }, + }, + type: 'keyword', + }, + tie_breaker_id: { + type: 'keyword', + }, + type: { + type: 'keyword', + }, + updated_by: { + type: 'keyword', + }, + version: { + type: 'keyword', + }, + comments: { + properties: { + comment: { + type: 'keyword', + }, + created_at: { + type: 'keyword', + }, + created_by: { + type: 'keyword', + }, + id: { + type: 'keyword', + }, + updated_at: { + type: 'keyword', + }, + updated_by: { + type: 'keyword', + }, + }, + }, + entries: { + properties: { + entries: { + properties: { + field: { + type: 'keyword', + }, + operator: { + type: 'keyword', + }, + type: { + type: 'keyword', + }, + value: { + fields: { + text: { + type: 'text', + }, + }, + type: 'keyword', + }, + }, + }, + field: { + type: 'keyword', + }, + list: { + properties: { + id: { + type: 'keyword', + }, + type: { + type: 'keyword', + }, + }, + }, + operator: { + type: 'keyword', + }, + type: { + type: 'keyword', + }, + value: { + fields: { + text: { + type: 'text', + }, + }, + type: 'keyword', + }, + }, + }, + expire_time: { + type: 'date', + }, + item_id: { + type: 'keyword', + }, + os_types: { + type: 'keyword', + }, + }, + }, + telemetry: { + properties: { + enabled: { + type: 'boolean', + }, + sendUsageFrom: { + type: 'keyword', + }, + lastReported: { + type: 'date', + }, + lastVersionChecked: { + type: 'keyword', + }, + userHasSeenNotice: { + type: 'boolean', + }, + reportFailureCount: { + type: 'integer', + }, + reportFailureVersion: { + type: 'keyword', + }, + allowChangingOptInStatus: { + type: 'boolean', + }, + }, + }, + file: { + dynamic: false, + properties: { + created: { + type: 'date', + }, + Updated: { + type: 'date', + }, + name: { + type: 'text', + }, + user: { + type: 'flattened', + }, + Status: { + type: 'keyword', + }, + mime_type: { + type: 'keyword', + }, + extension: { + type: 'keyword', + }, + size: { + type: 'long', + }, + Meta: { + type: 'flattened', + }, + FileKind: { + type: 'keyword', + }, + }, + }, + fileShare: { + dynamic: false, + properties: { + created: { + type: 'date', + }, + valid_until: { + type: 'long', + }, + token: { + type: 'keyword', + }, + name: { + type: 'keyword', + }, + }, + }, + action: { + properties: { + name: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + }, + }, + }, + actionTypeId: { + type: 'keyword', + }, + isMissingSecrets: { + type: 'boolean', + }, + config: { + enabled: false, + type: 'object', + }, + secrets: { + type: 'binary', + }, + }, + }, + action_task_params: { + dynamic: false, + properties: { + actionId: { + type: 'keyword', + }, + consumer: { + type: 'keyword', + }, + params: { + enabled: false, + type: 'object', + }, + apiKey: { + type: 'binary', + }, + executionId: { + type: 'keyword', + }, + relatedSavedObjects: { + enabled: false, + type: 'object', + }, + }, + }, + connector_token: { + properties: { + connectorId: { + type: 'keyword', + }, + tokenType: { + type: 'keyword', + }, + token: { + type: 'binary', + }, + expiresAt: { + type: 'date', + }, + createdAt: { + type: 'date', + }, + updatedAt: { + type: 'date', + }, + }, + }, + query: { + properties: { + title: { + type: 'text', + }, + description: { + type: 'text', + }, + query: { + properties: { + language: { + type: 'keyword', + }, + query: { + type: 'keyword', + index: false, + }, + }, + }, + filters: { + dynamic: false, + properties: {}, + }, + timefilter: { + dynamic: false, + properties: {}, + }, + }, + }, + 'kql-telemetry': { + properties: { + optInCount: { + type: 'long', + }, + optOutCount: { + type: 'long', + }, + }, + }, + 'search-session': { + properties: { + sessionId: { + type: 'keyword', + }, + name: { + type: 'keyword', + }, + created: { + type: 'date', + }, + expires: { + type: 'date', + }, + appId: { + type: 'keyword', + }, + locatorId: { + type: 'keyword', + }, + initialState: { + dynamic: false, + properties: {}, + }, + restoreState: { + dynamic: false, + properties: {}, + }, + idMapping: { + dynamic: false, + properties: {}, + }, + realmType: { + type: 'keyword', + }, + realmName: { + type: 'keyword', + }, + username: { + type: 'keyword', + }, + version: { + type: 'keyword', + }, + isCanceled: { + type: 'boolean', + }, + }, + }, + 'search-telemetry': { + dynamic: false, + properties: {}, + }, + 'file-upload-usage-collection-telemetry': { + properties: { + file_upload: { + properties: { + index_creation_count: { + type: 'long', + }, + }, + }, + }, + }, + alert: { + properties: { + enabled: { + type: 'boolean', + }, + name: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + normalizer: 'lowercase', + }, + }, + }, + tags: { + type: 'keyword', + }, + alertTypeId: { + type: 'keyword', + }, + schedule: { + properties: { + interval: { + type: 'keyword', + }, + }, + }, + consumer: { + type: 'keyword', + }, + legacyId: { + type: 'keyword', + }, + actions: { + dynamic: false, + type: 'nested', + properties: { + group: { + type: 'keyword', + }, + actionRef: { + type: 'keyword', + }, + actionTypeId: { + type: 'keyword', + }, + params: { + dynamic: false, + properties: {}, + }, + frequency: { + properties: { + summary: { + index: false, + type: 'boolean', + }, + notifyWhen: { + index: false, + type: 'keyword', + }, + throttle: { + index: false, + type: 'keyword', + }, + }, + }, + }, + }, + params: { + type: 'flattened', + ignore_above: 4096, + }, + mapped_params: { + properties: { + risk_score: { + type: 'float', + }, + severity: { + type: 'keyword', + }, + }, + }, + scheduledTaskId: { + type: 'keyword', + }, + createdBy: { + type: 'keyword', + }, + updatedBy: { + type: 'keyword', + }, + createdAt: { + type: 'date', + }, + updatedAt: { + type: 'date', + }, + apiKey: { + type: 'binary', + }, + apiKeyOwner: { + type: 'keyword', + }, + throttle: { + type: 'keyword', + }, + notifyWhen: { + type: 'keyword', + }, + muteAll: { + type: 'boolean', + }, + mutedInstanceIds: { + type: 'keyword', + }, + meta: { + properties: { + versionApiKeyLastmodified: { + type: 'keyword', + }, + }, + }, + monitoring: { + properties: { + run: { + properties: { + history: { + properties: { + duration: { + type: 'long', + }, + success: { + type: 'boolean', + }, + timestamp: { + type: 'date', + }, + outcome: { + type: 'keyword', + }, + }, + }, + calculated_metrics: { + properties: { + p50: { + type: 'long', + }, + p95: { + type: 'long', + }, + p99: { + type: 'long', + }, + success_ratio: { + type: 'float', + }, + }, + }, + last_run: { + properties: { + timestamp: { + type: 'date', + }, + metrics: { + properties: { + duration: { + type: 'long', + }, + total_search_duration_ms: { + type: 'long', + }, + total_indexing_duration_ms: { + type: 'long', + }, + total_alerts_detected: { + type: 'float', + }, + total_alerts_created: { + type: 'float', + }, + gap_duration_s: { + type: 'float', + }, + }, + }, + }, + }, + }, + }, + }, + }, + snoozeSchedule: { + type: 'nested', + properties: { + id: { + type: 'keyword', + }, + duration: { + type: 'long', + }, + skipRecurrences: { + type: 'date', + format: 'strict_date_time', + }, + rRule: { + type: 'nested', + properties: { + freq: { + type: 'keyword', + }, + dtstart: { + type: 'date', + format: 'strict_date_time', + }, + tzid: { + type: 'keyword', + }, + until: { + type: 'date', + format: 'strict_date_time', + }, + count: { + type: 'long', + }, + interval: { + type: 'long', + }, + wkst: { + type: 'keyword', + }, + byweekday: { + type: 'keyword', + }, + bymonth: { + type: 'short', + }, + bysetpos: { + type: 'long', + }, + bymonthday: { + type: 'short', + }, + byyearday: { + type: 'short', + }, + byweekno: { + type: 'short', + }, + byhour: { + type: 'long', + }, + byminute: { + type: 'long', + }, + bysecond: { + type: 'long', + }, + }, + }, + }, + }, + nextRun: { + type: 'date', + }, + executionStatus: { + properties: { + numberOfTriggeredActions: { + type: 'long', + }, + status: { + type: 'keyword', + }, + lastExecutionDate: { + type: 'date', + }, + lastDuration: { + type: 'long', + }, + error: { + properties: { + reason: { + type: 'keyword', + }, + message: { + type: 'keyword', + }, + }, + }, + warning: { + properties: { + reason: { + type: 'keyword', + }, + message: { + type: 'keyword', + }, + }, + }, + }, + }, + lastRun: { + properties: { + outcome: { + type: 'keyword', + }, + outcomeOrder: { + type: 'float', + }, + warning: { + type: 'text', + }, + outcomeMsg: { + type: 'text', + }, + alertsCount: { + properties: { + active: { + type: 'float', + }, + new: { + type: 'float', + }, + recovered: { + type: 'float', + }, + ignored: { + type: 'float', + }, + }, + }, + }, + }, + running: { + type: 'boolean', + }, + }, + }, + api_key_pending_invalidation: { + properties: { + apiKeyId: { + type: 'keyword', + }, + createdAt: { + type: 'date', + }, + }, + }, + 'rules-settings': { + properties: { + flapping: { + properties: { + enabled: { + type: 'boolean', + index: false, + }, + lookBackWindow: { + type: 'long', + index: false, + }, + statusChangeThreshold: { + type: 'long', + index: false, + }, + createdBy: { + type: 'keyword', + index: false, + }, + updatedBy: { + type: 'keyword', + index: false, + }, + createdAt: { + type: 'date', + index: false, + }, + updatedAt: { + type: 'date', + index: false, + }, + }, + }, + }, + }, + search: { + properties: { + columns: { + type: 'keyword', + index: false, + doc_values: false, + }, + description: { + type: 'text', + }, + viewMode: { + type: 'keyword', + index: false, + doc_values: false, + }, + hideChart: { + type: 'boolean', + index: false, + doc_values: false, + }, + isTextBasedQuery: { + type: 'boolean', + index: false, + doc_values: false, + }, + usesAdHocDataView: { + type: 'boolean', + index: false, + doc_values: false, + }, + hideAggregatedPreview: { + type: 'boolean', + index: false, + doc_values: false, + }, + hits: { + type: 'integer', + index: false, + doc_values: false, + }, + kibanaSavedObjectMeta: { + properties: { + searchSourceJSON: { + type: 'text', + index: false, + }, + }, + }, + sort: { + type: 'keyword', + index: false, + doc_values: false, + }, + title: { + type: 'text', + }, + grid: { + dynamic: false, + properties: {}, + }, + version: { + type: 'integer', + }, + rowHeight: { + type: 'text', + }, + timeRestore: { + type: 'boolean', + index: false, + doc_values: false, + }, + timeRange: { + dynamic: false, + properties: { + from: { + type: 'keyword', + index: false, + doc_values: false, + }, + to: { + type: 'keyword', + index: false, + doc_values: false, + }, + }, + }, + refreshInterval: { + dynamic: false, + properties: { + pause: { + type: 'boolean', + index: false, + doc_values: false, + }, + value: { + type: 'integer', + index: false, + doc_values: false, + }, + }, + }, + rowsPerPage: { + type: 'integer', + index: false, + doc_values: false, + }, + breakdownField: { + type: 'text', + }, + }, + }, + tag: { + properties: { + name: { + type: 'text', + }, + description: { + type: 'text', + }, + color: { + type: 'text', + }, + }, + }, + 'graph-workspace': { + properties: { + description: { + type: 'text', + }, + kibanaSavedObjectMeta: { + properties: { + searchSourceJSON: { + type: 'text', + }, + }, + }, + numLinks: { + type: 'integer', + }, + numVertices: { + type: 'integer', + }, + title: { + type: 'text', + }, + version: { + type: 'integer', + }, + wsState: { + type: 'text', + }, + legacyIndexPatternRef: { + type: 'text', + index: false, + }, + }, + }, + visualization: { + properties: { + description: { + type: 'text', + }, + kibanaSavedObjectMeta: { + properties: { + searchSourceJSON: { + type: 'text', + index: false, + }, + }, + }, + savedSearchRefName: { + type: 'keyword', + index: false, + doc_values: false, + }, + title: { + type: 'text', + }, + uiStateJSON: { + type: 'text', + index: false, + }, + version: { + type: 'integer', + }, + visState: { + type: 'text', + index: false, + }, + }, + }, + dashboard: { + properties: { + description: { + type: 'text', + }, + hits: { + type: 'integer', + index: false, + doc_values: false, + }, + kibanaSavedObjectMeta: { + properties: { + searchSourceJSON: { + type: 'text', + index: false, + }, + }, + }, + optionsJSON: { + type: 'text', + index: false, + }, + panelsJSON: { + type: 'text', + index: false, + }, + refreshInterval: { + properties: { + display: { + type: 'keyword', + index: false, + doc_values: false, + }, + pause: { + type: 'boolean', + index: false, + doc_values: false, + }, + section: { + type: 'integer', + index: false, + doc_values: false, + }, + value: { + type: 'integer', + index: false, + doc_values: false, + }, + }, + }, + controlGroupInput: { + properties: { + controlStyle: { + type: 'keyword', + index: false, + doc_values: false, + }, + chainingSystem: { + type: 'keyword', + index: false, + doc_values: false, + }, + panelsJSON: { + type: 'text', + index: false, + }, + ignoreParentSettingsJSON: { + type: 'text', + index: false, + }, + }, + }, + timeFrom: { + type: 'keyword', + index: false, + doc_values: false, + }, + timeRestore: { + type: 'boolean', + index: false, + doc_values: false, + }, + timeTo: { + type: 'keyword', + index: false, + doc_values: false, + }, + title: { + type: 'text', + }, + version: { + type: 'integer', + }, + }, + }, + todo: { + properties: { + title: { + type: 'keyword', + }, + task: { + type: 'text', + }, + icon: { + type: 'keyword', + }, + }, + }, + book: { + properties: { + title: { + type: 'keyword', + }, + author: { + type: 'keyword', + }, + readIt: { + type: 'boolean', + }, + }, + }, + searchableList: { + properties: { + title: { + type: 'text', + }, + version: { + type: 'integer', + }, + }, + }, + lens: { + properties: { + title: { + type: 'text', + }, + description: { + type: 'text', + }, + visualizationType: { + type: 'keyword', + }, + state: { + type: 'flattened', + }, + expression: { + index: false, + doc_values: false, + type: 'keyword', + }, + }, + }, + 'lens-ui-telemetry': { + properties: { + name: { + type: 'keyword', + }, + type: { + type: 'keyword', + }, + date: { + type: 'date', + }, + count: { + type: 'integer', + }, + }, + }, + map: { + properties: { + description: { + type: 'text', + }, + title: { + type: 'text', + }, + version: { + type: 'integer', + }, + mapStateJSON: { + type: 'text', + }, + layerListJSON: { + type: 'text', + }, + uiStateJSON: { + type: 'text', + }, + bounds: { + dynamic: false, + properties: {}, + }, + }, + }, + 'cases-comments': { + dynamic: false, + properties: { + comment: { + type: 'text', + }, + owner: { + type: 'keyword', + }, + type: { + type: 'keyword', + }, + actions: { + properties: { + type: { + type: 'keyword', + }, + }, + }, + alertId: { + type: 'keyword', + }, + created_at: { + type: 'date', + }, + created_by: { + properties: { + username: { + type: 'keyword', + }, + }, + }, + externalReferenceAttachmentTypeId: { + type: 'keyword', + }, + persistableStateAttachmentTypeId: { + type: 'keyword', + }, + pushed_at: { + type: 'date', + }, + updated_at: { + type: 'date', + }, + }, + }, + 'cases-configure': { + dynamic: false, + properties: { + created_at: { + type: 'date', + }, + closure_type: { + type: 'keyword', + }, + owner: { + type: 'keyword', + }, + }, + }, + 'cases-connector-mappings': { + dynamic: false, + properties: { + owner: { + type: 'keyword', + }, + }, + }, + cases: { + dynamic: false, + properties: { + assignees: { + properties: { + uid: { + type: 'keyword', + }, + }, + }, + closed_at: { + type: 'date', + }, + closed_by: { + properties: { + username: { + type: 'keyword', + }, + full_name: { + type: 'keyword', + }, + email: { + type: 'keyword', + }, + profile_uid: { + type: 'keyword', + }, + }, + }, + created_at: { + type: 'date', + }, + created_by: { + properties: { + username: { + type: 'keyword', + }, + full_name: { + type: 'keyword', + }, + email: { + type: 'keyword', + }, + profile_uid: { + type: 'keyword', + }, + }, + }, + duration: { + type: 'unsigned_long', + }, + description: { + type: 'text', + }, + connector: { + properties: { + name: { + type: 'text', + }, + type: { + type: 'keyword', + }, + fields: { + properties: { + key: { + type: 'text', + }, + value: { + type: 'text', + }, + }, + }, + }, + }, + external_service: { + properties: { + pushed_at: { + type: 'date', + }, + pushed_by: { + properties: { + username: { + type: 'keyword', + }, + full_name: { + type: 'keyword', + }, + email: { + type: 'keyword', + }, + profile_uid: { + type: 'keyword', + }, + }, + }, + connector_name: { + type: 'keyword', + }, + external_id: { + type: 'keyword', + }, + external_title: { + type: 'text', + }, + external_url: { + type: 'text', + }, + }, + }, + owner: { + type: 'keyword', + }, + title: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + }, + }, + }, + status: { + type: 'short', + }, + tags: { + type: 'keyword', + }, + updated_at: { + type: 'date', + }, + updated_by: { + properties: { + username: { + type: 'keyword', + }, + full_name: { + type: 'keyword', + }, + email: { + type: 'keyword', + }, + profile_uid: { + type: 'keyword', + }, + }, + }, + settings: { + properties: { + syncAlerts: { + type: 'boolean', + }, + }, + }, + severity: { + type: 'short', + }, + total_alerts: { + type: 'integer', + }, + total_comments: { + type: 'integer', + }, + }, + }, + 'cases-user-actions': { + dynamic: false, + properties: { + action: { + type: 'keyword', + }, + created_at: { + type: 'date', + }, + created_by: { + properties: { + username: { + type: 'keyword', + }, + }, + }, + payload: { + dynamic: false, + properties: { + connector: { + properties: { + type: { + type: 'keyword', + }, + }, + }, + comment: { + properties: { + type: { + type: 'keyword', + }, + externalReferenceAttachmentTypeId: { + type: 'keyword', + }, + persistableStateAttachmentTypeId: { + type: 'keyword', + }, + }, + }, + assignees: { + properties: { + uid: { + type: 'keyword', + }, + }, + }, + }, + }, + owner: { + type: 'keyword', + }, + type: { + type: 'keyword', + }, + }, + }, + 'cases-telemetry': { + dynamic: false, + properties: {}, + }, + 'canvas-element': { + dynamic: false, + properties: { + name: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + }, + }, + }, + help: { + type: 'text', + }, + content: { + type: 'text', + }, + image: { + type: 'text', + }, + '@timestamp': { + type: 'date', + }, + '@created': { + type: 'date', + }, + }, + }, + 'canvas-workpad': { + dynamic: false, + properties: { + name: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + }, + }, + }, + '@timestamp': { + type: 'date', + }, + '@created': { + type: 'date', + }, + }, + }, + 'canvas-workpad-template': { + dynamic: false, + properties: { + name: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + }, + }, + }, + help: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + }, + }, + }, + tags: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + }, + }, + }, + template_key: { + type: 'keyword', + }, + }, + }, + slo: { + dynamic: false, + properties: { + id: { + type: 'keyword', + }, + name: { + type: 'keyword', + }, + description: { + type: 'text', + }, + indicator: { + properties: { + type: { + type: 'keyword', + }, + params: { + type: 'flattened', + }, + }, + }, + timeWindow: { + properties: { + duration: { + type: 'keyword', + }, + isRolling: { + type: 'boolean', + }, + calendar: { + properties: { + startTime: { + type: 'date', + }, + }, + }, + }, + }, + budgetingMethod: { + type: 'keyword', + }, + objective: { + properties: { + target: { + type: 'float', + }, + timesliceTarget: { + type: 'float', + }, + timesliceWindow: { + type: 'keyword', + }, + }, + }, + settings: { + properties: { + timestampField: { + type: 'keyword', + }, + syncDelay: { + type: 'keyword', + }, + frequency: { + type: 'keyword', + }, + }, + }, + revision: { + type: 'short', + }, + enabled: { + type: 'boolean', + }, + createdAt: { + type: 'date', + }, + updatedAt: { + type: 'date', + }, + }, + }, + ingest_manager_settings: { + properties: { + fleet_server_hosts: { + type: 'keyword', + }, + has_seen_add_data_notice: { + type: 'boolean', + index: false, + }, + prerelease_integrations_enabled: { + type: 'boolean', + }, + }, + }, + 'ingest-agent-policies': { + properties: { + name: { + type: 'keyword', + }, + schema_version: { + type: 'version', + }, + description: { + type: 'text', + }, + namespace: { + type: 'keyword', + }, + is_managed: { + type: 'boolean', + }, + is_default: { + type: 'boolean', + }, + is_default_fleet_server: { + type: 'boolean', + }, + status: { + type: 'keyword', + }, + unenroll_timeout: { + type: 'integer', + }, + inactivity_timeout: { + type: 'integer', + }, + updated_at: { + type: 'date', + }, + updated_by: { + type: 'keyword', + }, + revision: { + type: 'integer', + }, + monitoring_enabled: { + type: 'keyword', + index: false, + }, + is_preconfigured: { + type: 'keyword', + }, + data_output_id: { + type: 'keyword', + }, + monitoring_output_id: { + type: 'keyword', + }, + download_source_id: { + type: 'keyword', + }, + fleet_server_host_id: { + type: 'keyword', + }, + agent_features: { + properties: { + name: { + type: 'keyword', + }, + enabled: { + type: 'boolean', + }, + }, + }, + }, + }, + 'ingest-outputs': { + properties: { + output_id: { + type: 'keyword', + index: false, + }, + name: { + type: 'keyword', + }, + type: { + type: 'keyword', + }, + is_default: { + type: 'boolean', + }, + is_default_monitoring: { + type: 'boolean', + }, + hosts: { + type: 'keyword', + }, + ca_sha256: { + type: 'keyword', + index: false, + }, + ca_trusted_fingerprint: { + type: 'keyword', + index: false, + }, + config: { + type: 'flattened', + }, + config_yaml: { + type: 'text', + }, + is_preconfigured: { + type: 'boolean', + index: false, + }, + ssl: { + type: 'binary', + }, + proxy_id: { + type: 'keyword', + }, + shipper: { + dynamic: false, + properties: {}, + }, + }, + }, + 'ingest-package-policies': { + properties: { + name: { + type: 'keyword', + }, + description: { + type: 'text', + }, + namespace: { + type: 'keyword', + }, + enabled: { + type: 'boolean', + }, + is_managed: { + type: 'boolean', + }, + policy_id: { + type: 'keyword', + }, + package: { + properties: { + name: { + type: 'keyword', + }, + title: { + type: 'keyword', + }, + version: { + type: 'keyword', + }, + }, + }, + elasticsearch: { + dynamic: false, + properties: {}, + }, + vars: { + type: 'flattened', + }, + inputs: { + dynamic: false, + properties: {}, + }, + revision: { + type: 'integer', + }, + updated_at: { + type: 'date', + }, + updated_by: { + type: 'keyword', + }, + created_at: { + type: 'date', + }, + created_by: { + type: 'keyword', + }, + }, + }, + 'epm-packages': { + properties: { + name: { + type: 'keyword', + }, + version: { + type: 'keyword', + }, + internal: { + type: 'boolean', + }, + keep_policies_up_to_date: { + type: 'boolean', + index: false, + }, + es_index_patterns: { + dynamic: false, + properties: {}, + }, + verification_status: { + type: 'keyword', + }, + verification_key_id: { + type: 'keyword', + }, + installed_es: { + type: 'nested', + properties: { + id: { + type: 'keyword', + }, + type: { + type: 'keyword', + }, + version: { + type: 'keyword', + }, + }, + }, + installed_kibana: { + dynamic: false, + properties: {}, + }, + installed_kibana_space_id: { + type: 'keyword', + }, + package_assets: { + dynamic: false, + properties: {}, + }, + install_started_at: { + type: 'date', + }, + install_version: { + type: 'keyword', + }, + install_status: { + type: 'keyword', + }, + install_source: { + type: 'keyword', + }, + install_format_schema_version: { + type: 'version', + }, + experimental_data_stream_features: { + type: 'nested', + properties: { + data_stream: { + type: 'keyword', + }, + features: { + type: 'nested', + dynamic: false, + properties: { + synthetic_source: { + type: 'boolean', + }, + tsdb: { + type: 'boolean', + }, + }, + }, + }, + }, + }, + }, + 'epm-packages-assets': { + properties: { + package_name: { + type: 'keyword', + }, + package_version: { + type: 'keyword', + }, + install_source: { + type: 'keyword', + }, + asset_path: { + type: 'keyword', + }, + media_type: { + type: 'keyword', + }, + data_utf8: { + type: 'text', + index: false, + }, + data_base64: { + type: 'binary', + }, + }, + }, + 'fleet-preconfiguration-deletion-record': { + properties: { + id: { + type: 'keyword', + }, + }, + }, + 'ingest-download-sources': { + properties: { + source_id: { + type: 'keyword', + index: false, + }, + name: { + type: 'keyword', + }, + is_default: { + type: 'boolean', + }, + host: { + type: 'keyword', + }, + }, + }, + 'fleet-fleet-server-host': { + properties: { + name: { + type: 'keyword', + }, + is_default: { + type: 'boolean', + }, + host_urls: { + type: 'keyword', + index: false, + }, + is_preconfigured: { + type: 'boolean', + }, + proxy_id: { + type: 'keyword', + }, + }, + }, + 'fleet-proxy': { + properties: { + name: { + type: 'keyword', + }, + url: { + type: 'keyword', + index: false, + }, + proxy_headers: { + type: 'text', + index: false, + }, + certificate_authorities: { + type: 'keyword', + index: false, + }, + certificate: { + type: 'keyword', + index: false, + }, + certificate_key: { + type: 'keyword', + index: false, + }, + is_preconfigured: { + type: 'boolean', + }, + }, + }, + 'fleet-message-signing-keys': { + dynamic: false, + properties: {}, + }, + 'osquery-manager-usage-metric': { + properties: { + count: { + type: 'long', + }, + errors: { + type: 'long', + }, + }, + }, + 'osquery-saved-query': { + dynamic: false, + properties: { + description: { + type: 'text', + }, + id: { + type: 'keyword', + }, + query: { + type: 'text', + }, + created_at: { + type: 'date', + }, + created_by: { + type: 'text', + }, + platform: { + type: 'keyword', + }, + version: { + type: 'keyword', + }, + updated_at: { + type: 'date', + }, + updated_by: { + type: 'text', + }, + interval: { + type: 'keyword', + }, + ecs_mapping: { + dynamic: false, + properties: {}, + }, + }, + }, + 'osquery-pack': { + properties: { + description: { + type: 'text', + }, + name: { + type: 'text', + }, + created_at: { + type: 'date', + }, + created_by: { + type: 'keyword', + }, + updated_at: { + type: 'date', + }, + updated_by: { + type: 'keyword', + }, + enabled: { + type: 'boolean', + }, + shards: { + dynamic: false, + properties: {}, + }, + version: { + type: 'long', + }, + queries: { + dynamic: false, + properties: { + id: { + type: 'keyword', + }, + query: { + type: 'text', + }, + interval: { + type: 'text', + }, + platform: { + type: 'keyword', + }, + version: { + type: 'keyword', + }, + ecs_mapping: { + dynamic: false, + properties: {}, + }, + }, + }, + }, + }, + 'osquery-pack-asset': { + dynamic: false, + properties: { + description: { + type: 'text', + }, + name: { + type: 'text', + }, + version: { + type: 'long', + }, + shards: { + dynamic: false, + properties: {}, + }, + queries: { + dynamic: false, + properties: { + id: { + type: 'keyword', + }, + query: { + type: 'text', + }, + interval: { + type: 'text', + }, + platform: { + type: 'keyword', + }, + version: { + type: 'keyword', + }, + ecs_mapping: { + dynamic: false, + properties: {}, + }, + }, + }, + }, + }, + 'csp-rule-template': { + dynamic: false, + properties: { + metadata: { + type: 'object', + properties: { + name: { + type: 'keyword', + fields: { + text: { + type: 'text', + }, + }, + }, + benchmark: { + type: 'object', + properties: { + id: { + type: 'keyword', + }, + }, + }, + }, + }, + }, + }, + 'ml-job': { + properties: { + job_id: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + }, + }, + }, + datafeed_id: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + }, + }, + }, + type: { + type: 'keyword', + }, + }, + }, + 'ml-trained-model': { + properties: { + model_id: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + }, + }, + }, + job: { + properties: { + job_id: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + }, + }, + }, + create_time: { + type: 'date', + }, + }, + }, + }, + }, + 'ml-module': { + dynamic: false, + properties: { + id: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + }, + }, + }, + title: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + }, + }, + }, + description: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + }, + }, + }, + type: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + }, + }, + }, + logo: { + type: 'object', + }, + defaultIndexPattern: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + }, + }, + }, + query: { + type: 'object', + }, + jobs: { + type: 'object', + }, + datafeeds: { + type: 'object', + }, + }, + }, + 'uptime-dynamic-settings': { + dynamic: false, + properties: {}, + }, + 'synthetics-privates-locations': { + dynamic: false, + properties: {}, + }, + 'synthetics-monitor': { + dynamic: false, + properties: { + name: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + normalizer: 'lowercase', + }, + }, + }, + type: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + urls: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + hosts: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + journey_id: { + type: 'keyword', + }, + project_id: { + type: 'keyword', + fields: { + text: { + type: 'text', + }, + }, + }, + origin: { + type: 'keyword', + }, + hash: { + type: 'keyword', + }, + locations: { + properties: { + id: { + type: 'keyword', + ignore_above: 256, + fields: { + text: { + type: 'text', + }, + }, + }, + label: { + type: 'text', + }, + }, + }, + custom_heartbeat_id: { + type: 'keyword', + }, + id: { + type: 'keyword', + }, + tags: { + type: 'keyword', + fields: { + text: { + type: 'text', + }, + }, + }, + schedule: { + properties: { + number: { + type: 'integer', + }, + }, + }, + enabled: { + type: 'boolean', + }, + alert: { + properties: { + status: { + properties: { + enabled: { + type: 'boolean', + }, + }, + }, + }, + }, + }, + }, + 'uptime-synthetics-api-key': { + dynamic: false, + properties: { + apiKey: { + type: 'binary', + }, + }, + }, + 'synthetics-param': { + dynamic: false, + properties: {}, + }, + 'siem-ui-timeline-note': { + properties: { + eventId: { + type: 'keyword', + }, + note: { + type: 'text', + }, + created: { + type: 'date', + }, + createdBy: { + type: 'text', + }, + updated: { + type: 'date', + }, + updatedBy: { + type: 'text', + }, + }, + }, + 'siem-ui-timeline-pinned-event': { + properties: { + eventId: { + type: 'keyword', + }, + created: { + type: 'date', + }, + createdBy: { + type: 'text', + }, + updated: { + type: 'date', + }, + updatedBy: { + type: 'text', + }, + }, + }, + 'siem-detection-engine-rule-actions': { + properties: { + alertThrottle: { + type: 'keyword', + }, + ruleAlertId: { + type: 'keyword', + }, + ruleThrottle: { + type: 'keyword', + }, + actions: { + properties: { + actionRef: { + type: 'keyword', + }, + group: { + type: 'keyword', + }, + id: { + type: 'keyword', + }, + action_type_id: { + type: 'keyword', + }, + params: { + dynamic: false, + properties: {}, + }, + }, + }, + }, + }, + 'security-rule': { + dynamic: false, + properties: { + name: { + type: 'keyword', + }, + rule_id: { + type: 'keyword', + }, + version: { + type: 'long', + }, + }, + }, + 'siem-ui-timeline': { + properties: { + columns: { + properties: { + aggregatable: { + type: 'boolean', + }, + category: { + type: 'keyword', + }, + columnHeaderType: { + type: 'keyword', + }, + description: { + type: 'text', + }, + example: { + type: 'text', + }, + indexes: { + type: 'keyword', + }, + id: { + type: 'keyword', + }, + name: { + type: 'text', + }, + placeholder: { + type: 'text', + }, + searchable: { + type: 'boolean', + }, + type: { + type: 'keyword', + }, + }, + }, + dataProviders: { + properties: { + id: { + type: 'keyword', + }, + name: { + type: 'text', + }, + enabled: { + type: 'boolean', + }, + excluded: { + type: 'boolean', + }, + kqlQuery: { + type: 'text', + }, + type: { + type: 'text', + }, + queryMatch: { + properties: { + field: { + type: 'text', + }, + displayField: { + type: 'text', + }, + value: { + type: 'text', + }, + displayValue: { + type: 'text', + }, + operator: { + type: 'text', + }, + }, + }, + and: { + properties: { + id: { + type: 'keyword', + }, + name: { + type: 'text', + }, + enabled: { + type: 'boolean', + }, + excluded: { + type: 'boolean', + }, + kqlQuery: { + type: 'text', + }, + type: { + type: 'text', + }, + queryMatch: { + properties: { + field: { + type: 'text', + }, + displayField: { + type: 'text', + }, + value: { + type: 'text', + }, + displayValue: { + type: 'text', + }, + operator: { + type: 'text', + }, + }, + }, + }, + }, + }, + }, + description: { + type: 'text', + }, + eqlOptions: { + properties: { + eventCategoryField: { + type: 'text', + }, + tiebreakerField: { + type: 'text', + }, + timestampField: { + type: 'text', + }, + query: { + type: 'text', + }, + size: { + type: 'text', + }, + }, + }, + eventType: { + type: 'keyword', + }, + excludedRowRendererIds: { + type: 'text', + }, + favorite: { + properties: { + keySearch: { + type: 'text', + }, + fullName: { + type: 'text', + }, + userName: { + type: 'text', + }, + favoriteDate: { + type: 'date', + }, + }, + }, + filters: { + properties: { + meta: { + properties: { + alias: { + type: 'text', + }, + controlledBy: { + type: 'text', + }, + disabled: { + type: 'boolean', + }, + field: { + type: 'text', + }, + formattedValue: { + type: 'text', + }, + index: { + type: 'keyword', + }, + key: { + type: 'keyword', + }, + negate: { + type: 'boolean', + }, + params: { + type: 'text', + }, + type: { + type: 'keyword', + }, + value: { + type: 'text', + }, + }, + }, + exists: { + type: 'text', + }, + match_all: { + type: 'text', + }, + missing: { + type: 'text', + }, + query: { + type: 'text', + }, + range: { + type: 'text', + }, + script: { + type: 'text', + }, + }, + }, + indexNames: { + type: 'text', + }, + kqlMode: { + type: 'keyword', + }, + kqlQuery: { + properties: { + filterQuery: { + properties: { + kuery: { + properties: { + kind: { + type: 'keyword', + }, + expression: { + type: 'text', + }, + }, + }, + serializedQuery: { + type: 'text', + }, + }, + }, + }, + }, + title: { + type: 'text', + }, + templateTimelineId: { + type: 'text', + }, + templateTimelineVersion: { + type: 'integer', + }, + timelineType: { + type: 'keyword', + }, + dateRange: { + properties: { + start: { + type: 'date', + }, + end: { + type: 'date', + }, + }, + }, + sort: { + dynamic: false, + properties: { + columnId: { + type: 'keyword', + }, + columnType: { + type: 'keyword', + }, + sortDirection: { + type: 'keyword', + }, + }, + }, + status: { + type: 'keyword', + }, + created: { + type: 'date', + }, + createdBy: { + type: 'text', + }, + updated: { + type: 'date', + }, + updatedBy: { + type: 'text', + }, + }, + }, + 'endpoint:user-artifact': { + properties: { + identifier: { + type: 'keyword', + }, + compressionAlgorithm: { + type: 'keyword', + index: false, + }, + encryptionAlgorithm: { + type: 'keyword', + index: false, + }, + encodedSha256: { + type: 'keyword', + }, + encodedSize: { + type: 'long', + index: false, + }, + decodedSha256: { + type: 'keyword', + index: false, + }, + decodedSize: { + type: 'long', + index: false, + }, + created: { + type: 'date', + index: false, + }, + body: { + type: 'binary', + }, + }, + }, + 'endpoint:user-artifact-manifest': { + properties: { + created: { + type: 'date', + index: false, + }, + schemaVersion: { + type: 'keyword', + }, + semanticVersion: { + type: 'keyword', + index: false, + }, + artifacts: { + type: 'nested', + properties: { + policyId: { + type: 'keyword', + index: false, + }, + artifactId: { + type: 'keyword', + index: false, + }, + }, + }, + }, + }, + 'security-solution-signals-migration': { + properties: { + sourceIndex: { + type: 'keyword', + }, + destinationIndex: { + type: 'keyword', + index: false, + }, + version: { + type: 'long', + }, + error: { + type: 'text', + index: false, + }, + taskId: { + type: 'keyword', + index: false, + }, + status: { + type: 'keyword', + index: false, + }, + created: { + type: 'date', + index: false, + }, + createdBy: { + type: 'text', + index: false, + }, + updated: { + type: 'date', + index: false, + }, + updatedBy: { + type: 'text', + index: false, + }, + }, + }, + 'infrastructure-ui-source': { + dynamic: false, + properties: {}, + }, + 'metrics-explorer-view': { + dynamic: false, + properties: {}, + }, + 'inventory-view': { + dynamic: false, + properties: {}, + }, + 'infrastructure-monitoring-log-view': { + dynamic: false, + properties: { + name: { + type: 'text', + }, + }, + }, + 'upgrade-assistant-reindex-operation': { + dynamic: false, + properties: { + indexName: { + type: 'keyword', + }, + status: { + type: 'integer', + }, + }, + }, + 'upgrade-assistant-ml-upgrade-operation': { + dynamic: false, + properties: { + snapshotId: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + }, + }, + 'monitoring-telemetry': { + properties: { + reportedClusterUuids: { + type: 'keyword', + }, + }, + }, + enterprise_search_telemetry: { + dynamic: false, + properties: {}, + }, + app_search_telemetry: { + dynamic: false, + properties: {}, + }, + workplace_search_telemetry: { + dynamic: false, + properties: {}, + }, + 'apm-indices': { + dynamic: false, + properties: {}, + }, + 'apm-telemetry': { + dynamic: false, + properties: {}, + }, + 'apm-server-schema': { + properties: { + schemaJson: { + type: 'text', + index: false, + }, + }, + }, + 'apm-service-group': { + properties: { + groupName: { + type: 'keyword', + }, + kuery: { + type: 'text', + }, + description: { + type: 'text', + }, + color: { + type: 'text', + }, + }, + }, + }, + }, + '.kibana_task_manager': { + typeMappings: { + task: { + properties: { + taskType: { + type: 'keyword', + }, + scheduledAt: { + type: 'date', + }, + runAt: { + type: 'date', + }, + startedAt: { + type: 'date', + }, + retryAt: { + type: 'date', + }, + enabled: { + type: 'boolean', + }, + schedule: { + properties: { + interval: { + type: 'keyword', + }, + }, + }, + attempts: { + type: 'integer', + }, + status: { + type: 'keyword', + }, + traceparent: { + type: 'text', + }, + params: { + type: 'text', + }, + state: { + type: 'text', + }, + user: { + type: 'keyword', + }, + scope: { + type: 'keyword', + }, + ownerId: { + type: 'keyword', + }, + }, + }, + }, + script: 'ctx._id = ctx._source.type + \':\' + ctx._id; ctx._source.remove("kibana")', + }, +}; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator_utils.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator_utils.test.ts new file mode 100644 index 0000000000000..8927b0e82f8aa --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator_utils.test.ts @@ -0,0 +1,257 @@ +/* + * Copyright 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 { errors } from '@elastic/elasticsearch'; +import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; +import { MAIN_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; +import { loggerMock } from '@kbn/logging-mocks'; +import { DEFAULT_INDEX_TYPES_MAP } from './kibana_migrator_constants'; +import { + calculateTypeStatuses, + createMultiPromiseDefer, + getIndicesInvolvedInRelocation, + indexMapToIndexTypesMap, +} from './kibana_migrator_utils'; +import { INDEX_MAP_BEFORE_SPLIT } from './kibana_migrator_utils.fixtures'; + +describe('createMultiPromiseDefer', () => { + it('creates defer objects with the same Promise', () => { + const defers = createMultiPromiseDefer(['.kibana', '.kibana_cases']); + expect(Object.keys(defers)).toHaveLength(2); + expect(defers['.kibana'].promise).toEqual(defers['.kibana_cases'].promise); + expect(defers['.kibana'].resolve).not.toEqual(defers['.kibana_cases'].resolve); + expect(defers['.kibana'].reject).not.toEqual(defers['.kibana_cases'].reject); + }); + + it('the common Promise resolves when all defers resolve', async () => { + const defers = createMultiPromiseDefer(['.kibana', '.kibana_cases']); + let resolved = 0; + Object.values(defers).forEach((defer) => defer.promise.then(() => ++resolved)); + defers['.kibana'].resolve(); + await new Promise((resolve) => setImmediate(resolve)); // next tick + expect(resolved).toEqual(0); + defers['.kibana_cases'].resolve(); + await new Promise((resolve) => setImmediate(resolve)); // next tick + expect(resolved).toEqual(2); + }); +}); + +describe('getIndicesInvolvedInRelocation', () => { + const getIndicesInvolvedInRelocationParams = () => { + const client = elasticsearchClientMock.createElasticsearchClient(); + (client as any).child = jest.fn().mockImplementation(() => client); + + return { + client, + mainIndex: MAIN_SAVED_OBJECT_INDEX, + indexTypesMap: {}, + defaultIndexTypesMap: DEFAULT_INDEX_TYPES_MAP, + logger: loggerMock.create(), + }; + }; + + it('tries to get the indexTypesMap from the mainIndex', async () => { + const params = getIndicesInvolvedInRelocationParams(); + try { + await getIndicesInvolvedInRelocation(params); + } catch (err) { + // ignore + } + + expect(params.client.indices.getMapping).toHaveBeenCalledTimes(1); + expect(params.client.indices.getMapping).toHaveBeenCalledWith({ + index: MAIN_SAVED_OBJECT_INDEX, + }); + }); + + it('fails if the query to get indexTypesMap fails with critical error', async () => { + const params = getIndicesInvolvedInRelocationParams(); + params.client.indices.getMapping.mockImplementation(() => + elasticsearchClientMock.createErrorTransportRequestPromise( + new errors.ResponseError({ + statusCode: 500, + body: { + error: { + type: 'error_type', + reason: 'error_reason', + }, + }, + warnings: [], + headers: {}, + meta: {} as any, + }) + ) + ); + expect(getIndicesInvolvedInRelocation(params)).rejects.toThrowErrorMatchingInlineSnapshot( + `"error_type"` + ); + }); + + it('assumes fresh deployment if the mainIndex does not exist, returns an empty list of moving types', async () => { + const params = getIndicesInvolvedInRelocationParams(); + params.client.indices.getMapping.mockImplementation(() => + elasticsearchClientMock.createErrorTransportRequestPromise( + new errors.ResponseError({ + statusCode: 404, + body: { + error: { + type: 'error_type', + reason: 'error_reason', + }, + }, + warnings: [], + headers: {}, + meta: {} as any, + }) + ) + ); + expect(getIndicesInvolvedInRelocation(params)).resolves.toEqual([]); + }); + + describe('if mainIndex exists', () => { + describe('but it does not have an indexTypeMap stored', () => { + it('uses the defaultIndexTypeMap and finds out which indices are involved in a relocation', async () => { + const params = getIndicesInvolvedInRelocationParams(); + params.client.indices.getMapping.mockReturnValue( + Promise.resolve({ + '.kibana_8.7.0_001': { + mappings: { + dynamic: 'strict', + _meta: { + migrationMappingPropertyHashes: { + someType: '7997cf5a56cc02bdc9c93361bde732b0', + }, + }, + properties: { + someProperty: {}, + }, + }, + }, + }) + ); + params.defaultIndexTypesMap = { + '.indexA': ['type1', 'type2', 'type3'], + '.indexB': ['type4', 'type5', 'type6'], + }; + + params.indexTypesMap = { + '.indexA': ['type1'], // move type2 and type 3 over to new indexC + '.indexB': ['type4', 'type5', 'type6'], // stays the same + '.indexC': ['type2', 'type3'], + }; + + expect(getIndicesInvolvedInRelocation(params)).resolves.toEqual(['.indexA', '.indexC']); + }); + }); + + describe('and it has an indexTypeMap stored', () => { + it('compares stored indexTypeMap against desired one, and finds out which indices are involved in a relocation', async () => { + const params = getIndicesInvolvedInRelocationParams(); + params.client.indices.getMapping.mockReturnValue( + Promise.resolve({ + '.kibana_8.8.0_001': { + mappings: { + dynamic: 'strict', + _meta: { + migrationMappingPropertyHashes: { + someType: '7997cf5a56cc02bdc9c93361bde732b0', + }, + // map stored on index + indexTypesMap: { + '.indexA': ['type1'], + '.indexB': ['type4', 'type5', 'type6'], + '.indexC': ['type2', 'type3'], + }, + }, + properties: { + someProperty: {}, + }, + }, + }, + }) + ); + + // exists on index, so this one will NOT be taken into account + params.defaultIndexTypesMap = { + '.indexA': ['type1', 'type2', 'type3'], + '.indexB': ['type4', 'type5', 'type6'], + }; + + params.indexTypesMap = { + '.indexA': ['type1'], + '.indexB': ['type4'], + '.indexC': ['type2', 'type3'], + '.indexD': ['type5', 'type6'], + }; + + expect(getIndicesInvolvedInRelocation(params)).resolves.toEqual(['.indexB', '.indexD']); + }); + }); + }); +}); + +describe('indexMapToIndexTypesMap', () => { + it('converts IndexMap to IndexTypesMap', () => { + expect(indexMapToIndexTypesMap(INDEX_MAP_BEFORE_SPLIT)).toEqual(DEFAULT_INDEX_TYPES_MAP); + }); +}); + +describe('calculateTypeStatuses', () => { + it('takes two indexTypesMaps and checks what types have been added, removed and relocated', () => { + const currentIndexTypesMap = { + '.indexA': ['type1', 'type2', 'type3'], + '.indexB': ['type4', 'type5', 'type6'], + }; + const desiredIndexTypesMap = { + '.indexA': ['type2'], + '.indexB': ['type3', 'type5'], + '.indexC': ['type4', 'type6', 'type7'], + '.indexD': ['type8'], + }; + + expect(calculateTypeStatuses(currentIndexTypesMap, desiredIndexTypesMap)).toEqual({ + type1: { + currentIndex: '.indexA', + status: 'removed', + }, + type2: { + currentIndex: '.indexA', + status: 'untouched', + targetIndex: '.indexA', + }, + type3: { + currentIndex: '.indexA', + status: 'moved', + targetIndex: '.indexB', + }, + type4: { + currentIndex: '.indexB', + status: 'moved', + targetIndex: '.indexC', + }, + type5: { + currentIndex: '.indexB', + status: 'untouched', + targetIndex: '.indexB', + }, + type6: { + currentIndex: '.indexB', + status: 'moved', + targetIndex: '.indexC', + }, + type7: { + status: 'added', + targetIndex: '.indexC', + }, + type8: { + status: 'added', + targetIndex: '.indexD', + }, + }); + }); +}); diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator_utils.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator_utils.ts new file mode 100644 index 0000000000000..408e88721c413 --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator_utils.ts @@ -0,0 +1,146 @@ +/* + * Copyright 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 { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { IndexTypesMap } from '@kbn/core-saved-objects-base-server-internal'; +import type { Logger } from '@kbn/logging'; +import type { IndexMap } from './core'; +import { TypeStatus, type TypeStatusDetails } from './kibana_migrator_constants'; + +// even though this utility class is present in @kbn/kibana-utils-plugin, we can't easily import it from Core +// aka. one does not simply reuse code +export class Defer { + public resolve!: (data: T) => void; + public reject!: (error: any) => void; + public promise: Promise = new Promise((resolve, reject) => { + (this as any).resolve = resolve; + (this as any).reject = reject; + }); +} + +export const defer = () => new Defer(); + +export function createMultiPromiseDefer(indices: string[]): Record> { + const defers: Array> = indices.map(defer); + const all = Promise.all(defers.map(({ promise }) => promise)); + return indices.reduce>>((acc, indexName, i) => { + const { resolve, reject } = defers[i]; + acc[indexName] = { resolve, reject, promise: all }; + return acc; + }, {}); +} + +export async function getCurrentIndexTypesMap({ + client, + mainIndex, + defaultIndexTypesMap, + logger, +}: { + client: ElasticsearchClient; + mainIndex: string; + defaultIndexTypesMap: IndexTypesMap; + logger: Logger; +}): Promise { + try { + // check if the main index (i.e. .kibana) exists + const mapping = await client.indices.getMapping({ + index: mainIndex, + }); + + // main index exists, try to extract the indexTypesMap from _meta + const meta = Object.values(mapping)?.[0]?.mappings._meta; + return meta?.indexTypesMap ?? defaultIndexTypesMap; + } catch (error) { + if (error.meta?.statusCode === 404) { + logger.debug(`The ${mainIndex} index do NOT exist. Assuming this is a fresh deployment`); + return undefined; + } else { + logger.fatal(`Cannot query the meta information on the ${mainIndex} saved object index`); + throw error; + } + } +} + +export async function getIndicesInvolvedInRelocation({ + client, + mainIndex, + indexTypesMap, + defaultIndexTypesMap, + logger, +}: { + client: ElasticsearchClient; + mainIndex: string; + indexTypesMap: IndexTypesMap; + defaultIndexTypesMap: IndexTypesMap; + logger: Logger; +}): Promise { + const indicesWithMovingTypesSet = new Set(); + + const currentIndexTypesMap = await getCurrentIndexTypesMap({ + client, + mainIndex, + defaultIndexTypesMap, + logger, + }); + + if (!currentIndexTypesMap) { + // this is a fresh deployment, no indices must be relocated + return []; + } + + const typeIndexDistribution = calculateTypeStatuses(currentIndexTypesMap, indexTypesMap); + + Object.values(typeIndexDistribution) + .filter(({ status }) => status === TypeStatus.Moved) + .forEach(({ currentIndex, targetIndex }) => { + indicesWithMovingTypesSet.add(currentIndex!); + indicesWithMovingTypesSet.add(targetIndex!); + }); + + return Array.from(indicesWithMovingTypesSet); +} + +export function indexMapToIndexTypesMap(indexMap: IndexMap): IndexTypesMap { + return Object.entries(indexMap).reduce((acc, [indexAlias, { typeMappings }]) => { + acc[indexAlias] = Object.keys(typeMappings).sort(); + return acc; + }, {}); +} + +export function calculateTypeStatuses( + currentIndexTypesMap: IndexTypesMap, + desiredIndexTypesMap: IndexTypesMap +): Record { + const statuses: Record = {}; + + Object.entries(currentIndexTypesMap).forEach(([currentIndex, types]) => { + types.forEach((type) => { + statuses[type] = { + currentIndex, + status: TypeStatus.Removed, // type is removed unless we still have it + }; + }); + }); + + Object.entries(desiredIndexTypesMap).forEach(([targetIndex, types]) => { + types.forEach((type) => { + if (!statuses[type]) { + statuses[type] = { + targetIndex, + status: TypeStatus.Added, // type didn't exist, it must be new + }; + } else { + statuses[type].targetIndex = targetIndex; + statuses[type].status = + statuses[type].currentIndex === targetIndex ? TypeStatus.Untouched : TypeStatus.Moved; + } + }); + }); + + return statuses; +} diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_action_machine.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_action_machine.test.ts index d03b4f7378da4..15a5ef1ce14d7 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_action_machine.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_action_machine.test.ts @@ -6,13 +6,11 @@ * Side Public License, v 1. */ -import { cleanupMock } from './migrations_state_machine_cleanup.mocks'; import { migrationStateActionMachine } from './migrations_state_action_machine'; import { docLinksServiceMock } from '@kbn/core-doc-links-server-mocks'; import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; import { LoggerAdapter } from '@kbn/core-logging-server-internal'; import { typeRegistryMock } from '@kbn/core-saved-objects-base-server-mocks'; -import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; import * as Either from 'fp-ts/lib/Either'; import * as Option from 'fp-ts/lib/Option'; import { errors } from '@elastic/elasticsearch'; @@ -21,8 +19,6 @@ import type { AllControlStates, State } from './state'; import { createInitialState } from './initial_state'; import { ByteSizeValue } from '@kbn/config-schema'; -const esClient = elasticsearchServiceMock.createElasticsearchClient(); - describe('migrationsStateActionMachine', () => { beforeAll(() => { jest @@ -33,6 +29,7 @@ describe('migrationsStateActionMachine', () => { jest.clearAllMocks(); }); + const abort = jest.fn(); const mockLogger = loggingSystemMock.create(); const typeRegistry = typeRegistryMock.create(); const docLinks = docLinksServiceMock.createSetupContract(); @@ -40,6 +37,12 @@ describe('migrationsStateActionMachine', () => { const initialState = createInitialState({ kibanaVersion: '7.11.0', waitForMigrationCompletion: false, + mustRelocateDocuments: true, + indexTypesMap: { + '.kibana': ['typeA', 'typeB', 'typeC'], + '.kibana_task_manager': ['task'], + '.kibana_cases': ['typeD', 'typeE'], + }, targetMappings: { properties: {} }, migrationVersionPerType: {}, indexPrefix: '.my-so-index', @@ -92,7 +95,7 @@ describe('migrationsStateActionMachine', () => { logger: mockLogger.get(), model: transitionModel(['LEGACY_REINDEX', 'LEGACY_DELETE', 'LEGACY_DELETE', 'DONE']), next, - client: esClient, + abort, }); const logs = loggingSystemMock.collect(mockLogger); const doneLog = logs.info.splice(8, 1)[0][0]; @@ -113,7 +116,7 @@ describe('migrationsStateActionMachine', () => { logger: mockLogger.get(), model: transitionModel(['LEGACY_DELETE', 'FATAL']), next, - client: esClient, + abort, }).catch((err) => err); expect(loggingSystemMock.collect(mockLogger)).toMatchSnapshot(); }); @@ -129,7 +132,7 @@ describe('migrationsStateActionMachine', () => { logger, model: transitionModel(['LEGACY_REINDEX', 'LEGACY_DELETE', 'LEGACY_DELETE', 'DONE']), next, - client: esClient, + abort, }) ).resolves.toEqual(expect.anything()); @@ -155,7 +158,7 @@ describe('migrationsStateActionMachine', () => { logger: mockLogger.get(), model: transitionModel(['LEGACY_REINDEX', 'LEGACY_DELETE', 'LEGACY_DELETE', 'DONE']), next, - client: esClient, + abort, }) ).resolves.toEqual(expect.anything()); }); @@ -167,7 +170,7 @@ describe('migrationsStateActionMachine', () => { logger: mockLogger.get(), model: transitionModel(['LEGACY_REINDEX', 'LEGACY_DELETE', 'LEGACY_DELETE', 'DONE']), next, - client: esClient, + abort, }) ).resolves.toEqual(expect.objectContaining({ status: 'migrated' })); }); @@ -179,7 +182,7 @@ describe('migrationsStateActionMachine', () => { logger: mockLogger.get(), model: transitionModel(['LEGACY_REINDEX', 'LEGACY_DELETE', 'LEGACY_DELETE', 'DONE']), next, - client: esClient, + abort, }) ).resolves.toEqual(expect.objectContaining({ status: 'patched' })); }); @@ -191,7 +194,7 @@ describe('migrationsStateActionMachine', () => { logger: mockLogger.get(), model: transitionModel(['LEGACY_REINDEX', 'LEGACY_DELETE', 'FATAL']), next, - client: esClient, + abort, }) ).rejects.toMatchInlineSnapshot( `[Error: Unable to complete saved object migrations for the [.my-so-index] index: the fatal reason]` @@ -219,7 +222,7 @@ describe('migrationsStateActionMachine', () => { }) ); }, - client: esClient, + abort, }) ).rejects.toMatchInlineSnapshot( `[Error: Unable to complete saved object migrations for the [.my-so-index] index. Please check the health of your Elasticsearch cluster and try again. Unexpected Elasticsearch ResponseError: statusCode: 200, method: POST, url: /mock error: [snapshot_in_progress_exception]: Cannot delete indices that are being snapshotted,]` @@ -249,7 +252,7 @@ describe('migrationsStateActionMachine', () => { next: () => { throw new Error('this action throws'); }, - client: esClient, + abort, }) ).rejects.toMatchInlineSnapshot( `[Error: Unable to complete saved object migrations for the [.my-so-index] index. Error: this action throws]` @@ -271,10 +274,7 @@ describe('migrationsStateActionMachine', () => { `); }); describe('cleanup', () => { - beforeEach(() => { - cleanupMock.mockClear(); - }); - it('calls cleanup function when an action throws', async () => { + it('calls abort function when an action throws', async () => { await expect( migrationStateActionMachine({ initialState: { ...initialState, reason: 'the fatal reason' } as State, @@ -283,24 +283,24 @@ describe('migrationsStateActionMachine', () => { next: () => { throw new Error('this action throws'); }, - client: esClient, + abort, }) ).rejects.toThrow(); - expect(cleanupMock).toHaveBeenCalledTimes(1); + expect(abort).toHaveBeenCalledTimes(1); }); - it('calls cleanup function when reaching the FATAL state', async () => { + it('calls abort function when reaching the FATAL state', async () => { await expect( migrationStateActionMachine({ initialState: { ...initialState, reason: 'the fatal reason' } as State, logger: mockLogger.get(), model: transitionModel(['LEGACY_REINDEX', 'LEGACY_DELETE', 'FATAL']), next, - client: esClient, + abort, }) ).rejects.toThrow(); - expect(cleanupMock).toHaveBeenCalledTimes(1); + expect(abort).toHaveBeenCalledTimes(1); }); }); }); diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_action_machine.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_action_machine.ts index 92b0054bd47c3..f96c52df52ab7 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_action_machine.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_action_machine.ts @@ -9,15 +9,14 @@ import { errors as EsErrors } from '@elastic/elasticsearch'; import * as Option from 'fp-ts/lib/Option'; import type { Logger } from '@kbn/logging'; -import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { getErrorMessage, getRequestDebugMeta, } from '@kbn/core-elasticsearch-client-server-internal'; import type { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; +import type { MigrationResult } from '@kbn/core-saved-objects-base-server-internal'; import { logActionResponse, logStateTransition } from './common/utils/logs'; import { type Model, type Next, stateActionMachine } from './state_action_machine'; -import { cleanup } from './migrations_state_machine_cleanup'; import type { ReindexSourceToTempTransform, ReindexSourceToTempIndexBulk, State } from './state'; import { redactBulkOperationBatches } from './common/redact_state'; @@ -35,14 +34,14 @@ export async function migrationStateActionMachine({ logger, next, model, - client, + abort, }: { initialState: State; logger: Logger; next: Next; model: Model; - client: ElasticsearchClient; -}) { + abort: (state?: State) => Promise; +}): Promise { const startTime = Date.now(); // Since saved object index names usually start with a `.` and can be // configured by users to include several `.`'s we can't use a logger tag to @@ -112,22 +111,28 @@ export async function migrationStateActionMachine({ } } else if (finalState.controlState === 'FATAL') { try { - await cleanup(client, finalState); + await abort(finalState); } catch (e) { logger.warn('Failed to cleanup after migrations:', e.message); } - return Promise.reject( - new Error( - `Unable to complete saved object migrations for the [${initialState.indexPrefix}] index: ` + - finalState.reason - ) - ); + + const errorMessage = + `Unable to complete saved object migrations for the [${initialState.indexPrefix}] index: ` + + finalState.reason; + + if (finalState.throwDelayMillis) { + return new Promise((_, reject) => + setTimeout(() => reject(errorMessage), finalState.throwDelayMillis) + ); + } + + return Promise.reject(new Error(errorMessage)); } else { throw new Error('Invalid terminating control state'); } } catch (e) { try { - await cleanup(client, lastState); + await abort(lastState); } catch (err) { logger.warn('Failed to cleanup after migrations:', err.message); } diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/create_batches.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/create_batches.test.ts index 3ae3b1c7f20d8..951ee86d92f0d 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/create_batches.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/create_batches.test.ts @@ -7,7 +7,7 @@ */ import * as Either from 'fp-ts/lib/Either'; import type { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; -import { createBatches } from './create_batches'; +import { buildTempIndexMap, createBatches } from './create_batches'; describe('createBatches', () => { const documentToOperation = (document: SavedObjectsRawDoc) => [ @@ -17,56 +17,145 @@ describe('createBatches', () => { const DOCUMENT_SIZE_BYTES = 77; // 76 + \n - it('returns right one batch if all documents fit in maxBatchSizeBytes', () => { - const documents = [ - { _id: '', _source: { type: 'dashboard', title: 'my saved object title ¹' } }, - { _id: '', _source: { type: 'dashboard', title: 'my saved object title ²' } }, - { _id: '', _source: { type: 'dashboard', title: 'my saved object title ®' } }, - ]; + describe('when indexTypesMap and kibanaVersion are not provided', () => { + it('returns right one batch if all documents fit in maxBatchSizeBytes', () => { + const documents = [ + { _id: '', _source: { type: 'dashboard', title: 'my saved object title ¹' } }, + { _id: '', _source: { type: 'dashboard', title: 'my saved object title ²' } }, + { _id: '', _source: { type: 'dashboard', title: 'my saved object title ®' } }, + ]; - expect(createBatches({ documents, maxBatchSizeBytes: DOCUMENT_SIZE_BYTES * 3 })).toEqual( - Either.right([documents.map(documentToOperation)]) - ); - }); - it('creates multiple batches with each batch limited to maxBatchSizeBytes', () => { - const documents = [ - { _id: '', _source: { type: 'dashboard', title: 'my saved object title ¹' } }, - { _id: '', _source: { type: 'dashboard', title: 'my saved object title ²' } }, - { _id: '', _source: { type: 'dashboard', title: 'my saved object title ®' } }, - { _id: '', _source: { type: 'dashboard', title: 'my saved object title 44' } }, - { _id: '', _source: { type: 'dashboard', title: 'my saved object title 55' } }, - ]; - expect(createBatches({ documents, maxBatchSizeBytes: DOCUMENT_SIZE_BYTES * 2 })).toEqual( - Either.right([ - documents.slice(0, 2).map(documentToOperation), - documents.slice(2, 4).map(documentToOperation), - documents.slice(4).map(documentToOperation), - ]) - ); - }); - it('creates a single empty batch if there are no documents', () => { - const documents = [] as SavedObjectsRawDoc[]; - expect(createBatches({ documents, maxBatchSizeBytes: 100 })).toEqual(Either.right([[]])); - }); - it('throws if any one document exceeds the maxBatchSizeBytes', () => { - const documents = [ - { _id: 'foo', _source: { type: 'dashboard', title: 'my saved object title ¹' } }, - { - _id: 'bar', - _source: { - type: 'dashboard', - title: 'my saved object title ² with a very long title that exceeds max size bytes', + expect( + createBatches({ + documents, + maxBatchSizeBytes: DOCUMENT_SIZE_BYTES * 3, + }) + ).toEqual(Either.right([documents.map(documentToOperation)])); + }); + it('creates multiple batches with each batch limited to maxBatchSizeBytes', () => { + const documents = [ + { _id: '', _source: { type: 'dashboard', title: 'my saved object title ¹' } }, + { _id: '', _source: { type: 'dashboard', title: 'my saved object title ²' } }, + { _id: '', _source: { type: 'dashboard', title: 'my saved object title ®' } }, + { _id: '', _source: { type: 'dashboard', title: 'my saved object title 44' } }, + { _id: '', _source: { type: 'dashboard', title: 'my saved object title 55' } }, + ]; + expect( + createBatches({ + documents, + maxBatchSizeBytes: DOCUMENT_SIZE_BYTES * 2, + }) + ).toEqual( + Either.right([ + documents.slice(0, 2).map(documentToOperation), + documents.slice(2, 4).map(documentToOperation), + documents.slice(4).map(documentToOperation), + ]) + ); + }); + it('creates a single empty batch if there are no documents', () => { + const documents = [] as SavedObjectsRawDoc[]; + expect(createBatches({ documents, maxBatchSizeBytes: 100 })).toEqual(Either.right([[]])); + }); + it('throws if any one document exceeds the maxBatchSizeBytes', () => { + const documents = [ + { _id: 'foo', _source: { type: 'dashboard', title: 'my saved object title ¹' } }, + { + _id: 'bar', + _source: { + type: 'dashboard', + title: 'my saved object title ² with a very long title that exceeds max size bytes', + }, }, - }, - { _id: 'baz', _source: { type: 'dashboard', title: 'my saved object title ®' } }, - ]; - expect(createBatches({ documents, maxBatchSizeBytes: 120 })).toEqual( - Either.left({ - maxBatchSizeBytes: 120, - docSizeBytes: 130, - type: 'document_exceeds_batch_size_bytes', - documentId: documents[1]._id, - }) - ); + { _id: 'baz', _source: { type: 'dashboard', title: 'my saved object title ®' } }, + ]; + expect(createBatches({ documents, maxBatchSizeBytes: 120 })).toEqual( + Either.left({ + maxBatchSizeBytes: 120, + docSizeBytes: 130, + type: 'document_exceeds_batch_size_bytes', + documentId: documents[1]._id, + }) + ); + }); + }); + + describe('when a type index map is provided', () => { + it('creates batches that contain the target index information for each type', () => { + const documents = [ + { _id: '', _source: { type: 'dashboard', title: 'my saved object title ¹' } }, + { _id: '', _source: { type: 'dashboard', title: 'my saved object title ²' } }, + { _id: '', _source: { type: 'cases', title: 'a case' } }, + { _id: '', _source: { type: 'cases-comments', title: 'a case comment #1' } }, + { _id: '', _source: { type: 'cases-user-actions', title: 'a case user action' } }, + ]; + expect( + createBatches({ + documents, + maxBatchSizeBytes: (DOCUMENT_SIZE_BYTES + 43) * 2, // add extra length for 'index' property + typeIndexMap: buildTempIndexMap( + { + '.kibana': ['dashboard'], + '.kibana_cases': ['cases', 'cases-comments', 'cases-user-actions'], + }, + '8.8.0' + ), + }) + ).toEqual( + Either.right([ + [ + [ + { + index: { + _id: '', + _index: '.kibana_8.8.0_reindex_temp', + }, + }, + { type: 'dashboard', title: 'my saved object title ¹' }, + ], + [ + { + index: { + _id: '', + _index: '.kibana_8.8.0_reindex_temp', + }, + }, + { type: 'dashboard', title: 'my saved object title ²' }, + ], + ], + [ + [ + { + index: { + _id: '', + _index: '.kibana_cases_8.8.0_reindex_temp', + }, + }, + { type: 'cases', title: 'a case' }, + ], + [ + { + index: { + _id: '', + _index: '.kibana_cases_8.8.0_reindex_temp', + }, + }, + { type: 'cases-comments', title: 'a case comment #1' }, + ], + ], + [ + [ + { + index: { + _id: '', + _index: '.kibana_cases_8.8.0_reindex_temp', + }, + }, + { type: 'cases-user-actions', title: 'a case user action' }, + ], + ], + ]) + ); + }); }); }); diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/create_batches.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/create_batches.ts index 008d074b2cd6f..dc5b09d6c3379 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/create_batches.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/create_batches.ts @@ -9,7 +9,12 @@ import * as Either from 'fp-ts/lib/Either'; import type { SavedObjectsRawDoc, SavedObjectsRawDocSource } from '@kbn/core-saved-objects-server'; import type { BulkOperationContainer } from '@elastic/elasticsearch/lib/api/types'; -import { createBulkDeleteOperationBody, createBulkIndexOperationTuple } from './helpers'; +import type { IndexTypesMap } from '@kbn/core-saved-objects-base-server-internal'; +import { + createBulkDeleteOperationBody, + createBulkIndexOperationTuple, + getTempIndexName, +} from './helpers'; import type { TransformErrorObjects } from '../core'; export type BulkIndexOperationTuple = [BulkOperationContainer, SavedObjectsRawDocSource]; @@ -21,6 +26,12 @@ export interface CreateBatchesParams { corruptDocumentIds?: string[]; transformErrors?: TransformErrorObjects[]; maxBatchSizeBytes: number; + /** This map holds a list of temporary index names for each SO type, e.g.: + * 'cases': '.kibana_cases_8.8.0_reindex_temp' + * 'task': '.kibana_task_manager_8.8.0_reindex_temp' + * ... + */ + typeIndexMap?: Record; } export interface DocumentExceedsBatchSize { @@ -30,6 +41,32 @@ export interface DocumentExceedsBatchSize { maxBatchSizeBytes: number; } +/** + * Build a relationship of temporary index names for each SO type, e.g.: + * 'cases': '.kibana_cases_8.8.0_reindex_temp' + * 'task': '.kibana_task_manager_8.8.0_reindex_temp' + * ... + * + * @param indexTypesMap information about which types are stored in each index + * @param kibanaVersion the target version of the indices + */ +export function buildTempIndexMap( + indexTypesMap: IndexTypesMap, + kibanaVersion: string +): Record { + return Object.entries(indexTypesMap || {}).reduce>( + (acc, [indexAlias, types]) => { + const tempIndex = getTempIndexName(indexAlias, kibanaVersion!); + + types.forEach((type) => { + acc[type] = tempIndex; + }); + return acc; + }, + {} + ); +} + /** * Creates batches of documents to be used by the bulk API. Each batch will * have a request body content length that's <= maxBatchSizeBytes @@ -39,6 +76,7 @@ export function createBatches({ corruptDocumentIds = [], transformErrors = [], maxBatchSizeBytes, + typeIndexMap, }: CreateBatchesParams): Either.Either { /* To build up the NDJSON request body we construct an array of objects like: * [ @@ -92,7 +130,7 @@ export function createBatches({ // create index (update) operations for all transformed documents for (const document of documents) { - const bulkIndexOperationBody = createBulkIndexOperationTuple(document); + const bulkIndexOperationBody = createBulkIndexOperationTuple(document, typeIndexMap); // take into account that this tuple's surrounding brackets `[]` won't be present in the NDJSON const docSizeBytes = Buffer.byteLength(JSON.stringify(bulkIndexOperationBody), 'utf8') - BRACKETS_BYTES; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.test.ts index 291f865f8c100..a20b517b7f3ba 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.test.ts @@ -16,6 +16,8 @@ import { buildRemoveAliasActions, versionMigrationCompleted, MigrationType, + getTempIndexName, + createBulkIndexOperationTuple, } from './helpers'; describe('addExcludedTypesToBoolQuery', () => { @@ -290,6 +292,46 @@ describe('buildRemoveAliasActions', () => { }); }); +describe('createBulkIndexOperationTuple', () => { + it('creates the proper request body to bulk index a document', () => { + const document = { _id: '', _source: { type: 'cases', title: 'a case' } }; + const typeIndexMap = { + cases: '.kibana_cases_8.8.0_reindex_temp', + }; + expect(createBulkIndexOperationTuple(document, typeIndexMap)).toMatchInlineSnapshot(` + Array [ + Object { + "index": Object { + "_id": "", + "_index": ".kibana_cases_8.8.0_reindex_temp", + }, + }, + Object { + "title": "a case", + "type": "cases", + }, + ] + `); + }); + + it('does not include the index property if it is not specified in the typeIndexMap', () => { + const document = { _id: '', _source: { type: 'cases', title: 'a case' } }; + expect(createBulkIndexOperationTuple(document)).toMatchInlineSnapshot(` + Array [ + Object { + "index": Object { + "_id": "", + }, + }, + Object { + "title": "a case", + "type": "cases", + }, + ] + `); + }); +}); + describe('getMigrationType', () => { it.each` isMappingsCompatible | isVersionMigrationCompleted | expected @@ -306,3 +348,9 @@ describe('getMigrationType', () => { } ); }); + +describe('getTempIndexName', () => { + it('composes a temporary index name for reindexing', () => { + expect(getTempIndexName('.kibana_cases', '8.8.0')).toEqual('.kibana_cases_8.8.0_reindex_temp'); + }); +}); diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.ts index 9ffdf8508c8f5..c8d8884c980cd 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.ts @@ -65,6 +65,7 @@ export function mergeMigrationMappingPropertyHashes( return { ...targetMappings, _meta: { + ...targetMappings._meta, migrationMappingPropertyHashes: { ...indexMappings._meta?.migrationMappingPropertyHashes, ...targetMappings._meta?.migrationMappingPropertyHashes, @@ -218,11 +219,15 @@ export function buildRemoveAliasActions( /** * Given a document, creates a valid body to index the document using the Bulk API. */ -export const createBulkIndexOperationTuple = (doc: SavedObjectsRawDoc): BulkIndexOperationTuple => { +export const createBulkIndexOperationTuple = ( + doc: SavedObjectsRawDoc, + typeIndexMap: Record = {} +): BulkIndexOperationTuple => { return [ { index: { _id: doc._id, + ...(typeIndexMap[doc._source.type] && { _index: typeIndexMap[doc._source.type] }), // use optimistic concurrency control to ensure that outdated // documents are only overwritten once with the latest version ...(typeof doc._seq_no !== 'undefined' && { if_seq_no: doc._seq_no }), @@ -271,3 +276,12 @@ export function getMigrationType({ return MigrationType.Invalid; } + +/** + * Generate a temporary index name, to reindex documents into it + * @param index The name of the SO index + * @param kibanaVersion The current kibana version + * @returns A temporary index name to reindex documents + */ +export const getTempIndexName = (indexPrefix: string, kibanaVersion: string): string => + `${indexPrefix}_${kibanaVersion}_reindex_temp`; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.test.ts index deba537b3f81e..969a7704e4e75 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.test.ts @@ -20,7 +20,7 @@ import type { CheckVersionIndexReadyActions, CleanupUnknownAndExcluded, CleanupUnknownAndExcludedWaitForTaskState, - CloneTempToSource, + CloneTempToTarget, CreateNewTargetState, CreateReindexTempState, FatalState, @@ -51,6 +51,8 @@ import type { UpdateTargetMappingsPropertiesState, UpdateTargetMappingsPropertiesWaitForTaskState, WaitForYellowSourceState, + ReadyToReindexSyncState, + DoneReindexingSyncState, } from '../state'; import { type TransformErrorObjects, TransformSavedObjectDocumentError } from '../core'; import type { AliasAction, RetryableEsClientError } from '../actions'; @@ -58,6 +60,7 @@ import type { ResponseType } from '../next'; import { createInitialProgress } from './progress'; import { model } from './model'; import type { BulkIndexOperationTuple, BulkOperation } from './create_batches'; +import { DEFAULT_INDEX_TYPES_MAP } from '../kibana_migrator_constants'; describe('migrations v2 model', () => { const indexMapping: IndexMapping = { @@ -115,6 +118,8 @@ describe('migrations v2 model', () => { clusterShardLimitExceeded: 'clusterShardLimitExceeded', }, waitForMigrationCompletion: false, + mustRelocateDocuments: false, + indexTypesMap: DEFAULT_INDEX_TYPES_MAP, }; const postInitState = { ...baseState, @@ -732,7 +737,7 @@ describe('migrations v2 model', () => { expect(newState.retryCount).toEqual(0); expect(newState.retryDelay).toEqual(0); }); - test('INIT -> CREATE_NEW_TARGET when no indices/aliases exist', () => { + test('INIT -> CREATE_NEW_TARGET when the index does not exist and the migrator is NOT involved in a relocation', () => { const res: ResponseType<'INIT'> = Either.right({}); const newState = model(initState, res); @@ -744,6 +749,29 @@ describe('migrations v2 model', () => { expect(newState.retryCount).toEqual(0); expect(newState.retryDelay).toEqual(0); }); + test('INIT -> CREATE_REINDEX_TEMP when the index does not exist and the migrator is involved in a relocation', () => { + const res: ResponseType<'INIT'> = Either.right({}); + const newState = model( + { + ...initState, + mustRelocateDocuments: true, + }, + res + ); + + expect(newState).toMatchObject({ + controlState: 'CREATE_REINDEX_TEMP', + sourceIndex: Option.none, + targetIndex: '.kibana_7.11.0_001', + versionIndexReadyActions: Option.some([ + { add: { index: '.kibana_7.11.0_001', alias: '.kibana' } }, + { add: { index: '.kibana_7.11.0_001', alias: '.kibana_7.11.0' } }, + { remove_index: { index: '.kibana_7.11.0_reindex_temp' } }, + ]), + }); + expect(newState.retryCount).toEqual(0); + expect(newState.retryDelay).toEqual(0); + }); }); }); @@ -1146,12 +1174,29 @@ describe('migrations v2 model', () => { expect(newState.retryDelay).toEqual(0); }); - test('WAIT_FOR_YELLOW_SOURCE -> UPDATE_SOURCE_MAPPINGS_PROPERTIES', () => { - const res: ResponseType<'WAIT_FOR_YELLOW_SOURCE'> = Either.right({}); - const newState = model(waitForYellowSourceState, res); + describe('if the migrator is NOT involved in a relocation', () => { + test('WAIT_FOR_YELLOW_SOURCE -> UPDATE_SOURCE_MAPPINGS_PROPERTIES', () => { + const res: ResponseType<'WAIT_FOR_YELLOW_SOURCE'> = Either.right({}); + const newState = model(waitForYellowSourceState, res); - expect(newState).toMatchObject({ - controlState: 'UPDATE_SOURCE_MAPPINGS_PROPERTIES', + expect(newState).toMatchObject({ + controlState: 'UPDATE_SOURCE_MAPPINGS_PROPERTIES', + }); + }); + }); + + describe('if the migrator is involved in a relocation', () => { + // no need to attempt to update the mappings, we are going to reindex + test('WAIT_FOR_YELLOW_SOURCE -> CHECK_UNKNOWN_DOCUMENTS', () => { + const res: ResponseType<'WAIT_FOR_YELLOW_SOURCE'> = Either.right({}); + const newState = model( + { ...waitForYellowSourceState, mustRelocateDocuments: true }, + res + ); + + expect(newState).toMatchObject({ + controlState: 'CHECK_UNKNOWN_DOCUMENTS', + }); }); }); }); @@ -1630,13 +1675,27 @@ describe('migrations v2 model', () => { sourceIndexMappings: Option.some({}) as Option.Some, tempIndexMappings: { properties: {} }, }; - it('CREATE_REINDEX_TEMP -> REINDEX_SOURCE_TO_TEMP_OPEN_PIT if action succeeds', () => { - const res: ResponseType<'CREATE_REINDEX_TEMP'> = Either.right('create_index_succeeded'); - const newState = model(state, res); - expect(newState.controlState).toEqual('REINDEX_SOURCE_TO_TEMP_OPEN_PIT'); - expect(newState.retryCount).toEqual(0); - expect(newState.retryDelay).toEqual(0); + + describe('if the migrator is NOT involved in a relocation', () => { + it('CREATE_REINDEX_TEMP -> REINDEX_SOURCE_TO_TEMP_OPEN_PIT if action succeeds', () => { + const res: ResponseType<'CREATE_REINDEX_TEMP'> = Either.right('create_index_succeeded'); + const newState = model(state, res); + expect(newState.controlState).toEqual('REINDEX_SOURCE_TO_TEMP_OPEN_PIT'); + expect(newState.retryCount).toEqual(0); + expect(newState.retryDelay).toEqual(0); + }); + }); + + describe('if the migrator is involved in a relocation', () => { + it('CREATE_REINDEX_TEMP -> READY_TO_REINDEX_SYNC if action succeeds', () => { + const res: ResponseType<'CREATE_REINDEX_TEMP'> = Either.right('create_index_succeeded'); + const newState = model({ ...state, mustRelocateDocuments: true }, res); + expect(newState.controlState).toEqual('READY_TO_REINDEX_SYNC'); + expect(newState.retryCount).toEqual(0); + expect(newState.retryDelay).toEqual(0); + }); }); + it('CREATE_REINDEX_TEMP -> CREATE_REINDEX_TEMP if action fails with index_not_green_timeout', () => { const res: ResponseType<'CREATE_REINDEX_TEMP'> = Either.left({ message: '[index_not_green_timeout] Timeout waiting for ...', @@ -1677,6 +1736,52 @@ describe('migrations v2 model', () => { }); }); + describe('READY_TO_REINDEX_SYNC', () => { + const state: ReadyToReindexSyncState = { + ...postInitState, + controlState: 'READY_TO_REINDEX_SYNC', + }; + + describe('if the migrator source index did NOT exist', () => { + test('READY_TO_REINDEX_SYNC -> DONE_REINDEXING_SYNC', () => { + const res: ResponseType<'READY_TO_REINDEX_SYNC'> = Either.right( + 'synchronized_successfully' as const + ); + const newState = model(state, res); + expect(newState.controlState).toEqual('DONE_REINDEXING_SYNC'); + }); + }); + + describe('if the migrator source index did exist', () => { + test('READY_TO_REINDEX_SYNC -> REINDEX_SOURCE_TO_TEMP_OPEN_PIT', () => { + const res: ResponseType<'READY_TO_REINDEX_SYNC'> = Either.right( + 'synchronized_successfully' as const + ); + const newState = model( + { + ...state, + sourceIndex: Option.fromNullable('.kibana'), + sourceIndexMappings: Option.fromNullable({} as IndexMapping), + }, + res + ); + expect(newState.controlState).toEqual('REINDEX_SOURCE_TO_TEMP_OPEN_PIT'); + }); + }); + + test('READY_TO_REINDEX_SYNC -> FATAL if the synchronization between migrators fails', () => { + const res: ResponseType<'READY_TO_REINDEX_SYNC'> = Either.left({ + type: 'sync_failed', + error: new Error('Other migrators failed to reach the synchronization point'), + }); + const newState = model(state, res); + expect(newState.controlState).toEqual('FATAL'); + expect((newState as FatalState).reason).toMatchInlineSnapshot( + `"An error occurred whilst waiting for other migrators to get to this step."` + ); + }); + }); + describe('REINDEX_SOURCE_TO_TEMP_OPEN_PIT', () => { const state: ReindexSourceToTempOpenPit = { ...postInitState, @@ -1812,11 +1917,50 @@ describe('migrations v2 model', () => { tempIndexMappings: { properties: {} }, }; - it('REINDEX_SOURCE_TO_TEMP_CLOSE_PIT -> SET_TEMP_WRITE_BLOCK if action succeeded', () => { - const res: ResponseType<'REINDEX_SOURCE_TO_TEMP_CLOSE_PIT'> = Either.right({}); - const newState = model(state, res) as ReindexSourceToTempTransform; - expect(newState.controlState).toBe('SET_TEMP_WRITE_BLOCK'); - expect(newState.sourceIndex).toEqual(state.sourceIndex); + describe('if the migrator is NOT involved in a relocation', () => { + it('REINDEX_SOURCE_TO_TEMP_CLOSE_PIT -> SET_TEMP_WRITE_BLOCK if action succeeded', () => { + const res: ResponseType<'REINDEX_SOURCE_TO_TEMP_CLOSE_PIT'> = Either.right({}); + const newState = model(state, res) as ReindexSourceToTempTransform; + expect(newState.controlState).toBe('SET_TEMP_WRITE_BLOCK'); + expect(newState.sourceIndex).toEqual(state.sourceIndex); + }); + }); + + describe('if the migrator is involved in a relocation', () => { + it('REINDEX_SOURCE_TO_TEMP_CLOSE_PIT -> DONE_REINDEXING_SYNC if action succeeded', () => { + const res: ResponseType<'REINDEX_SOURCE_TO_TEMP_CLOSE_PIT'> = Either.right({}); + const newState = model( + { ...state, mustRelocateDocuments: true }, + res + ) as ReindexSourceToTempTransform; + expect(newState.controlState).toBe('DONE_REINDEXING_SYNC'); + }); + }); + }); + + describe('DONE_REINDEXING_SYNC', () => { + const state: DoneReindexingSyncState = { + ...postInitState, + controlState: 'DONE_REINDEXING_SYNC', + }; + + test('DONE_REINDEXING_SYNC -> SET_TEMP_WRITE_BLOCK if synchronization succeeds', () => { + const res: ResponseType<'DONE_REINDEXING_SYNC'> = Either.right( + 'synchronized_successfully' as const + ); + const newState = model(state, res); + expect(newState.controlState).toEqual('SET_TEMP_WRITE_BLOCK'); + }); + test('DONE_REINDEXING_SYNC -> FATAL if the synchronization between migrators fails', () => { + const res: ResponseType<'DONE_REINDEXING_SYNC'> = Either.left({ + type: 'sync_failed', + error: new Error('Other migrators failed to reach the synchronization point'), + }); + const newState = model(state, res); + expect(newState.controlState).toEqual('FATAL'); + expect((newState as FatalState).reason).toMatchInlineSnapshot( + `"An error occurred whilst waiting for other migrators to get to this step."` + ); }); }); @@ -1977,7 +2121,7 @@ describe('migrations v2 model', () => { }); describe('CLONE_TEMP_TO_TARGET', () => { - const state: CloneTempToSource = { + const state: CloneTempToTarget = { ...postInitState, controlState: 'CLONE_TEMP_TO_TARGET', sourceIndex: Option.some('.kibana') as Option.Some, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.ts index 23ddee5043261..54657310912f8 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.ts @@ -44,7 +44,7 @@ import { buildRemoveAliasActions, MigrationType, } from './helpers'; -import { createBatches } from './create_batches'; +import { buildTempIndexMap, createBatches } from './create_batches'; import type { MigrationLog } from '../types'; import { CLUSTER_SHARD_LIMIT_EXCEEDED_REASON, @@ -121,6 +121,8 @@ export const model = (currentState: State, resW: ResponseType): // The source index .kibana is pointing to. E.g: ".kibana_8.7.0_001" const source = aliases[stateP.currentAlias]; + // The target index .kibana WILL be pointing to if we reindex. E.g: ".kibana_8.8.0_001" + const newVersionTarget = stateP.versionIndex; const postInitState = { aliases, @@ -137,7 +139,7 @@ export const model = (currentState: State, resW: ResponseType): ...stateP, ...postInitState, sourceIndex: Option.none, - targetIndex: `${stateP.indexPrefix}_${stateP.kibanaVersion}_001`, + targetIndex: newVersionTarget, controlState: 'WAIT_FOR_MIGRATION_COMPLETION', // Wait for 2s before checking again if the migration has completed retryDelay: 2000, @@ -153,7 +155,6 @@ export const model = (currentState: State, resW: ResponseType): // If the `.kibana` alias exists Option.isSome(postInitState.sourceIndex) ) { - // CHECKPOINT here we decide to go for yellow source return { ...stateP, ...postInitState, @@ -182,7 +183,6 @@ export const model = (currentState: State, resW: ResponseType): const legacyReindexTarget = `${stateP.indexPrefix}_${legacyVersion}_001`; - const target = stateP.versionIndex; return { ...stateP, ...postInitState, @@ -191,7 +191,7 @@ export const model = (currentState: State, resW: ResponseType): sourceIndexMappings: Option.some( indices[stateP.legacyIndex].mappings ) as Option.Some, - targetIndex: target, + targetIndex: newVersionTarget, legacyPreMigrationDoneActions: [ { remove_index: { index: stateP.legacyIndex } }, { @@ -209,24 +209,40 @@ export const model = (currentState: State, resW: ResponseType): must_exist: true, }, }, - { add: { index: target, alias: stateP.currentAlias } }, - { add: { index: target, alias: stateP.versionAlias } }, + { add: { index: newVersionTarget, alias: stateP.currentAlias } }, + { add: { index: newVersionTarget, alias: stateP.versionAlias } }, + { remove_index: { index: stateP.tempIndex } }, + ]), + }; + } else if ( + // if we must relocate documents to this migrator's index, but the index does NOT yet exist: + // this migrator must create a temporary index and synchronize with other migrators + // this is a similar flow to the reindex one, but this migrator will not reindexing anything + stateP.mustRelocateDocuments + ) { + return { + ...stateP, + ...postInitState, + controlState: 'CREATE_REINDEX_TEMP', + sourceIndex: Option.none as Option.None, + targetIndex: newVersionTarget, + versionIndexReadyActions: Option.some([ + { add: { index: newVersionTarget, alias: stateP.currentAlias } }, + { add: { index: newVersionTarget, alias: stateP.versionAlias } }, { remove_index: { index: stateP.tempIndex } }, ]), }; } else { - // This cluster doesn't have an existing Saved Object index, create a - // new version specific index. - const target = stateP.versionIndex; + // no need to copy anything over from other indices, we can start with a clean, empty index return { ...stateP, ...postInitState, controlState: 'CREATE_NEW_TARGET', sourceIndex: Option.none as Option.None, - targetIndex: target, + targetIndex: newVersionTarget, versionIndexReadyActions: Option.some([ - { add: { index: target, alias: stateP.currentAlias } }, - { add: { index: target, alias: stateP.versionAlias } }, + { add: { index: newVersionTarget, alias: stateP.currentAlias } }, + { add: { index: newVersionTarget, alias: stateP.versionAlias } }, ]) as Option.Some, }; } @@ -240,6 +256,7 @@ export const model = (currentState: State, resW: ResponseType): if ( // If this version's migration has already been completed we can proceed Either.isRight(aliasesRes) && + // TODO check that this behaves correctly when skipping reindexing versionMigrationCompleted(stateP.currentAlias, stateP.versionAlias, aliasesRes.right) ) { return { @@ -414,10 +431,21 @@ export const model = (currentState: State, resW: ResponseType): } else if (stateP.controlState === 'WAIT_FOR_YELLOW_SOURCE') { const res = resW as ExcludeRetryableEsError>; if (Either.isRight(res)) { - return { - ...stateP, - controlState: 'UPDATE_SOURCE_MAPPINGS_PROPERTIES', - }; + if (stateP.mustRelocateDocuments) { + // this migrator's index must dispatch documents to other indices, + // and/or it must receive documents from other indices + // we must reindex and synchronize with other migrators + return { + ...stateP, + controlState: 'CHECK_UNKNOWN_DOCUMENTS', + }; + } else { + // this migrator is not involved in a relocation, we can proceed with the standard flow + return { + ...stateP, + controlState: 'UPDATE_SOURCE_MAPPINGS_PROPERTIES', + }; + } } else if (Either.isLeft(res)) { const left = res.left; if (isTypeof(left, 'index_not_yellow_timeout')) { @@ -711,7 +739,18 @@ export const model = (currentState: State, resW: ResponseType): } else if (stateP.controlState === 'CREATE_REINDEX_TEMP') { const res = resW as ExcludeRetryableEsError>; if (Either.isRight(res)) { - return { ...stateP, controlState: 'REINDEX_SOURCE_TO_TEMP_OPEN_PIT' }; + if (stateP.mustRelocateDocuments) { + // we are reindexing, and this migrator's index is involved in document relocations + return { ...stateP, controlState: 'READY_TO_REINDEX_SYNC' }; + } else { + // we are reindexing but this migrator's index is not involved in any document relocation + return { + ...stateP, + controlState: 'REINDEX_SOURCE_TO_TEMP_OPEN_PIT', + sourceIndex: stateP.sourceIndex as Option.Some, + sourceIndexMappings: stateP.sourceIndexMappings as Option.Some, + }; + } } else if (Either.isLeft(res)) { const left = res.left; if (isTypeof(left, 'index_not_green_timeout')) { @@ -738,6 +777,32 @@ export const model = (currentState: State, resW: ResponseType): // left responses to handle here. throwBadResponse(stateP, res); } + } else if (stateP.controlState === 'READY_TO_REINDEX_SYNC') { + const res = resW as ExcludeRetryableEsError>; + if (Either.isRight(res)) { + if (Option.isSome(stateP.sourceIndex) && Option.isSome(stateP.sourceIndexMappings)) { + // this migrator's source index exist, reindex its entries + return { + ...stateP, + controlState: 'REINDEX_SOURCE_TO_TEMP_OPEN_PIT', + sourceIndex: stateP.sourceIndex as Option.Some, + sourceIndexMappings: stateP.sourceIndexMappings as Option.Some, + }; + } else { + // this migrator's source index did NOT exist + // this migrator does not need to reindex anything (others might need to) + return { ...stateP, controlState: 'DONE_REINDEXING_SYNC' }; + } + } else if (Either.isLeft(res)) { + return { + ...stateP, + controlState: 'FATAL', + reason: 'An error occurred whilst waiting for other migrators to get to this step.', + throwDelayMillis: 1000, // another migrator has failed for a reason, let it take Kibana down and log its problem + }; + } else { + return throwBadResponse(stateP, res as never); + } } else if (stateP.controlState === 'REINDEX_SOURCE_TO_TEMP_OPEN_PIT') { const res = resW as ExcludeRetryableEsError>; if (Either.isRight(res)) { @@ -816,14 +881,41 @@ export const model = (currentState: State, resW: ResponseType): const res = resW as ExcludeRetryableEsError>; if (Either.isRight(res)) { const { sourceIndexPitId, ...state } = stateP; + + if (stateP.mustRelocateDocuments) { + return { + ...state, + controlState: 'DONE_REINDEXING_SYNC', + }; + } else { + return { + ...stateP, + controlState: 'SET_TEMP_WRITE_BLOCK', + sourceIndex: stateP.sourceIndex as Option.Some, + sourceIndexMappings: Option.none, + }; + } + } else { + throwBadResponse(stateP, res); + } + } else if (stateP.controlState === 'DONE_REINDEXING_SYNC') { + const res = resW as ExcludeRetryableEsError>; + if (Either.isRight(res)) { return { - ...state, + ...stateP, controlState: 'SET_TEMP_WRITE_BLOCK', sourceIndex: stateP.sourceIndex as Option.Some, sourceIndexMappings: Option.none, }; + } else if (Either.isLeft(res)) { + return { + ...stateP, + controlState: 'FATAL', + reason: 'An error occurred whilst waiting for other migrators to get to this step.', + throwDelayMillis: 1000, // another migrator has failed for a reason, let it take Kibana down and log its problem + }; } else { - throwBadResponse(stateP, res); + return throwBadResponse(stateP, res as never); } } else if (stateP.controlState === 'REINDEX_SOURCE_TO_TEMP_TRANSFORM') { // We follow a similar control flow as for @@ -845,7 +937,11 @@ export const model = (currentState: State, resW: ResponseType): stateP.discardCorruptObjects ) { const documents = Either.isRight(res) ? res.right.processedDocs : res.left.processedDocs; - const batches = createBatches({ documents, maxBatchSizeBytes: stateP.maxBatchSizeBytes }); + const batches = createBatches({ + documents, + maxBatchSizeBytes: stateP.maxBatchSizeBytes, + typeIndexMap: buildTempIndexMap(stateP.indexTypesMap, stateP.kibanaVersion), + }); if (Either.isRight(batches)) { let corruptDocumentIds = stateP.corruptDocumentIds; let transformErrors = stateP.transformErrors; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.test.ts index cc4ee13673940..f7cabfb6e42db 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.test.ts @@ -7,6 +7,7 @@ */ import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { defer } from './kibana_migrator_utils'; import { next } from './next'; import type { State } from './state'; @@ -14,12 +15,12 @@ describe('migrations v2 next', () => { it.todo('when state.retryDelay > 0 delays execution of the next action'); it('DONE returns null', () => { const state = { controlState: 'DONE' } as State; - const action = next({} as ElasticsearchClient, (() => {}) as any)(state); + const action = next({} as ElasticsearchClient, (() => {}) as any, defer(), defer())(state); expect(action).toEqual(null); }); it('FATAL returns null', () => { const state = { controlState: 'FATAL', reason: '' } as State; - const action = next({} as ElasticsearchClient, (() => {}) as any)(state); + const action = next({} as ElasticsearchClient, (() => {}) as any, defer(), defer())(state); expect(action).toEqual(null); }); }); diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.ts index cc26bb543ef5e..426f73436cb71 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.ts @@ -7,8 +7,9 @@ */ import * as Option from 'fp-ts/lib/Option'; -import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { omit } from 'lodash'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { Defer } from './kibana_migrator_utils'; import type { AllActionStates, CalculateExcludeFiltersState, @@ -16,7 +17,7 @@ import type { CheckUnknownDocumentsState, CleanupUnknownAndExcluded, CleanupUnknownAndExcludedWaitForTaskState, - CloneTempToSource, + CloneTempToTarget, CreateNewTargetState, CreateReindexTempState, InitState, @@ -68,7 +69,12 @@ export type ResponseType = Awaited< ReturnType> >; -export const nextActionMap = (client: ElasticsearchClient, transformRawDocs: TransformRawDocs) => { +export const nextActionMap = ( + client: ElasticsearchClient, + transformRawDocs: TransformRawDocs, + readyToReindex: Defer, + doneReindexing: Defer +) => { return { INIT: (state: InitState) => Actions.initAction({ client, indices: [state.currentAlias, state.versionAlias] }), @@ -135,6 +141,7 @@ export const nextActionMap = (client: ElasticsearchClient, transformRawDocs: Tra indexName: state.tempIndex, mappings: state.tempIndexMappings, }), + READY_TO_REINDEX_SYNC: () => Actions.synchronizeMigrators(readyToReindex), REINDEX_SOURCE_TO_TEMP_OPEN_PIT: (state: ReindexSourceToTempOpenPit) => Actions.openPit({ client, index: state.sourceIndex.value }), REINDEX_SOURCE_TO_TEMP_READ: (state: ReindexSourceToTempRead) => @@ -167,9 +174,10 @@ export const nextActionMap = (client: ElasticsearchClient, transformRawDocs: Tra */ refresh: false, }), + DONE_REINDEXING_SYNC: () => Actions.synchronizeMigrators(doneReindexing), SET_TEMP_WRITE_BLOCK: (state: SetTempWriteBlock) => Actions.setWriteBlock({ client, index: state.tempIndex }), - CLONE_TEMP_TO_TARGET: (state: CloneTempToSource) => + CLONE_TEMP_TO_TARGET: (state: CloneTempToTarget) => Actions.cloneIndex({ client, source: state.tempIndex, target: state.targetIndex }), REFRESH_TARGET: (state: RefreshTarget) => Actions.refreshIndex({ client, index: state.targetIndex }), @@ -192,12 +200,13 @@ export const nextActionMap = (client: ElasticsearchClient, transformRawDocs: Tra taskId: state.updateTargetMappingsTaskId, timeout: '60s', }), - UPDATE_TARGET_MAPPINGS_META: (state: UpdateTargetMappingsMeta) => - Actions.updateMappings({ + UPDATE_TARGET_MAPPINGS_META: (state: UpdateTargetMappingsMeta) => { + return Actions.updateMappings({ client, index: state.targetIndex, - mappings: omit(state.targetIndexMappings, 'properties'), - }), + mappings: omit(state.targetIndexMappings, ['properties']), // properties already updated on a previous step + }); + }, CHECK_VERSION_INDEX_READY_ACTIONS: () => Actions.noop, OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT: (state: OutdatedDocumentsSearchOpenPit) => Actions.openPit({ client, index: state.targetIndex }), @@ -257,8 +266,13 @@ export const nextActionMap = (client: ElasticsearchClient, transformRawDocs: Tra }; }; -export const next = (client: ElasticsearchClient, transformRawDocs: TransformRawDocs) => { - const map = nextActionMap(client, transformRawDocs); +export const next = ( + client: ElasticsearchClient, + transformRawDocs: TransformRawDocs, + readyToReindex: Defer, + doneReindexing: Defer +) => { + const map = nextActionMap(client, transformRawDocs, readyToReindex, doneReindexing); return (state: State) => { const delay = createDelayFn(state); diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/run_resilient_migrator.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/run_resilient_migrator.ts index b94a94a715056..f3ae3f2c09f75 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/run_resilient_migrator.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/run_resilient_migrator.ts @@ -15,12 +15,16 @@ import type { IndexMapping, SavedObjectsMigrationConfigType, MigrationResult, + IndexTypesMap, } from '@kbn/core-saved-objects-base-server-internal'; +import type { Defer } from './kibana_migrator_utils'; import type { TransformRawDocs } from './types'; import { next } from './next'; import { model } from './model'; import { createInitialState } from './initial_state'; import { migrationStateActionMachine } from './migrations_state_action_machine'; +import { cleanup } from './migrations_state_machine_cleanup'; +import type { State } from './state'; /** * To avoid the Elasticsearch-js client aborting our requests before we @@ -45,9 +49,13 @@ export async function runResilientMigrator({ client, kibanaVersion, waitForMigrationCompletion, + mustRelocateDocuments, + indexTypesMap, targetMappings, logger, preMigrationScript, + readyToReindex, + doneReindexing, transformRawDocs, migrationVersionPerType, indexPrefix, @@ -58,8 +66,12 @@ export async function runResilientMigrator({ client: ElasticsearchClient; kibanaVersion: string; waitForMigrationCompletion: boolean; + mustRelocateDocuments: boolean; + indexTypesMap: IndexTypesMap; targetMappings: IndexMapping; preMigrationScript?: string; + readyToReindex: Defer; + doneReindexing: Defer; logger: Logger; transformRawDocs: TransformRawDocs; migrationVersionPerType: SavedObjectsMigrationVersion; @@ -71,6 +83,8 @@ export async function runResilientMigrator({ const initialState = createInitialState({ kibanaVersion, waitForMigrationCompletion, + mustRelocateDocuments, + indexTypesMap, targetMappings, preMigrationScript, migrationVersionPerType, @@ -84,8 +98,12 @@ export async function runResilientMigrator({ return migrationStateActionMachine({ initialState, logger, - next: next(migrationClient, transformRawDocs), + next: next(migrationClient, transformRawDocs, readyToReindex, doneReindexing), model, - client: migrationClient, + abort: async (state?: State) => { + // At this point, we could reject this migrator's defers and unblock other migrators + // but we are going to throw and shutdown Kibana anyway, so there's no real point in it + await cleanup(client, state); + }, }); } diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/state.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/state.ts index 5dd135d4b32fe..6a483da04a399 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/state.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/state.ts @@ -13,7 +13,7 @@ import type { SavedObjectsRawDoc, SavedObjectTypeExcludeFromUpgradeFilterHook, } from '@kbn/core-saved-objects-server'; -import type { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; +import type { IndexMapping, IndexTypesMap } from '@kbn/core-saved-objects-base-server-internal'; import type { ControlState } from './state_action_machine'; import type { AliasAction } from './actions'; import type { TransformErrorObjects } from './core'; @@ -152,6 +152,23 @@ export interface BaseState extends ControlState { */ readonly migrationDocLinks: DocLinks['kibanaUpgradeSavedObjects']; readonly waitForMigrationCompletion: boolean; + + /** + * This flag tells the migrator that SO documents must be redistributed, + * i.e. stored in different system indices, compared to where they are currently stored. + * This requires reindexing documents. + */ + readonly mustRelocateDocuments: boolean; + + /** + * This object holds a relation of all the types that are stored in each index, e.g.: + * { + * '.kibana': [ 'type_1', 'type_2', ... 'type_N' ], + * '.kibana_cases': [ 'type_N+1', 'type_N+2', ... 'type_N+M' ], + * ... + * } + */ + readonly indexTypesMap: IndexTypesMap; } export interface InitState extends BaseState { @@ -231,6 +248,8 @@ export interface FatalState extends BaseState { readonly controlState: 'FATAL'; /** The reason the migration was terminated */ readonly reason: string; + /** The delay in milliseconds before throwing the FATAL exception */ + readonly throwDelayMillis?: number; } export interface WaitForYellowSourceState extends SourceExistsState { @@ -263,7 +282,7 @@ export interface CreateNewTargetState extends PostInitState { readonly versionIndexReadyActions: Option.Some; } -export interface CreateReindexTempState extends SourceExistsState { +export interface CreateReindexTempState extends PostInitState { /** * Create a target index with mappings from the source index and registered * plugins @@ -271,6 +290,11 @@ export interface CreateReindexTempState extends SourceExistsState { readonly controlState: 'CREATE_REINDEX_TEMP'; } +export interface ReadyToReindexSyncState extends PostInitState { + /** Open PIT to the source index */ + readonly controlState: 'READY_TO_REINDEX_SYNC'; +} + export interface ReindexSourceToTempOpenPit extends SourceExistsState { /** Open PIT to the source index */ readonly controlState: 'REINDEX_SOURCE_TO_TEMP_OPEN_PIT'; @@ -304,11 +328,16 @@ export interface ReindexSourceToTempIndexBulk extends ReindexSourceToTempBatch { readonly currentBatch: number; } +export interface DoneReindexingSyncState extends PostInitState { + /** Open PIT to the source index */ + readonly controlState: 'DONE_REINDEXING_SYNC'; +} + export interface SetTempWriteBlock extends PostInitState { readonly controlState: 'SET_TEMP_WRITE_BLOCK'; } -export interface CloneTempToSource extends PostInitState { +export interface CloneTempToTarget extends PostInitState { /** * Clone the temporary reindex index into */ @@ -482,9 +511,10 @@ export type State = Readonly< | CheckVersionIndexReadyActions | CleanupUnknownAndExcluded | CleanupUnknownAndExcludedWaitForTaskState - | CloneTempToSource + | CloneTempToTarget | CreateNewTargetState | CreateReindexTempState + | DoneReindexingSyncState | DoneState | FatalState | InitState @@ -501,6 +531,7 @@ export type State = Readonly< | OutdatedDocumentsSearchRead | OutdatedDocumentsTransform | PrepareCompatibleMigration + | ReadyToReindexSyncState | RefreshSource | RefreshTarget | ReindexSourceToTempClosePit diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/tsconfig.json b/packages/core/saved-objects/core-saved-objects-migration-server-internal/tsconfig.json index e56ee4e2c3234..a770476aaf392 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/tsconfig.json +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/tsconfig.json @@ -26,7 +26,6 @@ "@kbn/core-doc-links-server-mocks", "@kbn/core-logging-server-internal", "@kbn/core-saved-objects-base-server-mocks", - "@kbn/core-elasticsearch-server-mocks", "@kbn/doc-links", "@kbn/safer-lodash-set", "@kbn/logging-mocks", diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/deprecations/unknown_object_types.test.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/deprecations/unknown_object_types.test.ts index 2199a7fb32b5e..defcbd591e2a4 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/deprecations/unknown_object_types.test.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/deprecations/unknown_object_types.test.ts @@ -12,7 +12,7 @@ import type { SearchResponse } from '@elastic/elasticsearch/lib/api/types'; import { deleteUnknownTypeObjects, getUnknownTypesDeprecations } from './unknown_object_types'; import { typeRegistryMock } from '@kbn/core-saved-objects-base-server-mocks'; import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; -import type { SavedObjectsType } from '@kbn/core-saved-objects-server'; +import { type SavedObjectsType, MAIN_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; const createAggregateTypesSearchResponse = ( typesIds: Record = {} @@ -50,7 +50,7 @@ describe('unknown saved object types deprecation', () => { let typeRegistry: ReturnType; let esClient: ReturnType; - const kibanaIndex = '.kibana'; + const kibanaIndex = MAIN_SAVED_OBJECT_INDEX; beforeEach(() => { typeRegistry = typeRegistryMock.create(); diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts index fc2cc5a4c91bc..72e36826bb89e 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts @@ -23,6 +23,7 @@ import { type RawPackageInfo, Env } from '@kbn/config'; import { ByteSizeValue } from '@kbn/config-schema'; import { REPO_ROOT } from '@kbn/repo-info'; import { getEnvOptions } from '@kbn/config-mocks'; +import { SavedObjectsType, MAIN_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { docLinksServiceMock } from '@kbn/core-doc-links-server-mocks'; import { nodeServiceMock } from '@kbn/core-node-server-mocks'; import { mockCoreContext } from '@kbn/core-base-server-mocks'; @@ -55,6 +56,14 @@ import { getSavedObjectsDeprecationsProvider } from './deprecations'; jest.mock('./object_types'); jest.mock('./deprecations'); +const createType = (parts: Partial): SavedObjectsType => ({ + name: 'test-type', + hidden: false, + namespaceType: 'single', + mappings: { properties: {} }, + ...parts, +}); + describe('SavedObjectsService', () => { let deprecationsSetup: ReturnType; @@ -630,5 +639,85 @@ describe('SavedObjectsService', () => { expect(includedHiddenTypes).toEqual(['someHiddenType']); }); }); + + describe('index retrieval APIs', () => { + let soService: SavedObjectsService; + + beforeEach(async () => { + const coreContext = createCoreContext({ skipMigration: false }); + soService = new SavedObjectsService(coreContext); + + typeRegistryInstanceMock.getType.mockImplementation((type: string) => { + if (type === 'dashboard') { + return createType({ + name: 'dashboard', + }); + } else if (type === 'foo') { + return createType({ + name: 'foo', + indexPattern: '.kibana_foo', + }); + } else if (type === 'bar') { + return createType({ + name: 'bar', + indexPattern: '.kibana_bar', + }); + } else if (type === 'bar_too') { + return createType({ + name: 'bar_too', + indexPattern: '.kibana_bar', + }); + } else { + return undefined; + } + }); + + await soService.setup(createSetupDeps()); + }); + + describe('#getDefaultIndex', () => { + it('return the default index', async () => { + const { getDefaultIndex } = await soService.start(createStartDeps()); + expect(getDefaultIndex()).toEqual(MAIN_SAVED_OBJECT_INDEX); + }); + }); + + describe('#getIndexForType', () => { + it('return the correct index for type specifying its indexPattern', async () => { + const { getIndexForType } = await soService.start(createStartDeps()); + expect(getIndexForType('bar')).toEqual('.kibana_bar'); + }); + it('return the correct index for type not specifying its indexPattern', async () => { + const { getIndexForType } = await soService.start(createStartDeps()); + expect(getIndexForType('dashboard')).toEqual(MAIN_SAVED_OBJECT_INDEX); + }); + it('return the default index for unknown type', async () => { + const { getIndexForType } = await soService.start(createStartDeps()); + expect(getIndexForType('unknown_type')).toEqual(MAIN_SAVED_OBJECT_INDEX); + }); + }); + + describe('#getIndicesForTypes', () => { + it('return the correct indices for specified types', async () => { + const { getIndicesForTypes } = await soService.start(createStartDeps()); + expect(getIndicesForTypes(['dashboard', 'foo', 'bar'])).toEqual([ + MAIN_SAVED_OBJECT_INDEX, + '.kibana_foo', + '.kibana_bar', + ]); + }); + it('ignore duplicate indices', async () => { + const { getIndicesForTypes } = await soService.start(createStartDeps()); + expect(getIndicesForTypes(['bar', 'bar_too'])).toEqual(['.kibana_bar']); + }); + it('return the default index for unknown type', async () => { + const { getIndicesForTypes } = await soService.start(createStartDeps()); + expect(getIndicesForTypes(['unknown', 'foo'])).toEqual([ + MAIN_SAVED_OBJECT_INDEX, + '.kibana_foo', + ]); + }); + }); + }); }); }); diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.ts index dbb04577d3f7e..36b515f5b0cb1 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.ts @@ -52,13 +52,12 @@ import { import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; import type { DeprecationRegistryProvider } from '@kbn/core-deprecations-server'; import type { NodeInfo } from '@kbn/core-node-server'; +import { MAIN_SAVED_OBJECT_INDEX, ALL_SAVED_OBJECT_INDICES } from '@kbn/core-saved-objects-server'; import { registerRoutes } from './routes'; import { calculateStatus$ } from './status'; import { registerCoreObjectTypes } from './object_types'; import { getSavedObjectsDeprecationsProvider } from './deprecations'; -const kibanaIndex = '.kibana'; - /** * @internal */ @@ -125,7 +124,7 @@ export class SavedObjectsService this.config = new SavedObjectConfig(savedObjectsConfig, savedObjectsMigrationConfig); deprecations.getRegistry('savedObjects').registerDeprecations( getSavedObjectsDeprecationsProvider({ - kibanaIndex, + kibanaIndex: MAIN_SAVED_OBJECT_INDEX, savedObjectsConfig: this.config, kibanaVersion: this.kibanaVersion, typeRegistry: this.typeRegistry, @@ -140,7 +139,7 @@ export class SavedObjectsService logger: this.logger, config: this.config, migratorPromise: firstValueFrom(this.migrator$), - kibanaIndex, + kibanaIndex: MAIN_SAVED_OBJECT_INDEX, kibanaVersion: this.kibanaVersion, }); @@ -198,7 +197,8 @@ export class SavedObjectsService this.typeRegistry.registerType(type); }, getTypeRegistry: () => this.typeRegistry, - getKibanaIndex: () => kibanaIndex, + getDefaultIndex: () => MAIN_SAVED_OBJECT_INDEX, + getAllIndices: () => [...ALL_SAVED_OBJECT_INDICES], }; } @@ -280,7 +280,7 @@ export class SavedObjectsService return SavedObjectsRepository.createRepository( migrator, this.typeRegistry, - kibanaIndex, + MAIN_SAVED_OBJECT_INDEX, esClient, this.logger.get('repository'), includedHiddenTypes, @@ -341,6 +341,21 @@ export class SavedObjectsService importSizeLimit: options?.importSizeLimit ?? this.config!.maxImportExportSize, }), getTypeRegistry: () => this.typeRegistry, + getDefaultIndex: () => MAIN_SAVED_OBJECT_INDEX, + getIndexForType: (type: string) => { + const definition = this.typeRegistry.getType(type); + return definition?.indexPattern ?? MAIN_SAVED_OBJECT_INDEX; + }, + getIndicesForTypes: (types: string[]) => { + const indices = new Set(); + types.forEach((type) => { + const definition = this.typeRegistry.getType(type); + const index = definition?.indexPattern ?? MAIN_SAVED_OBJECT_INDEX; + indices.add(index); + }); + return [...indices]; + }, + getAllIndices: () => [...ALL_SAVED_OBJECT_INDICES], }; } @@ -357,7 +372,7 @@ export class SavedObjectsService logger: this.logger, kibanaVersion: this.kibanaVersion, soMigrationsConfig, - kibanaIndex, + kibanaIndex: MAIN_SAVED_OBJECT_INDEX, client, docLinks, waitForMigrationCompletion, diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/status.test.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/status.test.ts index a6c07ee6ea1ba..8f7f1281f89ea 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/status.test.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/status.test.ts @@ -81,7 +81,13 @@ describe('calculateStatus$', () => { it('is available after migrations have ran', async () => { await expect( calculateStatus$( - of({ status: 'completed', result: [{ status: 'skipped' }, { status: 'patched' }] }), + of({ + status: 'completed', + result: [ + { status: 'skipped' }, + { status: 'patched', destIndex: '.kibana', elapsedMs: 28 }, + ], + }), esStatus$ ) .pipe(take(2)) diff --git a/packages/core/saved-objects/core-saved-objects-server-mocks/src/saved_objects_service.mock.ts b/packages/core/saved-objects/core-saved-objects-server-mocks/src/saved_objects_service.mock.ts index eccdb9ca16c54..2d7d5ff847330 100644 --- a/packages/core/saved-objects/core-saved-objects-server-mocks/src/saved_objects_service.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-server-mocks/src/saved_objects_service.mock.ts @@ -29,6 +29,7 @@ import { savedObjectsImporterMock, } from '@kbn/core-saved-objects-import-export-server-mocks'; import { migrationMocks } from '@kbn/core-saved-objects-migration-server-mocks'; +import { MAIN_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; type SavedObjectsServiceContract = PublicMethodsOf; @@ -41,6 +42,10 @@ const createStartContractMock = (typeRegistry?: jest.Mocked { setSecurityExtension: jest.fn(), setSpacesExtension: jest.fn(), registerType: jest.fn(), - getKibanaIndex: jest.fn(), + getDefaultIndex: jest.fn(), + getAllIndices: jest.fn(), }; - setupContract.getKibanaIndex.mockReturnValue('.kibana'); + setupContract.getDefaultIndex.mockReturnValue(MAIN_SAVED_OBJECT_INDEX); + setupContract.getAllIndices.mockReturnValue([MAIN_SAVED_OBJECT_INDEX]); return setupContract; }; diff --git a/packages/core/saved-objects/core-saved-objects-server/index.ts b/packages/core/saved-objects/core-saved-objects-server/index.ts index d3d2ffe71aa34..cad75db293a67 100644 --- a/packages/core/saved-objects/core-saved-objects-server/index.ts +++ b/packages/core/saved-objects/core-saved-objects-server/index.ts @@ -52,6 +52,15 @@ export type { SavedObjectsExportablePredicate, } from './src/saved_objects_management'; export type { SavedObjectStatusMeta } from './src/saved_objects_status'; +export { + MAIN_SAVED_OBJECT_INDEX, + TASK_MANAGER_SAVED_OBJECT_INDEX, + INGEST_SAVED_OBJECT_INDEX, + ALERTING_CASES_SAVED_OBJECT_INDEX, + SECURITY_SOLUTION_SAVED_OBJECT_INDEX, + ANALYTICS_SAVED_OBJECT_INDEX, + ALL_SAVED_OBJECT_INDICES, +} from './src/saved_objects_index_pattern'; export type { SavedObjectsType, SavedObjectTypeExcludeFromUpgradeFilterHook, diff --git a/packages/core/saved-objects/core-saved-objects-server/src/contracts.ts b/packages/core/saved-objects/core-saved-objects-server/src/contracts.ts index 3ee6e9c262c54..e08da10172f3c 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/contracts.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/contracts.ts @@ -136,7 +136,14 @@ export interface SavedObjectsServiceSetup { /** * Returns the default index used for saved objects. */ - getKibanaIndex: () => string; + getDefaultIndex: () => string; + + /** + * Returns all (aliases to) kibana system indices used for saved object storage. + * + * @deprecated use the `start` contract counterpart. + */ + getAllIndices: () => string[]; } /** @@ -209,4 +216,25 @@ export interface SavedObjectsServiceStart { * {@link SavedObjectsType | saved object types} */ getTypeRegistry: () => ISavedObjectTypeRegistry; + /** + * Returns the (alias to the) index that the specified saved object type is stored in. + * + * @param type The SO type to retrieve the index/alias for. + */ + getIndexForType: (type: string) => string; + /** + * Returns the (alias to the) index that the specified saved object type is stored in. + * + * @remark if multiple types are living in the same index, duplicates will be removed. + * @param types The SO types to retrieve the index/alias for. + */ + getIndicesForTypes: (types: string[]) => string[]; + /** + * Returns the default index used for saved objects. + */ + getDefaultIndex: () => string; + /** + * Returns all (aliases to) kibana system indices used for saved object storage. + */ + getAllIndices: () => string[]; } diff --git a/packages/core/saved-objects/core-saved-objects-server/src/saved_objects_index_pattern.ts b/packages/core/saved-objects/core-saved-objects-server/src/saved_objects_index_pattern.ts new file mode 100644 index 0000000000000..93160c6b9bd45 --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-server/src/saved_objects_index_pattern.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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. + */ + +/** + * Collect and centralize the names of the different saved object indices. + * Note that all of them start with the '.kibana' prefix. + * There are multiple places in the code that these indices have the form .kibana*. + * However, beware that there are some system indices that have the same prefix + * but are NOT used to store saved objects, e.g.: .kibana_security_session_1 + */ +export const MAIN_SAVED_OBJECT_INDEX = '.kibana'; +export const TASK_MANAGER_SAVED_OBJECT_INDEX = `${MAIN_SAVED_OBJECT_INDEX}_task_manager`; +export const INGEST_SAVED_OBJECT_INDEX = `${MAIN_SAVED_OBJECT_INDEX}_ingest`; +export const ALERTING_CASES_SAVED_OBJECT_INDEX = `${MAIN_SAVED_OBJECT_INDEX}_alerting_cases`; +export const SECURITY_SOLUTION_SAVED_OBJECT_INDEX = `${MAIN_SAVED_OBJECT_INDEX}_security_solution`; +export const ANALYTICS_SAVED_OBJECT_INDEX = `${MAIN_SAVED_OBJECT_INDEX}_analytics`; +export const ALL_SAVED_OBJECT_INDICES = [ + MAIN_SAVED_OBJECT_INDEX, + TASK_MANAGER_SAVED_OBJECT_INDEX, + ALERTING_CASES_SAVED_OBJECT_INDEX, + INGEST_SAVED_OBJECT_INDEX, + SECURITY_SOLUTION_SAVED_OBJECT_INDEX, + ANALYTICS_SAVED_OBJECT_INDEX, +]; diff --git a/packages/core/usage-data/core-usage-data-server-internal/src/core_usage_data_service.ts b/packages/core/usage-data/core-usage-data-server-internal/src/core_usage_data_service.ts index bd9fe1ac47859..63a8da844e090 100644 --- a/packages/core/usage-data/core-usage-data-server-internal/src/core_usage_data_service.ts +++ b/packages/core/usage-data/core-usage-data-server-internal/src/core_usage_data_service.ts @@ -40,7 +40,10 @@ import { type InternalCoreUsageDataSetup, } from '@kbn/core-usage-data-base-server-internal'; import type { SavedObjectTypeRegistry } from '@kbn/core-saved-objects-base-server-internal'; -import type { SavedObjectsServiceStart } from '@kbn/core-saved-objects-server'; +import { + MAIN_SAVED_OBJECT_INDEX, + type SavedObjectsServiceStart, +} from '@kbn/core-saved-objects-server'; import { isConfigured } from './is_configured'; import { coreUsageStatsType } from './saved_objects'; @@ -61,26 +64,6 @@ export interface StartDeps { exposedConfigsToUsage: ExposedConfigsToUsage; } -const kibanaIndex = '.kibana'; - -/** - * Because users can configure their Saved Object to any arbitrary index name, - * we need to map customized index names back to a "standard" index name. - * - * e.g. If a user configures `kibana.index: .my_saved_objects` we want to the - * collected data to be grouped under `.kibana` not ".my_saved_objects". - * - * This is rather brittle, but the option to configure index names might go - * away completely anyway (see #60053). - * - * @param index The index name configured for this SO type - * @param kibanaConfigIndex The default kibana index as configured by the user - * with `kibana.index` - */ -const kibanaOrTaskManagerIndex = (index: string, kibanaConfigIndex: string) => { - return index === kibanaConfigIndex ? '.kibana' : '.kibana_task_manager'; -}; - interface UsageDataAggs extends AggregationsMultiBucketAggregateBase { buckets: { disabled: AggregationsSingleBucketAggregateBase; @@ -133,7 +116,7 @@ export class CoreUsageDataService .getTypeRegistry() .getAllTypes() .reduce((acc, type) => { - const index = type.indexPattern ?? kibanaIndex; + const index = type.indexPattern ?? MAIN_SAVED_OBJECT_INDEX; return acc.add(index); }, new Set()) .values() @@ -151,7 +134,7 @@ export class CoreUsageDataService const stats = body[0]; return { - alias: kibanaOrTaskManagerIndex(index, kibanaIndex), + alias: index, docsCount: stats['docs.count'] ? parseInt(stats['docs.count'], 10) : 0, docsDeleted: stats['docs.deleted'] ? parseInt(stats['docs.deleted'], 10) : 0, storeSizeBytes: stats['store.size'] ? parseInt(stats['store.size'], 10) : 0, @@ -192,7 +175,7 @@ export class CoreUsageDataService unknown, { aliases: UsageDataAggs } >({ - index: kibanaIndex, + index: MAIN_SAVED_OBJECT_INDEX, // depends on the .kibana split (assuming 'legacy-url-alias' is stored in '.kibana') body: { track_total_hits: true, query: { match: { type: LEGACY_URL_ALIAS_TYPE } }, diff --git a/packages/kbn-es-archiver/src/actions/empty_kibana_index.ts b/packages/kbn-es-archiver/src/actions/empty_kibana_index.ts index c9b67e4745d45..2f02b51be5501 100644 --- a/packages/kbn-es-archiver/src/actions/empty_kibana_index.ts +++ b/packages/kbn-es-archiver/src/actions/empty_kibana_index.ts @@ -10,7 +10,8 @@ import type { Client } from '@elastic/elasticsearch'; import { ToolingLog } from '@kbn/tooling-log'; import { KbnClient } from '@kbn/test'; -import { migrateKibanaIndex, createStats, cleanKibanaIndices } from '../lib'; +import { ALL_SAVED_OBJECT_INDICES } from '@kbn/core-saved-objects-server'; +import { migrateSavedObjectIndices, createStats, cleanSavedObjectIndices } from '../lib'; export async function emptyKibanaIndexAction({ client, @@ -23,8 +24,8 @@ export async function emptyKibanaIndexAction({ }) { const stats = createStats('emptyKibanaIndex', log); - await cleanKibanaIndices({ client, stats, log }); - await migrateKibanaIndex(kbnClient); - stats.createdIndex('.kibana'); + await cleanSavedObjectIndices({ client, stats, log }); + await migrateSavedObjectIndices(kbnClient); + ALL_SAVED_OBJECT_INDICES.forEach((indexPattern) => stats.createdIndex(indexPattern)); return stats.toJSON(); } diff --git a/packages/kbn-es-archiver/src/actions/load.ts b/packages/kbn-es-archiver/src/actions/load.ts index b6a4c46d42743..1cbdff1883271 100644 --- a/packages/kbn-es-archiver/src/actions/load.ts +++ b/packages/kbn-es-archiver/src/actions/load.ts @@ -14,6 +14,7 @@ import { REPO_ROOT } from '@kbn/repo-info'; import type { KbnClient } from '@kbn/test'; import type { Client } from '@elastic/elasticsearch'; import { createPromiseFromStreams, concatStreamProviders } from '@kbn/utils'; +import { MAIN_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { ES_CLIENT_HEADERS } from '../client_headers'; import { @@ -24,7 +25,7 @@ import { createParseArchiveStreams, createCreateIndexStream, createIndexDocRecordsStream, - migrateKibanaIndex, + migrateSavedObjectIndices, Progress, createDefaultSpace, } from '../lib'; @@ -104,14 +105,15 @@ export async function loadAction({ } ); - // If we affected the Kibana index, we need to ensure it's migrated... - if (Object.keys(result).some((k) => k.startsWith('.kibana'))) { - await migrateKibanaIndex(kbnClient); + // If we affected saved objects indices, we need to ensure they are migrated... + if (Object.keys(result).some((k) => k.startsWith(MAIN_SAVED_OBJECT_INDEX))) { + await migrateSavedObjectIndices(kbnClient); log.debug('[%s] Migrated Kibana index after loading Kibana data', name); if (kibanaPluginIds.includes('spaces')) { - await createDefaultSpace({ client, index: '.kibana' }); - log.debug('[%s] Ensured that default space exists in .kibana', name); + // WARNING affected by #104081. Assumes 'spaces' saved objects are stored in MAIN_SAVED_OBJECT_INDEX + await createDefaultSpace({ client, index: MAIN_SAVED_OBJECT_INDEX }); + log.debug(`[%s] Ensured that default space exists in ${MAIN_SAVED_OBJECT_INDEX}`, name); } } diff --git a/packages/kbn-es-archiver/src/lib/docs/generate_doc_records_stream.ts b/packages/kbn-es-archiver/src/lib/docs/generate_doc_records_stream.ts index 6e3310a7347e7..f0c6db9c89fcb 100644 --- a/packages/kbn-es-archiver/src/lib/docs/generate_doc_records_stream.ts +++ b/packages/kbn-es-archiver/src/lib/docs/generate_doc_records_stream.ts @@ -8,6 +8,7 @@ import { Transform } from 'stream'; import type { Client } from '@elastic/elasticsearch'; +import { MAIN_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { Stats } from '../stats'; import { Progress } from '../progress'; import { ES_CLIENT_HEADERS } from '../../client_headers'; @@ -78,7 +79,9 @@ export function createGenerateDocRecordsStream({ // if keepIndexNames is false, rewrite the .kibana_* index to .kibana_1 so that // when it is loaded it can skip migration, if possible index: - hit._index.startsWith('.kibana') && !keepIndexNames ? '.kibana_1' : hit._index, + hit._index.startsWith(MAIN_SAVED_OBJECT_INDEX) && !keepIndexNames + ? `${MAIN_SAVED_OBJECT_INDEX}_1` + : hit._index, data_stream: dataStream, id: hit._id, source: hit._source, diff --git a/packages/kbn-es-archiver/src/lib/index.ts b/packages/kbn-es-archiver/src/lib/index.ts index 8a857fb24002a..b9c8a8688c654 100644 --- a/packages/kbn-es-archiver/src/lib/index.ts +++ b/packages/kbn-es-archiver/src/lib/index.ts @@ -12,9 +12,9 @@ export { createCreateIndexStream, createDeleteIndexStream, createGenerateIndexRecordsStream, - deleteKibanaIndices, - migrateKibanaIndex, - cleanKibanaIndices, + deleteSavedObjectIndices, + migrateSavedObjectIndices, + cleanSavedObjectIndices, createDefaultSpace, } from './indices'; diff --git a/packages/kbn-es-archiver/src/lib/indices/create_index_stream.test.mock.ts b/packages/kbn-es-archiver/src/lib/indices/create_index_stream.test.mock.ts index d17bd33fa07ab..7c0041ca46221 100644 --- a/packages/kbn-es-archiver/src/lib/indices/create_index_stream.test.mock.ts +++ b/packages/kbn-es-archiver/src/lib/indices/create_index_stream.test.mock.ts @@ -6,10 +6,12 @@ * Side Public License, v 1. */ -import type { deleteKibanaIndices } from './kibana_index'; +import type { deleteSavedObjectIndices } from './kibana_index'; -export const mockDeleteKibanaIndices = jest.fn() as jest.MockedFunction; +export const mockdeleteSavedObjectIndices = jest.fn() as jest.MockedFunction< + typeof deleteSavedObjectIndices +>; jest.mock('./kibana_index', () => ({ - deleteKibanaIndices: mockDeleteKibanaIndices, + deleteSavedObjectIndices: mockdeleteSavedObjectIndices, })); diff --git a/packages/kbn-es-archiver/src/lib/indices/create_index_stream.test.ts b/packages/kbn-es-archiver/src/lib/indices/create_index_stream.test.ts index 15efa53921743..9e1caf53eb06b 100644 --- a/packages/kbn-es-archiver/src/lib/indices/create_index_stream.test.ts +++ b/packages/kbn-es-archiver/src/lib/indices/create_index_stream.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { mockDeleteKibanaIndices } from './create_index_stream.test.mock'; +import { mockdeleteSavedObjectIndices } from './create_index_stream.test.mock'; import sinon from 'sinon'; import Chance from 'chance'; @@ -28,7 +28,7 @@ const chance = new Chance(); const log = createStubLogger(); beforeEach(() => { - mockDeleteKibanaIndices.mockClear(); + mockdeleteSavedObjectIndices.mockClear(); }); describe('esArchiver: createCreateIndexStream()', () => { @@ -187,7 +187,7 @@ describe('esArchiver: createCreateIndexStream()', () => { }); }); - describe('deleteKibanaIndices', () => { + describe('deleteSavedObjectIndices', () => { function doTest(...indices: string[]) { return createPromiseFromStreams([ createListStream(indices.map((index) => createStubIndexRecord(index))), @@ -199,15 +199,15 @@ describe('esArchiver: createCreateIndexStream()', () => { it('does not delete Kibana indices for indexes that do not start with .kibana', async () => { await doTest('.foo'); - expect(mockDeleteKibanaIndices).not.toHaveBeenCalled(); + expect(mockdeleteSavedObjectIndices).not.toHaveBeenCalled(); }); it('deletes Kibana indices at most once for indices that start with .kibana', async () => { // If we are loading the main Kibana index, we should delete all Kibana indices for backwards compatibility reasons. await doTest('.kibana_7.16.0_001', '.kibana_task_manager_7.16.0_001'); - expect(mockDeleteKibanaIndices).toHaveBeenCalledTimes(1); - expect(mockDeleteKibanaIndices).toHaveBeenCalledWith( + expect(mockdeleteSavedObjectIndices).toHaveBeenCalledTimes(1); + expect(mockdeleteSavedObjectIndices).toHaveBeenCalledWith( expect.not.objectContaining({ onlyTaskManager: true }) ); }); @@ -216,8 +216,8 @@ describe('esArchiver: createCreateIndexStream()', () => { // If we are loading the Kibana task manager index, we should only delete that index, not any other Kibana indices. await doTest('.kibana_task_manager_7.16.0_001', '.kibana_task_manager_7.16.0_002'); - expect(mockDeleteKibanaIndices).toHaveBeenCalledTimes(1); - expect(mockDeleteKibanaIndices).toHaveBeenCalledWith( + expect(mockdeleteSavedObjectIndices).toHaveBeenCalledTimes(1); + expect(mockdeleteSavedObjectIndices).toHaveBeenCalledWith( expect.objectContaining({ onlyTaskManager: true }) ); }); @@ -227,12 +227,12 @@ describe('esArchiver: createCreateIndexStream()', () => { // So, we first delete only the Kibana task manager indices, then we wind up deleting all Kibana indices. await doTest('.kibana_task_manager_7.16.0_001', '.kibana_7.16.0_001'); - expect(mockDeleteKibanaIndices).toHaveBeenCalledTimes(2); - expect(mockDeleteKibanaIndices).toHaveBeenNthCalledWith( + expect(mockdeleteSavedObjectIndices).toHaveBeenCalledTimes(2); + expect(mockdeleteSavedObjectIndices).toHaveBeenNthCalledWith( 1, expect.objectContaining({ onlyTaskManager: true }) ); - expect(mockDeleteKibanaIndices).toHaveBeenNthCalledWith( + expect(mockdeleteSavedObjectIndices).toHaveBeenNthCalledWith( 2, expect.not.objectContaining({ onlyTaskManager: true }) ); diff --git a/packages/kbn-es-archiver/src/lib/indices/create_index_stream.ts b/packages/kbn-es-archiver/src/lib/indices/create_index_stream.ts index 38f4bed755262..5b6245407f23f 100644 --- a/packages/kbn-es-archiver/src/lib/indices/create_index_stream.ts +++ b/packages/kbn-es-archiver/src/lib/indices/create_index_stream.ts @@ -14,8 +14,12 @@ import type { Client } from '@elastic/elasticsearch'; import { ToolingLog } from '@kbn/tooling-log'; import { IndicesPutIndexTemplateRequest } from '@elastic/elasticsearch/lib/api/types'; +import { + MAIN_SAVED_OBJECT_INDEX, + TASK_MANAGER_SAVED_OBJECT_INDEX, +} from '@kbn/core-saved-objects-server'; import { Stats } from '../stats'; -import { deleteKibanaIndices } from './kibana_index'; +import { deleteSavedObjectIndices } from './kibana_index'; import { deleteIndex } from './delete_index'; import { deleteDataStream } from './delete_data_stream'; import { ES_CLIENT_HEADERS } from '../../client_headers'; @@ -96,8 +100,8 @@ export function createCreateIndexStream({ async function handleIndex(record: DocRecord) { const { index, settings, mappings, aliases } = record.value; - const isKibanaTaskManager = index.startsWith('.kibana_task_manager'); - const isKibana = index.startsWith('.kibana') && !isKibanaTaskManager; + const isKibanaTaskManager = index.startsWith(TASK_MANAGER_SAVED_OBJECT_INDEX); + const isKibana = index.startsWith(MAIN_SAVED_OBJECT_INDEX) && !isKibanaTaskManager; if (docsOnly) { return; @@ -106,10 +110,10 @@ export function createCreateIndexStream({ async function attemptToCreate(attemptNumber = 1) { try { if (isKibana && !kibanaIndexAlreadyDeleted) { - await deleteKibanaIndices({ client, stats, log }); // delete all .kibana* indices + await deleteSavedObjectIndices({ client, stats, log }); // delete all .kibana* indices kibanaIndexAlreadyDeleted = kibanaTaskManagerIndexAlreadyDeleted = true; } else if (isKibanaTaskManager && !kibanaTaskManagerIndexAlreadyDeleted) { - await deleteKibanaIndices({ client, stats, onlyTaskManager: true, log }); // delete only .kibana_task_manager* indices + await deleteSavedObjectIndices({ client, stats, onlyTaskManager: true, log }); // delete only .kibana_task_manager* indices kibanaTaskManagerIndexAlreadyDeleted = true; } diff --git a/packages/kbn-es-archiver/src/lib/indices/delete_index_stream.ts b/packages/kbn-es-archiver/src/lib/indices/delete_index_stream.ts index c7633465ccc4c..f9b04b447f124 100644 --- a/packages/kbn-es-archiver/src/lib/indices/delete_index_stream.ts +++ b/packages/kbn-es-archiver/src/lib/indices/delete_index_stream.ts @@ -10,9 +10,10 @@ import { Transform } from 'stream'; import type { Client } from '@elastic/elasticsearch'; import { ToolingLog } from '@kbn/tooling-log'; +import { MAIN_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { Stats } from '../stats'; import { deleteIndex } from './delete_index'; -import { cleanKibanaIndices } from './kibana_index'; +import { cleanSavedObjectIndices } from './kibana_index'; import { deleteDataStream } from './delete_data_stream'; export function createDeleteIndexStream(client: Client, stats: Stats, log: ToolingLog) { @@ -28,8 +29,8 @@ export function createDeleteIndexStream(client: Client, stats: Stats, log: Tooli if (record.type === 'index') { const { index } = record.value; - if (index.startsWith('.kibana')) { - await cleanKibanaIndices({ client, stats, log }); + if (index.startsWith(MAIN_SAVED_OBJECT_INDEX)) { + await cleanSavedObjectIndices({ client, stats, log }); } else { await deleteIndex({ client, stats, log, index }); } diff --git a/packages/kbn-es-archiver/src/lib/indices/generate_index_records_stream.ts b/packages/kbn-es-archiver/src/lib/indices/generate_index_records_stream.ts index de32e93e27398..2f2dd60982a94 100644 --- a/packages/kbn-es-archiver/src/lib/indices/generate_index_records_stream.ts +++ b/packages/kbn-es-archiver/src/lib/indices/generate_index_records_stream.ts @@ -9,6 +9,7 @@ import type { Client } from '@elastic/elasticsearch'; import { Transform } from 'stream'; import { ToolingLog } from '@kbn/tooling-log'; +import { MAIN_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { Stats } from '../stats'; import { ES_CLIENT_HEADERS } from '../../client_headers'; import { getIndexTemplate } from '..'; @@ -100,7 +101,10 @@ export function createGenerateIndexRecordsStream({ value: { // if keepIndexNames is false, rewrite the .kibana_* index to .kibana_1 so that // when it is loaded it can skip migration, if possible - index: index.startsWith('.kibana') && !keepIndexNames ? '.kibana_1' : index, + index: + index.startsWith(MAIN_SAVED_OBJECT_INDEX) && !keepIndexNames + ? `${MAIN_SAVED_OBJECT_INDEX}_1` + : index, settings, mappings, aliases, diff --git a/packages/kbn-es-archiver/src/lib/indices/index.ts b/packages/kbn-es-archiver/src/lib/indices/index.ts index 0816ec31d091d..32abb365f1076 100644 --- a/packages/kbn-es-archiver/src/lib/indices/index.ts +++ b/packages/kbn-es-archiver/src/lib/indices/index.ts @@ -10,8 +10,8 @@ export { createCreateIndexStream } from './create_index_stream'; export { createDeleteIndexStream } from './delete_index_stream'; export { createGenerateIndexRecordsStream } from './generate_index_records_stream'; export { - migrateKibanaIndex, - deleteKibanaIndices, - cleanKibanaIndices, + migrateSavedObjectIndices, + deleteSavedObjectIndices, + cleanSavedObjectIndices, createDefaultSpace, } from './kibana_index'; diff --git a/packages/kbn-es-archiver/src/lib/indices/kibana_index.ts b/packages/kbn-es-archiver/src/lib/indices/kibana_index.ts index 6a02113bbf733..d45f9e67737a2 100644 --- a/packages/kbn-es-archiver/src/lib/indices/kibana_index.ts +++ b/packages/kbn-es-archiver/src/lib/indices/kibana_index.ts @@ -11,14 +11,19 @@ import { inspect } from 'util'; import type { Client } from '@elastic/elasticsearch'; import { ToolingLog } from '@kbn/tooling-log'; import { KbnClient } from '@kbn/test'; +import { + MAIN_SAVED_OBJECT_INDEX, + ALL_SAVED_OBJECT_INDICES, + TASK_MANAGER_SAVED_OBJECT_INDEX, +} from '@kbn/core-saved-objects-server'; import { Stats } from '../stats'; import { deleteIndex } from './delete_index'; import { ES_CLIENT_HEADERS } from '../../client_headers'; /** - * Deletes all indices that start with `.kibana`, or if onlyTaskManager==true, all indices that start with `.kibana_task_manager` + * Deletes all saved object indices, or if onlyTaskManager==true, it deletes task_manager indices */ -export async function deleteKibanaIndices({ +export async function deleteSavedObjectIndices({ client, stats, onlyTaskManager = false, @@ -29,8 +34,9 @@ export async function deleteKibanaIndices({ onlyTaskManager?: boolean; log: ToolingLog; }) { - const indexPattern = onlyTaskManager ? '.kibana_task_manager*' : '.kibana*'; - const indexNames = await fetchKibanaIndices(client, indexPattern); + const indexNames = (await fetchSavedObjectIndices(client)).filter( + (indexName) => !onlyTaskManager || indexName.includes(TASK_MANAGER_SAVED_OBJECT_INDEX) + ); if (!indexNames.length) { return; } @@ -60,27 +66,33 @@ export async function deleteKibanaIndices({ * builds up an object that implements just enough of the kbnMigrations interface * as is required by migrations. */ -export async function migrateKibanaIndex(kbnClient: KbnClient) { +export async function migrateSavedObjectIndices(kbnClient: KbnClient) { await kbnClient.savedObjects.migrate(); } /** - * Migrations mean that the Kibana index will look something like: - * .kibana, .kibana_1, .kibana_323, etc. This finds all indices starting - * with .kibana, then filters out any that aren't actually Kibana's core - * index (e.g. we don't want to remove .kibana_task_manager or the like). + * Check if the given index is a Kibana saved object index. + * This includes most .kibana_* + * but we must make sure that indices such as '.kibana_security_session_1' are NOT deleted. + * + * IMPORTANT + * Note that we can have more than 2 system indices (different SO types can go to different indices) + * ATM we have '.kibana', '.kibana_task_manager', '.kibana_cases' + * This method also takes into account legacy indices: .kibana_1, .kibana_task_manager_1. + * @param [index] the name of the index to check + * @returns boolean 'true' if the index is a Kibana saved object index. */ -function isKibanaIndex(index?: string): index is string { - return Boolean( - index && - (/^\.kibana(:?_\d*)?$/.test(index) || - /^\.kibana(_task_manager)?_(pre)?\d+\.\d+\.\d+/.test(index)) - ); + +const LEGACY_INDICES_REGEXP = new RegExp(`^(${ALL_SAVED_OBJECT_INDICES.join('|')})(:?_\\d*)?$`); +const INDICES_REGEXP = new RegExp(`^(${ALL_SAVED_OBJECT_INDICES.join('|')})_(pre)?\\d+.\\d+.\\d+`); + +function isSavedObjectIndex(index?: string): index is string { + return Boolean(index && (LEGACY_INDICES_REGEXP.test(index) || INDICES_REGEXP.test(index))); } -async function fetchKibanaIndices(client: Client, indexPattern: string) { +async function fetchSavedObjectIndices(client: Client) { const resp = await client.cat.indices( - { index: indexPattern, format: 'json' }, + { index: `${MAIN_SAVED_OBJECT_INDEX}*`, format: 'json' }, { headers: ES_CLIENT_HEADERS, } @@ -90,12 +102,12 @@ async function fetchKibanaIndices(client: Client, indexPattern: string) { throw new Error(`expected response to be an array ${inspect(resp)}`); } - return resp.map((x: { index?: string }) => x.index).filter(isKibanaIndex); + return resp.map((x: { index?: string }) => x.index).filter(isSavedObjectIndex); } const delay = (delayInMs: number) => new Promise((resolve) => setTimeout(resolve, delayInMs)); -export async function cleanKibanaIndices({ +export async function cleanSavedObjectIndices({ client, stats, log, @@ -107,7 +119,7 @@ export async function cleanKibanaIndices({ while (true) { const resp = await client.deleteByQuery( { - index: `.kibana,.kibana_task_manager`, + index: ALL_SAVED_OBJECT_INDICES, body: { query: { bool: { @@ -144,7 +156,7 @@ export async function cleanKibanaIndices({ `.kibana rather than deleting the whole index` ); - stats.deletedIndex('.kibana'); + ALL_SAVED_OBJECT_INDICES.forEach((indexPattern) => stats.deletedIndex(indexPattern)); } export async function createDefaultSpace({ index, client }: { index: string; client: Client }) { diff --git a/packages/kbn-es-archiver/tsconfig.json b/packages/kbn-es-archiver/tsconfig.json index 0301480548fc7..15fccdf68be4f 100644 --- a/packages/kbn-es-archiver/tsconfig.json +++ b/packages/kbn-es-archiver/tsconfig.json @@ -11,6 +11,7 @@ "**/*.ts" ], "kbn_references": [ + "@kbn/core-saved-objects-server", "@kbn/dev-utils", "@kbn/test", "@kbn/tooling-log", diff --git a/packages/kbn-ftr-common-functional-services/services/kibana_server/extend_es_archiver.ts b/packages/kbn-ftr-common-functional-services/services/kibana_server/extend_es_archiver.ts index 98c28960bf523..4c2613d273c4a 100644 --- a/packages/kbn-ftr-common-functional-services/services/kibana_server/extend_es_archiver.ts +++ b/packages/kbn-ftr-common-functional-services/services/kibana_server/extend_es_archiver.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { MAIN_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import type { ProvidedType } from '@kbn/test'; import type { EsArchiverProvider } from '../es_archiver'; @@ -13,7 +14,6 @@ import type { RetryService } from '../retry'; import type { KibanaServerProvider } from './kibana_server'; const ES_ARCHIVER_LOAD_METHODS = ['load', 'loadIfNeeded', 'unload', 'emptyKibanaIndex'] as const; -const KIBANA_INDEX = '.kibana'; interface Options { esArchiver: ProvidedType; @@ -38,7 +38,7 @@ export function extendEsArchiver({ esArchiver, kibanaServer, retry, defaults }: const statsKeys = Object.keys(stats); const kibanaKeys = statsKeys.filter( // this also matches stats keys like '.kibana_1' and '.kibana_2,.kibana_1' - (key) => key.includes(KIBANA_INDEX) && stats[key].created + (key) => key.includes(MAIN_SAVED_OBJECT_INDEX) && stats[key].created ); // if the kibana index was created by the esArchiver then update the uiSettings diff --git a/packages/kbn-ftr-common-functional-services/tsconfig.json b/packages/kbn-ftr-common-functional-services/tsconfig.json index 639991bb2ce77..3641c807e4d6d 100644 --- a/packages/kbn-ftr-common-functional-services/tsconfig.json +++ b/packages/kbn-ftr-common-functional-services/tsconfig.json @@ -11,6 +11,7 @@ "**/*.ts", ], "kbn_references": [ + "@kbn/core-saved-objects-server", "@kbn/tooling-log", "@kbn/es-archiver", "@kbn/test" diff --git a/src/core/server/integration_tests/saved_objects/migrations/group1/7.7.2_xpack_100k.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group1/7.7.2_xpack_100k.test.ts index b8010eacbbae0..487938bbfde3a 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group1/7.7.2_xpack_100k.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group1/7.7.2_xpack_100k.test.ts @@ -8,9 +8,6 @@ import path from 'path'; import { unlink } from 'fs/promises'; -import { REPO_ROOT } from '@kbn/repo-info'; -import { Env } from '@kbn/config'; -import { getEnvOptions } from '@kbn/config-mocks'; import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import type { InternalCoreStart } from '@kbn/core-lifecycle-server-internal'; import { Root } from '@kbn/core-root-server-internal'; @@ -19,8 +16,8 @@ import { createRootWithCorePlugins, type TestElasticsearchUtils, } from '@kbn/core-test-helpers-kbn-server'; +import { ALL_SAVED_OBJECT_INDICES } from '@kbn/core-saved-objects-server'; -const kibanaVersion = Env.createDefault(REPO_ROOT, getEnvOptions()).packageInfo.version; const logFilePath = path.join(__dirname, '7.7.2_xpack_100k.log'); async function removeLogFile() { @@ -105,8 +102,6 @@ describe('migration from 7.7.2-xpack with 100k objects', () => { await new Promise((resolve) => setTimeout(resolve, 10000)); }; - const migratedIndex = `.kibana_${kibanaVersion}_001`; - beforeAll(async () => { await removeLogFile(); await startServers({ @@ -121,7 +116,7 @@ describe('migration from 7.7.2-xpack with 100k objects', () => { it('copies all the document of the previous index to the new one', async () => { const migratedIndexResponse = await esClient.count({ - index: migratedIndex, + index: ALL_SAVED_OBJECT_INDICES, }); const oldIndexResponse = await esClient.count({ index: '.kibana_1', diff --git a/src/core/server/integration_tests/saved_objects/migrations/group1/7_13_0_transform_failures.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group1/7_13_0_transform_failures.test.ts index 8a798508ce18f..6ef36d46b4a27 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group1/7_13_0_transform_failures.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group1/7_13_0_transform_failures.test.ts @@ -21,6 +21,11 @@ import { TestElasticsearchUtils, createTestServers as createkbnServerTestServers, } from '@kbn/core-test-helpers-kbn-server'; +import { + MAIN_SAVED_OBJECT_INDEX, + TASK_MANAGER_SAVED_OBJECT_INDEX, + ANALYTICS_SAVED_OBJECT_INDEX, +} from '@kbn/core-saved-objects-server'; const migrationDocLink = getMigrationDocLink().resolveMigrationFailures; const logFilePath = Path.join(__dirname, '7_13_corrupt_transform_failures.log'); @@ -114,18 +119,33 @@ describe('migration v2', () => { const esClient: ElasticsearchClient = esServer.es.getClient(); const docs = await esClient.search({ - index: '.kibana', + index: [ + MAIN_SAVED_OBJECT_INDEX, + TASK_MANAGER_SAVED_OBJECT_INDEX, + ANALYTICS_SAVED_OBJECT_INDEX, + ], _source: false, fields: ['_id'], size: 50, }); - // 23 saved objects + 14 corrupt (discarded) = 37 total in the old index - expect((docs.hits.total as SearchTotalHits).value).toEqual(23); + // 34 saved objects (11 tasks + 23 misc) + 14 corrupt (discarded) = 48 total in the old indices + expect((docs.hits.total as SearchTotalHits).value).toEqual(34); expect(docs.hits.hits.map(({ _id }) => _id).sort()).toEqual([ 'config:7.13.0', 'index-pattern:logs-*', 'index-pattern:metrics-*', + 'task:Actions-actions_telemetry', + 'task:Actions-cleanup_failed_action_executions', + 'task:Alerting-alerting_health_check', + 'task:Alerting-alerting_telemetry', + 'task:Alerts-alerts_invalidate_api_keys', + 'task:Lens-lens_telemetry', + 'task:apm-telemetry-task', + 'task:data_enhanced_search_sessions_monitor', + 'task:endpoint:user-artifact-packager:1.0.0', + 'task:security:endpoint-diagnostics:1.0.0', + 'task:session_cleanup', 'ui-metric:console:DELETE_delete', 'ui-metric:console:GET_get', 'ui-metric:console:GET_search', diff --git a/src/core/server/integration_tests/saved_objects/migrations/group2/batch_size_bytes.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group2/batch_size_bytes.test.ts index 7f468e11bae4a..4a64b26e1125a 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group2/batch_size_bytes.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group2/batch_size_bytes.test.ts @@ -118,7 +118,7 @@ describe('migration v2', () => { await root.preboot(); await root.setup(); await expect(root.start()).rejects.toMatchInlineSnapshot( - `[Error: Unable to complete saved object migrations for the [.kibana] index: The document with _id "canvas-workpad-template:workpad-template-061d7868-2b4e-4dc8-8bf7-3772b52926e5" is 1715264 bytes which exceeds the configured maximum batch size of 1015275 bytes. To proceed, please increase the 'migrations.maxBatchSizeBytes' Kibana configuration option and ensure that the Elasticsearch 'http.max_content_length' configuration option is set to an equal or larger value.]` + `[Error: Unable to complete saved object migrations for the [.kibana] index: The document with _id "canvas-workpad-template:workpad-template-061d7868-2b4e-4dc8-8bf7-3772b52926e5" is 1715312 bytes which exceeds the configured maximum batch size of 1015275 bytes. To proceed, please increase the 'migrations.maxBatchSizeBytes' Kibana configuration option and ensure that the Elasticsearch 'http.max_content_length' configuration option is set to an equal or larger value.]` ); await retryAsync( @@ -131,7 +131,7 @@ describe('migration v2', () => { expect( records.find((rec) => rec.message.startsWith( - `Unable to complete saved object migrations for the [.kibana] index: The document with _id "canvas-workpad-template:workpad-template-061d7868-2b4e-4dc8-8bf7-3772b52926e5" is 1715264 bytes which exceeds the configured maximum batch size of 1015275 bytes. To proceed, please increase the 'migrations.maxBatchSizeBytes' Kibana configuration option and ensure that the Elasticsearch 'http.max_content_length' configuration option is set to an equal or larger value.` + `Unable to complete saved object migrations for the [.kibana] index: The document with _id "canvas-workpad-template:workpad-template-061d7868-2b4e-4dc8-8bf7-3772b52926e5" is 1715312 bytes which exceeds the configured maximum batch size of 1015275 bytes. To proceed, please increase the 'migrations.maxBatchSizeBytes' Kibana configuration option and ensure that the Elasticsearch 'http.max_content_length' configuration option is set to an equal or larger value.` ) ) ).toBeDefined(); diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions.test.ts index e51e0ef12a89f..057498745ca43 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions.test.ts @@ -93,7 +93,7 @@ describe('migration actions', () => { await bulkOverwriteTransformedDocuments({ client, index: 'existing_index_with_docs', - operations: docs.map(createBulkIndexOperationTuple), + operations: docs.map((doc) => createBulkIndexOperationTuple(doc)), refresh: 'wait_for', })(); @@ -106,7 +106,7 @@ describe('migration actions', () => { await bulkOverwriteTransformedDocuments({ client, index: 'existing_index_with_write_block', - operations: docs.map(createBulkIndexOperationTuple), + operations: docs.map((doc) => createBulkIndexOperationTuple(doc)), refresh: 'wait_for', })(); await setWriteBlock({ client, index: 'existing_index_with_write_block' })(); @@ -307,7 +307,7 @@ describe('migration actions', () => { const res = (await bulkOverwriteTransformedDocuments({ client, index: 'new_index_without_write_block', - operations: sourceDocs.map(createBulkIndexOperationTuple), + operations: sourceDocs.map((doc) => createBulkIndexOperationTuple(doc)), refresh: 'wait_for', })()) as Either.Left; @@ -887,7 +887,7 @@ describe('migration actions', () => { await bulkOverwriteTransformedDocuments({ client, index: 'reindex_target_4', - operations: sourceDocs.map(createBulkIndexOperationTuple), + operations: sourceDocs.map((doc) => createBulkIndexOperationTuple(doc)), refresh: 'wait_for', })(); @@ -1445,7 +1445,7 @@ describe('migration actions', () => { await bulkOverwriteTransformedDocuments({ client, index: 'existing_index_without_mappings', - operations: sourceDocs.map(createBulkIndexOperationTuple), + operations: sourceDocs.map((doc) => createBulkIndexOperationTuple(doc)), refresh: 'wait_for', })(); @@ -1895,7 +1895,7 @@ describe('migration actions', () => { const task = bulkOverwriteTransformedDocuments({ client, index: 'existing_index_with_docs', - operations: newDocs.map(createBulkIndexOperationTuple), + operations: newDocs.map((doc) => createBulkIndexOperationTuple(doc)), refresh: 'wait_for', }); @@ -1921,7 +1921,7 @@ describe('migration actions', () => { operations: [ ...existingDocs, { _source: { title: 'doc 8' } } as unknown as SavedObjectsRawDoc, - ].map(createBulkIndexOperationTuple), + ].map((doc) => createBulkIndexOperationTuple(doc)), refresh: 'wait_for', }); await expect(task()).resolves.toMatchInlineSnapshot(` @@ -1941,7 +1941,7 @@ describe('migration actions', () => { bulkOverwriteTransformedDocuments({ client, index: 'existing_index_with_write_block', - operations: newDocs.map(createBulkIndexOperationTuple), + operations: newDocs.map((doc) => createBulkIndexOperationTuple(doc)), refresh: 'wait_for', })() ).resolves.toMatchInlineSnapshot(` @@ -1964,7 +1964,7 @@ describe('migration actions', () => { const task = bulkOverwriteTransformedDocuments({ client, index: 'existing_index_with_docs', - operations: newDocs.map(createBulkIndexOperationTuple), + operations: newDocs.map((doc) => createBulkIndexOperationTuple(doc)), }); await expect(task()).resolves.toMatchInlineSnapshot(` Object { diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/split_kibana_index.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/split_kibana_index.test.ts new file mode 100644 index 0000000000000..8c5f78a574db1 --- /dev/null +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/split_kibana_index.test.ts @@ -0,0 +1,386 @@ +/* + * Copyright 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 Path from 'path'; +import type { TestElasticsearchUtils } from '@kbn/core-test-helpers-kbn-server'; +import { + type ISavedObjectTypeRegistry, + type SavedObjectsType, + MAIN_SAVED_OBJECT_INDEX, +} from '@kbn/core-saved-objects-server'; +import { + readLog, + startElasticsearch, + getKibanaMigratorTestKit, + getCurrentVersionTypeRegistry, + overrideTypeRegistry, + clearLog, + getAggregatedTypesCount, + currentVersion, + type KibanaMigratorTestKit, +} from '../kibana_migrator_test_kit'; +import { delay } from '../test_utils'; + +// define a type => index distribution +const RELOCATE_TYPES: Record = { + dashboard: '.kibana_so_ui', + visualization: '.kibana_so_ui', + 'canvas-workpad': '.kibana_so_ui', + search: '.kibana_so_search', + task: '.kibana_task_manager', + // the remaining types will be forced to go to '.kibana', + // overriding `indexPattern: foo` defined in the registry +}; + +describe('split .kibana index into multiple system indices', () => { + let esServer: TestElasticsearchUtils['es']; + let typeRegistry: ISavedObjectTypeRegistry; + let migratorTestKitFactory: () => Promise; + + beforeAll(async () => { + typeRegistry = await getCurrentVersionTypeRegistry({ oss: false }); + + esServer = await startElasticsearch({ + dataArchive: Path.join(__dirname, '..', 'archives', '7.3.0_xpack_sample_saved_objects.zip'), + }); + }); + + beforeEach(async () => { + await clearLog(); + }); + + describe('when migrating from a legacy version', () => { + it('performs v1 migration and then relocates saved objects into different indices, depending on their types', async () => { + const updatedTypeRegistry = overrideTypeRegistry( + typeRegistry, + (type: SavedObjectsType) => { + return { + ...type, + indexPattern: RELOCATE_TYPES[type.name] ?? MAIN_SAVED_OBJECT_INDEX, + }; + } + ); + + migratorTestKitFactory = () => + getKibanaMigratorTestKit({ + types: updatedTypeRegistry.getAllTypes(), + kibanaIndex: '.kibana', + }); + + const { runMigrations, client } = await migratorTestKitFactory(); + + // count of types in the legacy index + expect(await getAggregatedTypesCount(client, '.kibana_1')).toEqual({ + 'canvas-workpad': 3, + config: 1, + dashboard: 3, + 'index-pattern': 3, + map: 3, + 'maps-telemetry': 1, + 'sample-data-telemetry': 3, + search: 2, + telemetry: 1, + space: 1, + visualization: 39, + }); + + await runMigrations(); + + await client.indices.refresh({ + index: ['.kibana', '.kibana_so_search', '.kibana_so_ui'], + }); + + expect(await getAggregatedTypesCount(client, '.kibana')).toEqual({ + 'index-pattern': 3, + map: 3, + 'sample-data-telemetry': 3, + config: 1, + telemetry: 1, + space: 1, + }); + expect(await getAggregatedTypesCount(client, '.kibana_so_search')).toEqual({ + search: 2, + }); + expect(await getAggregatedTypesCount(client, '.kibana_so_ui')).toEqual({ + visualization: 39, + 'canvas-workpad': 3, + dashboard: 3, + }); + + const indicesInfo = await client.indices.get({ index: '.kibana*' }); + expect(indicesInfo).toEqual( + expect.objectContaining({ + '.kibana_8.8.0_001': { + aliases: { '.kibana': expect.any(Object), '.kibana_8.8.0': expect.any(Object) }, + mappings: { + dynamic: 'strict', + _meta: { + migrationMappingPropertyHashes: expect.any(Object), + indexTypesMap: expect.any(Object), + }, + properties: expect.any(Object), + }, + settings: { index: expect.any(Object) }, + }, + '.kibana_so_search_8.8.0_001': { + aliases: { + '.kibana_so_search': expect.any(Object), + '.kibana_so_search_8.8.0': expect.any(Object), + }, + mappings: { + dynamic: 'strict', + _meta: { + migrationMappingPropertyHashes: expect.any(Object), + indexTypesMap: expect.any(Object), + }, + properties: expect.any(Object), + }, + settings: { index: expect.any(Object) }, + }, + '.kibana_so_ui_8.8.0_001': { + aliases: { + '.kibana_so_ui': expect.any(Object), + '.kibana_so_ui_8.8.0': expect.any(Object), + }, + mappings: { + dynamic: 'strict', + _meta: { + migrationMappingPropertyHashes: expect.any(Object), + indexTypesMap: expect.any(Object), + }, + properties: expect.any(Object), + }, + settings: { index: expect.any(Object) }, + }, + }) + ); + + expect(indicesInfo[`.kibana_${currentVersion}_001`].mappings?._meta?.indexTypesMap) + .toMatchInlineSnapshot(` + Object { + ".kibana": Array [ + "action", + "action_task_params", + "alert", + "api_key_pending_invalidation", + "apm-indices", + "apm-server-schema", + "apm-service-group", + "apm-telemetry", + "app_search_telemetry", + "application_usage_daily", + "application_usage_totals", + "canvas-element", + "canvas-workpad-template", + "cases", + "cases-comments", + "cases-configure", + "cases-connector-mappings", + "cases-telemetry", + "cases-user-actions", + "config", + "config-global", + "connector_token", + "core-usage-stats", + "csp-rule-template", + "endpoint:user-artifact", + "endpoint:user-artifact-manifest", + "enterprise_search_telemetry", + "epm-packages", + "epm-packages-assets", + "event_loop_delays_daily", + "exception-list", + "exception-list-agnostic", + "file", + "file-upload-usage-collection-telemetry", + "fileShare", + "fleet-fleet-server-host", + "fleet-message-signing-keys", + "fleet-preconfiguration-deletion-record", + "fleet-proxy", + "graph-workspace", + "guided-onboarding-guide-state", + "guided-onboarding-plugin-state", + "index-pattern", + "infrastructure-monitoring-log-view", + "infrastructure-ui-source", + "ingest-agent-policies", + "ingest-download-sources", + "ingest-outputs", + "ingest-package-policies", + "ingest_manager_settings", + "inventory-view", + "kql-telemetry", + "legacy-url-alias", + "lens", + "lens-ui-telemetry", + "maintenance-window", + "map", + "metrics-explorer-view", + "ml-job", + "ml-module", + "ml-trained-model", + "monitoring-telemetry", + "osquery-manager-usage-metric", + "osquery-pack", + "osquery-pack-asset", + "osquery-saved-query", + "query", + "rules-settings", + "sample-data-telemetry", + "search-session", + "search-telemetry", + "security-rule", + "security-solution-signals-migration", + "siem-detection-engine-rule-actions", + "siem-ui-timeline", + "siem-ui-timeline-note", + "siem-ui-timeline-pinned-event", + "slo", + "space", + "spaces-usage-stats", + "synthetics-monitor", + "synthetics-param", + "synthetics-privates-locations", + "tag", + "telemetry", + "ui-metric", + "upgrade-assistant-ml-upgrade-operation", + "upgrade-assistant-reindex-operation", + "uptime-dynamic-settings", + "uptime-synthetics-api-key", + "url", + "usage-counters", + "workplace_search_telemetry", + ], + ".kibana_so_search": Array [ + "search", + ], + ".kibana_so_ui": Array [ + "canvas-workpad", + "dashboard", + "visualization", + ], + ".kibana_task_manager": Array [ + "task", + ], + } + `); + + const logs = await readLog(); + + // .kibana_task_manager index exists and has no aliases => LEGACY_* migration path + expect(logs).toMatch('[.kibana_task_manager] INIT -> LEGACY_SET_WRITE_BLOCK.'); + // .kibana_task_manager migrator is NOT involved in relocation, must not sync + expect(logs).not.toMatch('[.kibana_task_manager] READY_TO_REINDEX_SYNC'); + + // newer indices migrators did not exist, so they all have to reindex (create temp index + sync) + ['.kibana_so_ui', '.kibana_so_search'].forEach((newIndex) => { + expect(logs).toMatch(`[${newIndex}] INIT -> CREATE_REINDEX_TEMP.`); + expect(logs).toMatch(`[${newIndex}] CREATE_REINDEX_TEMP -> READY_TO_REINDEX_SYNC.`); + // no docs to reindex, as source index did NOT exist + expect(logs).toMatch(`[${newIndex}] READY_TO_REINDEX_SYNC -> DONE_REINDEXING_SYNC.`); + }); + + // the .kibana migrator is involved in a relocation, it must also reindex + expect(logs).toMatch('[.kibana] INIT -> WAIT_FOR_YELLOW_SOURCE.'); + expect(logs).toMatch('[.kibana] WAIT_FOR_YELLOW_SOURCE -> CHECK_UNKNOWN_DOCUMENTS.'); + expect(logs).toMatch('[.kibana] CHECK_UNKNOWN_DOCUMENTS -> SET_SOURCE_WRITE_BLOCK.'); + expect(logs).toMatch('[.kibana] SET_SOURCE_WRITE_BLOCK -> CALCULATE_EXCLUDE_FILTERS.'); + expect(logs).toMatch('[.kibana] CALCULATE_EXCLUDE_FILTERS -> CREATE_REINDEX_TEMP.'); + expect(logs).toMatch('[.kibana] CREATE_REINDEX_TEMP -> READY_TO_REINDEX_SYNC.'); + expect(logs).toMatch('[.kibana] READY_TO_REINDEX_SYNC -> REINDEX_SOURCE_TO_TEMP_OPEN_PIT.'); + expect(logs).toMatch( + '[.kibana] REINDEX_SOURCE_TO_TEMP_OPEN_PIT -> REINDEX_SOURCE_TO_TEMP_READ.' + ); + expect(logs).toMatch('[.kibana] Starting to process 59 documents.'); + expect(logs).toMatch( + '[.kibana] REINDEX_SOURCE_TO_TEMP_READ -> REINDEX_SOURCE_TO_TEMP_TRANSFORM.' + ); + expect(logs).toMatch( + '[.kibana] REINDEX_SOURCE_TO_TEMP_TRANSFORM -> REINDEX_SOURCE_TO_TEMP_INDEX_BULK.' + ); + expect(logs).toMatch('[.kibana_task_manager] LEGACY_REINDEX_WAIT_FOR_TASK -> LEGACY_DELETE.'); + expect(logs).toMatch( + '[.kibana] REINDEX_SOURCE_TO_TEMP_INDEX_BULK -> REINDEX_SOURCE_TO_TEMP_READ.' + ); + expect(logs).toMatch('[.kibana] Processed 59 documents out of 59.'); + expect(logs).toMatch( + '[.kibana] REINDEX_SOURCE_TO_TEMP_READ -> REINDEX_SOURCE_TO_TEMP_CLOSE_PIT.' + ); + expect(logs).toMatch('[.kibana] REINDEX_SOURCE_TO_TEMP_CLOSE_PIT -> DONE_REINDEXING_SYNC.'); + + // after .kibana migrator is done relocating documents + // the 3 migrators share the final part of the flow + [ + ['.kibana', 8], + ['.kibana_so_ui', 45], + ['.kibana_so_search', 2], + ].forEach(([index, docCount]) => { + expect(logs).toMatch(`[${index}] DONE_REINDEXING_SYNC -> SET_TEMP_WRITE_BLOCK.`); + expect(logs).toMatch(`[${index}] SET_TEMP_WRITE_BLOCK -> CLONE_TEMP_TO_TARGET.`); + + expect(logs).toMatch(`[${index}] CLONE_TEMP_TO_TARGET -> REFRESH_TARGET.`); + expect(logs).toMatch(`[${index}] REFRESH_TARGET -> OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT.`); + expect(logs).toMatch( + `[${index}] OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT -> OUTDATED_DOCUMENTS_SEARCH_READ.` + ); + expect(logs).toMatch(`[${index}] Starting to process ${docCount} documents.`); + expect(logs).toMatch( + `[${index}] OUTDATED_DOCUMENTS_SEARCH_READ -> OUTDATED_DOCUMENTS_TRANSFORM.` + ); + expect(logs).toMatch( + `[${index}] OUTDATED_DOCUMENTS_TRANSFORM -> TRANSFORMED_DOCUMENTS_BULK_INDEX.` + ); + expect(logs).toMatch( + `[${index}] OUTDATED_DOCUMENTS_SEARCH_READ -> OUTDATED_DOCUMENTS_SEARCH_CLOSE_PIT.` + ); + expect(logs).toMatch( + `[${index}] OUTDATED_DOCUMENTS_SEARCH_CLOSE_PIT -> OUTDATED_DOCUMENTS_REFRESH.` + ); + expect(logs).toMatch(`[${index}] OUTDATED_DOCUMENTS_REFRESH -> CHECK_TARGET_MAPPINGS.`); + expect(logs).toMatch( + `[${index}] CHECK_TARGET_MAPPINGS -> UPDATE_TARGET_MAPPINGS_PROPERTIES.` + ); + + expect(logs).toMatch( + `[${index}] UPDATE_TARGET_MAPPINGS_PROPERTIES -> UPDATE_TARGET_MAPPINGS_PROPERTIES_WAIT_FOR_TASK.` + ); + expect(logs).toMatch( + `[${index}] UPDATE_TARGET_MAPPINGS_PROPERTIES_WAIT_FOR_TASK -> UPDATE_TARGET_MAPPINGS_META.` + ); + expect(logs).toMatch( + `[${index}] UPDATE_TARGET_MAPPINGS_META -> CHECK_VERSION_INDEX_READY_ACTIONS.` + ); + expect(logs).toMatch( + `[${index}] CHECK_VERSION_INDEX_READY_ACTIONS -> MARK_VERSION_INDEX_READY.` + ); + + expect(logs).toMatch(`[${index}] MARK_VERSION_INDEX_READY -> DONE.`); + expect(logs).toMatch(`[${index}] Migration completed`); + }); + }); + }); + + afterEach(async () => { + // we run the migrator again to ensure that the next time state is loaded everything still works as expected + const { runMigrations } = await migratorTestKitFactory(); + await clearLog(); + await runMigrations(); + + const logs = await readLog(); + expect(logs).not.toMatch('REINDEX'); + expect(logs).not.toMatch('CREATE'); + expect(logs).not.toMatch('UPDATE_TARGET_MAPPINGS'); + }); + + afterAll(async () => { + await esServer?.stop(); + await delay(10); + }); +}); diff --git a/src/core/server/integration_tests/saved_objects/migrations/kibana_migrator_test_kit.ts b/src/core/server/integration_tests/saved_objects/migrations/kibana_migrator_test_kit.ts index 64da61be1f0d3..22e3fe218a495 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/kibana_migrator_test_kit.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/kibana_migrator_test_kit.ts @@ -36,7 +36,7 @@ import { type LoggingConfigType, LoggingSystem } from '@kbn/core-logging-server- import type { ISavedObjectTypeRegistry, SavedObjectsType } from '@kbn/core-saved-objects-server'; import { esTestConfig, kibanaServerTestUser } from '@kbn/test'; import type { LoggerFactory } from '@kbn/logging'; -import { createTestServers } from '@kbn/core-test-helpers-kbn-server'; +import { createRootWithCorePlugins, createTestServers } from '@kbn/core-test-helpers-kbn-server'; import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { registerServiceConfig } from '@kbn/core-root-server-internal'; import type { ISavedObjectsRepository } from '@kbn/core-saved-objects-api-server'; @@ -72,7 +72,7 @@ export interface KibanaMigratorTestKitParams { export interface KibanaMigratorTestKit { client: ElasticsearchClient; migrator: IKibanaMigrator; - runMigrations: (rerun?: boolean) => Promise; + runMigrations: () => Promise; typeRegistry: ISavedObjectTypeRegistry; savedObjectsRepository: ISavedObjectsRepository; } @@ -282,6 +282,42 @@ const getMigrator = async ( }); }; +export const getAggregatedTypesCount = async (client: ElasticsearchClient, index: string) => { + await client.indices.refresh(); + const response = await client.search({ + index, + _source: false, + aggs: { + typesAggregation: { + terms: { + // assign type __UNKNOWN__ to those documents that don't define one + missing: '__UNKNOWN__', + field: 'type', + size: 100, + }, + aggs: { + docs: { + top_hits: { + size: 10, + _source: { + excludes: ['*'], + }, + }, + }, + }, + }, + }, + }); + + return (response.aggregations!.typesAggregation.buckets as unknown as any).reduce( + (acc: any, current: any) => { + acc[current.key] = current.doc_count; + return acc; + }, + {} + ); +}; + const registerTypes = ( typeRegistry: SavedObjectTypeRegistry, types?: Array> @@ -390,6 +426,28 @@ export const getIncompatibleMappingsMigrator = async ({ }); }; +export const getCurrentVersionTypeRegistry = async ({ + oss, +}: { + oss: boolean; +}): Promise => { + const root = createRootWithCorePlugins({}, { oss }); + await root.preboot(); + const coreSetup = await root.setup(); + const typeRegistry = coreSetup.savedObjects.getTypeRegistry(); + root.shutdown(); // do not await for it, or we might block the tests + return typeRegistry; +}; + +export const overrideTypeRegistry = ( + typeRegistry: ISavedObjectTypeRegistry, + transform: (type: SavedObjectsType) => SavedObjectsType +): ISavedObjectTypeRegistry => { + const updatedTypeRegistry = new SavedObjectTypeRegistry(); + typeRegistry.getAllTypes().forEach((type) => updatedTypeRegistry.registerType(transform(type))); + return updatedTypeRegistry; +}; + export const readLog = async (logFilePath: string = defaultLogFilePath): Promise => { await delay(0.1); return await fs.readFile(logFilePath, 'utf-8'); diff --git a/src/core/server/integration_tests/saved_objects/service/lib/repository_with_proxy.test.ts b/src/core/server/integration_tests/saved_objects/service/lib/repository_with_proxy.test.ts index e38231a6e0ea4..255658d7fbe7a 100644 --- a/src/core/server/integration_tests/saved_objects/service/lib/repository_with_proxy.test.ts +++ b/src/core/server/integration_tests/saved_objects/service/lib/repository_with_proxy.test.ts @@ -9,7 +9,7 @@ import Hapi from '@hapi/hapi'; import h2o2 from '@hapi/h2o2'; import { URL } from 'url'; -import type { SavedObject } from '@kbn/core-saved-objects-server'; +import { SavedObject, ALL_SAVED_OBJECT_INDICES } from '@kbn/core-saved-objects-server'; import type { ISavedObjectsRepository } from '@kbn/core-saved-objects-api-server'; import type { InternalCoreSetup, InternalCoreStart } from '@kbn/core-lifecycle-server-internal'; import { Root } from '@kbn/core-root-server-internal'; @@ -18,6 +18,7 @@ import { createTestServers, type TestElasticsearchUtils, } from '@kbn/core-test-helpers-kbn-server'; +import { kibanaPackageJson as pkg } from '@kbn/repo-info'; import { declareGetRoute, declareDeleteRoute, @@ -30,6 +31,7 @@ import { declarePostUpdateByQueryRoute, declarePassthroughRoute, setProxyInterrupt, + allCombinationsPermutations, } from './repository_with_proxy_utils'; let esServer: TestElasticsearchUtils; @@ -98,17 +100,24 @@ describe('404s from proxies', () => { await hapiServer.register(h2o2); // register specific routes to modify the response and a catch-all to relay the request/response as-is - declareGetRoute(hapiServer, esHostname, esPort); - declareDeleteRoute(hapiServer, esHostname, esPort); - declarePostUpdateRoute(hapiServer, esHostname, esPort); + allCombinationsPermutations( + ALL_SAVED_OBJECT_INDICES.map((indexPattern) => `${indexPattern}_${pkg.version}`) + ) + .map((indices) => indices.join(',')) + .forEach((kbnIndexPath) => { + declareGetRoute(hapiServer, esHostname, esPort, kbnIndexPath); + declareDeleteRoute(hapiServer, esHostname, esPort, kbnIndexPath); + declarePostUpdateRoute(hapiServer, esHostname, esPort, kbnIndexPath); + + declareGetSearchRoute(hapiServer, esHostname, esPort, kbnIndexPath); + declarePostSearchRoute(hapiServer, esHostname, esPort, kbnIndexPath); + declarePostPitRoute(hapiServer, esHostname, esPort, kbnIndexPath); + declarePostUpdateByQueryRoute(hapiServer, esHostname, esPort, kbnIndexPath); + }); - declareGetSearchRoute(hapiServer, esHostname, esPort); - declarePostSearchRoute(hapiServer, esHostname, esPort); + // register index-agnostic routes declarePostBulkRoute(hapiServer, esHostname, esPort); declarePostMgetRoute(hapiServer, esHostname, esPort); - declarePostPitRoute(hapiServer, esHostname, esPort); - declarePostUpdateByQueryRoute(hapiServer, esHostname, esPort); - declarePassthroughRoute(hapiServer, esHostname, esPort); await hapiServer.start(); diff --git a/src/core/server/integration_tests/saved_objects/service/lib/repository_with_proxy_utils.ts b/src/core/server/integration_tests/saved_objects/service/lib/repository_with_proxy_utils.ts index 499d0d01d9de1..35b6b37b9c413 100644 --- a/src/core/server/integration_tests/saved_objects/service/lib/repository_with_proxy_utils.ts +++ b/src/core/server/integration_tests/saved_objects/service/lib/repository_with_proxy_utils.ts @@ -7,7 +7,6 @@ */ import Hapi from '@hapi/hapi'; import { IncomingMessage } from 'http'; -import { kibanaPackageJson as pkg } from '@kbn/repo-info'; // proxy setup const defaultProxyOptions = (hostname: string, port: string) => ({ @@ -52,10 +51,13 @@ const proxyOnResponseHandler = async (res: IncomingMessage, h: Hapi.ResponseTool .code(404); }; -const kbnIndex = `.kibana_${pkg.version}`; - // GET /.kibana_8.0.0/_doc/{type*} route (repository.get calls) -export const declareGetRoute = (hapiServer: Hapi.Server, hostname: string, port: string) => +export const declareGetRoute = ( + hapiServer: Hapi.Server, + hostname: string, + port: string, + kbnIndex: string +) => hapiServer.route({ method: 'GET', path: `/${kbnIndex}/_doc/{type*}`, @@ -70,7 +72,12 @@ export const declareGetRoute = (hapiServer: Hapi.Server, hostname: string, port: }, }); // DELETE /.kibana_8.0.0/_doc/{type*} route (repository.delete calls) -export const declareDeleteRoute = (hapiServer: Hapi.Server, hostname: string, port: string) => +export const declareDeleteRoute = ( + hapiServer: Hapi.Server, + hostname: string, + port: string, + kbnIndex: string +) => hapiServer.route({ method: 'DELETE', path: `/${kbnIndex}/_doc/{_id*}`, @@ -133,7 +140,12 @@ export const declarePostMgetRoute = (hapiServer: Hapi.Server, hostname: string, }, }); // GET _search route -export const declareGetSearchRoute = (hapiServer: Hapi.Server, hostname: string, port: string) => +export const declareGetSearchRoute = ( + hapiServer: Hapi.Server, + hostname: string, + port: string, + kbnIndex: string +) => hapiServer.route({ method: 'GET', path: `/${kbnIndex}/_search`, @@ -149,7 +161,12 @@ export const declareGetSearchRoute = (hapiServer: Hapi.Server, hostname: string, }, }); // POST _search route (`find` calls) -export const declarePostSearchRoute = (hapiServer: Hapi.Server, hostname: string, port: string) => +export const declarePostSearchRoute = ( + hapiServer: Hapi.Server, + hostname: string, + port: string, + kbnIndex: string +) => hapiServer.route({ method: 'POST', path: `/${kbnIndex}/_search`, @@ -168,7 +185,12 @@ export const declarePostSearchRoute = (hapiServer: Hapi.Server, hostname: string }, }); // POST _update -export const declarePostUpdateRoute = (hapiServer: Hapi.Server, hostname: string, port: string) => +export const declarePostUpdateRoute = ( + hapiServer: Hapi.Server, + hostname: string, + port: string, + kbnIndex: string +) => hapiServer.route({ method: 'POST', path: `/${kbnIndex}/_update/{_id*}`, @@ -187,7 +209,12 @@ export const declarePostUpdateRoute = (hapiServer: Hapi.Server, hostname: string }, }); // POST _pit -export const declarePostPitRoute = (hapiServer: Hapi.Server, hostname: string, port: string) => +export const declarePostPitRoute = ( + hapiServer: Hapi.Server, + hostname: string, + port: string, + kbnIndex: string +) => hapiServer.route({ method: 'POST', path: `/${kbnIndex}/_pit`, @@ -209,7 +236,8 @@ export const declarePostPitRoute = (hapiServer: Hapi.Server, hostname: string, p export const declarePostUpdateByQueryRoute = ( hapiServer: Hapi.Server, hostname: string, - port: string + port: string, + kbnIndex: string ) => hapiServer.route({ method: 'POST', @@ -244,3 +272,22 @@ export const declarePassthroughRoute = (hapiServer: Hapi.Server, hostname: strin }, }, }); + +export function allCombinationsPermutations(collection: T[]): T[][] { + const recur = (subcollection: T[], size: number): T[][] => { + if (size <= 0) { + return [[]]; + } + const permutations: T[][] = []; + subcollection.forEach((value, index, array) => { + array = array.slice(); + array.splice(index, 1); + recur(array, size - 1).forEach((permutation) => { + permutation.unshift(value); + permutations.push(permutation); + }); + }); + return permutations; + }; + return collection.map((_, n) => recur(collection, n + 1)).flat(); +} diff --git a/src/plugins/content_management/docs/content_onboarding.md b/src/plugins/content_management/docs/content_onboarding.md index 411c718945b03..d170d80f87981 100644 --- a/src/plugins/content_management/docs/content_onboarding.md +++ b/src/plugins/content_management/docs/content_onboarding.md @@ -239,7 +239,7 @@ import { cmServicesDefinition } from '../../common/content_management/cm_service * that we won't leak any additional fields in our Response, even when the SO client adds new fields to its responses. */ function savedObjectToMapItem( - savedObject: SavedObject + savedObject: SavedObject ): MapItem { const { id, @@ -293,7 +293,7 @@ export class MapsStorage implements ContentStorage(SO_TYPE, id); + } = await soClient.resolve(SO_TYPE, id); const response: MapGetOut = { item: savedObjectToMapItem(savedObject), @@ -327,8 +327,8 @@ export class MapsStorage implements ContentStorage(data); if (dataError) { throw Boom.badRequest(`Invalid payload. ${dataError.message}`); @@ -345,7 +345,7 @@ export class MapsStorage implements ContentStorage( + const savedObject = await soClient.create( SO_TYPE, dataToLatest, optionsToLatest diff --git a/src/plugins/dashboard/server/dashboard_saved_object/dashboard_saved_object.ts b/src/plugins/dashboard/server/dashboard_saved_object/dashboard_saved_object.ts index b8474149ca87b..9844d565ee36f 100644 --- a/src/plugins/dashboard/server/dashboard_saved_object/dashboard_saved_object.ts +++ b/src/plugins/dashboard/server/dashboard_saved_object/dashboard_saved_object.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { ANALYTICS_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { SavedObjectsType } from '@kbn/core/server'; import { createDashboardSavedObjectTypeMigrations, @@ -18,6 +19,7 @@ export const createDashboardSavedObjectType = ({ migrationDeps: DashboardSavedObjectTypeMigrationsDeps; }): SavedObjectsType => ({ name: 'dashboard', + indexPattern: ANALYTICS_SAVED_OBJECT_INDEX, hidden: false, namespaceType: 'multiple-isolated', convertToMultiNamespaceTypeVersion: '8.0.0', diff --git a/src/plugins/dashboard/server/usage/dashboard_telemetry_collection_task.ts b/src/plugins/dashboard/server/usage/dashboard_telemetry_collection_task.ts index 1ca13b4308586..bac38fdbb5c75 100644 --- a/src/plugins/dashboard/server/usage/dashboard_telemetry_collection_task.ts +++ b/src/plugins/dashboard/server/usage/dashboard_telemetry_collection_task.ts @@ -114,12 +114,14 @@ export function dashboardTaskRunner(logger: Logger, core: CoreSetup, embeddable: return dashboardData; }; - const kibanaIndex = core.savedObjects.getKibanaIndex(); + const dashboardIndex = await core + .getStartServices() + .then(([coreStart]) => coreStart.savedObjects.getIndexForType('dashboard')); const pageSize = 50; const searchParams = { size: pageSize, - index: kibanaIndex, + index: dashboardIndex, ignore_unavailable: true, filter_path: ['hits.hits', '_scroll_id'], body: { query: { bool: { filter: { term: { type: 'dashboard' } } } } }, diff --git a/src/plugins/dashboard/tsconfig.json b/src/plugins/dashboard/tsconfig.json index 4bfb899c5301d..ba94e006057ec 100644 --- a/src/plugins/dashboard/tsconfig.json +++ b/src/plugins/dashboard/tsconfig.json @@ -56,6 +56,7 @@ "@kbn/saved-objects-finder-plugin", "@kbn/saved-objects-management-plugin", "@kbn/shared-ux-button-toolbar", + "@kbn/core-saved-objects-server", ], "exclude": [ "target/**/*", diff --git a/src/plugins/data/server/kql_telemetry/kql_telemetry_service.ts b/src/plugins/data/server/kql_telemetry/kql_telemetry_service.ts index 6302af57166c4..35a285f2f5f30 100644 --- a/src/plugins/data/server/kql_telemetry/kql_telemetry_service.ts +++ b/src/plugins/data/server/kql_telemetry/kql_telemetry_service.ts @@ -28,7 +28,9 @@ export class KqlTelemetryService implements Plugin { if (usageCollection) { try { - makeKQLUsageCollector(usageCollection, savedObjects.getKibanaIndex()); + const getIndexForType = (type: string) => + getStartServices().then(([coreStart]) => coreStart.savedObjects.getIndexForType(type)); + makeKQLUsageCollector(usageCollection, getIndexForType); } catch (e) { this.initializerContext.logger .get('kql-telemetry') diff --git a/src/plugins/data/server/kql_telemetry/usage_collector/fetch.test.ts b/src/plugins/data/server/kql_telemetry/usage_collector/fetch.test.ts index 679b2e67158eb..91158343885ab 100644 --- a/src/plugins/data/server/kql_telemetry/usage_collector/fetch.test.ts +++ b/src/plugins/data/server/kql_telemetry/usage_collector/fetch.test.ts @@ -75,7 +75,7 @@ function setupMockCallCluster( describe('makeKQLUsageCollector', () => { describe('fetch method', () => { beforeEach(() => { - fetch = fetchProvider('.kibana'); + fetch = fetchProvider(() => Promise.resolve('.kibana')); }); it('should return opt in data from the .kibana/kql-telemetry doc', async () => { diff --git a/src/plugins/data/server/kql_telemetry/usage_collector/fetch.ts b/src/plugins/data/server/kql_telemetry/usage_collector/fetch.ts index 3f0d2129c6b92..90499f1e920bc 100644 --- a/src/plugins/data/server/kql_telemetry/usage_collector/fetch.ts +++ b/src/plugins/data/server/kql_telemetry/usage_collector/fetch.ts @@ -18,19 +18,19 @@ export interface Usage { defaultQueryLanguage: string; } -export function fetchProvider(index: string) { +export function fetchProvider(getIndexForType: (type: string) => Promise) { return async ({ esClient }: CollectorFetchContext): Promise => { const [response, config] = await Promise.all([ esClient.get( { - index, + index: await getIndexForType('kql-telemetry'), id: 'kql-telemetry:kql-telemetry', }, { ignore: [404] } ), esClient.search( { - index, + index: await getIndexForType('config'), body: { query: { term: { type: 'config' } } }, }, { ignore: [404] } diff --git a/src/plugins/data/server/kql_telemetry/usage_collector/make_kql_usage_collector.test.ts b/src/plugins/data/server/kql_telemetry/usage_collector/make_kql_usage_collector.test.ts index 129bf59ca80a9..a96346714c0f9 100644 --- a/src/plugins/data/server/kql_telemetry/usage_collector/make_kql_usage_collector.test.ts +++ b/src/plugins/data/server/kql_telemetry/usage_collector/make_kql_usage_collector.test.ts @@ -12,6 +12,8 @@ import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server'; describe('makeKQLUsageCollector', () => { let usageCollectionMock: jest.Mocked; + const getIndexForType = () => Promise.resolve('.kibana'); + beforeEach(() => { usageCollectionMock = { makeUsageCollector: jest.fn(), @@ -20,12 +22,12 @@ describe('makeKQLUsageCollector', () => { }); it('should call registerCollector', () => { - makeKQLUsageCollector(usageCollectionMock as UsageCollectionSetup, '.kibana'); + makeKQLUsageCollector(usageCollectionMock as UsageCollectionSetup, getIndexForType); expect(usageCollectionMock.registerCollector).toHaveBeenCalledTimes(1); }); it('should call makeUsageCollector with type = kql', () => { - makeKQLUsageCollector(usageCollectionMock as UsageCollectionSetup, '.kibana'); + makeKQLUsageCollector(usageCollectionMock as UsageCollectionSetup, getIndexForType); expect(usageCollectionMock.makeUsageCollector).toHaveBeenCalledTimes(1); expect(usageCollectionMock.makeUsageCollector.mock.calls[0][0].type).toBe('kql'); }); diff --git a/src/plugins/data/server/kql_telemetry/usage_collector/make_kql_usage_collector.ts b/src/plugins/data/server/kql_telemetry/usage_collector/make_kql_usage_collector.ts index 885c6b1807e92..6a9190a953fdd 100644 --- a/src/plugins/data/server/kql_telemetry/usage_collector/make_kql_usage_collector.ts +++ b/src/plugins/data/server/kql_telemetry/usage_collector/make_kql_usage_collector.ts @@ -9,10 +9,13 @@ import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server'; import { fetchProvider, Usage } from './fetch'; -export function makeKQLUsageCollector(usageCollection: UsageCollectionSetup, kibanaIndex: string) { +export function makeKQLUsageCollector( + usageCollection: UsageCollectionSetup, + getIndexForType: (type: string) => Promise +) { const kqlUsageCollector = usageCollection.makeUsageCollector({ type: 'kql', - fetch: fetchProvider(kibanaIndex), + fetch: fetchProvider(getIndexForType), isReady: () => true, schema: { optInCount: { type: 'long' }, diff --git a/src/plugins/data/server/saved_objects/kql_telemetry.ts b/src/plugins/data/server/saved_objects/kql_telemetry.ts index 51575ad2d3d54..15758d4ba5be9 100644 --- a/src/plugins/data/server/saved_objects/kql_telemetry.ts +++ b/src/plugins/data/server/saved_objects/kql_telemetry.ts @@ -6,11 +6,13 @@ * Side Public License, v 1. */ +import { ANALYTICS_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { SavedObjectsType } from '@kbn/core/server'; import { SCHEMA_KQL_TELEMETRY_V8_8_0 } from './schemas/kql_telemetry'; export const kqlTelemetry: SavedObjectsType = { name: 'kql-telemetry', + indexPattern: ANALYTICS_SAVED_OBJECT_INDEX, namespaceType: 'agnostic', hidden: false, mappings: { diff --git a/src/plugins/data/server/saved_objects/query.ts b/src/plugins/data/server/saved_objects/query.ts index cf75c28743d4f..f59adbbef8750 100644 --- a/src/plugins/data/server/saved_objects/query.ts +++ b/src/plugins/data/server/saved_objects/query.ts @@ -6,12 +6,14 @@ * Side Public License, v 1. */ +import { ANALYTICS_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { SavedObjectsType } from '@kbn/core/server'; import { savedQueryMigrations } from './migrations/query'; import { SCHEMA_QUERY_V8_8_0 } from './schemas/query'; export const querySavedObjectType: SavedObjectsType = { name: 'query', + indexPattern: ANALYTICS_SAVED_OBJECT_INDEX, hidden: false, namespaceType: 'multiple-isolated', convertToMultiNamespaceTypeVersion: '8.0.0', diff --git a/src/plugins/data/server/saved_objects/search_telemetry.ts b/src/plugins/data/server/saved_objects/search_telemetry.ts index 2f72712801648..581e719a9183c 100644 --- a/src/plugins/data/server/saved_objects/search_telemetry.ts +++ b/src/plugins/data/server/saved_objects/search_telemetry.ts @@ -6,12 +6,14 @@ * Side Public License, v 1. */ +import { ANALYTICS_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { SavedObjectsType } from '@kbn/core/server'; import { migrate712 } from './migrations/to_v7_12_0'; import { SCHEMA_SEARCH_TELEMETRY_V8_8_0 } from './schemas/search_telemetry'; export const searchTelemetry: SavedObjectsType = { name: 'search-telemetry', + indexPattern: ANALYTICS_SAVED_OBJECT_INDEX, namespaceType: 'agnostic', hidden: false, mappings: { diff --git a/src/plugins/data/server/search/collectors/search/fetch.ts b/src/plugins/data/server/search/collectors/search/fetch.ts index 08f365d0a6073..5fcecd1e60a65 100644 --- a/src/plugins/data/server/search/collectors/search/fetch.ts +++ b/src/plugins/data/server/search/collectors/search/fetch.ts @@ -13,11 +13,12 @@ interface SearchTelemetry { 'search-telemetry': CollectedUsage; } -export function fetchProvider(kibanaIndex: string) { +export function fetchProvider(getIndexForType: (type: string) => Promise) { return async ({ esClient }: CollectorFetchContext): Promise => { + const searchIndex = await getIndexForType('search-telemetry'); const esResponse = await esClient.search( { - index: kibanaIndex, + index: searchIndex, body: { query: { term: { type: { value: 'search-telemetry' } } }, }, diff --git a/src/plugins/data/server/search/collectors/search/register.ts b/src/plugins/data/server/search/collectors/search/register.ts index 927c5274e8128..f335e8c5de7c5 100644 --- a/src/plugins/data/server/search/collectors/search/register.ts +++ b/src/plugins/data/server/search/collectors/search/register.ts @@ -21,12 +21,15 @@ export interface ReportedUsage { averageDuration: number | null; } -export function registerUsageCollector(usageCollection: UsageCollectionSetup, kibanaIndex: string) { +export function registerUsageCollector( + usageCollection: UsageCollectionSetup, + getIndexForType: (type: string) => Promise +) { try { const collector = usageCollection.makeUsageCollector({ type: 'search', isReady: () => true, - fetch: fetchProvider(kibanaIndex), + fetch: fetchProvider(getIndexForType), schema: { successCount: { type: 'long' }, errorCount: { type: 'long' }, diff --git a/src/plugins/data/server/search/collectors/search_session/fetch.test.ts b/src/plugins/data/server/search/collectors/search_session/fetch.test.ts index 1290b06b94ee0..e0fc723de44d1 100644 --- a/src/plugins/data/server/search/collectors/search_session/fetch.test.ts +++ b/src/plugins/data/server/search/collectors/search_session/fetch.test.ts @@ -17,12 +17,13 @@ describe('fetchProvider', () => { beforeEach(async () => { const kibanaIndex = '123'; + const getIndexForType = () => Promise.resolve(kibanaIndex); mockLogger = { warn: jest.fn(), debug: jest.fn(), } as any; esClient = elasticsearchServiceMock.createElasticsearchClient(); - fetchFn = fetchProvider(kibanaIndex, mockLogger); + fetchFn = fetchProvider(getIndexForType, mockLogger); }); test('returns when ES returns no results', async () => { diff --git a/src/plugins/data/server/search/collectors/search_session/fetch.ts b/src/plugins/data/server/search/collectors/search_session/fetch.ts index 536ca4b168188..08660f4adb136 100644 --- a/src/plugins/data/server/search/collectors/search_session/fetch.ts +++ b/src/plugins/data/server/search/collectors/search_session/fetch.ts @@ -17,11 +17,12 @@ interface SessionPersistedTermsBucket { doc_count: number; } -export function fetchProvider(kibanaIndex: string, logger: Logger) { +export function fetchProvider(getIndexForType: (type: string) => Promise, logger: Logger) { return async ({ esClient }: CollectorFetchContext): Promise => { try { + const searchSessionIndex = await getIndexForType(SEARCH_SESSION_TYPE); const esResponse = await esClient.search({ - index: kibanaIndex, + index: searchSessionIndex, body: { size: 0, aggs: { diff --git a/src/plugins/data/server/search/collectors/search_session/register.ts b/src/plugins/data/server/search/collectors/search_session/register.ts index c6ea85a2b53d3..b30b1fb888797 100644 --- a/src/plugins/data/server/search/collectors/search_session/register.ts +++ b/src/plugins/data/server/search/collectors/search_session/register.ts @@ -18,14 +18,14 @@ export interface ReportedUsage { export function registerUsageCollector( usageCollection: UsageCollectionSetup, - kibanaIndex: string, + getIndexForType: (type: string) => Promise, logger: Logger ) { try { const collector = usageCollection.makeUsageCollector({ type: 'search-session', isReady: () => true, - fetch: fetchProvider(kibanaIndex, logger), + fetch: fetchProvider(getIndexForType, logger), schema: { transientCount: { type: 'long' }, persistedCount: { type: 'long' }, diff --git a/src/plugins/data/server/search/saved_objects/search_session.ts b/src/plugins/data/server/search/saved_objects/search_session.ts index 598229075df42..67f3acb819b98 100644 --- a/src/plugins/data/server/search/saved_objects/search_session.ts +++ b/src/plugins/data/server/search/saved_objects/search_session.ts @@ -7,12 +7,14 @@ */ import { schema } from '@kbn/config-schema'; +import { ANALYTICS_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { SavedObjectsType } from '@kbn/core/server'; import { SEARCH_SESSION_TYPE } from '../../../common'; import { searchSessionSavedObjectMigrations } from './search_session_migration'; export const searchSessionSavedObjectType: SavedObjectsType = { name: SEARCH_SESSION_TYPE, + indexPattern: ANALYTICS_SAVED_OBJECT_INDEX, namespaceType: 'single', hidden: true, mappings: { diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index eba7ea22acf4f..8189177c4b58b 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -213,12 +213,10 @@ export class SearchService implements Plugin { core.savedObjects.registerType(searchTelemetry); if (usageCollection) { - registerSearchUsageCollector(usageCollection, core.savedObjects.getKibanaIndex()); - registerSearchSessionUsageCollector( - usageCollection, - core.savedObjects.getKibanaIndex(), - this.logger - ); + const getIndexForType = (type: string) => + core.getStartServices().then(([coreStart]) => coreStart.savedObjects.getIndexForType(type)); + registerSearchUsageCollector(usageCollection, getIndexForType); + registerSearchSessionUsageCollector(usageCollection, getIndexForType, this.logger); } expressions.registerFunction(getEsaggs({ getStartServices: core.getStartServices })); diff --git a/src/plugins/data/tsconfig.json b/src/plugins/data/tsconfig.json index dc9b57dca02df..9d7fb6227448a 100644 --- a/src/plugins/data/tsconfig.json +++ b/src/plugins/data/tsconfig.json @@ -47,6 +47,7 @@ "@kbn/config", "@kbn/config-schema", "@kbn/core-application-browser", + "@kbn/core-saved-objects-server", ], "exclude": [ "target/**/*", diff --git a/src/plugins/data_views/server/saved_objects/data_views.ts b/src/plugins/data_views/server/saved_objects/data_views.ts index 4b8318b3d065f..2dbc4584e2aed 100644 --- a/src/plugins/data_views/server/saved_objects/data_views.ts +++ b/src/plugins/data_views/server/saved_objects/data_views.ts @@ -7,11 +7,13 @@ */ import type { SavedObjectsType } from '@kbn/core/server'; +import { ANALYTICS_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { indexPatternSavedObjectTypeMigrations } from './index_pattern_migrations'; import { DATA_VIEW_SAVED_OBJECT_TYPE } from '../../common'; export const dataViewSavedObjectType: SavedObjectsType = { name: DATA_VIEW_SAVED_OBJECT_TYPE, + indexPattern: ANALYTICS_SAVED_OBJECT_INDEX, hidden: false, namespaceType: 'multiple', convertToMultiNamespaceTypeVersion: '8.0.0', diff --git a/src/plugins/data_views/tsconfig.json b/src/plugins/data_views/tsconfig.json index 94fe86cb31321..dae61e82637c6 100644 --- a/src/plugins/data_views/tsconfig.json +++ b/src/plugins/data_views/tsconfig.json @@ -28,6 +28,7 @@ "@kbn/utility-types-jest", "@kbn/safer-lodash-set", "@kbn/core-http-server", + "@kbn/core-saved-objects-server", ], "exclude": [ "target/**/*", diff --git a/src/plugins/home/server/services/sample_data/sample_data_registry.ts b/src/plugins/home/server/services/sample_data/sample_data_registry.ts index a5cf89f65cb21..47ddc122f82cb 100644 --- a/src/plugins/home/server/services/sample_data/sample_data_registry.ts +++ b/src/plugins/home/server/services/sample_data/sample_data_registry.ts @@ -31,6 +31,7 @@ import { registerSampleDatasetWithIntegration } from './lib/register_with_integr export class SampleDataRegistry { constructor(private readonly initContext: PluginInitializerContext) {} + private readonly sampleDatasets: SampleDatasetSchema[] = []; private readonly appLinksMap = new Map(); @@ -68,8 +69,9 @@ export class SampleDataRegistry { isDevMode?: boolean ) { if (usageCollections) { - const kibanaIndex = core.savedObjects.getKibanaIndex(); - makeSampleDataUsageCollector(usageCollections, kibanaIndex); + const getIndexForType = (type: string) => + core.getStartServices().then(([coreStart]) => coreStart.savedObjects.getIndexForType(type)); + makeSampleDataUsageCollector(usageCollections, getIndexForType); } const usageTracker = usage( core.getStartServices().then(([coreStart]) => coreStart.savedObjects), @@ -176,6 +178,7 @@ export class SampleDataRegistry { return {}; } } + /** @public */ export type SampleDataRegistrySetup = ReturnType; diff --git a/src/plugins/home/server/services/sample_data/usage/collector.ts b/src/plugins/home/server/services/sample_data/usage/collector.ts index 5f32d4c79369c..716e630b55215 100644 --- a/src/plugins/home/server/services/sample_data/usage/collector.ts +++ b/src/plugins/home/server/services/sample_data/usage/collector.ts @@ -11,11 +11,11 @@ import { fetchProvider, TelemetryResponse } from './collector_fetch'; export function makeSampleDataUsageCollector( usageCollection: UsageCollectionSetup, - kibanaIndex: string + getIndexForType: (type: string) => Promise ) { const collector = usageCollection.makeUsageCollector({ type: 'sample-data', - fetch: fetchProvider(kibanaIndex), + fetch: fetchProvider(getIndexForType), isReady: () => true, schema: { installed: { type: 'array', items: { type: 'keyword' } }, diff --git a/src/plugins/home/server/services/sample_data/usage/collector_fetch.test.ts b/src/plugins/home/server/services/sample_data/usage/collector_fetch.test.ts index 54df964e4bdec..fcf49eb7f744d 100644 --- a/src/plugins/home/server/services/sample_data/usage/collector_fetch.test.ts +++ b/src/plugins/home/server/services/sample_data/usage/collector_fetch.test.ts @@ -19,8 +19,10 @@ const getMockFetchClients = (hits?: unknown[]) => { describe('Sample Data Fetch', () => { let collectorFetchContext: CollectorFetchContext; + const getIndexForType = (index: string) => (type: string) => Promise.resolve(index); + test('uninitialized .kibana', async () => { - const fetch = fetchProvider('index'); + const fetch = fetchProvider(getIndexForType('index')); collectorFetchContext = getMockFetchClients(); const telemetry = await fetch(collectorFetchContext); @@ -28,7 +30,7 @@ describe('Sample Data Fetch', () => { }); test('installed data set', async () => { - const fetch = fetchProvider('index'); + const fetch = fetchProvider(getIndexForType('index')); collectorFetchContext = getMockFetchClients([ { _id: 'sample-data-telemetry:test1', @@ -55,7 +57,7 @@ Object { }); test('multiple installed data sets', async () => { - const fetch = fetchProvider('index'); + const fetch = fetchProvider(getIndexForType('index')); collectorFetchContext = getMockFetchClients([ { _id: 'sample-data-telemetry:test1', @@ -90,7 +92,7 @@ Object { }); test('installed data set, missing counts', async () => { - const fetch = fetchProvider('index'); + const fetch = fetchProvider(getIndexForType('index')); collectorFetchContext = getMockFetchClients([ { _id: 'sample-data-telemetry:test1', @@ -112,7 +114,7 @@ Object { }); test('installed and uninstalled data sets', async () => { - const fetch = fetchProvider('index'); + const fetch = fetchProvider(getIndexForType('index')); collectorFetchContext = getMockFetchClients([ { _id: 'sample-data-telemetry:test0', diff --git a/src/plugins/home/server/services/sample_data/usage/collector_fetch.ts b/src/plugins/home/server/services/sample_data/usage/collector_fetch.ts index a5673fdc12a2f..28e753cb46b60 100644 --- a/src/plugins/home/server/services/sample_data/usage/collector_fetch.ts +++ b/src/plugins/home/server/services/sample_data/usage/collector_fetch.ts @@ -33,8 +33,9 @@ export interface TelemetryResponse { type ESResponse = SearchResponse; -export function fetchProvider(index: string) { +export function fetchProvider(getIndexForType: (type: string) => Promise) { return async ({ esClient }: CollectorFetchContext) => { + const index = await getIndexForType('sample-data-telemetry'); const response = await esClient.search( { index, diff --git a/src/plugins/kibana_usage_collection/server/collectors/saved_objects_counts/kibana_usage_collector.test.ts b/src/plugins/kibana_usage_collection/server/collectors/saved_objects_counts/kibana_usage_collector.test.ts index cdf2ca35d6ecc..7d6e600ded94b 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/saved_objects_counts/kibana_usage_collector.test.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/saved_objects_counts/kibana_usage_collector.test.ts @@ -27,8 +27,9 @@ describe('kibana_usage', () => { }); const kibanaIndex = '.kibana-tests'; + const getIndicesForTypes = () => Promise.resolve([kibanaIndex]); - beforeAll(() => registerKibanaUsageCollector(usageCollectionMock, kibanaIndex)); + beforeAll(() => registerKibanaUsageCollector(usageCollectionMock, getIndicesForTypes)); afterAll(() => jest.clearAllTimers()); afterEach(() => getSavedObjectsCountsMock.mockReset()); diff --git a/src/plugins/kibana_usage_collection/server/collectors/saved_objects_counts/kibana_usage_collector.ts b/src/plugins/kibana_usage_collection/server/collectors/saved_objects_counts/kibana_usage_collector.ts index 9a128888f05d5..8451b3eadeb00 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/saved_objects_counts/kibana_usage_collector.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/saved_objects_counts/kibana_usage_collector.ts @@ -43,7 +43,7 @@ export async function getKibanaSavedObjectCounts( export function registerKibanaUsageCollector( usageCollection: UsageCollectionSetup, - kibanaIndex: string + getIndicesForTypes: (types: string[]) => Promise ) { usageCollection.registerCollector( usageCollection.makeUsageCollector({ @@ -80,8 +80,9 @@ export function registerKibanaUsageCollector( }, }, async fetch({ soClient }) { + const indices = await getIndicesForTypes(['dashboard', 'visualization', 'search']); return { - index: kibanaIndex, + index: indices[0], ...(await getKibanaSavedObjectCounts(soClient)), }; }, diff --git a/src/plugins/kibana_usage_collection/server/plugin.ts b/src/plugins/kibana_usage_collection/server/plugin.ts index 8787d085c692e..5c807e01c0ce0 100644 --- a/src/plugins/kibana_usage_collection/server/plugin.ts +++ b/src/plugins/kibana_usage_collection/server/plugin.ts @@ -123,7 +123,6 @@ export class KibanaUsageCollectionPlugin implements Plugin { pluginStop$: Subject, registerType: SavedObjectsRegisterType ) { - const kibanaIndex = coreSetup.savedObjects.getKibanaIndex(); const getSavedObjectsClient = () => this.savedObjectsClient; const getUiSettingsClient = () => this.uiSettingsClient; const getCoreUsageDataService = () => this.coreUsageData!; @@ -138,7 +137,12 @@ export class KibanaUsageCollectionPlugin implements Plugin { registerUsageCountersUsageCollector(usageCollection); registerOpsStatsCollector(usageCollection, metric$); - registerKibanaUsageCollector(usageCollection, kibanaIndex); + + const getIndicesForTypes = (types: string[]) => + coreSetup + .getStartServices() + .then(([coreStart]) => coreStart.savedObjects.getIndicesForTypes(types)); + registerKibanaUsageCollector(usageCollection, getIndicesForTypes); const coreStartPromise = coreSetup.getStartServices().then(([coreStart]) => coreStart); const getAllSavedObjectTypes = async () => { diff --git a/src/plugins/saved_search/server/saved_objects/search.ts b/src/plugins/saved_search/server/saved_objects/search.ts index 6995442737b36..b9d41b48a60ea 100644 --- a/src/plugins/saved_search/server/saved_objects/search.ts +++ b/src/plugins/saved_search/server/saved_objects/search.ts @@ -7,6 +7,7 @@ */ import { schema } from '@kbn/config-schema'; +import { ANALYTICS_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { SavedObjectsType } from '@kbn/core/server'; import { MigrateFunctionsObject } from '@kbn/kibana-utils-plugin/common'; import { VIEW_MODE } from '../../common'; @@ -17,6 +18,7 @@ export function getSavedSearchObjectType( ): SavedObjectsType { return { name: 'search', + indexPattern: ANALYTICS_SAVED_OBJECT_INDEX, hidden: false, namespaceType: 'multiple-isolated', convertToMultiNamespaceTypeVersion: '8.0.0', diff --git a/src/plugins/saved_search/tsconfig.json b/src/plugins/saved_search/tsconfig.json index cf6225bf1f223..7ecf44063dac7 100644 --- a/src/plugins/saved_search/tsconfig.json +++ b/src/plugins/saved_search/tsconfig.json @@ -17,6 +17,7 @@ "@kbn/saved-objects-tagging-oss-plugin", "@kbn/i18n", "@kbn/config-schema", + "@kbn/core-saved-objects-server", ], "exclude": [ "target/**/*", diff --git a/src/plugins/usage_collection/server/plugin.ts b/src/plugins/usage_collection/server/plugin.ts index c8c44679cfa23..7139de5091b50 100644 --- a/src/plugins/usage_collection/server/plugin.ts +++ b/src/plugins/usage_collection/server/plugin.ts @@ -98,7 +98,7 @@ export class UsageCollectionPlugin implements Plugin { public setup(core: CoreSetup): UsageCollectionSetup { const config = this.initializerContext.config.get(); - const kibanaIndex = core.savedObjects.getKibanaIndex(); + const kibanaIndex = core.savedObjects.getDefaultIndex(); const collectorSet = new CollectorSet({ logger: this.logger.get('usage-collection', 'collector-set'), diff --git a/src/plugins/visualizations/server/saved_objects/visualization.ts b/src/plugins/visualizations/server/saved_objects/visualization.ts index 178eba2eacb3e..c7c3ae37ab0dd 100644 --- a/src/plugins/visualizations/server/saved_objects/visualization.ts +++ b/src/plugins/visualizations/server/saved_objects/visualization.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { ANALYTICS_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { SavedObjectsType } from '@kbn/core/server'; import { MigrateFunctionsObject } from '@kbn/kibana-utils-plugin/common'; import { getAllMigrations } from '../migrations/visualization_saved_object_migrations'; @@ -14,6 +15,7 @@ export const getVisualizationSavedObjectType = ( getSearchSourceMigrations: () => MigrateFunctionsObject ): SavedObjectsType => ({ name: 'visualization', + indexPattern: ANALYTICS_SAVED_OBJECT_INDEX, hidden: false, namespaceType: 'multiple-isolated', convertToMultiNamespaceTypeVersion: '8.0.0', diff --git a/src/plugins/visualizations/tsconfig.json b/src/plugins/visualizations/tsconfig.json index a15ec048cb784..340631d4b0582 100644 --- a/src/plugins/visualizations/tsconfig.json +++ b/src/plugins/visualizations/tsconfig.json @@ -54,6 +54,7 @@ "@kbn/shared-ux-router", "@kbn/saved-objects-management-plugin", "@kbn/saved-objects-finder-plugin", + "@kbn/core-saved-objects-server", ], "exclude": [ "target/**/*", diff --git a/test/api_integration/apis/kql_telemetry/kql_telemetry.ts b/test/api_integration/apis/kql_telemetry/kql_telemetry.ts index 310b99a5fb781..151a417c8aa6e 100644 --- a/test/api_integration/apis/kql_telemetry/kql_telemetry.ts +++ b/test/api_integration/apis/kql_telemetry/kql_telemetry.ts @@ -8,6 +8,7 @@ import expect from '@kbn/expect'; import { get } from 'lodash'; +import { ANALYTICS_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { @@ -27,7 +28,7 @@ export default function ({ getService }: FtrProviderContext) { ); }); - it('should increment the opt *in* counter in the .kibana/kql-telemetry document', async () => { + it('should increment the opt *in* counter in the .kibana_analytics/kql-telemetry document', async () => { await supertest .post('/api/kibana/kql_opt_in_stats') .set('content-type', 'application/json') @@ -36,7 +37,7 @@ export default function ({ getService }: FtrProviderContext) { return es .search({ - index: '.kibana', + index: ANALYTICS_SAVED_OBJECT_INDEX, q: 'type:kql-telemetry', }) .then((response) => { @@ -45,7 +46,7 @@ export default function ({ getService }: FtrProviderContext) { }); }); - it('should increment the opt *out* counter in the .kibana/kql-telemetry document', async () => { + it('should increment the opt *out* counter in the .kibana_analytics/kql-telemetry document', async () => { await supertest .post('/api/kibana/kql_opt_in_stats') .set('content-type', 'application/json') @@ -54,7 +55,7 @@ export default function ({ getService }: FtrProviderContext) { return es .search({ - index: '.kibana', + index: ANALYTICS_SAVED_OBJECT_INDEX, q: 'type:kql-telemetry', }) .then((response) => { diff --git a/test/api_integration/apis/saved_objects/delete_unknown_types.ts b/test/api_integration/apis/saved_objects/delete_unknown_types.ts index af9e413de0279..9a4b6fd517c5b 100644 --- a/test/api_integration/apis/saved_objects/delete_unknown_types.ts +++ b/test/api_integration/apis/saved_objects/delete_unknown_types.ts @@ -7,6 +7,10 @@ */ import expect from '@kbn/expect'; +import { + MAIN_SAVED_OBJECT_INDEX, + ANALYTICS_SAVED_OBJECT_INDEX, +} from '@kbn/core-saved-objects-server'; import { FtrProviderContext } from '../../ftr_provider_context'; const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); @@ -32,7 +36,7 @@ export default function ({ getService }: FtrProviderContext) { const fetchIndexContent = async () => { const body = await es.search<{ type: string }>({ - index: '.kibana', + index: [MAIN_SAVED_OBJECT_INDEX, ANALYTICS_SAVED_OBJECT_INDEX], body: { size: 100, }, diff --git a/test/api_integration/fixtures/es_archiver/saved_objects/delete_unknown_types/data.json b/test/api_integration/fixtures/es_archiver/saved_objects/delete_unknown_types/data.json index 3d6ecd160db00..067be99543a95 100644 --- a/test/api_integration/fixtures/es_archiver/saved_objects/delete_unknown_types/data.json +++ b/test/api_integration/fixtures/es_archiver/saved_objects/delete_unknown_types/data.json @@ -2,7 +2,7 @@ "type": "doc", "value": { "id": "index-pattern:8963ca30-3224-11e8-a572-ffca06da1357", - "index": ".kibana", + "index": ".kibana_analytics", "source": { "coreMigrationVersion": "7.14.0", "index-pattern": { @@ -25,7 +25,7 @@ "type": "doc", "value": { "id": "search:960372e0-3224-11e8-a572-ffca06da1357", - "index": ".kibana", + "index": ".kibana_analytics", "source": { "coreMigrationVersion": "7.14.0", "migrationVersion": { @@ -67,7 +67,7 @@ "type": "doc", "value": { "id": "visualization:a42c0580-3224-11e8-a572-ffca06da1357", - "index": ".kibana", + "index": ".kibana_analytics", "source": { "coreMigrationVersion": "7.14.0", "migrationVersion": { @@ -102,7 +102,7 @@ "type": "doc", "value": { "id": "dashboard:b70c7ae0-3224-11e8-a572-ffca06da1357", - "index": ".kibana", + "index": ".kibana_analytics", "source": { "coreMigrationVersion": "7.14.0", "dashboard": { diff --git a/test/api_integration/fixtures/es_archiver/saved_objects/delete_unknown_types/mappings.json b/test/api_integration/fixtures/es_archiver/saved_objects/delete_unknown_types/mappings.json index fb2337c15216c..4468d6849f6ca 100644 --- a/test/api_integration/fixtures/es_archiver/saved_objects/delete_unknown_types/mappings.json +++ b/test/api_integration/fixtures/es_archiver/saved_objects/delete_unknown_types/mappings.json @@ -37,6 +37,766 @@ "url": "c7f66a0df8b1b52f17c28c4adb111105", "usage-counters": "8cc260bdceffec4ffc3ad165c97dc1b4", "visualization": "f819cf6636b75c9e76ba733a0c6ef355" + }, + "indexTypesMap": { + ".kibana_task_manager": [ + "task" + ], + ".kibana": [ + "apm-indices", + "apm-server-schema", + "apm-service-group", + "apm-telemetry", + "app_search_telemetry", + "application_usage_daily", + "application_usage_totals", + "config", + "config-global", + "core-usage-stats", + "enterprise_search_telemetry", + "event_loop_delays_daily", + "file", + "file-upload-usage-collection-telemetry", + "fileShare", + "guided-onboarding-guide-state", + "guided-onboarding-plugin-state", + "infrastructure-monitoring-log-view", + "infrastructure-ui-source", + "inventory-view", + "legacy-url-alias", + "metrics-explorer-view", + "ml-job", + "ml-module", + "ml-trained-model", + "monitoring-telemetry", + "sample-data-telemetry", + "slo", + "space", + "spaces-usage-stats", + "synthetics-monitor", + "synthetics-param", + "synthetics-privates-locations", + "tag", + "telemetry", + "ui-metric", + "upgrade-assistant-ml-upgrade-operation", + "upgrade-assistant-reindex-operation", + "uptime-dynamic-settings", + "uptime-synthetics-api-key", + "url", + "usage-counters", + "workplace_search_telemetry" + ], + ".kibana_ingest": [ + "epm-packages", + "epm-packages-assets", + "fleet-fleet-server-host", + "fleet-message-signing-keys", + "fleet-preconfiguration-deletion-record", + "fleet-proxy", + "ingest-agent-policies", + "ingest-download-sources", + "ingest-outputs", + "ingest-package-policies", + "ingest_manager_settings" + ], + ".kibana_security_solution": [ + "csp-rule-template", + "endpoint:user-artifact", + "endpoint:user-artifact-manifest", + "exception-list", + "exception-list-agnostic", + "osquery-manager-usage-metric", + "osquery-pack", + "osquery-pack-asset", + "osquery-saved-query", + "security-rule", + "security-solution-signals-migration", + "siem-detection-engine-rule-actions", + "siem-ui-timeline", + "siem-ui-timeline-note", + "siem-ui-timeline-pinned-event" + ], + ".kibana_alerting_cases": [ + "action", + "action_task_params", + "alert", + "api_key_pending_invalidation", + "cases", + "cases-comments", + "cases-configure", + "cases-connector-mappings", + "cases-telemetry", + "cases-user-actions", + "connector_token", + "maintenance-window", + "rules-settings" + ], + ".kibana_analytics": [ + "canvas-element", + "canvas-workpad", + "canvas-workpad-template", + "dashboard", + "graph-workspace", + "index-pattern", + "kql-telemetry", + "lens", + "lens-ui-telemetry", + "map", + "query", + "search", + "search-session", + "search-telemetry", + "visualization" + ] + } + }, + "dynamic": "strict", + "properties": { + "application_usage_daily": { + "dynamic": "false", + "properties": { + "timestamp": { + "type": "date" + } + } + }, + "application_usage_totals": { + "dynamic": "false", + "type": "object" + }, + "application_usage_transactional": { + "dynamic": "false", + "type": "object" + }, + "config": { + "dynamic": "false", + "properties": { + "buildNum": { + "type": "keyword" + } + } + }, + "unknown-type": { + "dynamic": "false", + "properties": { + "foo": { + "type": "keyword" + } + } + }, + "unknown-shareable-type": { + "dynamic": "false", + "properties": { + "foo": { + "type": "keyword" + } + } + }, + "core-usage-stats": { + "dynamic": "false", + "type": "object" + }, + "coreMigrationVersion": { + "type": "keyword" + }, + "dashboard": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "doc_values": false, + "index": false, + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "index": false, + "type": "text" + } + } + }, + "optionsJSON": { + "index": false, + "type": "text" + }, + "panelsJSON": { + "index": false, + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "pause": { + "doc_values": false, + "index": false, + "type": "boolean" + }, + "section": { + "doc_values": false, + "index": false, + "type": "integer" + }, + "value": { + "doc_values": false, + "index": false, + "type": "integer" + } + } + }, + "timeFrom": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "timeRestore": { + "doc_values": false, + "index": false, + "type": "boolean" + }, + "timeTo": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "index-pattern": { + "dynamic": "false", + "properties": { + "title": { + "type": "text" + }, + "type": { + "type": "keyword" + } + } + }, + "kql-telemetry": { + "properties": { + "optInCount": { + "type": "long" + }, + "optOutCount": { + "type": "long" + } + } + }, + "legacy-url-alias": { + "dynamic": "false", + "properties": { + "disabled": { + "type": "boolean" + }, + "sourceId": { + "type": "keyword" + }, + "targetType": { + "type": "keyword" + } + } + }, + "migrationVersion": { + "dynamic": "true", + "properties": { + "config": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "dashboard": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "index-pattern": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "search": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "visualization": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "namespace": { + "type": "keyword" + }, + "namespaces": { + "type": "keyword" + }, + "originId": { + "type": "keyword" + }, + "query": { + "properties": { + "description": { + "type": "text" + }, + "filters": { + "enabled": false, + "type": "object" + }, + "query": { + "properties": { + "language": { + "type": "keyword" + }, + "query": { + "index": false, + "type": "keyword" + } + } + }, + "timefilter": { + "enabled": false, + "type": "object" + }, + "title": { + "type": "text" + } + } + }, + "references": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "sample-data-telemetry": { + "properties": { + "installCount": { + "type": "long" + }, + "unInstallCount": { + "type": "long" + } + } + }, + "search": { + "properties": { + "columns": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "description": { + "type": "text" + }, + "grid": { + "enabled": false, + "type": "object" + }, + "hideChart": { + "doc_values": false, + "index": false, + "type": "boolean" + }, + "hits": { + "doc_values": false, + "index": false, + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "index": false, + "type": "text" + } + } + }, + "sort": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "search-telemetry": { + "dynamic": "false", + "type": "object" + }, + "server": { + "dynamic": "false", + "type": "object" + }, + "telemetry": { + "properties": { + "allowChangingOptInStatus": { + "type": "boolean" + }, + "enabled": { + "type": "boolean" + }, + "lastReported": { + "type": "date" + }, + "lastVersionChecked": { + "type": "keyword" + }, + "reportFailureCount": { + "type": "integer" + }, + "reportFailureVersion": { + "type": "keyword" + }, + "sendUsageFrom": { + "type": "keyword" + }, + "userHasSeenNotice": { + "type": "boolean" + } + } + }, + "timelion-sheet": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "type": { + "type": "keyword" + }, + "ui-counter": { + "properties": { + "count": { + "type": "integer" + } + } + }, + "ui-metric": { + "properties": { + "count": { + "type": "integer" + } + } + }, + "updated_at": { + "type": "date" + }, + "url": { + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "usage-counters": { + "dynamic": "false", + "properties": { + "domainId": { + "type": "keyword" + } + } + }, + "visualization": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "index": false, + "type": "text" + } + } + }, + "savedSearchRefName": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "index": false, + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "index": false, + "type": "text" + } + } + } + } + }, + "settings": { + "index": { + "auto_expand_replicas": "0-1", + "number_of_replicas": "0", + "number_of_shards": "1", + "priority": "10", + "refresh_interval": "1s", + "routing_partition_size": "1", + "mapping": { + "total_fields": { + "limit": 1500 + } + } + } + } + } +} + +{ + "type": "index", + "value": { + "aliases": { + ".kibana_analytics_$KIBANA_PACKAGE_VERSION": {}, + ".kibana_analytics": {} + }, + "index": ".kibana_analytics_$KIBANA_PACKAGE_VERSION_001", + "mappings": { + "_meta": { + "migrationMappingPropertyHashes": { + "application_usage_daily": "43b8830d5d0df85a6823d290885fc9fd", + "application_usage_totals": "3d1b76c39bfb2cc8296b024d73854724", + "application_usage_transactional": "3d1b76c39bfb2cc8296b024d73854724", + "config": "c63748b75f39d0c54de12d12c1ccbc20", + "core-usage-stats": "3d1b76c39bfb2cc8296b024d73854724", + "coreMigrationVersion": "2f4316de49999235636386fe51dc06c1", + "dashboard": "40554caf09725935e2c02e02563a2d07", + "index-pattern": "45915a1ad866812242df474eb0479052", + "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", + "legacy-url-alias": "6155300fd11a00e23d5cbaa39f0fce0a", + "migrationVersion": "4a1746014a75ade3a714e1db5763276f", + "namespace": "2f4316de49999235636386fe51dc06c1", + "namespaces": "2f4316de49999235636386fe51dc06c1", + "originId": "2f4316de49999235636386fe51dc06c1", + "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", + "references": "7997cf5a56cc02bdc9c93361bde732b0", + "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", + "search": "db2c00e39b36f40930a3b9fc71c823e1", + "search-telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "telemetry": "36a616f7026dfa617d6655df850fe16d", + "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", + "type": "2f4316de49999235636386fe51dc06c1", + "ui-counter": "0d409297dc5ebe1e3a1da691c6ee32e3", + "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", + "updated_at": "00da57df13e94e9d98437d13ace4bfe0", + "url": "c7f66a0df8b1b52f17c28c4adb111105", + "usage-counters": "8cc260bdceffec4ffc3ad165c97dc1b4", + "visualization": "f819cf6636b75c9e76ba733a0c6ef355" + }, + "indexTypesMap": { + ".kibana_task_manager": [ + "task" + ], + ".kibana": [ + "apm-indices", + "apm-server-schema", + "apm-service-group", + "apm-telemetry", + "app_search_telemetry", + "application_usage_daily", + "application_usage_totals", + "config", + "config-global", + "core-usage-stats", + "enterprise_search_telemetry", + "event_loop_delays_daily", + "file", + "file-upload-usage-collection-telemetry", + "fileShare", + "guided-onboarding-guide-state", + "guided-onboarding-plugin-state", + "infrastructure-monitoring-log-view", + "infrastructure-ui-source", + "inventory-view", + "legacy-url-alias", + "metrics-explorer-view", + "ml-job", + "ml-module", + "ml-trained-model", + "monitoring-telemetry", + "sample-data-telemetry", + "slo", + "space", + "spaces-usage-stats", + "synthetics-monitor", + "synthetics-param", + "synthetics-privates-locations", + "tag", + "telemetry", + "ui-metric", + "upgrade-assistant-ml-upgrade-operation", + "upgrade-assistant-reindex-operation", + "uptime-dynamic-settings", + "uptime-synthetics-api-key", + "url", + "usage-counters", + "workplace_search_telemetry" + ], + ".kibana_ingest": [ + "epm-packages", + "epm-packages-assets", + "fleet-fleet-server-host", + "fleet-message-signing-keys", + "fleet-preconfiguration-deletion-record", + "fleet-proxy", + "ingest-agent-policies", + "ingest-download-sources", + "ingest-outputs", + "ingest-package-policies", + "ingest_manager_settings" + ], + ".kibana_security_solution": [ + "csp-rule-template", + "endpoint:user-artifact", + "endpoint:user-artifact-manifest", + "exception-list", + "exception-list-agnostic", + "osquery-manager-usage-metric", + "osquery-pack", + "osquery-pack-asset", + "osquery-saved-query", + "security-rule", + "security-solution-signals-migration", + "siem-detection-engine-rule-actions", + "siem-ui-timeline", + "siem-ui-timeline-note", + "siem-ui-timeline-pinned-event" + ], + ".kibana_alerting_cases": [ + "action", + "action_task_params", + "alert", + "api_key_pending_invalidation", + "cases", + "cases-comments", + "cases-configure", + "cases-connector-mappings", + "cases-telemetry", + "cases-user-actions", + "connector_token", + "maintenance-window", + "rules-settings" + ], + ".kibana_analytics": [ + "canvas-element", + "canvas-workpad", + "canvas-workpad-template", + "dashboard", + "graph-workspace", + "index-pattern", + "kql-telemetry", + "lens", + "lens-ui-telemetry", + "map", + "query", + "search", + "search-session", + "search-telemetry", + "visualization" + ] } }, "dynamic": "strict", @@ -525,7 +1285,9 @@ "refresh_interval": "1s", "routing_partition_size": "1", "mapping": { - "total_fields": { "limit": 1500 } + "total_fields": { + "limit": 1500 + } } } } diff --git a/test/functional/apps/management/_handle_version_conflict.ts b/test/functional/apps/management/_handle_version_conflict.ts index 2f65f966c5596..c2f9f3f7e6393 100644 --- a/test/functional/apps/management/_handle_version_conflict.ts +++ b/test/functional/apps/management/_handle_version_conflict.ts @@ -16,6 +16,7 @@ */ import expect from '@kbn/expect'; +import { ANALYTICS_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { @@ -28,7 +29,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['common', 'home', 'settings', 'discover', 'header']); const log = getService('log'); - describe('index version conflict', function describeIndexTests() { + describe('FOO index version conflict', function describeIndexTests() { before(async function () { await browser.setWindowSize(1200, 800); await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover'); @@ -48,7 +49,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.settings.setScriptedFieldScript(`doc['bytes'].value`); const response = await es.update( { - index: '.kibana', + index: ANALYTICS_SAVED_OBJECT_INDEX, id: 'index-pattern:logstash-*', body: { doc: { 'index-pattern': { fieldFormatMap: '{"geo.src":{"id":"number"}}' } }, @@ -82,7 +83,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.settings.setFieldFormat('url'); const response = await es.update( { - index: '.kibana', + index: ANALYTICS_SAVED_OBJECT_INDEX, id: 'index-pattern:logstash-*', body: { doc: { 'index-pattern': { fieldFormatMap: '{"geo.dest":{"id":"number"}}' } }, diff --git a/test/functional/fixtures/es_archiver/deprecations_service/mappings.json b/test/functional/fixtures/es_archiver/deprecations_service/mappings.json index 47bd39ebf8bcf..f9bec7fdca4d5 100644 --- a/test/functional/fixtures/es_archiver/deprecations_service/mappings.json +++ b/test/functional/fixtures/es_archiver/deprecations_service/mappings.json @@ -36,6 +36,118 @@ "url": "c7f66a0df8b1b52f17c28c4adb111105", "usage-counters": "8cc260bdceffec4ffc3ad165c97dc1b4", "visualization": "f819cf6636b75c9e76ba733a0c6ef355" + }, + "indexTypesMap": { + ".kibana_task_manager": [ + "task" + ], + ".kibana": [ + "apm-indices", + "apm-server-schema", + "apm-service-group", + "apm-telemetry", + "app_search_telemetry", + "application_usage_daily", + "application_usage_totals", + "config", + "config-global", + "core-usage-stats", + "enterprise_search_telemetry", + "event_loop_delays_daily", + "file", + "file-upload-usage-collection-telemetry", + "fileShare", + "guided-onboarding-guide-state", + "guided-onboarding-plugin-state", + "infrastructure-monitoring-log-view", + "infrastructure-ui-source", + "inventory-view", + "legacy-url-alias", + "metrics-explorer-view", + "ml-job", + "ml-module", + "ml-trained-model", + "monitoring-telemetry", + "sample-data-telemetry", + "slo", + "space", + "spaces-usage-stats", + "synthetics-monitor", + "synthetics-param", + "synthetics-privates-locations", + "tag", + "telemetry", + "ui-metric", + "upgrade-assistant-ml-upgrade-operation", + "upgrade-assistant-reindex-operation", + "uptime-dynamic-settings", + "uptime-synthetics-api-key", + "url", + "usage-counters", + "workplace_search_telemetry" + ], + ".kibana_ingest": [ + "epm-packages", + "epm-packages-assets", + "fleet-fleet-server-host", + "fleet-message-signing-keys", + "fleet-preconfiguration-deletion-record", + "fleet-proxy", + "ingest-agent-policies", + "ingest-download-sources", + "ingest-outputs", + "ingest-package-policies", + "ingest_manager_settings" + ], + ".kibana_security_solution": [ + "csp-rule-template", + "endpoint:user-artifact", + "endpoint:user-artifact-manifest", + "exception-list", + "exception-list-agnostic", + "osquery-manager-usage-metric", + "osquery-pack", + "osquery-pack-asset", + "osquery-saved-query", + "security-rule", + "security-solution-signals-migration", + "siem-detection-engine-rule-actions", + "siem-ui-timeline", + "siem-ui-timeline-note", + "siem-ui-timeline-pinned-event" + ], + ".kibana_alerting_cases": [ + "action", + "action_task_params", + "alert", + "api_key_pending_invalidation", + "cases", + "cases-comments", + "cases-configure", + "cases-connector-mappings", + "cases-telemetry", + "cases-user-actions", + "connector_token", + "maintenance-window", + "rules-settings" + ], + ".kibana_analytics": [ + "canvas-element", + "canvas-workpad", + "canvas-workpad-template", + "dashboard", + "graph-workspace", + "index-pattern", + "kql-telemetry", + "lens", + "lens-ui-telemetry", + "map", + "query", + "search", + "search-session", + "search-telemetry", + "visualization" + ] } }, "dynamic": "strict", @@ -425,9 +537,11 @@ "refresh_interval": "1s", "routing_partition_size": "1", "mapping": { - "total_fields": { "limit": 1500 } + "total_fields": { + "limit": 1500 + } } } } } -} \ No newline at end of file +} diff --git a/test/functional/fixtures/es_archiver/huge_fields/data.json.gz b/test/functional/fixtures/es_archiver/huge_fields/data.json.gz index 1ce42c64c53a34db899fedd40bc012fa2e6769ba..794ee4f944c846e97d9514075a8876fb20148634 100644 GIT binary patch delta 1882 zcmZWoc~nzZ9&RMb02L_EVG%XpD1%~xE1AN8QmGvVFsFip6(o#0AP7W2NLb>lOF;>O z@*Fv*G>XLnWlIAINqi*HQjAC;MNv{>$i#wxA$dSHmV`{G2VzhEy1(-~_dDNqZ|@g2 zk}qs*F*IA-H@5pvF>MdFh2f#>uaA6KJkGN}SM0aL2XNZ$yOxKzeEq6ZlEUa08Ww&7 zrmtsYd~9?qKXruD1T{4YhL7u1I*%W>d{9*wlTQH4+XB|iKD8WJq|{Dh|Jx8e*Y*u4 zv}_!L@|VErHmoOFiW@;k%m-bG^u}G{B45oyO}6o_twj!%?H+c81dO1Tej>XVbmn-2 zHEj;4mS>x<`GTf43~1l(h*EwZap@Z91NnjbAtZVzi1zg8DNxaY0iD_%0@>w_dt&ED zAKh}qk;`=65Va*(nxV4Sr78$chHMH&l< zZq=dcu@Z167mG6eoLUkHW_4gu7|QSi2k;?pJL-k`ww}Cl8Q-ci)vM(7cu5|MA68%6 zzo95(A@i?Oi4@{N;WY!P5l;I9`OC=BKU*YXk(|`_w*jjTzYRFZ@!Q`Ayy|v#^7-|a zO>qMBtJT%=(8~XzL@9r-a^~bpl4$maRhPLQpf?-yTL)OauVU|N2a&3X(-qbZo|d}& zYqbLu`)1wcJuBtmvGi?<*ifjQK-h=3CwmA68oCBH{5NDf37%5t%MT0d3~&dD{5x|k zTm6rLOcoaH_LIgU6=brvSOP`aa>mci1ped6kcHUIYksP<_g0lZ(I(4t`Spb*tjWsw z1k;)1{Q(fwAtS<)uZ&I*)l{WB&{Kiix4yW<@)v@D%(`>^>hMdR8??79ZNdvTAf;x?ah! zYTP`Hyx?VZhX_GTSdr%m)6hFe*p)(BK?3Z1W~6EH)f>>wk9=Ewb)i2l zof?`cQ*G!b;>`;uYN~ChS&#b;2#)^m^%_#Z<3nc=yy(aL8i{XrVp5uGOu*k*J$Kz} zenIxVUzuJxirN$^>zxPFj@lD@O=9+U~xZi<4j)yG{H=+IF5|l&Bc{Aeje-I zGZcKn;Pua4vZH?V!_yot)>s;LHX7~r!pdWO4$R`+>A!-1F$xR*7P z=%G$0ib6AuHs;g(Km^V z)<=h{qeiQ*OFen`o$TE)q>ITZr$@rO`(r!p3h7Kvd;FnHd~Qq5P^Nv{_*q$OUH^aX zZw;Y8%ZZ5(;*8Z%&XtRzb0-o?pfP#j5?Kiu1hCs#7y&om1{Y*&RkELe`mmA zAb)Ode2l;l6c~TioCFGHo{wm0^&(=ZVp0eFDYU3$A4Bg%AjtXgx(X6TeV1dFCc^kf U0di?T$1s3M8BM46Jh6H2|GY=fw*UYD delta 1952 zcmZvbc~nzp8pZ_^77?@+iGYy+5~o0txCY0H6ZsmnBA|S3u0(>UK{7U+932)8|m~CShE|b zW$prI>Y>n(=u-}v0EU4uTw3oRIF)_N-AVlN_vW$g+QJA@<-*w}ilhLnlgE<%c82w?Ighk$6KkG_-9FT%~oqj|zJN ze>it-bIxa>X5U%1MlK(4jbGP38!;@w&Qhq9S;u|emFl~ilMp|Y1{|tKLpBv8UidHH z2FQ(4Yte^zdc{Q=)+qA{kO`HsT3w@PwGz#oj)O% z8xt%?8T0-DRY|r6Odf9oHb1sp{vfe}s1=IhwG_SuU;ZD8cOI`RoWH?Wk*!z2-V+L2 zWD9ZYo@>^u|8CIXP~-Zc^l)It;eB`iRPB7+wiXUbyok5-0Gyc!L$?+y^?ez9IM1l} zp&!qVcP|q4m*x~X-t^ljz3^iAO?O8jos^)Oc@kGf=<6(%w~1{6wZ6B55a{#<$jB99 z$KBPwY@z1!YihE9`>X&0vkW^&9DAg0Y^+1Drm=5&W&Km+=WjoZG#>7vHb#Qdy>cHN zdXE(8i9FxC7Rih_`0x&WpRq4!NJ*57m!W19l#R&-8)@9mGM4 zXGF8wTV;O_CN-+aoTv3|atcKchJ*J&xUtYN@X*?aW66g1KYMovoMSk^aGY|+wFx5( zQ_Z8QZL+q*jq{*xJ9i-lKMeYuZ+XjmHKjaKDlDn*|43-a z2*p&fETqwuMZeIB!YIF+j#c$>PD&TbVU><7zj&#p-(;%$bzwW{fI5|{U=pS}r2_x> z<@bkDjL0ASSnZnIdUns^rq1+>lW3n&v1;8&bv})LXL)?i~-JLK4so1gKZxgjQ zPKEJ9f$>D;#4BDqGpZ^~`Bz38I<3M!6o&8jEYFO%gzUp_@ydVWlAPCj^^{jvIh)o_ zkE#z%2xJ5$4onpDHfNP2h34Y>Jkc+HEwh2e@)OdDb8$IKUQH~aK94J0pmg=fsa)i% zL=(c^n5J{sp~#x1dcNvrcuIuMwfa~MB~5@9P}xLBqVy&61d}#7bbIJ%f_$I6lwJQ& zl^vRb*Liq%L_gpKq$fIvGIX@=2Hu^Cg6q^RHGb=LNEZ@v?pVA0o+v+e=%+I5A;#W) zasN0U?JmjSbiWk#qhPH3#sM}Fd$ZzdL#%u7#2)dFh23x2WvBw-mEZ%uZLdpw6RMsb zoKx&wN>PfVrD}_Yo>bDA9=v|XE!D+02W_W2ra!BD&~EI?k&h ze}a=j#r}{2)5eylVkgC?r^23{+51V!`X`i*i0 zU*|I*{Z`E=<^@$9h@(&=FUekb2pW_8inj{4qW={-Z7T85gtaHTHJNZ+|FDjM4^BfA z_EhuaETyhRcb~UN8b>gIWx0;hS^Rs>EAzx9{mK;`69n3N38EGl+@3*((mg*l5F zuxnEsFNwM!7Z47BI;@m?Jvn)CS|;^BNL^B9If0-?_WdTsu^x;+Pr+Iu>jYQ=@A}_B zJ=UMB*7kVl`YX2TrB?nNC5x3spw8;&L%_ux{P&w^;h<7g(dLiisY ({ v4: () => 'uuidv4', })); -const defaultKibanaIndex = '.kibana'; +const kibanaIndices = ['.kibana']; const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); const scopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); const actionExecutor = actionExecutorMock.create(); @@ -141,7 +141,7 @@ beforeEach(() => { actionTypeRegistry, unsecuredSavedObjectsClient, scopedClusterClient, - defaultKibanaIndex, + kibanaIndices, preconfiguredActions: [], actionExecutor, executionEnqueuer, @@ -601,7 +601,7 @@ describe('create()', () => { actionTypeRegistry, unsecuredSavedObjectsClient, scopedClusterClient, - defaultKibanaIndex, + kibanaIndices, preconfiguredActions: [], actionExecutor, executionEnqueuer, @@ -712,7 +712,7 @@ describe('create()', () => { actionTypeRegistry, unsecuredSavedObjectsClient, scopedClusterClient, - defaultKibanaIndex, + kibanaIndices, preconfiguredActions: [ { id: preDefinedId, @@ -783,7 +783,7 @@ describe('get()', () => { actionTypeRegistry, unsecuredSavedObjectsClient, scopedClusterClient, - defaultKibanaIndex, + kibanaIndices, actionExecutor, executionEnqueuer, ephemeralExecutionEnqueuer, @@ -844,7 +844,7 @@ describe('get()', () => { actionTypeRegistry, unsecuredSavedObjectsClient, scopedClusterClient, - defaultKibanaIndex, + kibanaIndices, actionExecutor, executionEnqueuer, ephemeralExecutionEnqueuer, @@ -967,7 +967,7 @@ describe('get()', () => { actionTypeRegistry, unsecuredSavedObjectsClient, scopedClusterClient, - defaultKibanaIndex, + kibanaIndices, actionExecutor, executionEnqueuer, ephemeralExecutionEnqueuer, @@ -1043,7 +1043,7 @@ describe('getAll()', () => { actionTypeRegistry, unsecuredSavedObjectsClient, scopedClusterClient, - defaultKibanaIndex, + kibanaIndices, actionExecutor, executionEnqueuer, ephemeralExecutionEnqueuer, @@ -1186,7 +1186,7 @@ describe('getAll()', () => { actionTypeRegistry, unsecuredSavedObjectsClient, scopedClusterClient, - defaultKibanaIndex, + kibanaIndices, actionExecutor, executionEnqueuer, ephemeralExecutionEnqueuer, @@ -1269,7 +1269,7 @@ describe('getBulk()', () => { actionTypeRegistry, unsecuredSavedObjectsClient, scopedClusterClient, - defaultKibanaIndex, + kibanaIndices, actionExecutor, executionEnqueuer, ephemeralExecutionEnqueuer, @@ -1406,7 +1406,7 @@ describe('getBulk()', () => { actionTypeRegistry, unsecuredSavedObjectsClient, scopedClusterClient, - defaultKibanaIndex, + kibanaIndices, actionExecutor, executionEnqueuer, ephemeralExecutionEnqueuer, @@ -1466,7 +1466,7 @@ describe('getOAuthAccessToken()', () => { actionTypeRegistry, unsecuredSavedObjectsClient, scopedClusterClient, - defaultKibanaIndex, + kibanaIndices, actionExecutor, executionEnqueuer, ephemeralExecutionEnqueuer, @@ -2687,7 +2687,7 @@ describe('isPreconfigured()', () => { actionTypeRegistry, unsecuredSavedObjectsClient, scopedClusterClient, - defaultKibanaIndex, + kibanaIndices, actionExecutor, executionEnqueuer, ephemeralExecutionEnqueuer, @@ -2726,7 +2726,7 @@ describe('isPreconfigured()', () => { actionTypeRegistry, unsecuredSavedObjectsClient, scopedClusterClient, - defaultKibanaIndex, + kibanaIndices, actionExecutor, executionEnqueuer, ephemeralExecutionEnqueuer, diff --git a/x-pack/plugins/actions/server/actions_client.ts b/x-pack/plugins/actions/server/actions_client.ts index 201237ce33033..3efae4952692a 100644 --- a/x-pack/plugins/actions/server/actions_client.ts +++ b/x-pack/plugins/actions/server/actions_client.ts @@ -111,7 +111,7 @@ export interface CreateOptions { interface ConstructorOptions { logger: Logger; - defaultKibanaIndex: string; + kibanaIndices: string[]; scopedClusterClient: IScopedClusterClient; actionTypeRegistry: ActionTypeRegistry; unsecuredSavedObjectsClient: SavedObjectsClientContract; @@ -135,7 +135,7 @@ export interface UpdateOptions { export class ActionsClient { private readonly logger: Logger; - private readonly defaultKibanaIndex: string; + private readonly kibanaIndices: string[]; private readonly scopedClusterClient: IScopedClusterClient; private readonly unsecuredSavedObjectsClient: SavedObjectsClientContract; private readonly actionTypeRegistry: ActionTypeRegistry; @@ -154,7 +154,7 @@ export class ActionsClient { constructor({ logger, actionTypeRegistry, - defaultKibanaIndex, + kibanaIndices, scopedClusterClient, unsecuredSavedObjectsClient, preconfiguredActions, @@ -173,7 +173,7 @@ export class ActionsClient { this.actionTypeRegistry = actionTypeRegistry; this.unsecuredSavedObjectsClient = unsecuredSavedObjectsClient; this.scopedClusterClient = scopedClusterClient; - this.defaultKibanaIndex = defaultKibanaIndex; + this.kibanaIndices = kibanaIndices; this.preconfiguredActions = preconfiguredActions; this.actionExecutor = actionExecutor; this.executionEnqueuer = executionEnqueuer; @@ -462,11 +462,7 @@ export class ActionsClient { isDeprecated: isConnectorDeprecated(preconfiguredAction), })), ].sort((a, b) => a.name.localeCompare(b.name)); - return await injectExtraFindData( - this.defaultKibanaIndex, - this.scopedClusterClient, - mergedResult - ); + return await injectExtraFindData(this.kibanaIndices, this.scopedClusterClient, mergedResult); } /** @@ -901,7 +897,7 @@ function actionFromSavedObject( } async function injectExtraFindData( - defaultKibanaIndex: string, + kibanaIndices: string[], scopedClusterClient: IScopedClusterClient, actionResults: ActionResult[] ): Promise { @@ -940,7 +936,7 @@ async function injectExtraFindData( }; } const aggregationResult = await scopedClusterClient.asInternalUser.search({ - index: defaultKibanaIndex, + index: kibanaIndices, body: { aggs, size: 0, diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index 63bea300af7f7..128c3efe50295 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -116,13 +116,16 @@ export interface PluginSetupContract { >( actionType: ActionType ): void; + registerSubActionConnectorType< Config extends ActionTypeConfig = ActionTypeConfig, Secrets extends ActionTypeSecrets = ActionTypeSecrets >( connector: SubActionConnectorType ): void; + isPreconfiguredConnector(connectorId: string): boolean; + getSubActionConnectorClass: () => IServiceAbstract; getCaseConnectorClass: () => IServiceAbstract; getActionsHealth: () => { hasPermanentEncryptionKey: boolean }; @@ -197,7 +200,6 @@ export class ActionsPlugin implements Plugin, plugins: ActionsPluginsSetup ): PluginSetupContract { - this.kibanaIndex = core.savedObjects.getKibanaIndex(); - this.licenseState = new LicenseState(plugins.licensing.license$); this.isESOCanEncrypt = plugins.encryptedSavedObjects.canEncrypt; @@ -295,17 +295,15 @@ export class ActionsPlugin implements Plugin( 'actions', - this.createRouteHandlerContext(core, this.kibanaIndex) + this.createRouteHandlerContext(core) ); if (usageCollection) { const eventLogIndex = this.eventLogService.getIndexPattern(); - const kibanaIndex = this.kibanaIndex; initializeActionsTelemetry( this.telemetryLogger, plugins.taskManager, core, - kibanaIndex, this.preconfiguredActions, eventLogIndex ); @@ -381,7 +379,6 @@ export class ActionsPlugin implements Plugin, - defaultKibanaIndex: string + core: CoreSetup ): IContextProvider => { const { actionTypeRegistry, @@ -622,7 +618,7 @@ export class ActionsPlugin implements Plugin client.asInternalUser ); + const getActionIndex = () => + core + .getStartServices() + .then(([coreStart]) => coreStart.savedObjects.getIndexForType('action')); return { async run() { + const actionIndex = await getActionIndex(); const esClient = await getEsClient(); return Promise.all([ - getTotalCount(esClient, kibanaIndex, logger, preconfiguredActions), - getInUseTotalCount(esClient, kibanaIndex, logger, undefined, preconfiguredActions), + getTotalCount(esClient, actionIndex, logger, preconfiguredActions), + getInUseTotalCount(esClient, actionIndex, logger, undefined, preconfiguredActions), getExecutionsPerDayCount(esClient, eventLogIndex, logger), ]).then(([totalAggegations, totalInUse, totalExecutionsPerDay]) => { const hasErrors = diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index e5e7aaa8f8610..f73b7070ce55b 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -131,6 +131,7 @@ export interface PluginSetupContract { RecoveryActionGroupId > ): void; + getSecurityHealth: () => Promise; getConfig: () => AlertingRulesConfig; frameworkAlerts: PublicFrameworkAlertsService; @@ -219,7 +220,6 @@ export class AlertingPlugin { core: CoreSetup, plugins: AlertingPluginsSetup ): PluginSetupContract { - const kibanaIndex = core.savedObjects.getKibanaIndex(); this.kibanaBaseUrl = core.http.basePath.publicBaseUrl; this.licenseState = new LicenseState(plugins.licensing.license$); this.security = plugins.security; @@ -284,13 +284,7 @@ export class AlertingPlugin { core.getStartServices().then(([_, { taskManager }]) => taskManager) ); const eventLogIndex = this.eventLogService.getIndexPattern(); - initializeAlertingTelemetry( - this.telemetryLogger, - core, - plugins.taskManager, - kibanaIndex, - eventLogIndex - ); + initializeAlertingTelemetry(this.telemetryLogger, core, plugins.taskManager, eventLogIndex); } // Usage counter for telemetry diff --git a/x-pack/plugins/alerting/server/saved_objects/index.ts b/x-pack/plugins/alerting/server/saved_objects/index.ts index 807a1475db9bb..ba9f2ac76077e 100644 --- a/x-pack/plugins/alerting/server/saved_objects/index.ts +++ b/x-pack/plugins/alerting/server/saved_objects/index.ts @@ -13,6 +13,7 @@ import type { } from '@kbn/core/server'; import { EncryptedSavedObjectsPluginSetup } from '@kbn/encrypted-saved-objects-plugin/server'; import { MigrateFunctionsObject } from '@kbn/kibana-utils-plugin/common'; +import { ALERTING_CASES_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { alertMappings } from './mappings'; import { rulesSettingsMappings } from './rules_settings_mappings'; import { maintenanceWindowMappings } from './maintenance_window_mapping'; @@ -78,6 +79,7 @@ export function setupSavedObjects( ) { savedObjects.registerType({ name: 'alert', + indexPattern: ALERTING_CASES_SAVED_OBJECT_INDEX, hidden: true, namespaceType: 'multiple-isolated', convertToMultiNamespaceTypeVersion: '8.0.0', @@ -108,6 +110,7 @@ export function setupSavedObjects( savedObjects.registerType({ name: 'api_key_pending_invalidation', + indexPattern: ALERTING_CASES_SAVED_OBJECT_INDEX, hidden: true, namespaceType: 'agnostic', mappings: { @@ -124,6 +127,7 @@ export function setupSavedObjects( savedObjects.registerType({ name: RULES_SETTINGS_SAVED_OBJECT_TYPE, + indexPattern: ALERTING_CASES_SAVED_OBJECT_INDEX, hidden: true, namespaceType: 'single', mappings: rulesSettingsMappings, @@ -131,6 +135,7 @@ export function setupSavedObjects( savedObjects.registerType({ name: MAINTENANCE_WINDOW_SAVED_OBJECT_TYPE, + indexPattern: ALERTING_CASES_SAVED_OBJECT_INDEX, hidden: true, namespaceType: 'multiple-isolated', mappings: maintenanceWindowMappings, diff --git a/x-pack/plugins/alerting/server/usage/lib/get_telemetry_from_kibana.test.ts b/x-pack/plugins/alerting/server/usage/lib/get_telemetry_from_kibana.test.ts index d16b76d91afa9..58a449d5f7004 100644 --- a/x-pack/plugins/alerting/server/usage/lib/get_telemetry_from_kibana.test.ts +++ b/x-pack/plugins/alerting/server/usage/lib/get_telemetry_from_kibana.test.ts @@ -157,7 +157,7 @@ describe('kibana index telemetry', () => { const telemetry = await getTotalCountAggregations({ esClient, - kibanaIndex: 'test', + alertIndex: 'test', logger, }); @@ -228,7 +228,7 @@ describe('kibana index telemetry', () => { const telemetry = await getTotalCountAggregations({ esClient, - kibanaIndex: 'test', + alertIndex: 'test', logger, }); @@ -333,7 +333,7 @@ describe('kibana index telemetry', () => { const telemetry = await getTotalCountInUse({ esClient, - kibanaIndex: 'test', + alertIndex: 'test', logger, }); @@ -357,7 +357,7 @@ describe('kibana index telemetry', () => { const telemetry = await getTotalCountInUse({ esClient, - kibanaIndex: 'test', + alertIndex: 'test', logger, }); diff --git a/x-pack/plugins/alerting/server/usage/lib/get_telemetry_from_kibana.ts b/x-pack/plugins/alerting/server/usage/lib/get_telemetry_from_kibana.ts index 0c6d01016c313..a1055aa075521 100644 --- a/x-pack/plugins/alerting/server/usage/lib/get_telemetry_from_kibana.ts +++ b/x-pack/plugins/alerting/server/usage/lib/get_telemetry_from_kibana.ts @@ -25,7 +25,7 @@ import { parseSimpleRuleTypeBucket } from './parse_simple_rule_type_bucket'; interface Opts { esClient: ElasticsearchClient; - kibanaIndex: string; + alertIndex: string; logger: Logger; } @@ -57,12 +57,12 @@ interface GetTotalCountInUseResults { export async function getTotalCountAggregations({ esClient, - kibanaIndex, + alertIndex, logger, }: Opts): Promise { try { const query = { - index: kibanaIndex, + index: alertIndex, size: 0, body: { query: { @@ -400,12 +400,12 @@ export async function getTotalCountAggregations({ export async function getTotalCountInUse({ esClient, - kibanaIndex, + alertIndex, logger, }: Opts): Promise { try { const query = { - index: kibanaIndex, + index: alertIndex, size: 0, body: { query: { diff --git a/x-pack/plugins/alerting/server/usage/task.ts b/x-pack/plugins/alerting/server/usage/task.ts index e17d17dc6f4f5..9c6a3607535ee 100644 --- a/x-pack/plugins/alerting/server/usage/task.ts +++ b/x-pack/plugins/alerting/server/usage/task.ts @@ -29,10 +29,9 @@ export function initializeAlertingTelemetry( logger: Logger, core: CoreSetup, taskManager: TaskManagerSetupContract, - kibanaIndex: string, eventLogIndex: string ) { - registerAlertingTelemetryTask(logger, core, taskManager, kibanaIndex, eventLogIndex); + registerAlertingTelemetryTask(logger, core, taskManager, eventLogIndex); } export function scheduleAlertingTelemetry(logger: Logger, taskManager?: TaskManagerStartContract) { @@ -45,20 +44,13 @@ function registerAlertingTelemetryTask( logger: Logger, core: CoreSetup, taskManager: TaskManagerSetupContract, - kibanaIndex: string, eventLogIndex: string ) { taskManager.registerTaskDefinitions({ [TELEMETRY_TASK_TYPE]: { title: 'Alerting usage fetch task', timeout: '5m', - createTaskRunner: telemetryTaskRunner( - logger, - core, - kibanaIndex, - eventLogIndex, - taskManager.index - ), + createTaskRunner: telemetryTaskRunner(logger, core, eventLogIndex, taskManager.index), }, }); } @@ -80,7 +72,6 @@ async function scheduleTasks(logger: Logger, taskManager: TaskManagerStartContra export function telemetryTaskRunner( logger: Logger, core: CoreSetup, - kibanaIndex: string, eventLogIndex: string, taskManagerIndex: string ) { @@ -94,13 +85,18 @@ export function telemetryTaskRunner( }, ]) => client.asInternalUser ); + const getAlertIndex = () => + core + .getStartServices() + .then(([coreStart]) => coreStart.savedObjects.getIndexForType('alert')); return { async run() { const esClient = await getEsClient(); + const alertIndex = await getAlertIndex(); return Promise.all([ - getTotalCountAggregations({ esClient, kibanaIndex, logger }), - getTotalCountInUse({ esClient, kibanaIndex, logger }), + getTotalCountAggregations({ esClient, alertIndex, logger }), + getTotalCountInUse({ esClient, alertIndex, logger }), getExecutionsPerDayCount({ esClient, eventLogIndex, logger }), getExecutionTimeoutsPerDayCount({ esClient, eventLogIndex, logger }), getFailedAndUnrecognizedTasksPerDay({ esClient, taskManagerIndex, logger }), diff --git a/x-pack/plugins/canvas/server/collectors/collector.ts b/x-pack/plugins/canvas/server/collectors/collector.ts index f98bd319a8ac0..31919bc663c1e 100644 --- a/x-pack/plugins/canvas/server/collectors/collector.ts +++ b/x-pack/plugins/canvas/server/collectors/collector.ts @@ -29,7 +29,7 @@ const collectors: TelemetryCollector[] = [workpadCollector, customElementCollect */ export function registerCanvasUsageCollector( usageCollection: UsageCollectionSetup | undefined, - kibanaIndex: string + getIndexForType: (type: string) => Promise ) { if (!usageCollection) { return; @@ -40,7 +40,7 @@ export function registerCanvasUsageCollector( isReady: () => true, fetch: async ({ esClient }: CollectorFetchContext) => { const collectorResults = await Promise.all( - collectors.map((collector) => collector(kibanaIndex, esClient)) + collectors.map((collector) => collector(getIndexForType, esClient)) ); return collectorResults.reduce((reduction, usage) => { diff --git a/x-pack/plugins/canvas/server/collectors/custom_element_collector.ts b/x-pack/plugins/canvas/server/collectors/custom_element_collector.ts index 66238abfe89df..51aa616f4e5a7 100644 --- a/x-pack/plugins/canvas/server/collectors/custom_element_collector.ts +++ b/x-pack/plugins/canvas/server/collectors/custom_element_collector.ts @@ -141,12 +141,13 @@ export function summarizeCustomElements( } const customElementCollector: TelemetryCollector = async function customElementCollector( - kibanaIndex, + getIndexForType, esClient ) { + const index = await getIndexForType(CUSTOM_ELEMENT_TYPE); const customElementParams = { size: 10000, - index: kibanaIndex, + index, ignore_unavailable: true, filter_path: [`hits.hits._source.${CUSTOM_ELEMENT_TYPE}.content`], body: { query: { bool: { filter: { term: { type: CUSTOM_ELEMENT_TYPE } } } } }, diff --git a/x-pack/plugins/canvas/server/collectors/workpad_collector.ts b/x-pack/plugins/canvas/server/collectors/workpad_collector.ts index 6ab5f62f8f5d2..252d05bfc4f7e 100644 --- a/x-pack/plugins/canvas/server/collectors/workpad_collector.ts +++ b/x-pack/plugins/canvas/server/collectors/workpad_collector.ts @@ -377,10 +377,11 @@ export function summarizeWorkpads(workpadDocs: CanvasWorkpad[]): WorkpadTelemetr }; } -const workpadCollector: TelemetryCollector = async function (kibanaIndex, esClient) { +const workpadCollector: TelemetryCollector = async function (getIndexForType, esClient) { + const index = await getIndexForType(CANVAS_TYPE); const searchParams = { size: 10000, // elasticsearch index.max_result_window default value - index: kibanaIndex, + index, ignore_unavailable: true, filter_path: ['hits.hits._source.canvas-workpad', '-hits.hits._source.canvas-workpad.assets'], body: { query: { bool: { filter: { term: { type: CANVAS_TYPE } } } } }, diff --git a/x-pack/plugins/canvas/server/plugin.ts b/x-pack/plugins/canvas/server/plugin.ts index ec5cb4969be19..8c9748cc47887 100644 --- a/x-pack/plugins/canvas/server/plugin.ts +++ b/x-pack/plugins/canvas/server/plugin.ts @@ -90,9 +90,11 @@ export class CanvasPlugin implements Plugin { plugins.home.sampleData.addAppLinksToSampleDataset ); - // we need the kibana index for the Canvas usage collector - const kibanaIndex = coreSetup.savedObjects.getKibanaIndex(); - registerCanvasUsageCollector(plugins.usageCollection, kibanaIndex); + const getIndexForType = (type: string) => + coreSetup + .getStartServices() + .then(([coreStart]) => coreStart.savedObjects.getIndexForType(type)); + registerCanvasUsageCollector(plugins.usageCollection, getIndexForType); } public start(coreStart: CoreStart) { diff --git a/x-pack/plugins/canvas/server/saved_objects/custom_element.ts b/x-pack/plugins/canvas/server/saved_objects/custom_element.ts index c9f9ea8453e5f..c4a25f1d2e385 100644 --- a/x-pack/plugins/canvas/server/saved_objects/custom_element.ts +++ b/x-pack/plugins/canvas/server/saved_objects/custom_element.ts @@ -5,12 +5,14 @@ * 2.0. */ +import { ANALYTICS_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { SavedObjectsType } from '@kbn/core/server'; import { CUSTOM_ELEMENT_TYPE } from '../../common/lib/constants'; import { customElementMigrationsFactory, CanvasSavedObjectTypeMigrationsDeps } from './migrations'; export const customElementType = (deps: CanvasSavedObjectTypeMigrationsDeps): SavedObjectsType => ({ name: CUSTOM_ELEMENT_TYPE, + indexPattern: ANALYTICS_SAVED_OBJECT_INDEX, hidden: false, namespaceType: 'multiple-isolated', convertToMultiNamespaceTypeVersion: '8.0.0', diff --git a/x-pack/plugins/canvas/server/saved_objects/workpad.ts b/x-pack/plugins/canvas/server/saved_objects/workpad.ts index 6c392aaaa62b1..dd79ebec576bc 100644 --- a/x-pack/plugins/canvas/server/saved_objects/workpad.ts +++ b/x-pack/plugins/canvas/server/saved_objects/workpad.ts @@ -6,6 +6,7 @@ */ import { SavedObjectsType } from '@kbn/core/server'; +import { ANALYTICS_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { CANVAS_TYPE } from '../../common/lib/constants'; import { workpadMigrationsFactory } from './migrations'; import type { CanvasSavedObjectTypeMigrationsDeps } from './migrations'; @@ -14,6 +15,7 @@ export const workpadTypeFactory = ( deps: CanvasSavedObjectTypeMigrationsDeps ): SavedObjectsType => ({ name: CANVAS_TYPE, + indexPattern: ANALYTICS_SAVED_OBJECT_INDEX, hidden: false, namespaceType: 'multiple-isolated', convertToMultiNamespaceTypeVersion: '8.0.0', diff --git a/x-pack/plugins/canvas/server/saved_objects/workpad_template.ts b/x-pack/plugins/canvas/server/saved_objects/workpad_template.ts index 224af9bed6759..2bf84d3d983c4 100644 --- a/x-pack/plugins/canvas/server/saved_objects/workpad_template.ts +++ b/x-pack/plugins/canvas/server/saved_objects/workpad_template.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { ANALYTICS_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { SavedObjectsType } from '@kbn/core/server'; import { TEMPLATE_TYPE } from '../../common/lib/constants'; import { @@ -16,6 +17,7 @@ export const workpadTemplateType = ( deps: CanvasSavedObjectTypeMigrationsDeps ): SavedObjectsType => ({ name: TEMPLATE_TYPE, + indexPattern: ANALYTICS_SAVED_OBJECT_INDEX, hidden: false, namespaceType: 'agnostic', mappings: { diff --git a/x-pack/plugins/canvas/tsconfig.json b/x-pack/plugins/canvas/tsconfig.json index 228ae010ddf43..6bd18423c138c 100644 --- a/x-pack/plugins/canvas/tsconfig.json +++ b/x-pack/plugins/canvas/tsconfig.json @@ -80,6 +80,7 @@ "@kbn/shared-ux-button-toolbar", "@kbn/saved-objects-finder-plugin", "@kbn/saved-objects-management-plugin", + "@kbn/core-saved-objects-server", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/canvas/types/telemetry.ts b/x-pack/plugins/canvas/types/telemetry.ts index 2f9f6d48ecda7..24c8126d29de6 100644 --- a/x-pack/plugins/canvas/types/telemetry.ts +++ b/x-pack/plugins/canvas/types/telemetry.ts @@ -8,11 +8,10 @@ import type { ElasticsearchClient } from '@kbn/core/server'; /** - Function for collecting information about canvas usage + Function for collecting information about canvas usage */ export type TelemetryCollector = ( - /** The server instance */ - kibanaIndex: string, + getIndexForType: (type: string) => Promise, /** Function for calling elasticsearch */ esClient: ElasticsearchClient ) => Record; diff --git a/x-pack/plugins/cases/server/saved_object_types/cases.ts b/x-pack/plugins/cases/server/saved_object_types/cases.ts index 1d70808f14db2..2a9492b6c755f 100644 --- a/x-pack/plugins/cases/server/saved_object_types/cases.ts +++ b/x-pack/plugins/cases/server/saved_object_types/cases.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { ALERTING_CASES_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import type { CoreSetup, Logger, @@ -22,6 +23,7 @@ export const createCaseSavedObjectType = ( logger: Logger ): SavedObjectsType => ({ name: CASE_SAVED_OBJECT, + indexPattern: ALERTING_CASES_SAVED_OBJECT_INDEX, hidden: true, namespaceType: 'multiple-isolated', convertToMultiNamespaceTypeVersion: '8.0.0', diff --git a/x-pack/plugins/cases/server/saved_object_types/comments.ts b/x-pack/plugins/cases/server/saved_object_types/comments.ts index 071f26b1e2bad..af15343741b3a 100644 --- a/x-pack/plugins/cases/server/saved_object_types/comments.ts +++ b/x-pack/plugins/cases/server/saved_object_types/comments.ts @@ -6,6 +6,7 @@ */ import type { SavedObjectsType } from '@kbn/core/server'; +import { ALERTING_CASES_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { CASE_COMMENT_SAVED_OBJECT } from '../../common/constants'; import type { CreateCommentsMigrationsDeps } from './migrations'; import { createCommentsMigrations } from './migrations'; @@ -21,6 +22,7 @@ export const createCaseCommentSavedObjectType = ({ migrationDeps: CreateCommentsMigrationsDeps; }): SavedObjectsType => ({ name: CASE_COMMENT_SAVED_OBJECT, + indexPattern: ALERTING_CASES_SAVED_OBJECT_INDEX, hidden: true, namespaceType: 'multiple-isolated', convertToMultiNamespaceTypeVersion: '8.0.0', diff --git a/x-pack/plugins/cases/server/saved_object_types/configure.ts b/x-pack/plugins/cases/server/saved_object_types/configure.ts index cd8b98daa3c94..105198825c68e 100644 --- a/x-pack/plugins/cases/server/saved_object_types/configure.ts +++ b/x-pack/plugins/cases/server/saved_object_types/configure.ts @@ -6,6 +6,7 @@ */ import type { SavedObjectsType } from '@kbn/core/server'; +import { ALERTING_CASES_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { CASE_CONFIGURE_SAVED_OBJECT } from '../../common/constants'; import { configureMigrations } from './migrations'; @@ -16,6 +17,7 @@ import { configureMigrations } from './migrations'; export const caseConfigureSavedObjectType: SavedObjectsType = { name: CASE_CONFIGURE_SAVED_OBJECT, + indexPattern: ALERTING_CASES_SAVED_OBJECT_INDEX, hidden: true, namespaceType: 'multiple-isolated', convertToMultiNamespaceTypeVersion: '8.0.0', diff --git a/x-pack/plugins/cases/server/saved_object_types/connector_mappings.ts b/x-pack/plugins/cases/server/saved_object_types/connector_mappings.ts index 81f9e1ca6f2f1..f25c14e3e8ec9 100644 --- a/x-pack/plugins/cases/server/saved_object_types/connector_mappings.ts +++ b/x-pack/plugins/cases/server/saved_object_types/connector_mappings.ts @@ -6,6 +6,7 @@ */ import type { SavedObjectsType } from '@kbn/core/server'; +import { ALERTING_CASES_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { CASE_CONNECTOR_MAPPINGS_SAVED_OBJECT } from '../../common/constants'; import { connectorMappingsMigrations } from './migrations'; @@ -16,6 +17,7 @@ import { connectorMappingsMigrations } from './migrations'; export const caseConnectorMappingsSavedObjectType: SavedObjectsType = { name: CASE_CONNECTOR_MAPPINGS_SAVED_OBJECT, + indexPattern: ALERTING_CASES_SAVED_OBJECT_INDEX, hidden: true, namespaceType: 'multiple-isolated', convertToMultiNamespaceTypeVersion: '8.0.0', diff --git a/x-pack/plugins/cases/server/saved_object_types/telemetry.ts b/x-pack/plugins/cases/server/saved_object_types/telemetry.ts index 515d1e63c7858..86beb594f466a 100644 --- a/x-pack/plugins/cases/server/saved_object_types/telemetry.ts +++ b/x-pack/plugins/cases/server/saved_object_types/telemetry.ts @@ -6,10 +6,12 @@ */ import type { SavedObjectsType } from '@kbn/core/server'; +import { ALERTING_CASES_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { CASE_TELEMETRY_SAVED_OBJECT } from '../../common/constants'; export const casesTelemetrySavedObjectType: SavedObjectsType = { name: CASE_TELEMETRY_SAVED_OBJECT, + indexPattern: ALERTING_CASES_SAVED_OBJECT_INDEX, hidden: false, namespaceType: 'agnostic', mappings: { diff --git a/x-pack/plugins/cases/server/saved_object_types/user_actions.ts b/x-pack/plugins/cases/server/saved_object_types/user_actions.ts index 60180595999aa..0195992105562 100644 --- a/x-pack/plugins/cases/server/saved_object_types/user_actions.ts +++ b/x-pack/plugins/cases/server/saved_object_types/user_actions.ts @@ -6,6 +6,7 @@ */ import type { SavedObjectsType } from '@kbn/core/server'; +import { ALERTING_CASES_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { CASE_USER_ACTION_SAVED_OBJECT } from '../../common/constants'; import type { UserActionsMigrationsDeps } from './migrations/user_actions'; import { createUserActionsMigrations } from './migrations/user_actions'; @@ -19,6 +20,7 @@ export const createCaseUserActionSavedObjectType = ( migrationDeps: UserActionsMigrationsDeps ): SavedObjectsType => ({ name: CASE_USER_ACTION_SAVED_OBJECT, + indexPattern: ALERTING_CASES_SAVED_OBJECT_INDEX, hidden: true, namespaceType: 'multiple-isolated', convertToMultiNamespaceTypeVersion: '8.0.0', diff --git a/x-pack/plugins/cloud_security_posture/server/saved_objects/saved_objects.ts b/x-pack/plugins/cloud_security_posture/server/saved_objects/saved_objects.ts index 92b709db1825c..e218808caa420 100644 --- a/x-pack/plugins/cloud_security_posture/server/saved_objects/saved_objects.ts +++ b/x-pack/plugins/cloud_security_posture/server/saved_objects/saved_objects.ts @@ -6,6 +6,7 @@ */ import { SavedObjectsServiceSetup } from '@kbn/core/server'; +import { SECURITY_SOLUTION_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { cspRuleTemplateSavedObjectMapping } from './mappings'; import { cspRuleTemplateMigrations } from './migrations'; import { @@ -20,6 +21,7 @@ import { CSP_RULE_TEMPLATE_SAVED_OBJECT_TYPE } from '../../common/constants'; export function setupSavedObjects(savedObjects: SavedObjectsServiceSetup) { savedObjects.registerType({ name: CSP_RULE_TEMPLATE_SAVED_OBJECT_TYPE, + indexPattern: SECURITY_SOLUTION_SAVED_OBJECT_INDEX, hidden: false, namespaceType: 'agnostic', management: { diff --git a/x-pack/plugins/cloud_security_posture/tsconfig.json b/x-pack/plugins/cloud_security_posture/tsconfig.json index a0fe377baf69c..468ef332df26f 100755 --- a/x-pack/plugins/cloud_security_posture/tsconfig.json +++ b/x-pack/plugins/cloud_security_posture/tsconfig.json @@ -46,6 +46,7 @@ "@kbn/ecs", "@kbn/core-saved-objects-api-server", "@kbn/shared-ux-router", + "@kbn/core-saved-objects-server", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/event_log/server/plugin.ts b/x-pack/plugins/event_log/server/plugin.ts index 98ddd770ac962..d5bf14a1259c1 100644 --- a/x-pack/plugins/event_log/server/plugin.ts +++ b/x-pack/plugins/event_log/server/plugin.ts @@ -61,13 +61,11 @@ export class Plugin implements CorePlugin ({ // Deprecated [GLOBAL_SETTINGS_SAVED_OBJECT_TYPE]: { name: GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, + indexPattern: INGEST_SAVED_OBJECT_INDEX, hidden: false, namespaceType: 'agnostic', management: { @@ -87,6 +89,7 @@ const getSavedObjectTypes = (): { [key: string]: SavedObjectsType } => ({ }, [AGENT_POLICY_SAVED_OBJECT_TYPE]: { name: AGENT_POLICY_SAVED_OBJECT_TYPE, + indexPattern: INGEST_SAVED_OBJECT_INDEX, hidden: false, namespaceType: 'agnostic', management: { @@ -130,6 +133,7 @@ const getSavedObjectTypes = (): { [key: string]: SavedObjectsType } => ({ }, [OUTPUT_SAVED_OBJECT_TYPE]: { name: OUTPUT_SAVED_OBJECT_TYPE, + indexPattern: INGEST_SAVED_OBJECT_INDEX, hidden: false, namespaceType: 'agnostic', management: { @@ -163,6 +167,7 @@ const getSavedObjectTypes = (): { [key: string]: SavedObjectsType } => ({ }, [PACKAGE_POLICY_SAVED_OBJECT_TYPE]: { name: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + indexPattern: INGEST_SAVED_OBJECT_INDEX, hidden: false, namespaceType: 'agnostic', management: { @@ -218,6 +223,7 @@ const getSavedObjectTypes = (): { [key: string]: SavedObjectsType } => ({ }, [PACKAGES_SAVED_OBJECT_TYPE]: { name: PACKAGES_SAVED_OBJECT_TYPE, + indexPattern: INGEST_SAVED_OBJECT_INDEX, hidden: false, namespaceType: 'agnostic', management: { @@ -286,6 +292,7 @@ const getSavedObjectTypes = (): { [key: string]: SavedObjectsType } => ({ }, [ASSETS_SAVED_OBJECT_TYPE]: { name: ASSETS_SAVED_OBJECT_TYPE, + indexPattern: INGEST_SAVED_OBJECT_INDEX, hidden: false, namespaceType: 'agnostic', management: { @@ -305,6 +312,7 @@ const getSavedObjectTypes = (): { [key: string]: SavedObjectsType } => ({ }, [PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE]: { name: PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE, + indexPattern: INGEST_SAVED_OBJECT_INDEX, hidden: false, namespaceType: 'agnostic', management: { @@ -318,6 +326,7 @@ const getSavedObjectTypes = (): { [key: string]: SavedObjectsType } => ({ }, [DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE]: { name: DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE, + indexPattern: INGEST_SAVED_OBJECT_INDEX, hidden: false, namespaceType: 'agnostic', management: { @@ -334,6 +343,7 @@ const getSavedObjectTypes = (): { [key: string]: SavedObjectsType } => ({ }, [FLEET_SERVER_HOST_SAVED_OBJECT_TYPE]: { name: FLEET_SERVER_HOST_SAVED_OBJECT_TYPE, + indexPattern: INGEST_SAVED_OBJECT_INDEX, hidden: false, namespaceType: 'agnostic', management: { @@ -351,6 +361,7 @@ const getSavedObjectTypes = (): { [key: string]: SavedObjectsType } => ({ }, [FLEET_PROXY_SAVED_OBJECT_TYPE]: { name: FLEET_PROXY_SAVED_OBJECT_TYPE, + indexPattern: INGEST_SAVED_OBJECT_INDEX, hidden: false, namespaceType: 'agnostic', management: { @@ -370,6 +381,7 @@ const getSavedObjectTypes = (): { [key: string]: SavedObjectsType } => ({ }, [MESSAGE_SIGNING_KEYS_SAVED_OBJECT_TYPE]: { name: MESSAGE_SIGNING_KEYS_SAVED_OBJECT_TYPE, + indexPattern: INGEST_SAVED_OBJECT_INDEX, hidden: true, namespaceType: 'agnostic', management: { diff --git a/x-pack/plugins/graph/server/saved_objects/graph_workspace.ts b/x-pack/plugins/graph/server/saved_objects/graph_workspace.ts index 4a53bba847543..c9ff8edae3b1c 100644 --- a/x-pack/plugins/graph/server/saved_objects/graph_workspace.ts +++ b/x-pack/plugins/graph/server/saved_objects/graph_workspace.ts @@ -5,11 +5,13 @@ * 2.0. */ +import { ANALYTICS_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { SavedObjectsType } from '@kbn/core/server'; import { graphMigrations } from './migrations'; export const graphWorkspace: SavedObjectsType = { name: 'graph-workspace', + indexPattern: ANALYTICS_SAVED_OBJECT_INDEX, namespaceType: 'multiple-isolated', convertToMultiNamespaceTypeVersion: '8.0.0', hidden: false, diff --git a/x-pack/plugins/graph/tsconfig.json b/x-pack/plugins/graph/tsconfig.json index 3979bb5b4d9a0..579cda78b0fed 100644 --- a/x-pack/plugins/graph/tsconfig.json +++ b/x-pack/plugins/graph/tsconfig.json @@ -40,6 +40,7 @@ "@kbn/shared-ux-router", "@kbn/saved-objects-management-plugin", "@kbn/saved-objects-finder-plugin", + "@kbn/core-saved-objects-server", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/lens/server/saved_objects.ts b/x-pack/plugins/lens/server/saved_objects.ts index ed3451862b176..b91d138902140 100644 --- a/x-pack/plugins/lens/server/saved_objects.ts +++ b/x-pack/plugins/lens/server/saved_objects.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { ANALYTICS_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { CoreSetup } from '@kbn/core/server'; import { DataViewPersistableStateService } from '@kbn/data-views-plugin/common'; import { MigrateFunctionsObject } from '@kbn/kibana-utils-plugin/common'; @@ -19,6 +20,7 @@ export function setupSavedObjects( ) { core.savedObjects.registerType({ name: 'lens', + indexPattern: ANALYTICS_SAVED_OBJECT_INDEX, hidden: false, namespaceType: 'multiple-isolated', convertToMultiNamespaceTypeVersion: '8.0.0', @@ -59,6 +61,7 @@ export function setupSavedObjects( core.savedObjects.registerType({ name: 'lens-ui-telemetry', + indexPattern: ANALYTICS_SAVED_OBJECT_INDEX, hidden: false, namespaceType: 'single', mappings: { diff --git a/x-pack/plugins/lists/server/saved_objects/exception_list.ts b/x-pack/plugins/lists/server/saved_objects/exception_list.ts index 4d401da8d74d2..b475f1586eb1f 100644 --- a/x-pack/plugins/lists/server/saved_objects/exception_list.ts +++ b/x-pack/plugins/lists/server/saved_objects/exception_list.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { SECURITY_SOLUTION_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { SavedObjectsType } from '@kbn/core/server'; import { exceptionListAgnosticSavedObjectType, @@ -182,6 +183,7 @@ const combinedMappings: SavedObjectsType['mappings'] = { export const exceptionListType: SavedObjectsType = { convertToMultiNamespaceTypeVersion: '8.0.0', hidden: false, + indexPattern: SECURITY_SOLUTION_SAVED_OBJECT_INDEX, mappings: combinedMappings, migrations, name: exceptionListSavedObjectType, @@ -190,6 +192,7 @@ export const exceptionListType: SavedObjectsType = { export const exceptionListAgnosticType: SavedObjectsType = { hidden: false, + indexPattern: SECURITY_SOLUTION_SAVED_OBJECT_INDEX, mappings: combinedMappings, migrations, name: exceptionListAgnosticSavedObjectType, diff --git a/x-pack/plugins/lists/tsconfig.json b/x-pack/plugins/lists/tsconfig.json index e6fb0a15e7586..b18887c254336 100644 --- a/x-pack/plugins/lists/tsconfig.json +++ b/x-pack/plugins/lists/tsconfig.json @@ -38,6 +38,7 @@ "@kbn/logging-mocks", "@kbn/utility-types", "@kbn/core-elasticsearch-client-server-mocks", + "@kbn/core-saved-objects-server", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/maps/server/saved_objects/setup_saved_objects.ts b/x-pack/plugins/maps/server/saved_objects/setup_saved_objects.ts index a8500968af3cf..af9dc73dbc646 100644 --- a/x-pack/plugins/maps/server/saved_objects/setup_saved_objects.ts +++ b/x-pack/plugins/maps/server/saved_objects/setup_saved_objects.ts @@ -8,8 +8,9 @@ import { mapValues } from 'lodash'; import type { CoreSetup, SavedObjectUnsanitizedDoc } from '@kbn/core/server'; import type { SavedObjectMigrationMap } from '@kbn/core/server'; -import { MigrateFunctionsObject } from '@kbn/kibana-utils-plugin/common'; +import type { MigrateFunctionsObject } from '@kbn/kibana-utils-plugin/common'; import { mergeSavedObjectMigrationMaps } from '@kbn/core/server'; +import { ANALYTICS_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { APP_ICON, getFullPath } from '../../common/constants'; import { CONTENT_ID } from '../../common/content_management'; import { migrateDataPersistedState } from '../../common/migrations/migrate_data_persisted_state'; @@ -24,6 +25,7 @@ export function setupSavedObjects( ) { core.savedObjects.registerType({ name: CONTENT_ID, + indexPattern: ANALYTICS_SAVED_OBJECT_INDEX, hidden: false, namespaceType: 'multiple-isolated', convertToMultiNamespaceTypeVersion: '8.0.0', diff --git a/x-pack/plugins/maps/tsconfig.json b/x-pack/plugins/maps/tsconfig.json index 398139fe00d34..cdbebc4ad74b3 100644 --- a/x-pack/plugins/maps/tsconfig.json +++ b/x-pack/plugins/maps/tsconfig.json @@ -68,6 +68,7 @@ "@kbn/object-versioning", "@kbn/field-types", "@kbn/content-management-utils", + "@kbn/core-saved-objects-server", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/ml/server/plugin.ts b/x-pack/plugins/ml/server/plugin.ts index 81e8316f1bda6..db150f7c42f32 100644 --- a/x-pack/plugins/ml/server/plugin.ts +++ b/x-pack/plugins/ml/server/plugin.ts @@ -252,7 +252,11 @@ export class MlServerPlugin } if (plugins.usageCollection) { - registerCollector(plugins.usageCollection, coreSetup.savedObjects.getKibanaIndex()); + const getIndexForType = (type: string) => + coreSetup + .getStartServices() + .then(([coreStart]) => coreStart.savedObjects.getIndexForType(type)); + registerCollector(plugins.usageCollection, getIndexForType); } if (plugins.cases) { diff --git a/x-pack/plugins/ml/server/usage/collector.ts b/x-pack/plugins/ml/server/usage/collector.ts index be09ccae6367a..876e604fcf6f2 100644 --- a/x-pack/plugins/ml/server/usage/collector.ts +++ b/x-pack/plugins/ml/server/usage/collector.ts @@ -31,7 +31,10 @@ export interface MlUsageData { }; } -export function registerCollector(usageCollection: UsageCollectionSetup, kibanaIndex: string) { +export function registerCollector( + usageCollection: UsageCollectionSetup, + getIndexForType: (type: string) => Promise +) { const collector = usageCollection.makeUsageCollector({ type: 'ml', schema: { @@ -86,11 +89,12 @@ export function registerCollector(usageCollection: UsageCollectionSetup, kibanaI }, }, }, - isReady: () => !!kibanaIndex, + isReady: () => true, fetch: async ({ esClient }) => { + const alertIndex = await getIndexForType('alert'); const result = await esClient.search( { - index: kibanaIndex, + index: alertIndex, size: 0, body: { query: { @@ -137,7 +141,7 @@ export function registerCollector(usageCollection: UsageCollectionSetup, kibanaI }; }>( { - index: kibanaIndex, + index: alertIndex, size: 10000, body: { query: { diff --git a/x-pack/plugins/monitoring/server/plugin.ts b/x-pack/plugins/monitoring/server/plugin.ts index c81fb90e614cb..5996eb125b6dc 100644 --- a/x-pack/plugins/monitoring/server/plugin.ts +++ b/x-pack/plugins/monitoring/server/plugin.ts @@ -104,7 +104,7 @@ export class MonitoringPlugin kibanaStats: { uuid: this.initializerContext.env.instanceUuid, name: serverInfo.name, - index: coreSetup.savedObjects.getKibanaIndex(), + index: coreSetup.savedObjects.getDefaultIndex(), host: serverInfo.hostname, locale: i18n.getLocale(), port: serverInfo.port.toString(), diff --git a/x-pack/plugins/monitoring_collection/server/plugin.ts b/x-pack/plugins/monitoring_collection/server/plugin.ts index 1c30a8439cf3c..fdfe532ced5db 100644 --- a/x-pack/plugins/monitoring_collection/server/plugin.ts +++ b/x-pack/plugins/monitoring_collection/server/plugin.ts @@ -72,7 +72,7 @@ export class MonitoringCollectionPlugin implements Plugin { const mockIndex = 'mock_index'; + const getIndexForType = () => Promise.resolve(mockIndex); it('makes a usage collector and registers it`', () => { const mockCollectorSet = createUsageCollectionSetupMock(); - registerRollupUsageCollector(mockCollectorSet, mockIndex); + registerRollupUsageCollector(mockCollectorSet, getIndexForType); expect(mockCollectorSet.makeUsageCollector).toBeCalledTimes(1); expect(mockCollectorSet.registerCollector).toBeCalledTimes(1); }); it('makeUsageCollector configs fit the shape', () => { const mockCollectorSet = createUsageCollectionSetupMock(); - registerRollupUsageCollector(mockCollectorSet, mockIndex); + registerRollupUsageCollector(mockCollectorSet, getIndexForType); expect(mockCollectorSet.makeUsageCollector).toHaveBeenCalledWith({ type: 'rollups', isReady: expect.any(Function), @@ -81,7 +82,7 @@ describe('registerRollupUsageCollector', () => { it('makeUsageCollector config.isReady returns true', () => { const mockCollectorSet = createUsageCollectionSetupMock(); - registerRollupUsageCollector(mockCollectorSet, mockIndex); + registerRollupUsageCollector(mockCollectorSet, getIndexForType); const usageCollectorConfig = mockCollectorSet.makeUsageCollector.mock.calls[0][0]; expect(usageCollectorConfig.isReady()).toBe(true); }); diff --git a/x-pack/plugins/rollup/server/collectors/register.ts b/x-pack/plugins/rollup/server/collectors/register.ts index 6fe44ee0309f9..6c893e058f0e7 100644 --- a/x-pack/plugins/rollup/server/collectors/register.ts +++ b/x-pack/plugins/rollup/server/collectors/register.ts @@ -38,7 +38,7 @@ interface Usage { export function registerRollupUsageCollector( usageCollection: UsageCollectionSetup, - kibanaIndex: string + getIndexForType: (type: string) => Promise ): void { const collector = usageCollection.makeUsageCollector({ type: 'rollups', @@ -92,23 +92,26 @@ export function registerRollupUsageCollector( }, }, fetch: async ({ esClient }: CollectorFetchContext) => { - const rollupIndexPatterns = await fetchRollupIndexPatterns(kibanaIndex, esClient); + const indexPatternIndex = await getIndexForType('index-pattern'); + const rollupIndexPatterns = await fetchRollupIndexPatterns(indexPatternIndex, esClient); const rollupIndexPatternToFlagMap = createIdToFlagMap(rollupIndexPatterns); + const searchIndex = await getIndexForType('search'); const rollupSavedSearches = await fetchRollupSavedSearches( - kibanaIndex, + searchIndex, esClient, rollupIndexPatternToFlagMap ); const rollupSavedSearchesToFlagMap = createIdToFlagMap(rollupSavedSearches); + const visualizationIndex = await getIndexForType('visualization'); const { rollupVisualizations, rollupVisualizationsFromSavedSearches, rollupLensVisualizations, rollupLensVisualizationsFromSavedSearches, } = await fetchRollupVisualizations( - kibanaIndex, + visualizationIndex, esClient, rollupIndexPatternToFlagMap, rollupSavedSearchesToFlagMap diff --git a/x-pack/plugins/rollup/server/plugin.ts b/x-pack/plugins/rollup/server/plugin.ts index 91538ae9a375d..06416685ab508 100644 --- a/x-pack/plugins/rollup/server/plugin.ts +++ b/x-pack/plugins/rollup/server/plugin.ts @@ -92,7 +92,9 @@ export class RollupPlugin implements Plugin { if (usageCollection) { try { - registerRollupUsageCollector(usageCollection, savedObjects.getKibanaIndex()); + const getIndexForType = (type: string) => + getStartServices().then(([coreStart]) => coreStart.savedObjects.getIndexForType(type)); + registerRollupUsageCollector(usageCollection, getIndexForType); } catch (e) { this.logger.warn(`Registering Rollup collector failed: ${e}`); } diff --git a/x-pack/plugins/saved_objects_tagging/server/plugin.ts b/x-pack/plugins/saved_objects_tagging/server/plugin.ts index 59c40ab4f124b..39ab63e1b01d4 100644 --- a/x-pack/plugins/saved_objects_tagging/server/plugin.ts +++ b/x-pack/plugins/saved_objects_tagging/server/plugin.ts @@ -36,7 +36,7 @@ export class SavedObjectTaggingPlugin implements Plugin<{}, SavedObjectTaggingStart, SetupDeps, StartDeps> { public setup( - { savedObjects, http }: CoreSetup, + { savedObjects, http, getStartServices }: CoreSetup, { features, usageCollection, security }: SetupDeps ) { savedObjects.registerType(tagType); @@ -54,10 +54,11 @@ export class SavedObjectTaggingPlugin features.registerKibanaFeature(savedObjectsTaggingFeature); if (usageCollection) { + const kibanaIndices = savedObjects.getAllIndices(); usageCollection.registerCollector( createTagUsageCollector({ usageCollection, - kibanaIndex: savedObjects.getKibanaIndex(), + kibanaIndices, }) ); } diff --git a/x-pack/plugins/saved_objects_tagging/server/usage/fetch_tag_usage_data.ts b/x-pack/plugins/saved_objects_tagging/server/usage/fetch_tag_usage_data.ts index 8e10bfd5ad1f5..a98b39888a6cc 100644 --- a/x-pack/plugins/saved_objects_tagging/server/usage/fetch_tag_usage_data.ts +++ b/x-pack/plugins/saved_objects_tagging/server/usage/fetch_tag_usage_data.ts @@ -31,13 +31,13 @@ interface AggregatedTagUsage { export const fetchTagUsageData = async ({ esClient, - kibanaIndex, + kibanaIndices, }: { esClient: ElasticsearchClient; - kibanaIndex: string; + kibanaIndices: string[]; }): Promise => { const body = await esClient.search({ - index: [kibanaIndex], + index: kibanaIndices, ignore_unavailable: true, filter_path: 'aggregations', body: { diff --git a/x-pack/plugins/saved_objects_tagging/server/usage/tag_usage_collector.ts b/x-pack/plugins/saved_objects_tagging/server/usage/tag_usage_collector.ts index 6e4494f225fe7..9af63f3cb6adb 100644 --- a/x-pack/plugins/saved_objects_tagging/server/usage/tag_usage_collector.ts +++ b/x-pack/plugins/saved_objects_tagging/server/usage/tag_usage_collector.ts @@ -12,17 +12,17 @@ import { tagUsageCollectorSchema } from './schema'; export const createTagUsageCollector = ({ usageCollection, - kibanaIndex, + kibanaIndices, }: { usageCollection: UsageCollectionSetup; - kibanaIndex: string; + kibanaIndices: string[]; }) => { return usageCollection.makeUsageCollector({ type: 'saved_objects_tagging', isReady: () => true, schema: tagUsageCollectorSchema, - fetch: ({ esClient }) => { - return fetchTagUsageData({ esClient, kibanaIndex }); + fetch: async ({ esClient }) => { + return fetchTagUsageData({ esClient, kibanaIndices }); }, }); }; diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts index 7e29008ae754b..137d54308dc31 100644 --- a/x-pack/plugins/security/server/plugin.ts +++ b/x-pack/plugins/security/server/plugin.ts @@ -229,7 +229,7 @@ export class SecurityPlugin core: CoreSetup, { features, licensing, taskManager, usageCollection, spaces }: PluginSetupDependencies ) { - this.kibanaIndexName = core.savedObjects.getKibanaIndex(); + this.kibanaIndexName = core.savedObjects.getDefaultIndex(); const config$ = this.initializerContext.config.create>().pipe( map((rawConfig) => createConfig(rawConfig, this.initializerContext.logger.get('config'), { diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/saved_object_mappings.ts b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/saved_object_mappings.ts index 9ce550859da4d..826002f5970cd 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/saved_object_mappings.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/saved_object_mappings.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { SECURITY_SOLUTION_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import type { SavedObjectsType } from '@kbn/core/server'; import { ArtifactConstants, ManifestConstants } from './common'; @@ -82,6 +83,7 @@ export const manifestSavedObjectMappings: SavedObjectsType['mappings'] = { export const exceptionsArtifactType: SavedObjectsType = { name: exceptionsArtifactSavedObjectType, + indexPattern: SECURITY_SOLUTION_SAVED_OBJECT_INDEX, hidden: false, namespaceType: 'agnostic', mappings: exceptionsArtifactSavedObjectMappings, @@ -89,6 +91,7 @@ export const exceptionsArtifactType: SavedObjectsType = { export const manifestType: SavedObjectsType = { name: manifestSavedObjectType, + indexPattern: SECURITY_SOLUTION_SAVED_OBJECT_INDEX, hidden: false, namespaceType: 'agnostic', mappings: manifestSavedObjectMappings, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/saved_objects.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/saved_objects.ts index fcb6aede4973f..180aecfeb34d6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/saved_objects.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/saved_objects.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { SECURITY_SOLUTION_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import type { SavedObjectsType } from '@kbn/core/server'; export const signalsMigrationType = 'security-solution-signals-migration'; @@ -26,6 +27,7 @@ export const signalsMigrationMappings: SavedObjectsType['mappings'] = { export const type: SavedObjectsType = { name: signalsMigrationType, + indexPattern: SECURITY_SOLUTION_SAVED_OBJECT_INDEX, hidden: false, namespaceType: 'single', mappings: signalsMigrationMappings, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/rule_assets/prebuilt_rule_assets_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/rule_assets/prebuilt_rule_assets_type.ts index 74b5d6fbffadd..be989326f6963 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/rule_assets/prebuilt_rule_assets_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/rule_assets/prebuilt_rule_assets_type.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { SECURITY_SOLUTION_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import type { SavedObjectsType } from '@kbn/core/server'; export const PREBUILT_RULE_ASSETS_SO_TYPE = 'security-rule'; @@ -23,6 +24,7 @@ const prebuiltRuleAssetMappings: SavedObjectsType['mappings'] = { export const prebuiltRuleAssetType: SavedObjectsType = { name: PREBUILT_RULE_ASSETS_SO_TYPE, + indexPattern: SECURITY_SOLUTION_SAVED_OBJECT_INDEX, hidden: false, management: { importableAndExportable: true, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_saved_object_mappings.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_saved_object_mappings.ts index 1868cb238b11e..3612a4bf744f0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_saved_object_mappings.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_saved_object_mappings.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { SECURITY_SOLUTION_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import type { SavedObjectsType } from '@kbn/core/server'; // eslint-disable-next-line no-restricted-imports import { legacyRuleActionsSavedObjectMigration } from './legacy_migrations'; @@ -61,6 +62,7 @@ const legacyRuleActionsSavedObjectMappings: SavedObjectsType['mappings'] = { export const legacyType: SavedObjectsType = { convertToMultiNamespaceTypeVersion: '8.0.0', name: legacyRuleActionsSavedObjectType, + indexPattern: SECURITY_SOLUTION_SAVED_OBJECT_INDEX, hidden: false, namespaceType: 'multiple-isolated', mappings: legacyRuleActionsSavedObjectMappings, diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/receiver.ts b/x-pack/plugins/security_solution/server/lib/telemetry/receiver.ts index 03614a4bfc0ac..9526ec07ea2a6 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/receiver.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/receiver.ts @@ -73,7 +73,7 @@ import { PREBUILT_RULES_PACKAGE_NAME } from '../../../common/detection_engine/co export interface ITelemetryReceiver { start( core?: CoreStart, - kibanaIndex?: string, + getIndexForType?: (type: string) => string, alertsIndex?: string, endpointContextService?: EndpointAppContextService, exceptionListClient?: ExceptionListClient, @@ -185,7 +185,7 @@ export class TelemetryReceiver implements ITelemetryReceiver { private esClient?: ElasticsearchClient; private exceptionListClient?: ExceptionListClient; private soClient?: SavedObjectsClientContract; - private kibanaIndex?: string; + private getIndexForType?: (type: string) => string; private alertsIndex?: string; private clusterInfo?: ESClusterInfo; private processTreeFetcher?: Fetcher; @@ -198,13 +198,13 @@ export class TelemetryReceiver implements ITelemetryReceiver { public async start( core?: CoreStart, - kibanaIndex?: string, + getIndexForType?: (type: string) => string, alertsIndex?: string, endpointContextService?: EndpointAppContextService, exceptionListClient?: ExceptionListClient, packageService?: PackageService ) { - this.kibanaIndex = kibanaIndex; + this.getIndexForType = getIndexForType; this.alertsIndex = alertsIndex; this.agentClient = endpointContextService?.getInternalFleetServices().agent; this.agentPolicyService = endpointContextService?.getInternalFleetServices().agentPolicy; @@ -493,7 +493,7 @@ export class TelemetryReceiver implements ITelemetryReceiver { const query: SearchRequest = { expand_wildcards: ['open' as const, 'hidden' as const], - index: `${this.kibanaIndex}*`, + index: this.getIndexForType?.('alert'), ignore_unavailable: true, body: { size: this.maxRecords, @@ -924,7 +924,7 @@ export class TelemetryReceiver implements ITelemetryReceiver { }; const exceptionListQuery: SearchRequest = { expand_wildcards: ['open' as const, 'hidden' as const], - index: `${this.kibanaIndex}*`, + index: this.getIndexForType?.('exception-list'), ignore_unavailable: true, body: { size: 0, // no query results required - only aggregation quantity @@ -944,7 +944,7 @@ export class TelemetryReceiver implements ITelemetryReceiver { }; const indicatorMatchRuleQuery: SearchRequest = { expand_wildcards: ['open' as const, 'hidden' as const], - index: `${this.kibanaIndex}*`, + index: this.getIndexForType?.('alert'), ignore_unavailable: true, body: { size: 0, diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/notes.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/notes.ts index 98a965ea0c26b..0a8f59adfa19f 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/notes.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/notes.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { SECURITY_SOLUTION_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import type { SavedObjectsType } from '@kbn/core/server'; import { notesMigrations } from './migrations/notes'; @@ -35,6 +36,7 @@ export const noteSavedObjectMappings: SavedObjectsType['mappings'] = { export const noteType: SavedObjectsType = { name: noteSavedObjectType, + indexPattern: SECURITY_SOLUTION_SAVED_OBJECT_INDEX, hidden: false, namespaceType: 'multiple-isolated', convertToMultiNamespaceTypeVersion: '8.0.0', diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/pinned_events.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/pinned_events.ts index 0df23655c6af8..863f14b3ab39f 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/pinned_events.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/pinned_events.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { SECURITY_SOLUTION_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import type { SavedObjectsType } from '@kbn/core/server'; import { pinnedEventsMigrations } from './migrations/pinned_events'; @@ -32,6 +33,7 @@ export const pinnedEventSavedObjectMappings: SavedObjectsType['mappings'] = { export const pinnedEventType: SavedObjectsType = { name: pinnedEventSavedObjectType, + indexPattern: SECURITY_SOLUTION_SAVED_OBJECT_INDEX, hidden: false, namespaceType: 'multiple-isolated', convertToMultiNamespaceTypeVersion: '8.0.0', diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/timelines.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/timelines.ts index e58c7a70739ab..4f7df977662f4 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/timelines.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/timelines.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { SECURITY_SOLUTION_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import type { SavedObjectsType } from '@kbn/core/server'; import { timelinesMigrations } from './migrations/timelines'; @@ -317,6 +318,7 @@ export const timelineSavedObjectMappings: SavedObjectsType['mappings'] = { export const timelineType: SavedObjectsType = { name: timelineSavedObjectType, + indexPattern: SECURITY_SOLUTION_SAVED_OBJECT_INDEX, hidden: false, namespaceType: 'multiple-isolated', convertToMultiNamespaceTypeVersion: '8.0.0', diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index e6ad26f1f405f..e74138f61e195 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -128,7 +128,6 @@ export class Plugin implements ISecuritySolutionPlugin { private checkMetadataTransformsTask: CheckMetadataTransformsTask | undefined; private artifactsCache: LRU; private telemetryUsageCounter?: UsageCounter; - private kibanaIndex?: string; private endpointContext: EndpointAppContext; constructor(context: PluginInitializerContext) { @@ -159,7 +158,6 @@ export class Plugin implements ISecuritySolutionPlugin { const { appClientFactory, pluginContext, config, logger } = this; const experimentalFeatures = config.experimentalFeatures; - this.kibanaIndex = core.savedObjects.getKibanaIndex(); initSavedObjects(core.savedObjects); initUiSettings(core.uiSettings, experimentalFeatures); @@ -516,8 +514,7 @@ export class Plugin implements ISecuritySolutionPlugin { this.telemetryReceiver.start( core, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - this.kibanaIndex!, + (type: string) => core.savedObjects.getIndexForType(type), DEFAULT_ALERTS_INDEX, this.endpointAppContextService, exceptionListClient, diff --git a/x-pack/plugins/spaces/server/plugin.ts b/x-pack/plugins/spaces/server/plugin.ts index ad73be5e5c19b..19c79c244e6c1 100644 --- a/x-pack/plugins/spaces/server/plugin.ts +++ b/x-pack/plugins/spaces/server/plugin.ts @@ -173,8 +173,10 @@ export class SpacesPlugin setupCapabilities(core, getSpacesService, this.log); if (plugins.usageCollection) { + const getIndexForType = (type: string) => + core.getStartServices().then(([coreStart]) => coreStart.savedObjects.getIndexForType(type)); registerSpacesUsageCollector(plugins.usageCollection, { - kibanaIndex: core.savedObjects.getKibanaIndex(), + getIndexForType, features: plugins.features, licensing: plugins.licensing, usageStatsServicePromise, diff --git a/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.test.ts b/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.test.ts index 1d93fb7787050..36d2c99503c11 100644 --- a/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.test.ts +++ b/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.test.ts @@ -45,6 +45,7 @@ const MOCK_USAGE_STATS: UsageStats = { }; const kibanaIndex = '.kibana-tests'; +const getIndexForType = () => Promise.resolve(kibanaIndex); function setup({ license = { isAvailable: true }, @@ -122,7 +123,7 @@ describe('error handling', () => { license: { isAvailable: true, type: 'basic' }, }); const collector = getSpacesUsageCollector(usageCollection as any, { - kibanaIndex, + getIndexForType, features, licensing, usageStatsServicePromise: Promise.resolve(usageStatsService), @@ -146,7 +147,7 @@ describe('with a basic license', () => { beforeAll(async () => { const collector = getSpacesUsageCollector(usageCollection as any, { - kibanaIndex, + getIndexForType, features, licensing, usageStatsServicePromise: Promise.resolve(usageStatsService), @@ -205,7 +206,7 @@ describe('with no license', () => { beforeAll(async () => { const collector = getSpacesUsageCollector(usageCollection as any, { - kibanaIndex, + getIndexForType, features, licensing, usageStatsServicePromise: Promise.resolve(usageStatsService), @@ -246,7 +247,7 @@ describe('with platinum license', () => { beforeAll(async () => { const collector = getSpacesUsageCollector(usageCollection as any, { - kibanaIndex, + getIndexForType, features, licensing, usageStatsServicePromise: Promise.resolve(usageStatsService), diff --git a/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.ts b/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.ts index d994d42f85a8a..bb41531ea9100 100644 --- a/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.ts +++ b/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.ts @@ -149,7 +149,7 @@ export interface UsageData extends UsageStats { } interface CollectorDeps { - kibanaIndex: string; + getIndexForType: (type: string) => Promise; features: PluginsSetup['features']; licensing: PluginsSetup['licensing']; usageStatsServicePromise: Promise; @@ -453,11 +453,12 @@ export function getSpacesUsageCollector( }, }, fetch: async ({ esClient }: CollectorFetchContext) => { - const { licensing, kibanaIndex, features, usageStatsServicePromise } = deps; + const { licensing, getIndexForType, features, usageStatsServicePromise } = deps; const license = await firstValueFrom(licensing.license$); const available = license.isAvailable; // some form of spaces is available for all valid licenses - const usageData = await getSpacesUsage(esClient, kibanaIndex, features, available); + const spaceIndex = await getIndexForType('space'); + const usageData = await getSpacesUsage(esClient, spaceIndex, features, available); const usageStats = await getUsageStats(usageStatsServicePromise, available); return { diff --git a/x-pack/plugins/task_manager/server/plugin.ts b/x-pack/plugins/task_manager/server/plugin.ts index 1fb28465f5bce..98b6d711b5539 100644 --- a/x-pack/plugins/task_manager/server/plugin.ts +++ b/x-pack/plugins/task_manager/server/plugin.ts @@ -127,7 +127,7 @@ export class TaskManagerPlugin config: this.config!, usageCounter: this.usageCounter!, kibanaVersion: this.kibanaVersion, - kibanaIndexName: core.savedObjects.getKibanaIndex(), + kibanaIndexName: core.savedObjects.getDefaultIndex(), getClusterClient: () => startServicesPromise.then(({ elasticsearch }) => elasticsearch.client), shouldRunTasks: this.shouldRunBackgroundTasks, @@ -141,7 +141,7 @@ export class TaskManagerPlugin config: this.config!, usageCounter: this.usageCounter!, kibanaVersion: this.kibanaVersion, - kibanaIndexName: core.savedObjects.getKibanaIndex(), + kibanaIndexName: core.savedObjects.getDefaultIndex(), getClusterClient: () => startServicesPromise.then(({ elasticsearch }) => elasticsearch.client), }); diff --git a/x-pack/test/alerting_api_integration/common/lib/task_manager_utils.ts b/x-pack/test/alerting_api_integration/common/lib/task_manager_utils.ts index 19d62bdd0682a..a4ab6442df1fd 100644 --- a/x-pack/test/alerting_api_integration/common/lib/task_manager_utils.ts +++ b/x-pack/test/alerting_api_integration/common/lib/task_manager_utils.ts @@ -5,6 +5,7 @@ * 2.0. */ import type { Client } from '@elastic/elasticsearch'; +import { ALERTING_CASES_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { SerializedConcreteTaskInstance } from '@kbn/task-manager-plugin/server/task'; export interface TaskManagerDoc { @@ -138,7 +139,7 @@ export class TaskManagerUtils { async waitForActionTaskParamsToBeCleanedUp(createdAtFilter: Date): Promise { return await this.retry.try(async () => { const searchResult = await this.es.search({ - index: '.kibana', + index: ALERTING_CASES_SAVED_OBJECT_INDEX, body: { query: { bool: { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/action_task_params/migrations.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/action_task_params/migrations.ts index 8a6532302a2e1..c7c9611b21312 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/action_task_params/migrations.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/action_task_params/migrations.ts @@ -7,6 +7,7 @@ import expect from '@kbn/expect'; import { SavedObject, SavedObjectReference } from '@kbn/core/server'; +import { ALERTING_CASES_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { ActionTaskParams } from '@kbn/actions-plugin/server/types'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; @@ -28,7 +29,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { // Inspect migration of non-preconfigured connector ID const response = await es.get>( { - index: '.kibana', + index: ALERTING_CASES_SAVED_OBJECT_INDEX, id: 'action_task_params:b9af6280-0052-11ec-917b-f7aa317691ed', }, { meta: true } @@ -54,7 +55,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { // Inspect migration of preconfigured connector ID const preconfiguredConnectorResponse = await es.get>( { - index: '.kibana', + index: ALERTING_CASES_SAVED_OBJECT_INDEX, id: 'action_task_params:0205a520-0054-11ec-917b-f7aa317691ed', }, { meta: true } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/create.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/create.ts index 3f540927a9be7..7c49eedd3b85e 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/create.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/create.ts @@ -8,6 +8,7 @@ import expect from '@kbn/expect'; import { SavedObject } from '@kbn/core/server'; import { RawRule } from '@kbn/alerting-plugin/server/types'; +import { ALERTING_CASES_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { Spaces } from '../../../scenarios'; import { checkAAD, @@ -213,7 +214,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { const esResponse = await es.get>( { - index: '.kibana', + index: ALERTING_CASES_SAVED_OBJECT_INDEX, id: `alert:${response.body.id}`, }, { meta: true } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/migrations.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/migrations.ts index 50667e7742374..788fe8babbf29 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/migrations.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/migrations.ts @@ -10,6 +10,7 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { RawRule, RawRuleAction } from '@kbn/alerting-plugin/server/types'; import { FILEBEAT_7X_INDICATOR_PATH } from '@kbn/alerting-plugin/server/saved_objects/migrations'; import type { SavedObjectReference } from '@kbn/core/server'; +import { ALERTING_CASES_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { getUrlPrefix } from '../../../../common/lib'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; @@ -185,7 +186,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { // NOTE: We have to use elasticsearch directly against the ".kibana" index because alerts do not expose the references which we want to test exists const response = await es.get<{ references: [{}] }>( { - index: '.kibana', + index: ALERTING_CASES_SAVED_OBJECT_INDEX, id: 'alert:38482620-ef1b-11eb-ad71-7de7959be71c', }, { meta: true } @@ -208,7 +209,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { it('7.16.0 migrates existing alerts to contain legacyId field', async () => { const searchResult = await es.search( { - index: '.kibana', + index: ALERTING_CASES_SAVED_OBJECT_INDEX, body: { query: { term: { @@ -230,7 +231,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { it('7.16.0 migrates existing rules so predefined connectors are not stored in references', async () => { const searchResult = await es.search( { - index: '.kibana', + index: ALERTING_CASES_SAVED_OBJECT_INDEX, body: { query: { term: { @@ -273,7 +274,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { // NOTE: We hae to use elastic search directly against the ".kibana" index because alerts do not expose the references which we want to test exists const response = await es.get<{ references: [{}] }>( { - index: '.kibana', + index: ALERTING_CASES_SAVED_OBJECT_INDEX, id: 'alert:d7a8c6a1-9394-48df-a634-d5457c35d747', }, { meta: true } @@ -297,7 +298,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { }; }>( { - index: '.kibana', + index: ALERTING_CASES_SAVED_OBJECT_INDEX, id: 'alert:ece1ece2-9394-48df-a634-d5457c351ece', }, { meta: true } @@ -317,7 +318,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { }; }>( { - index: '.kibana', + index: ALERTING_CASES_SAVED_OBJECT_INDEX, id: 'alert:fce1ece2-9394-48df-a634-d5457c351fce', }, { meta: true } @@ -337,7 +338,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { }; }>( { - index: '.kibana', + index: ALERTING_CASES_SAVED_OBJECT_INDEX, id: 'alert:1ce1ece2-9394-48df-a634-d5457c3511ce', }, { meta: true } @@ -351,7 +352,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { it('8.0 migrates incorrect action group spellings on the Metrics Inventory Threshold rule type', async () => { const response = await es.get<{ alert: RawRule }>( { - index: '.kibana', + index: ALERTING_CASES_SAVED_OBJECT_INDEX, id: 'alert:92237b30-4e03-11ec-9ab9-d980518a2d28', }, { meta: true } @@ -365,7 +366,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { it('8.0 migrates and disables pre-existing rules', async () => { const response = await es.get<{ alert: RawRule }>( { - index: '.kibana', + index: ALERTING_CASES_SAVED_OBJECT_INDEX, id: 'alert:38482620-ef1b-11eb-ad71-7de7959be71c', }, { meta: true } @@ -378,7 +379,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { it('8.0.1 migrates and adds tags to disabled rules in 8.0', async () => { const responseEnabledBeforeMigration = await es.get<{ alert: RawRule }>( { - index: '.kibana', + index: ALERTING_CASES_SAVED_OBJECT_INDEX, id: 'alert:1efdfa40-8ec7-11ec-a700-5524407a7653', }, { meta: true } @@ -386,7 +387,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { expect(responseEnabledBeforeMigration.statusCode).toEqual(200); const responseDisabledBeforeMigration = await es.get<{ alert: RawRule }>( { - index: '.kibana', + index: ALERTING_CASES_SAVED_OBJECT_INDEX, id: 'alert:13fdfa40-8ec7-11ec-a700-5524407a7667', }, { meta: true } @@ -407,7 +408,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { it('8.2.0 migrates params to mapped_params for specific params properties', async () => { const response = await es.get<{ alert: RawRule }>( { - index: '.kibana', + index: ALERTING_CASES_SAVED_OBJECT_INDEX, id: 'alert:66560b6f-5ca4-41e2-a1a1-dcfd7117e124', }, { meta: true } @@ -423,7 +424,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { it('8.2.0 migrates existing esQuery alerts to contain searchType param', async () => { const response = await es.get<{ alert: RawRule }>( { - index: '.kibana', + index: ALERTING_CASES_SAVED_OBJECT_INDEX, id: 'alert:776cb5c0-ad1e-11ec-ab9e-5f5932f4fad8', }, { meta: true } @@ -435,7 +436,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { it('8.3.0 removes internal tags in Security Solution rule', async () => { const response = await es.get<{ alert: RawRule }>( { - index: '.kibana', + index: ALERTING_CASES_SAVED_OBJECT_INDEX, id: 'alert:8990af61-c09a-11ec-9164-4bfd6fc32c43', }, { meta: true } @@ -448,7 +449,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { it('8.4.1 removes IsSnoozedUntil', async () => { const searchResult = await es.search( { - index: '.kibana', + index: ALERTING_CASES_SAVED_OBJECT_INDEX, body: { query: { term: { @@ -474,7 +475,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { }; }>( { - index: '.kibana', + index: ALERTING_CASES_SAVED_OBJECT_INDEX, id: 'alert:c8b39c29-d860-43b6-8817-b8058d80ddbc', }, { meta: true } @@ -494,7 +495,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { }; }>( { - index: '.kibana', + index: ALERTING_CASES_SAVED_OBJECT_INDEX, id: 'alert:62c62b7f-8bf3-4104-a064-6247b7bda44f', }, { meta: true } @@ -514,7 +515,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { }; }>( { - index: '.kibana', + index: ALERTING_CASES_SAVED_OBJECT_INDEX, id: 'alert:f0d13f4d-35ae-4554-897a-6392e97bb84c', }, { meta: true } @@ -526,7 +527,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { it('8.6.0 migrates executionStatus and monitoring', async () => { const response = await es.get<{ alert: RawRule }>( { - index: '.kibana', + index: ALERTING_CASES_SAVED_OBJECT_INDEX, id: 'alert:8370ffd2-f2db-49dc-9741-92c657189b9b', }, { meta: true } @@ -571,7 +572,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { it('8.6 migrates executionStatus warnings and errors', async () => { const response = await es.get<{ alert: RawRule }>( { - index: '.kibana', + index: ALERTING_CASES_SAVED_OBJECT_INDEX, id: 'alert:c87707ac-7328-47f7-b212-2cb40a4fc9b9', }, { meta: true } @@ -587,7 +588,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { it('8.7.0 adds aggType and groupBy to ES query rules', async () => { const response = await es.search( { - index: '.kibana', + index: ALERTING_CASES_SAVED_OBJECT_INDEX, body: { query: { bool: { @@ -629,7 +630,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { const response = await es.get<{ alert: RawRule; references: SavedObjectReference[] }>( { - index: '.kibana', + index: ALERTING_CASES_SAVED_OBJECT_INDEX, id: 'alert:8bd01ff0-9d84-11ed-994d-f1971f849da5', }, { meta: true } @@ -642,7 +643,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { it('8.7 adds uuid to the actions', async () => { const response = await es.get<{ alert: RawRule }>( { - index: '.kibana', + index: ALERTING_CASES_SAVED_OBJECT_INDEX, id: 'alert:9c003b00-00ee-11ec-b067-2524946ba327', }, { meta: true } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/migrations/8_2_0.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/migrations/8_2_0.ts index 963f856845c10..a075fdaa387a5 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/migrations/8_2_0.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/migrations/8_2_0.ts @@ -7,6 +7,7 @@ import expect from '@kbn/expect'; import type { RawRule } from '@kbn/alerting-plugin/server/types'; +import { ALERTING_CASES_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export @@ -32,7 +33,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { it('has snoozeEndTime removed', async () => { const response = await es.get<{ alert: RawRule }>( { - index: '.kibana', + index: ALERTING_CASES_SAVED_OBJECT_INDEX, id: 'alert:bdfce750-fba0-11ec-9157-2f379249da99', }, { meta: true } @@ -63,7 +64,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { it('has snoozeEndTime migrated to snoozeSchedule', async () => { const response = await es.get<{ alert: RawRule }>( { - index: '.kibana', + index: ALERTING_CASES_SAVED_OBJECT_INDEX, id: 'alert:402084f0-fbb8-11ec-856c-39466bd4c433', }, { meta: true } diff --git a/x-pack/test/api_integration/apis/security/index_fields.ts b/x-pack/test/api_integration/apis/security/index_fields.ts index 8fc9c36accd69..c5ca5ec5d6ea5 100644 --- a/x-pack/test/api_integration/apis/security/index_fields.ts +++ b/x-pack/test/api_integration/apis/security/index_fields.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { ALL_SAVED_OBJECT_INDICES } from '@kbn/core-saved-objects-server'; import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; @@ -24,7 +25,7 @@ export default function ({ getService }: FtrProviderContext) { describe('GET /internal/security/fields/{query}', () => { it('should return a list of available index mapping fields', async () => { await supertest - .get('/internal/security/fields/.kibana') + .get(`/internal/security/fields/${ALL_SAVED_OBJECT_INDICES.join(',')}`) .set('kbn-xsrf', 'xxx') .send() .expect(200) diff --git a/x-pack/test/api_integration/apis/security_solution/utils.ts b/x-pack/test/api_integration/apis/security_solution/utils.ts index f5e65c6da3e7c..c4ce2e852c26a 100644 --- a/x-pack/test/api_integration/apis/security_solution/utils.ts +++ b/x-pack/test/api_integration/apis/security_solution/utils.ts @@ -9,6 +9,7 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { TransportResult } from '@elastic/elasticsearch'; import type { Client } from '@elastic/elasticsearch'; import { JsonObject } from '@kbn/utility-types'; +import { ALL_SAVED_OBJECT_INDICES } from '@kbn/core-saved-objects-server'; export async function getSavedObjectFromES( es: Client, @@ -17,7 +18,7 @@ export async function getSavedObjectFromES( ): Promise, unknown>> { return await es.search( { - index: '.kibana', + index: ALL_SAVED_OBJECT_INDICES, body: { query: { bool: { diff --git a/x-pack/test/cases_api_integration/common/lib/api/index.ts b/x-pack/test/cases_api_integration/common/lib/api/index.ts index 3bf0c470b9ba2..88fe7a1482379 100644 --- a/x-pack/test/cases_api_integration/common/lib/api/index.ts +++ b/x-pack/test/cases_api_integration/common/lib/api/index.ts @@ -10,15 +10,20 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { TransportResult } from '@elastic/elasticsearch'; import type { Client } from '@elastic/elasticsearch'; import { GetResponse } from '@elastic/elasticsearch/lib/api/types'; +import { ALERTING_CASES_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server/src/saved_objects_index_pattern'; import type SuperTest from 'supertest'; import { CASES_INTERNAL_URL, CASES_URL, + CASE_COMMENT_SAVED_OBJECT, + CASE_CONFIGURE_SAVED_OBJECT, CASE_CONFIGURE_URL, CASE_REPORTERS_URL, + CASE_SAVED_OBJECT, CASE_STATUS_URL, CASE_TAGS_URL, + CASE_USER_ACTION_SAVED_OBJECT, } from '@kbn/cases-plugin/common/constants'; import { CasesConfigureResponse, @@ -190,7 +195,7 @@ export const deleteAllCaseItems = async (es: Client) => { export const deleteCasesUserActions = async (es: Client): Promise => { await es.deleteByQuery({ - index: '.kibana', + index: ALERTING_CASES_SAVED_OBJECT_INDEX, q: 'type:cases-user-actions', wait_for_completion: true, refresh: true, @@ -201,7 +206,7 @@ export const deleteCasesUserActions = async (es: Client): Promise => { export const deleteCasesByESQuery = async (es: Client): Promise => { await es.deleteByQuery({ - index: '.kibana', + index: ALERTING_CASES_SAVED_OBJECT_INDEX, q: 'type:cases', wait_for_completion: true, refresh: true, @@ -212,7 +217,7 @@ export const deleteCasesByESQuery = async (es: Client): Promise => { export const deleteComments = async (es: Client): Promise => { await es.deleteByQuery({ - index: '.kibana', + index: ALERTING_CASES_SAVED_OBJECT_INDEX, q: 'type:cases-comments', wait_for_completion: true, refresh: true, @@ -223,7 +228,7 @@ export const deleteComments = async (es: Client): Promise => { export const deleteConfiguration = async (es: Client): Promise => { await es.deleteByQuery({ - index: '.kibana', + index: ALERTING_CASES_SAVED_OBJECT_INDEX, q: 'type:cases-configure', wait_for_completion: true, refresh: true, @@ -234,7 +239,7 @@ export const deleteConfiguration = async (es: Client): Promise => { export const deleteMappings = async (es: Client): Promise => { await es.deleteByQuery({ - index: '.kibana', + index: ALERTING_CASES_SAVED_OBJECT_INDEX, q: 'type:cases-connector-mappings', wait_for_completion: true, refresh: true, @@ -289,7 +294,7 @@ export const getConnectorMappingsFromES = async ({ es }: { es: Client }) => { unknown > = await es.search( { - index: '.kibana', + index: ALERTING_CASES_SAVED_OBJECT_INDEX, body: { query: { term: { @@ -319,12 +324,12 @@ export const getConfigureSavedObjectsFromES = async ({ es }: { es: Client }) => unknown > = await es.search( { - index: '.kibana', + index: ALERTING_CASES_SAVED_OBJECT_INDEX, body: { query: { term: { type: { - value: 'cases-configure', + value: CASE_CONFIGURE_SAVED_OBJECT, }, }, }, @@ -337,17 +342,17 @@ export const getConfigureSavedObjectsFromES = async ({ es }: { es: Client }) => }; export const getCaseSavedObjectsFromES = async ({ es }: { es: Client }) => { - const configure: TransportResult< + const cases: TransportResult< estypes.SearchResponse<{ cases: ESCaseAttributes }>, unknown > = await es.search( { - index: '.kibana', + index: ALERTING_CASES_SAVED_OBJECT_INDEX, body: { query: { term: { type: { - value: 'cases', + value: CASE_SAVED_OBJECT, }, }, }, @@ -356,7 +361,53 @@ export const getCaseSavedObjectsFromES = async ({ es }: { es: Client }) => { { meta: true } ); - return configure; + return cases; +}; + +export const getCaseCommentSavedObjectsFromES = async ({ es }: { es: Client }) => { + const comments: TransportResult< + estypes.SearchResponse<{ ['cases-comments']: ESCaseAttributes }>, + unknown + > = await es.search( + { + index: ALERTING_CASES_SAVED_OBJECT_INDEX, + body: { + query: { + term: { + type: { + value: CASE_COMMENT_SAVED_OBJECT, + }, + }, + }, + }, + }, + { meta: true } + ); + + return comments; +}; + +export const getCaseUserActionsSavedObjectsFromES = async ({ es }: { es: Client }) => { + const userActions: TransportResult< + estypes.SearchResponse<{ ['cases-user-actions']: ESCaseAttributes }>, + unknown + > = await es.search( + { + index: ALERTING_CASES_SAVED_OBJECT_INDEX, + body: { + query: { + term: { + type: { + value: CASE_USER_ACTION_SAVED_OBJECT, + }, + }, + }, + }, + }, + { meta: true } + ); + + return userActions; }; export const updateCase = async ({ @@ -724,7 +775,7 @@ export const getSOFromKibanaIndex = async ({ }) => { const esResponse = await es.get( { - index: '.kibana', + index: ALERTING_CASES_SAVED_OBJECT_INDEX, id: `${soType}:${soId}`, }, { meta: true } diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/basic/index.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/basic/index.ts index e40323068b6c0..6fc840f873ba1 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/basic/index.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/basic/index.ts @@ -39,5 +39,8 @@ export default ({ loadTestFile, getService }: FtrProviderContext): void => { // NOTE: These need to be at the end because they could delete the .kibana index and inadvertently remove the users and spaces loadTestFile(require.resolve('../common/migrations')); + + // NOTE: These need to be at the end because they could delete the .kibana index and inadvertently remove the users and spaces + loadTestFile(require.resolve('../common/kibana_alerting_cases_index')); }); }; diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/find_cases.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/find_cases.ts index 712ec4722d500..c0e6322f638e5 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/find_cases.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/find_cases.ts @@ -9,6 +9,7 @@ import { v1 as uuidv1 } from 'uuid'; import expect from '@kbn/expect'; import { CASES_URL } from '@kbn/cases-plugin/common/constants'; +import { ALERTING_CASES_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { CaseResponse, CaseSeverity, @@ -523,7 +524,7 @@ export default ({ getService }: FtrProviderContext): void => { */ const getAllCasesSortedByCreatedAtAsc = async () => { const cases = await es.search({ - index: '.kibana', + index: ALERTING_CASES_SAVED_OBJECT_INDEX, body: { size: 10000, sort: [{ 'cases.created_at': { unmapped_type: 'date', order: 'asc' } }], diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/kibana_alerting_cases_index.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/kibana_alerting_cases_index.ts new file mode 100644 index 0000000000000..5b14a3a85aea4 --- /dev/null +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/kibana_alerting_cases_index.ts @@ -0,0 +1,204 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { + MAIN_SAVED_OBJECT_INDEX, + ALERTING_CASES_SAVED_OBJECT_INDEX, +} from '@kbn/core-saved-objects-server'; +import { + CASE_COMMENT_SAVED_OBJECT, + CASE_CONFIGURE_SAVED_OBJECT, + CASE_SAVED_OBJECT, + CASE_USER_ACTION_SAVED_OBJECT, +} from '@kbn/cases-plugin/common/constants'; +import { + deleteAllCaseItems, + getCaseCommentSavedObjectsFromES, + getCaseSavedObjectsFromES, + getCaseUserActionsSavedObjectsFromES, + getConfigureSavedObjectsFromES, +} from '../../../common/lib/api'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext): void => { + const esArchiver = getService('esArchiver'); + const es = getService('es'); + + describe('Kibana index: Alerting & Cases', () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/cases/migrations/8.8.0'); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/cases/migrations/8.8.0'); + await deleteAllCaseItems(es); + }); + + it(`migrates the ${CASE_SAVED_OBJECT} SO from the ${MAIN_SAVED_OBJECT_INDEX} index to the ${ALERTING_CASES_SAVED_OBJECT_INDEX} correctly`, async () => { + const res = await getCaseSavedObjectsFromES({ es }); + const cases = res.body.hits.hits; + + expect(cases.length).to.be(1); + expect(cases[0]._source?.cases).to.eql({ + assignees: [], + closed_at: null, + closed_by: null, + connector: { fields: [], name: 'none', type: '.none' }, + created_at: '2023-04-19T08:14:05.032Z', + created_by: { + email: null, + full_name: null, + profile_uid: 'u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0', + username: 'elastic', + }, + description: 'cases in the new index', + duration: null, + external_service: null, + owner: 'cases', + settings: { syncAlerts: false }, + severity: 0, + status: 0, + tags: ['new index', 'test'], + title: 'cases in the new index', + total_alerts: -1, + total_comments: -1, + updated_at: '2023-04-19T08:14:18.693Z', + updated_by: { + email: null, + full_name: null, + profile_uid: 'u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0', + username: 'elastic', + }, + }); + }); + + it(`migrates the ${CASE_COMMENT_SAVED_OBJECT} SO from the ${MAIN_SAVED_OBJECT_INDEX} index to the ${ALERTING_CASES_SAVED_OBJECT_INDEX} correctly`, async () => { + const res = await getCaseCommentSavedObjectsFromES({ es }); + const comments = res.body.hits.hits; + + expect(comments.length).to.be(1); + expect(comments[0]._source?.['cases-comments']).to.eql({ + comment: 'This is amazing!', + created_at: '2023-04-19T08:14:11.290Z', + created_by: { + email: null, + full_name: null, + profile_uid: 'u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0', + username: 'elastic', + }, + owner: 'cases', + pushed_at: null, + pushed_by: null, + type: 'user', + updated_at: null, + updated_by: null, + }); + }); + + it(`migrates the ${CASE_USER_ACTION_SAVED_OBJECT} SO from the ${MAIN_SAVED_OBJECT_INDEX} index to the ${ALERTING_CASES_SAVED_OBJECT_INDEX} correctly`, async () => { + const res = await getCaseUserActionsSavedObjectsFromES({ es }); + const userActions = res.body.hits.hits.map( + (userAction) => userAction._source?.['cases-user-actions'] + ); + + expect(userActions.length).to.be(3); + expect(userActions).to.eql([ + { + action: 'create', + created_at: '2023-04-19T08:14:05.052Z', + created_by: { + email: null, + full_name: null, + profile_uid: 'u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0', + username: 'elastic', + }, + owner: 'cases', + payload: { + assignees: [], + connector: { + fields: null, + name: 'none', + type: '.none', + }, + description: 'cases in the new index', + owner: 'cases', + settings: { + syncAlerts: false, + }, + severity: 'low', + status: 'open', + tags: [], + title: 'cases in the new index', + }, + type: 'create_case', + }, + { + action: 'create', + created_at: '2023-04-19T08:14:11.318Z', + created_by: { + email: null, + full_name: null, + profile_uid: 'u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0', + username: 'elastic', + }, + owner: 'cases', + payload: { + comment: { + comment: 'This is amazing!', + owner: 'cases', + type: 'user', + }, + }, + type: 'comment', + }, + { + action: 'add', + created_at: '2023-04-19T08:14:18.719Z', + created_by: { + email: null, + full_name: null, + profile_uid: 'u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0', + username: 'elastic', + }, + owner: 'cases', + payload: { + tags: ['new index', 'test'], + }, + type: 'tags', + }, + ]); + }); + + it(`migrates the ${CASE_CONFIGURE_SAVED_OBJECT} SO from the ${MAIN_SAVED_OBJECT_INDEX} index to the ${ALERTING_CASES_SAVED_OBJECT_INDEX} correctly`, async () => { + const res = await getConfigureSavedObjectsFromES({ es }); + const configure = res.body.hits.hits; + + expect(configure.length).to.be(1); + expect(configure[0]._source?.['cases-configure']).to.eql({ + closure_type: 'close-by-user', + connector: { fields: [], name: 'none', type: '.none' }, + created_at: '2023-04-19T08:14:42.212Z', + created_by: { + email: null, + full_name: null, + profile_uid: 'u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0', + username: 'elastic', + }, + owner: 'cases', + updated_at: '2023-04-19T08:14:44.202Z', + updated_by: { + email: null, + full_name: null, + profile_uid: 'u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0', + username: 'elastic', + }, + }); + }); + }); +}; diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/index.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/index.ts index 9cf0aaa18be46..9e68a8379b702 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/index.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/index.ts @@ -49,5 +49,8 @@ export default ({ loadTestFile, getService }: FtrProviderContext): void => { // NOTE: These need to be at the end because they could delete the .kibana index and inadvertently remove the users and spaces loadTestFile(require.resolve('../common/migrations')); + + // NOTE: These need to be at the end because they could delete the .kibana index and inadvertently remove the users and spaces + loadTestFile(require.resolve('../common/kibana_alerting_cases_index')); }); }; diff --git a/x-pack/test/common/lib/test_data_loader.ts b/x-pack/test/common/lib/test_data_loader.ts index b379d4b61e3ba..d8f7453c89ddb 100644 --- a/x-pack/test/common/lib/test_data_loader.ts +++ b/x-pack/test/common/lib/test_data_loader.ts @@ -6,6 +6,7 @@ */ import { LegacyUrlAlias } from '@kbn/core-saved-objects-base-server-internal'; +import { ALL_SAVED_OBJECT_INDICES } from '@kbn/core-saved-objects-server'; import Fs from 'fs/promises'; import { FtrProviderContext } from '../ftr_provider_context'; @@ -176,7 +177,7 @@ export function getTestDataLoader({ getService }: Pick { await es.deleteByQuery({ - index: '.kibana', + index: ALL_SAVED_OBJECT_INDICES, wait_for_completion: true, body: { // @ts-expect-error diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/fleet_integration.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/fleet_integration.ts index 2aaa0778ccf5e..e48530ad16513 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/fleet_integration.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/fleet_integration.ts @@ -5,6 +5,7 @@ * 2.0. */ import expect from 'expect'; +import { ALL_SAVED_OBJECT_INDICES } from '@kbn/core-saved-objects-server'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { deleteAllRules, @@ -46,6 +47,20 @@ export default ({ getService }: FtrProviderContext): void => { overrideExistingPackage: true, }); + // Before we proceed, we need to refresh saved object indices. This comment will explain why. + // At the previous step we installed the Fleet package with prebuilt detection rules. + // Prebuilt rules are assets that Fleet indexes as saved objects of a certain type. + // Fleet does this via a savedObjectsClient.import() call with explicit `refresh: false`. + // So, despite of the fact that the endpoint waits until the prebuilt rule assets will be + // successfully indexed, it doesn't wait until they become "visible" for subsequent read + // operations. Which is what we do next: we read these SOs in getPrebuiltRulesAndTimelinesStatus(). + // Now, the time left until the next refresh can be anything from 0 to the default value, and + // it depends on the time when savedObjectsClient.import() call happens relative to the time of + // the next refresh. Also, probably the refresh time can be delayed when ES is under load? + // Anyway, here we have a race condition between a write and subsequent read operation, and to + // fix it deterministically we have to refresh saved object indices and wait until it's done. + await es.indices.refresh({ index: ALL_SAVED_OBJECT_INDICES }); + // Verify that status is updated after package installation const statusAfterPackageInstallation = await getPrebuiltRulesAndTimelinesStatus(supertest); expect(statusAfterPackageInstallation.rules_installed).toBe(0); @@ -57,6 +72,15 @@ export default ({ getService }: FtrProviderContext): void => { expect(response.rules_installed).toBe(statusAfterPackageInstallation.rules_not_installed); expect(response.rules_updated).toBe(0); + // Similar to the previous refresh, we need to do it again between the two operations: + // - previous write operation: install prebuilt rules and timelines + // - subsequent read operation: get prebuilt rules and timelines status + // You may ask why? I'm not sure, probably because the write operation can install the Fleet + // package under certain circumstances, and it all works with `refresh: false` again. + // Anyway, there were flaky runs failing specifically at one of the next assertions, + // which means some kind of the same race condition we have here too. + await es.indices.refresh({ index: ALL_SAVED_OBJECT_INDICES }); + // Verify that status is updated after rules installation const statusAfterRuleInstallation = await getPrebuiltRulesAndTimelinesStatus(supertest); expect(statusAfterRuleInstallation.rules_installed).toBe(response.rules_installed); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/migrations.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/migrations.ts index 04586999aa163..d43ecaff6823e 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/migrations.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/migrations.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { SECURITY_SOLUTION_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import expect from '@kbn/expect'; import { FtrProviderContext } from '../../common/ftr_provider_context'; @@ -32,7 +33,7 @@ export default ({ getService }: FtrProviderContext): void => { references: [{}]; }>( { - index: '.kibana', + index: SECURITY_SOLUTION_SAVED_OBJECT_INDEX, id: 'siem-detection-engine-rule-actions:fce024a0-0452-11ec-9b15-d13d79d162f3', }, { @@ -79,7 +80,7 @@ export default ({ getService }: FtrProviderContext): void => { }; }>( { - index: '.kibana', + index: SECURITY_SOLUTION_SAVED_OBJECT_INDEX, id: 'siem-detection-engine-rule-actions:fce024a0-0452-11ec-9b15-d13d79d162f3', }, { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/resolve_read_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/resolve_read_rules.ts index 64b87eea43b38..5839a03cf5c1a 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/resolve_read_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/resolve_read_rules.ts @@ -7,6 +7,7 @@ import expect from '@kbn/expect'; +import { ALERTING_CASES_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createSignalsIndex, deleteAllRules, deleteSignalsIndex } from '../../utils'; @@ -60,7 +61,7 @@ export default ({ getService }: FtrProviderContext) => { // and we won't have a conflict await es.index({ id: 'alert:90e3ca0e-71f7-513a-b60a-ac678efd8887', - index: '.kibana', + index: ALERTING_CASES_SAVED_OBJECT_INDEX, refresh: true, body: { alert: { diff --git a/x-pack/test/detection_engine_api_integration/utils/delete_all_rule_execution_info.ts b/x-pack/test/detection_engine_api_integration/utils/delete_all_rule_execution_info.ts index 3a7e3c91fd402..d1f158506a4f1 100644 --- a/x-pack/test/detection_engine_api_integration/utils/delete_all_rule_execution_info.ts +++ b/x-pack/test/detection_engine_api_integration/utils/delete_all_rule_execution_info.ts @@ -6,11 +6,12 @@ */ import type { Client } from '@elastic/elasticsearch'; +import { SECURITY_SOLUTION_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import type { ToolingLog } from '@kbn/tooling-log'; import { countDownES } from './count_down_es'; /** - * Remove all rules execution info saved objects from the .kibana index + * Remove all rules execution info saved objects from the security solution savedObjects index * This will retry 50 times before giving up and hopefully still not interfere with other tests * @param es The ElasticSearch handle * @param log The tooling logger @@ -20,7 +21,7 @@ export const deleteAllRuleExecutionInfo = async (es: Client, log: ToolingLog): P async () => { return es.deleteByQuery( { - index: '.kibana', + index: SECURITY_SOLUTION_SAVED_OBJECT_INDEX, q: 'type:siem-detection-engine-rule-execution-info', wait_for_completion: true, refresh: true, diff --git a/x-pack/test/detection_engine_api_integration/utils/delete_all_timelines.ts b/x-pack/test/detection_engine_api_integration/utils/delete_all_timelines.ts index 973995fc1b619..291cd269580b0 100644 --- a/x-pack/test/detection_engine_api_integration/utils/delete_all_timelines.ts +++ b/x-pack/test/detection_engine_api_integration/utils/delete_all_timelines.ts @@ -6,14 +6,15 @@ */ import type { Client } from '@elastic/elasticsearch'; +import { SECURITY_SOLUTION_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; /** - * Remove all timelines from the .kibana index + * Remove all timelines from the security solution savedObjects index * @param es The ElasticSearch handle */ export const deleteAllTimelines = async (es: Client): Promise => { await es.deleteByQuery({ - index: '.kibana', + index: SECURITY_SOLUTION_SAVED_OBJECT_INDEX, q: 'type:siem-ui-timeline', wait_for_completion: true, refresh: true, diff --git a/x-pack/test/detection_engine_api_integration/utils/downgrade_immutable_rule.ts b/x-pack/test/detection_engine_api_integration/utils/downgrade_immutable_rule.ts index 57f20a1c0e645..19e15e9b4679b 100644 --- a/x-pack/test/detection_engine_api_integration/utils/downgrade_immutable_rule.ts +++ b/x-pack/test/detection_engine_api_integration/utils/downgrade_immutable_rule.ts @@ -7,6 +7,7 @@ import type { ToolingLog } from '@kbn/tooling-log'; import type { Client } from '@elastic/elasticsearch'; +import { ALERTING_CASES_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { countDownES } from './count_down_es'; export const downgradeImmutableRule = async ( @@ -18,7 +19,7 @@ export const downgradeImmutableRule = async ( async () => { return es.updateByQuery( { - index: '.kibana', + index: ALERTING_CASES_SAVED_OBJECT_INDEX, refresh: true, wait_for_completion: true, body: { diff --git a/x-pack/test/detection_engine_api_integration/utils/get_legacy_action_notification_so.ts b/x-pack/test/detection_engine_api_integration/utils/get_legacy_action_notification_so.ts index 836ad5390250e..c56cb7a98b440 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_legacy_action_notification_so.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_legacy_action_notification_so.ts @@ -7,6 +7,7 @@ import type { Client } from '@elastic/elasticsearch'; import type { SearchResponse } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { ALERTING_CASES_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { SavedObjectReference } from '@kbn/core/server'; import { LegacyRuleNotificationAlertTypeParams } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_actions_legacy'; @@ -15,13 +16,13 @@ interface LegacyActionNotificationSO extends LegacyRuleNotificationAlertTypePara } /** - * Fetch all legacy action sidecar notification SOs from the .kibana index + * Fetch all legacy action sidecar notification SOs from the alerting savedObjects index * @param es The ElasticSearch service */ export const getLegacyActionNotificationSO = async ( es: Client ): Promise> => es.search({ - index: '.kibana', + index: ALERTING_CASES_SAVED_OBJECT_INDEX, q: 'alert.alertTypeId:siem.notifications', }); diff --git a/x-pack/test/detection_engine_api_integration/utils/get_legacy_action_notifications_so_by_id.ts b/x-pack/test/detection_engine_api_integration/utils/get_legacy_action_notifications_so_by_id.ts index 2e104a454bf78..3d92779e010d2 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_legacy_action_notifications_so_by_id.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_legacy_action_notifications_so_by_id.ts @@ -7,6 +7,7 @@ import type { Client } from '@elastic/elasticsearch'; import type { SearchResponse } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { ALERTING_CASES_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { SavedObjectReference } from '@kbn/core/server'; import { LegacyRuleNotificationAlertTypeParams } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_actions_legacy'; @@ -15,7 +16,7 @@ interface LegacyActionNotificationSO extends LegacyRuleNotificationAlertTypePara } /** - * Fetch legacy action sidecar notification SOs from the .kibana index + * Fetch legacy action sidecar notification SOs from the alerting savedObjects index * @param es The ElasticSearch service * @param id SO id */ @@ -24,6 +25,6 @@ export const getLegacyActionNotificationSOById = async ( id: string ): Promise> => es.search({ - index: '.kibana', + index: ALERTING_CASES_SAVED_OBJECT_INDEX, q: `alert.alertTypeId:siem.notifications AND alert.params.ruleAlertId:"${id}"`, }); diff --git a/x-pack/test/detection_engine_api_integration/utils/get_legacy_action_so.ts b/x-pack/test/detection_engine_api_integration/utils/get_legacy_action_so.ts index 57cf8b5efe71a..e714dfcec28cc 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_legacy_action_so.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_legacy_action_so.ts @@ -6,6 +6,7 @@ */ import type { Client } from '@elastic/elasticsearch'; import type { SearchResponse } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { SECURITY_SOLUTION_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import type { SavedObjectReference } from '@kbn/core/server'; import type { LegacyRuleActions } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_actions_legacy'; @@ -14,11 +15,11 @@ interface LegacyActionSO extends LegacyRuleActions { } /** - * Fetch all legacy action sidecar SOs from the .kibana index + * Fetch all legacy action sidecar SOs from the security solution savedObjects index * @param es The ElasticSearch service */ export const getLegacyActionSO = async (es: Client): Promise> => es.search({ - index: '.kibana', + index: SECURITY_SOLUTION_SAVED_OBJECT_INDEX, q: 'type:siem-detection-engine-rule-actions', }); diff --git a/x-pack/test/detection_engine_api_integration/utils/get_legacy_actions_so_by_id.ts b/x-pack/test/detection_engine_api_integration/utils/get_legacy_actions_so_by_id.ts index 1be0506359172..9e6b6a31e9786 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_legacy_actions_so_by_id.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_legacy_actions_so_by_id.ts @@ -7,6 +7,7 @@ import type { Client } from '@elastic/elasticsearch'; import type { SearchResponse } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { SECURITY_SOLUTION_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { SavedObjectReference } from '@kbn/core/server'; import type { LegacyRuleActions } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_actions_legacy'; @@ -15,7 +16,7 @@ interface LegacyActionSO extends LegacyRuleActions { } /** - * Fetch legacy action sidecar SOs from the .kibana index + * Fetch legacy action sidecar SOs from the security solution savedObjects index * @param es The ElasticSearch service * @param id SO id */ @@ -24,6 +25,6 @@ export const getLegacyActionSOById = async ( id: string ): Promise> => es.search({ - index: '.kibana', + index: SECURITY_SOLUTION_SAVED_OBJECT_INDEX, q: `type:siem-detection-engine-rule-actions AND _id:"siem-detection-engine-rule-actions:${id}"`, }); diff --git a/x-pack/test/detection_engine_api_integration/utils/get_rule_so_by_id.ts b/x-pack/test/detection_engine_api_integration/utils/get_rule_so_by_id.ts index 5a46f96cc400a..159d1e3010a2a 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_rule_so_by_id.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_rule_so_by_id.ts @@ -7,6 +7,7 @@ import type { Client } from '@elastic/elasticsearch'; import { SavedObjectReference } from '@kbn/core/server'; +import { ALERTING_CASES_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import type { SearchResponse } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { Rule } from '@kbn/alerting-plugin/common'; @@ -16,12 +17,12 @@ interface RuleSO { } /** - * Fetch legacy action sidecar SOs from the .kibana index + * Fetch legacy action sidecar SOs from the alerting savedObjects index * @param es The ElasticSearch service * @param id SO id */ export const getRuleSOById = async (es: Client, id: string): Promise> => es.search({ - index: '.kibana', + index: ALERTING_CASES_SAVED_OBJECT_INDEX, q: `type:alert AND _id:"alert:${id}"`, }); diff --git a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/create_prebuilt_rule_saved_objects.ts b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/create_prebuilt_rule_saved_objects.ts index c6c44fe37c337..0b4bfd9254b15 100644 --- a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/create_prebuilt_rule_saved_objects.ts +++ b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/create_prebuilt_rule_saved_objects.ts @@ -12,6 +12,7 @@ import { getPrebuiltRuleWithExceptionsMock, } from '@kbn/security-solution-plugin/server/lib/detection_engine/prebuilt_rules/mocks'; import { ELASTIC_SECURITY_RULE_ID } from '@kbn/security-solution-plugin/common'; +import { SECURITY_SOLUTION_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; /** * A helper function to create a rule asset saved object @@ -73,7 +74,12 @@ export const createPrebuiltRuleAssetSavedObjects = async ( await es.bulk({ refresh: true, body: rules.flatMap((doc) => [ - { index: { _index: '.kibana', _id: `security-rule:${doc['security-rule'].rule_id}` } }, + { + index: { + _index: SECURITY_SOLUTION_SAVED_OBJECT_INDEX, + _id: `security-rule:${doc['security-rule'].rule_id}`, + }, + }, doc, ]), }); @@ -97,7 +103,7 @@ export const createHistoricalPrebuiltRuleAssetSavedObjects = async ( body: rules.flatMap((doc) => [ { index: { - _index: '.kibana', + _index: SECURITY_SOLUTION_SAVED_OBJECT_INDEX, _id: `security-rule:${doc['security-rule'].rule_id}_${doc['security-rule'].version}`, }, }, diff --git a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/delete_all_prebuilt_rule_assets.ts b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/delete_all_prebuilt_rule_assets.ts index 11f9c75c81cc1..899d5ddd7f83f 100644 --- a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/delete_all_prebuilt_rule_assets.ts +++ b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/delete_all_prebuilt_rule_assets.ts @@ -6,14 +6,15 @@ */ import type { Client } from '@elastic/elasticsearch'; +import { SECURITY_SOLUTION_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; /** - * Remove all prebuilt rule assets from the .kibana index + * Remove all prebuilt rule assets from the security solution savedObjects index * @param es The ElasticSearch handle */ export const deleteAllPrebuiltRuleAssets = async (es: Client): Promise => { await es.deleteByQuery({ - index: '.kibana', + index: SECURITY_SOLUTION_SAVED_OBJECT_INDEX, q: 'type:security-rule', wait_for_completion: true, refresh: true, diff --git a/x-pack/test/fleet_api_integration/apis/agents/status.ts b/x-pack/test/fleet_api_integration/apis/agents/status.ts index 4908e1d2e8c6d..ed2c71cb4d8bc 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/status.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/status.ts @@ -7,6 +7,7 @@ import expect from '@kbn/expect'; +import { INGEST_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { AGENTS_INDEX } from '@kbn/fleet-plugin/common'; import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; import { testUsers } from '../test_users'; @@ -22,7 +23,7 @@ export default function ({ getService }: FtrProviderContext) { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/fleet/agents'); await es.create({ id: 'ingest-agent-policies:policy-inactivity-timeout', - index: '.kibana', + index: INGEST_SAVED_OBJECT_INDEX, refresh: 'wait_for', document: { type: 'ingest-agent-policies', @@ -270,7 +271,7 @@ export default function ({ getService }: FtrProviderContext) { policiesToAdd.map((policyId) => es.create({ id: 'ingest-agent-policies:' + policyId, - index: '.kibana', + index: INGEST_SAVED_OBJECT_INDEX, refresh: 'wait_for', document: { type: 'ingest-agent-policies', diff --git a/x-pack/test/fleet_api_integration/apis/epm/install_by_upload.ts b/x-pack/test/fleet_api_integration/apis/epm/install_by_upload.ts index fcf7fdaa4ed81..65103398f8ab4 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/install_by_upload.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/install_by_upload.ts @@ -8,6 +8,7 @@ import fs from 'fs'; import path from 'path'; import expect from '@kbn/expect'; +import { INGEST_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; import { skipIfNoDockerRegistry } from '../../helpers'; @@ -111,7 +112,7 @@ export default function (providerContext: FtrProviderContext) { await deletePackage(testPkgName, testPkgVersion); const epmPackageRes = await esClient.search({ - index: '.kibana', + index: INGEST_SAVED_OBJECT_INDEX, size: 0, rest_total_hits_as_int: true, query: { @@ -127,7 +128,7 @@ export default function (providerContext: FtrProviderContext) { }, }); const epmPackageAssetsRes = await esClient.search({ - index: '.kibana', + index: INGEST_SAVED_OBJECT_INDEX, size: 0, rest_total_hits_as_int: true, query: { diff --git a/x-pack/test/fleet_api_integration/apis/epm/install_with_signature_verification.ts b/x-pack/test/fleet_api_integration/apis/epm/install_with_signature_verification.ts index 1c1ee8e1ed3b9..3bd2079fc187c 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/install_with_signature_verification.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/install_with_signature_verification.ts @@ -6,6 +6,7 @@ */ import type { Client } from '@elastic/elasticsearch'; import expect from '@kbn/expect'; +import { INGEST_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { Installation } from '@kbn/fleet-plugin/server/types'; import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; import { skipIfNoDockerRegistry } from '../../helpers'; @@ -32,7 +33,7 @@ export default function (providerContext: FtrProviderContext) { const getInstallationSavedObject = async (pkg: string): Promise => { const res: { _source?: { 'epm-packages': Installation } } = await es.transport.request({ method: 'GET', - path: `/.kibana/_doc/epm-packages:${pkg}`, + path: `/${INGEST_SAVED_OBJECT_INDEX}/_doc/epm-packages:${pkg}`, }); return res?._source?.['epm-packages'] as Installation; diff --git a/x-pack/test/fleet_api_integration/apis/package_policy/create.ts b/x-pack/test/fleet_api_integration/apis/package_policy/create.ts index ca8ac94656402..d8511e0cb4418 100644 --- a/x-pack/test/fleet_api_integration/apis/package_policy/create.ts +++ b/x-pack/test/fleet_api_integration/apis/package_policy/create.ts @@ -6,6 +6,7 @@ */ import type { Client } from '@elastic/elasticsearch'; import expect from '@kbn/expect'; +import { INGEST_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { Installation } from '@kbn/fleet-plugin/common'; import { v4 as uuidv4 } from 'uuid'; @@ -649,7 +650,7 @@ export default function (providerContext: FtrProviderContext) { const getInstallationSavedObject = async (pkg: string): Promise => { const res: { _source?: { 'epm-packages': Installation } } = await es.transport.request({ method: 'GET', - path: `/.kibana/_doc/epm-packages:${pkg}`, + path: `/${INGEST_SAVED_OBJECT_INDEX}/_doc/epm-packages:${pkg}`, }); return res?._source?.['epm-packages'] as Installation; diff --git a/x-pack/test/fleet_api_integration/apis/package_policy/get.ts b/x-pack/test/fleet_api_integration/apis/package_policy/get.ts index be2c7cc73e5f3..841048cceb0f9 100644 --- a/x-pack/test/fleet_api_integration/apis/package_policy/get.ts +++ b/x-pack/test/fleet_api_integration/apis/package_policy/get.ts @@ -6,6 +6,7 @@ */ import expect from '@kbn/expect'; +import { INGEST_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; import { skipIfNoDockerRegistry } from '../../helpers'; import { testUsers } from '../test_users'; @@ -337,7 +338,7 @@ export default function (providerContext: FtrProviderContext) { // Delete the agent policy directly from ES to orphan the package policy const esClient = getService('es'); await esClient.delete({ - index: '.kibana', + index: INGEST_SAVED_OBJECT_INDEX, id: `ingest-agent-policies:${agentPolicyId}`, refresh: 'wait_for', }); diff --git a/x-pack/test/functional/es_archives/cases/migrations/8.8.0/data.json.gz b/x-pack/test/functional/es_archives/cases/migrations/8.8.0/data.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..887afd9d4a907a3c5a5d2cb24169a7cb33200c21 GIT binary patch literal 1911509 zcmV(%K;pk2iwFP!000026YRZtbE8PUHv0d33g4c1<2`R^)M8)y#N4$@R(Kp2^?;ba(etd#R(Je!2T8tb(8Z z0uxSNeuOE12RGn03E|VgD^FfCJXeRT_QGKs6?N{l(J#*vh7`)61daMb@H-q5I7<&b zB*cekfW#<;AhCyH*Lq;lWf*m^^q;fD_e$??=);qyIv#?@M%;a}0Z+1kOuHzrVR_Rs zuC@oS-X+1XX)k$ncZllh(B7Ssc6XO2r|2%&d55;SukT4mzotXiVoI;Lj;C=f3h!`v z`l$7`4S4kbensI}cuf3 z{PpKWvUvS@o_yH)-hW{v`Im?P$p882Nsj;d7~c&9_fI?g?(e^rSFrzb_h+2IX#)N4 zUjdCN+u=D%w!8M1I|4yK(m#GJe=T?9)9e&WBeVdYeZ0JO*B3$Y7)c`^J^3^R0S{|$ z3m+mc5??OEzkhTGvPF4RL}mLj=WDrdZ*=t`YMP`fVS}2TS4Vfh{?zP`-XZ$+r@J`G z^S}Q3(|sR7f?plN=m3&l8Rid<*Gbs!{)*fcUQ)K;KiAYz(Ec^{@+SJV{Qm&027KXg z2L>M$^z)7IudZp69F8m;=AZ6@>iTkYFd%{d>)$`V|Hrd=|A@xlkHe6TSe&L&Vu*Vr zJtRT`9r{5)4;hm6u@DP=f~8+Cr>>f$y^YFi;|8f`xy@fp)N9j^qU0yCioeB!)3bl0$-F(IFFK_%Mp8Af#wEWP|v-W0kqd-NMH;$^e848l0dx+BA@hd^mpL>DE@o!ehguVw*}SzaQ{ClR#Aim zgq2~25l#S|D)x|}7sv4s@hG1`C>#gBzdu%S`uD&pP#wg(X1cMA2&j;)u z=vPCZjFBN0Qyzsy6p&QC?+#SRbjjaGP$ilF$f^I7KqVOpCkUJwhD-n`Do!y&CL+)w zMIa$VGB{1Vuqyu$pnfCW{~b7`zptPQkl99u20|!|iYoM44_+7EAq%T>2|)bkKmYm8 ztXeMz$Z6U;|8iF!suY0l4tmq>K?TjjfBifBeHVL0l3xG{{Rpi4I8&%4m{4`YTG@d3mxe!MkvnuXfKHpzdwwAmwI)21#Xg zD7+k`93jYa0N~qVxJy7&li+sqj~7eh*Pq^{*2gpz(1bU{7$}4SxpU};bU5@;A17Fr z^-whW9n_^ah$Z&*0Hl8`q5K5;>Ix7!2#u(@>%gQzbo|#n>L5vk=(@js0+J6Y*CO(& z1!czGF>mh)-L1fTD(4-Vzd~*EZdVv^?pTCe$N98s1cedN6*bi*2Oq^Iu_ zk&jX2d*5%)NqkS*K9s;B0n6Nm@B=})GSeHsjRD$*m!a1s4P>@=aD{@^MF4L3e5)T}ilBgpF+)F!0-!&W1j$4+ z$#~!2yF(yvXw4*xehcvqBi^#_5af^NnTuoL1C&(p1HWwkJ$R=d#y-xX$dHUF67Wup z7_z<}4I>JRBcF}P2t|K9?_|htBEY@LKXID*(MSYXl=8}d5a7b3X%C5igd_d0i9nP> zJueQtq3_WsP$D99=wlu>3_YKvJemnYGu#J`>nqUaw{dV7IB{Rz$c7=Hhv01T0U zz*(TX24N5m2@fNOEQ7>D1`nwi@xqv5{s8L3C%rNM^ay;eKRoIR5LiXzmH$;ohX8c) zZ^s%&3;v(4RU`hrRfZogvwyA9@`lX+m1@s(^V}yD0E}e5PyvWMG{#v18zK}H4}m0( z4LvqQhA2iNG#26{94DX0|Xu72o{9|7E#{s68`etB`q4G{wXA_dpY0x7<&JrQWnC}D30Uo z5Fv;NkUWMM`b0zwNkHrTfW_I6@qRe?XpH@O^pS75UU3Bd5$J1ocb^icuENuy+QMS) zuKqAEujgG9K{VWWCO{%UM_bdbLvR+sNi6*CNX6b_&NM}QHB=cKe>0#3 z`|+gm19PqqNf7;e*yVyCq^U3rhFE~$LxN<&p@&31Afs^*q7g=r+oT-c7fpZug@>1};}6|M&)e~# z>X(mhkuJmgS6_GFwc)jRz(U}9%!k)C_sd<8gkc{2_j`71-BsY_IIqrNaFo_xG+~%p2h`6!uNRV`==;#e8t};l$6> zj|h>uBL3ZS;?o)50iZzK`QE7WXi$cdoGS$IR17m z(vt~!pGO4c6X^Xl%Ho)CyPhxV@?2AP=Aj^1uH3Ly!&(xp^i`+}#-b zXZZW05L0_$2v9P7jQDw0*c=YWD_Ni9Z2c;{T(g8pQ|I30m%BKRy4T``9y|D&boDOL zoFYP>qF7`YhBy$-f!r`;X*?W;41+`n1;q9jXD<8zhQcu4KoKO_H;oZ2f_+C`0-3~( zpNs*j4;2i-D8RC6uW^QOT^tLjtvw!D^0DdZX*9P9vgk>WMF+jA;i~xC-~40SR^=5F zH@OvzN>xVhgvG64A2;(ZK2*h3IXn+hA_Qx*2P{hxLm%i+B-M?k1JWBJ7=e&Ph)_882f!bTf149iDDq7egdnItM364TUJEDm zUmbxyi}*AOSTBmR{*XY~c<2+jKlFioPJ4b#)3pEnk3wVLLg2UAp+K?B+a7rg{{tY< zXubB|BRPr2x5{9 z1H_|)?~lwkNF&hQzXfT;>9=*{?+ebPfKngCoQ4a=<5}pg$-p>uL}71R6u!xha~yxup774i0MAnX=Xtq5!Bd^;*G^D>ne{d$@N9N? zW)4PTcr^4pA_PJR4k#MJVnd%HSOzD33PWBxS$?{Hy5iM|`04S0DOd*-lYjct8x-NE z_5dXBPfhN}J^#likHkNDK#B+K?)eaWoa@Pn;*Y}OKVJE?#ow~%ay=gGcGmsySvOn z9=~(((;f<9HWqiGP^wZ`Y>+n6z`_sb|{Nqpm_|u>M{vE#3 zf>3BkGZZt#e7H|2445I~$Mg`VY0M`v7Nb7IB7eWHwD16rVHDm$js%wab?olH&;kUX z=&s&XZS|f{wETwNT#I@5z=EE%!S1aOv^;Iy*M6pj7~+VDA-`bkFBtkWMX>MqOv}SN zIPnY4{7lg3dp^_hkPcpFqL4wOAwzgzXgtUW0vs7q1RBI(bP$}TUZDoBv>hK}tceo8 zkmxT61+Cu_vR%32G9gCd68B>nifu?s8e#9&}`3(DK!7g39_4qRxb7#=MZ7>}#t2xZu zu0Zm3B5>y^?7w&$9FgUgQ!p;e6~CJ9RBIwlP;tj!W}PS~eCAE2Q$*>`E89R~E>p4D zWxLH3aThq!s>)|cP$@=*EKCR2gp67uD{+;DQ^TrFTeR0Ux^vgXB6HVn=}GxMz*lHk zhD5a43%luB+7ZbtsTDJyt=Bo3xW!&cYh_*3R8sE`O5e`cSW745v6%~NY-LOwN;s@ws*RljAdi<1+@C07in_ z_$+7mHE%bW@W!O$cQf8vhPz;;CTcaAiW6m3`7%E?)ZRLAwo}em)Z}Yr9Te$u3-MS9|3G2{$et$vL&Hw8_r#uriz;Z=JL5J0coE5*J!Xh`dO_;FrP=SByx^yQ0MDoCqaXy|XSMd=YoJtHH( zq_lGX(fF_x0F(;W#&iYx$LVK*K&xrLS}nN($EyKtAQm?}vio(iO&1)$TooBMVCUDz zwWlcDI7yN%SAidHGdnK@v{pV&RUYr_#pwhKPmQmK(qKmDlbK#y7?qP_n*n_uhTaFIt69a$%y_mq6aDtY`f2HhtZF*-BKm zC#~J4Z#7K-UCduhg?27;5+5x_VP|?@P)R=PjW?zo;(w_=CYtStZt8ZEk$qZh`Sw29 z@K4^B_Y^JwxaT#+3~8&qFNJqJVu@`qce+*EquZl;Ye(OBytctaM!v^%1O>9lQCvX! zq(Ghx&WV#a#rTqv>!qI0Smlq3(QKPBd)r+kr&bFO!c<51T1_jMM`fWHirA_l=Zmew z%|^BLCDh~hxECq`z!KqRE0dJH6&Ch+p&6e0kkeQ!6l9rrt0ZB?86hKk?Yj);evPdc zWNz5!WWuzRH61HSd6`<7Yhs#;Ts~dKSVcgGW-Uuu0_c@NYGcmavMUP7|2TvGoIX6y zWa6o7+eytiu0>D?r+#+CwgFhmAgQ*ch>+2WGu}n~dp;N`#)@V=|C6oeCC~I}r2S-9 zSW*rxJI$u5Kv|cx0pkpw&qsVqP~@9*@(*`Ut;VUp27Oq37ZPC?b9p|=%A;9iPdN&I zt|cxQw6n)XnRwXFd^Nd@rbIg9m0iLG(dn?{ zx|EdC{gYiNBKm z*YxttU1Cqnl))N9swf?uNwz`x*U(+dU>z2-F^Q!OAUnTx-t*~{0{!~YoGF&cxQ`r3 zzm$H8)<7iU#)lRc;?l=E19n;-d@WSw7K^e4a}+MRibtEl03V(C(Ts{P)!sbEfi02S zs?SR9tk}8I*wg7$Up|$LUah5)?Ae6}9rzA|m2-1dv?dvnD+kd$TA7Sh+;}W0nKOl~ z^Swgk z+hl(5yMa4T2Vy|S7wNj*od>aC6Vy*r{rq@j#LxUK4ZdQ2U~c4K`}vZ}dIicQopw zhx1`S$RfFwQHpIxO0#g+HZ8K}S-xJh2U+$r6qyT0OY`PpyzX|O9=lK9!DM?Q2zOWg zawwVHx5atE8`Fzt?RaC51;_ z#4}*Fy)jwk$!zV+(?%P474;>}NtxAP@t#pLt73dn`uUNY&ZMYfm6PYs>bf}M=vWH& zeq_>G!*kty#BqKzI&=DFu}}ypwhzX^Q)pDdTNlUyNSf!iRA%y}+m^gDIPVXum57Ds z*}Padhs2nWABfHr z3v%adX1irnn7th(sr5>owXLZPM@pIpR`hEvYBy4aBW&tp6O`0v9){7E=_Z>Q^B`zj zdFkr2&i*pw)fd(FryVjS>-KXUHTB@q`YWt+ikUQ5PRFR;OP{Mj)4J!ArmSaecS$Xc6|-zqjxc5_t#CvsLVy=n(?l{RnE)^j-x-=_ug1*pR>uDneUgRt*z3`{g`-Z z*ywl}kkL_f0a^PNtKYbC&1ruXi?3^S>%1E=+jId%15W(dl3iZiq>xm6w?&+$&DWM= z?^;dWj*Kr^FqVU58N=>A;g8s|yW-}NA=q;ITp7qEdXKjtwOa=TlxRMr@aN>>Ody1ds=G|u*Gj*!>S1!#H_{g(A$ z&*ka;pFlDUdvTE$m%-6CdWo`oWGtDrw(fYwUZtX98FqtmSykuJTvnq|{LAqQ-?7Tc$ zl74~?+PAyB9Gly5MrYBJZnIN7P%l9Lk=9e0llPu$H7b)?7GgBZ>^wg5IH#=!EjO{h zqiku<{j*`7n#4K|RC=k!QMLc9iJI}NeRIk9E;rL{0tAWKd^X)HbaP%h)`U@Xpu@cb z2qkaW?hdg6B-yWf===Fi3-fF5p${TAma!$jq3-jx)dbx8G&19=>uZL0AAB+EuC8br zt3MqDLuZ4Jb1T#lugrV0%8;HW-H$TlsTPw9q=QW!+G#v=S8NHm|Q0V$uby;PtMce5tHH36U- zpvVd6$NV>Yam?gKlHXM1SOPcwlXW;+s>+@tR;_!DCh+4L+bSxM5G-zdDD~-0l=f~u z(G~TL)U{qrj0n&bB>(b)2D1h2;L1t1)9qVMsRq4}L3$-9|2#$!fdRoMJXr7UfZm5BB=grqAt3lKQTvt?v>LWHpnyLAOUemk81k zQpQVuKl{X?s69XSJjB?b2i@1_QnR1WC2Kx~%CEd1YfJCA(5#G{dDC$T8vi@sf8MoI zFw0W*`x9>EU&Ale7tMz8&CqfsfJ7m~+(FRr zqb8lbsyR<6oEIzfM0(DA$6|VM;ZrF#suyiDZ;RpTw07QS$dART!(yUE=N)tIQ@qZv z9QL~CUbp;Ah-+0LZB?50Rbv^^d~N~ZkiW$1;!U+^Hk+Mn5mSy`;Bus{TL=eq2BOWE zs*s-W#o~DlQ15z-h`T2?Z=*{~x%uI!@tLbv%G!0%B3L&4YL|#JX}r1y%himj941Xc zZwa#N&G#FABhW}$|2Poqi@jEm+n90YaYm;t49n`Um$}VNU3q2D)9baw=TvAvZf`=Ywy!pRfETyight!1Zt9$>s*xxrEw+W~Xd zpyT1&ta+Jpt#T@9So9Fhcr;U9dkn^S|McBks!ao^ft*0y2W4|Y@v;e5dF&(8^$a6s z7alO#v5H)Y$USSf?)jnkl-bX^dsEk~j^B#3M@sO5{l0(j;>nlu z!7%2Nb?Tlkv9eUf?Kl{0X;l7nNJ7my;tmw0n6Jhqex|`^3v%DJ4Q&X1&W|*jayqS84BEQxd0;$Fjmtr*W^TG$^5|lE$=6DwPA1#Y zI;}YDp_HfkFn3$7&!otFI-T*-Sn+S@|7g&R)%jwqj?}bQibcl}Qp6V(m8E%>u8NkG>Un=o8tJq9fTaF*JTw)pYbqX?R#7Dnz(6J+u+eAVbA$7(mC^# zTxB<*lbh~;n-+0@9~>Mj_39p>1T7qe6N~TiN7tOU&KGLyr)r zPWt^}my$8D|K9kvll5%&M*NV`ECWY^;gBC5*1T-J!334@3R%ph$$X@j{+4{UD@yD1 zdWBEfC#ob9_{O4Lx-bm%b4xMKM>|$AUWP?;{VTGi*DyY1^^PEN^HohdNb+I~_^-L;LeL?T zsO2PE8!F+h1#Uo)pnck53c7DQGJmCQR^H8Lx<1oYGBQ?4-$eC{Lgnvl9WjW3_3GA@ zveTT}&MYoBOI{Y$@40ypNCG6U%d)1u8i%oW=A0T9H1Z@yz_TWYV%_=T2z)9fGl(=`z2CT;_{ z5zlERaL>_dG(Q0C@GT?t#QjKle;Pk_(+#c~!+q4tz1YA0t@;aHFioZHG}DB)=quK; zX&r9&>1@VvtZCLnJ)LcKg;h^eac!~76)JL*@cmO|SA(feD}aOUs(xdxm#f9@sQN61 z_=D09j~#87mW8l$z#lNR^u|0jfkDI{2Vg3kiHNT$%VDCwC-`d2)(D!5d^8W&K;ybB zcYe6Vcxgb$LA=>c;?!2Msy>>YG0B~);pY4tAQxrhDAmPSa=L}iqBVln!DY>!i_3bE z9QIK)-A^a`vukdX($JW+p_^XqE|A6}-uiQL?LX^1K4U)_R5}7<`oa5h-Ujpn!`ruZ z-h8in?&d3*UoCOmO_BX{Ow+S{#1c~Kx?|xF%RSPF{zX>1UsdyUY)vZ~BL@9?5SKYd zw-p#)+Gmx=kpoQQ|V#3Sexio37&ib^z^G; zq8ghiuY0c@67bAcFxJ&o#&rCaz_O7VS4#S(iHu2ixiwx)z|-G6y{z%5N|8cI?E5T~FM=Dv|mRe!Pga4U2y8Ow_S5b(`YuF3m0~pqCx1 zN+0YuJfj{ub-hZ2R9aE;!F;O<{VWq7ofYHEX5Vd8YAR8?X63m|SmsIBA{5_--^!T@{mi!5KH*%~6-{WY3 z+Q!&Rd=}sDwbEk8%#A2s7gvHO2PyKco1{tK{`iOIk*S-lzIY_Uw?Fm;h`M?1Xr?^5}wB46VCePOMAYpcyGA=Zz4t$l52-8MzuOk2n|)2*#tl1-+$vS?=5t^x9< zipo}58U-dcuB1-=XwrAbF4dX+*F>BOz}wkfiuBT7skjSn1qj1+Df z{-$PIEbZZ99-RD*HQN=YM2^*oE5#JbTY0yDf#&OIMb0FuAur}&@tdqzmPHD!BXinV z*s_A5hP~Kut`-ROyz?)h5Kd0%;sKNt(T9UA9wO_b&5|Z`eNcI`M z+Q#`S`|_aiH+^z&-w$g4)rmKIzCmGramS?J^QprlER}wD;#=dF!6v)7{(w%dHbq6) z8T$Gjhw@3BCJH@W9!RWgf-tZywOnM8qRq;gp=;-GS7u-4YTBoB&IQX8okz=>4=$^j z+FbS+N2Qz-wcKR!Bk(l7+Rgfm`|QD|ADtQVkx_8#R@3g$K?ClsQlC=7qo1tyxF=f^ zYVCS}Cp1n@?tec$UZj5feeZ&uZwI?tiJEkuP7jUf)0b_@4=Nh@PA3lCyd!l4cSAnY z&7S6*_79<%PoeeHZ~J1ZP|8(B)9tozC1PY0 zuV||r;mAncH=D53)Qlv==RxoFsECg%xt00;D6fle5t55d&zfg^LlO|OWC8>y!TT!e zvAlxWY)_{Zbnr=tML$^gTT!=FlB{E8bmkAN0!5f?GS8Qu?Z^wb3u&!Sqx2$I+ySv& z@fZbyXpCiYIpGOeTj3Q~TXsC?C!sn@S8NfFrWIFNb~X3Zs1)aHs{1LU@C!_(N>Jqi z)RVCLY~6L($k=XF7drN^EQ}OXZ{l`+_Tyzotc}H7qx>~?ywH8IjI4R=ZxwNLa>*HG z@%>>Rlx>BfP3)Ww15xp1+1qMP2l{9nq&`$I-knCH^ElTB-2o>?BHB?TJ>)!O9r92p3XOEXeHq9yCcS(jF-6t4 ztn*aSJ@_bY`oW2^Vz*>HtVoxQndBH3YD!rM*e+f8P<$yyZi4)iAU!`^^HA$e%BKj57~>rqA=SlDBBR!bDS_{fIQ!fv3+v1wx@nx2&5HxUZ$H#R_0~b7YbN(&F1JVbiiq zSjkz5YOuaFr$X9|KC?oYO`rPLGVHnCUnccoO-mrh9hhEJ!8QZl;{SU=MB z&ZtjwAE|Xk4~R-CgQTkI6+w^cC6L4&z*;{<7r!{Gc6}p%<7oT7$f)(4x*QKue3@Uc zvaPpVmlmm_a?$R*6#6B|K(ip2>EW z+RnMQCSo&+$j4aA2Zz>JpuSeNW2acSb_2(9H8Z*KVnz7MGd}lgELSI#sq7m|3siNB zO3`}ubO-)bf(`*tu0jx&l-C$Ecm?1#!cJgCp25y9aMr)yIz8< zIci*aleDg{%Z^N0BU(DeNroM_L6_3ws9fju!yb%A5k#9WVN+GXaws z2XwKxXWX&uLe@OWM!WjR@)(Bds(cCsb=chgft zMXkosPV_7?eHIfJ^}xo~}6|APL}c$)ZL)jaU1_LQe-W z>p4iF;S?fRa}hQ6tXMAwbi=2}J}3LIE1BE7DdX`>9q|1UwA3~ka)(HI2fDpOh)T%k zM=A|R<-%%5?drttIntny6>b~pjJyO$x+u1qbu4TiuXW8t_ch4lE*DA{F{pB%!f;;2 z;;jkvd2n6MC{gO%RP1aBDGBiKo7x)5X+X>ziVKq_GO00QmtF(Sz>j>XZs>%Vom7*~csx^bKs% znPo<4w>s(GJ}$~rZzcf}RAL#yVi^{mn`wdjLrh!#$lJHmu3KHU>%wW##llTaOLSwA zy#e3J*WD612#4PG86)0%{^{2KjO~kD;O*J$Ixp3w+|zMNgpC{cJxipG9yNoU?@V$z!?QRoGPkis zFoe*F4bs-R!+K^v1_)l!Hv>gbP09kGK61`rXyjX7YimXU9=IbZ?eV>mY{#1(uNZ=b zSW5u$$r*YCgsgmRuPIwtEzL_d7=f`Kpi9f*uQ#(n4!b5Z%eeBqG6LXgd=xNztgk5V?%<*0I;C!r^|8n6O7q0*8h}X}t6h0oR$WR6aw>#;oWesJ_CP5F4-!DUG7MoQiUDeMP<0ixcow!umTIw`W(fAgT;2GIsKGPpzs}~c_5;jm4w*T2@EJC8FS~&il;LqC z)V8-Bua(FEyks%vn*DBbQAtjNbA0u03yUw`^ESt)7va+%X0TE@)`=fr5!3*agDPp* z>T!}o^$~7ku`zrK=&FPmc|ES}0Y&u4;OHgHj^Y$n+=yUiNeSo!#>^A*OH5khAWQCf zre}pkh;d7G&>FF$2csPzcdLxP@ym9#Hu6Rc5SkWg8}J|1p_YKkoTj?t9Go?h%GP#d z)bSK3d0Hz&R(M8%mSh~fNf>!p48-(tjC-16=D{UyR{APHmHCuKyLGw+n6#jR!y<}; zX@I2_Ri6RYcT@u;2SsUXUjh6{2mC2yixf~t^ZtbP0P6;qYylXQwW>5!(9?E_U@;M+ z^+wv;gNmF0O{VP)R3=Y@e$dKA23XWiBh8(Y)ff=2Pk<6RfY`2!c|JD9q!!&{RmWYJV1I{Ssb1-6;P7e3$7+Ra6o8mqBP63Os3x2+!3f!jSk(-Ur1& z>&BAn09%-W5ru9gAdhX=(o!fzP7t*KX*EX(jOsiNhJJf4J*h0l4-YLCJvfp74_|a$q9GV!pVTSDaOCIY3rG{#vE_f}0dK zR(S8)K~NHkLzXw~@F{$J0vhu4KInmIf**n~HE64kv4h6)ro z^AxA@!|GapO!vG&X8$+A8Cma=Yqdon?|}wA@Lz`-`hMhLfp2V$JU5TFyfBvQeXO5^I2>%`!_w@leyJkI zGdoZav;iX8nNxD3qmXe3XiMyjY70=`1_ zKyjJ?y#%dsy2wauLgMPqU!|p|AFM1aa1Dvq?ed~%TOnSjeavt!H^}GMPCqqO?Z`*f zor*v^QUX%b&wC$IQ>Yuw`s|XAO`}-BzYyUsiLC zk?Y>D?W&S;r?vW?$BF&1>%hX;cPkyziOe%)Q2W*)swL@6(ycrLW12f@F#{{p@L{Ps zx;C)lRJ*=5!5BeSR$|*(Q4z6rp+)F8L<7w!y=9vM?hR?56#>~8Pl4_!6mo-kE|j5R zX(wpX-N0e#ak3Vsk;*Vm4Wmvcc#zu(5b<-8E*Hx!S$n*|-qf-L9taQAlGzat8>d$8 z3mBAVZ^c<>AeW@zAa&-OaCeR623a$}jc#(ugXKMiXMEnaW;{N4VmN?D zaMM?Nx}olU+chxb)Vr+bUyV@w*^FFb=5FY2?Ttn46z-R8bxf##h$$2{z7g_L@1^}l!92K3TRfvmcbdYu51sr4+C(J zJ#1$#>VOGLJ$}%0p^#v)SN4)$VCkTB1Aq*WL!NduF5UWkuCL5|dIpckrC3Xva%0r8 z!r`F41Te(-DkW5;5jm@?f>^I5Z=L2+89;p!f~!$k%IkWpA^{l+ATzQ1s{uIYR)8JN zVK&fGfdko4CK;?Ta0eKa`zwfWW`VLtJxs)n=Wjum6sk~4Dg9nqx z-R1^T${G^MRhnE~X&#gbP}rLqhzmkno>mP@!LS8+byJVHTcmaUo&(MEbKp*=JOv5( zCI~C5+=dD_sWJ96S6pItx&JJ~{bX~}sT!O%>zvx`!<~P|=#EOqQ@6-dL9}_n-3+K8 zJ*>?u8?~?JT4Wz{i(6pNmHl4dz+J%>BBhLcxds3gxywI0{ogzV61-2z+bi3iQqsU7`-^>9+%V(q zFUNJD*phucUa-!eexJt+_U_erytf@`p-+T-3lA|JXM0k1FN;#YjCK~FUnt~d3$4~w zmjEv{u3uvZGi|ifKs4uGr9pq#?6O&!rPGaOF|s!=^1(CEWn1bdR-cn)xZgnERC@4S z+J?@%xMURM99Ty}ICQIi!t~0rQTGA`n8pfoNMe8P3EC>JicK99Y!@__g>UmXP{{1m zUiV3rYGj>WSUn%DGpkzU^c=Ge*f)o)>A>B;}s=Q?> zirbkDNQW9cSabm68rx|Cm6sLVh38D}+2M*K>JV;>BvT+{maWMJ)`Yz7ikO?%V^K(` z!z{T$QdkzC+e|ERo_n9{ICA_C zH=cb4lov=>rDY8)&AIXnb95XGJZ-h>C$2cRQbCV)t8}rUmDN1FXh{iZR}SU<=#s7a z`F@35i7B;bw?IeAyhFJV=1C0*&Y3A2lU`48@EC<0#sm~Mz4RElA327AHHLnp!nF?l zF7e!Nd;2gx?M*ri$MdJp@a$wCcnGL&yb=K2y6O+c;Of}4=i>!E9yHYsDq4_Y#?ZY8 zeZ*)zvT#(OfP4eBIjRe)sBu<>9zT= zY;@$3G%*_Irdhg;AHW<5dzyB30@es6ETDT2LlIhwR_7qFPHY)&qv+h(gY&hfF$Nve z(K;H5e(J^PQDBwL0IY&4-mU2IG{W7TuY>j*IcSsbQIXW|c7th6VVrEAkUBaia$ARj zz#M>11rm<^h!c_)AM~dfS}wMu9-u^$18?8QOA^qECOtr>CwNM6RGgj$`HbDBG$EsV z-8};d*WLQ?DV*y=v@sDW>;}-M4>F~q+z>!Ss0+T)<0N%VggnH9bH}F8(bvd>008i; zu(f(#+?X0}Ax$1++t@5>onDMEq+&}>_e@&WqcvWyU_r-ZV@~wFUK=CK(9ZlI2lWUR z*^s>h#zs1qb-!7SFuNRiqj5U8B+;%Ph;uC98Kf`otD^M5B^c2g04zY$zrqrXWt5Px zBF%IA;wDdo6D*%7#!~7i>r8D*8gM}%iUgoLrCz9@3L~;dZCtO}k$VE;BC`e1)(jy< zi8|_2Al?+gsHrgY6gzq`yJ)dd$mT^W1U6F4s&jzAMoF`qfVjR~0f(TiuCS{l2eWzd z0Fk_{cJ$Qn`qHh_^-Ny{2$)72AS>67L4bfIih@Nc!6~Uvm|`A}p6;v!j8;!hgfHfx zt4PsGoBdHSX*D)dYz!aLw?ztLEmC&B#12WuXoZZXy`tSl04$=zsGA$tNP(0(9(R_< z7|FTAYvm;2Q%VqF*g|YyPJXA@;=0%dP|I4e8v&yfR9we1Ks46-&F-+HL(xlWd>1DL zSo$WrQGgVFtopKmST!RnA}i(q7y_tBZYRr8#5R_W?Kc;Uwu)|KWR#jMOmhmgeGDv9 znz4Kt;J>K_K)Z}oi#*>>A-#e|XJoHChQb~uyJ~OETAHl(v1`cmDukaig)-cY4BEY* zK%s33TT+uV=m+tcrxqODjUe*)PN$bi=VTEXf9Xlp2HH;MErN4Jl6Mj|EUNwPcU zQcnAGfLt2BUHCi5q^Do~z3tivpt5eZ_{9(RY(REqMCymHGupxusRq6sCH%W5D>rIq)MYjh0`q>~mGz5V~s+)#k2l z#_FE)0MJhZFp-W?Q(zp~!tB-R`*-$c1svVg9iaK@oiNX6$EH9%J-xa5Y1uid|H`

r>I+la+cZ6TgjVoIT>9v${iTI=?rAce(ks!U&0AZ*rg=kUSV?=pcdVfM9*wre>DfFMZxVvrZ9k@vd_oG0e8lo z@3oW6M?Fk0aO}~eU}sjv0;yrEA|7UpNf=zGp58<-;_8>wb8wkl;cdS^ULy*XPBStB zaKlC~YsCB<*>l_su#*_)+e^-$E}_w_7oBI&thG$~ar<&3fgXeE+g8PFHxPKikSt?_ zyL53UHU|&E*gtu&czrLpohCh@655BH+10$SW7Nehk-?)!39a%D!wVw{le<$T1!DckGA=Wi+%RLzi|O4Hw1K-Zp~y|P_adq zP0JkfJ3G)4;$H^K$l7#LLyX`*r!$ZEX6z;a!chK8LvoA2l42oS^T8n61}VB-YrVwjFEu-iO4 zEu#4sYD&tPZGQ&;f{&E04*~_O-FVdLw+)D>LS<3F)@3!5$O&7#BvT8veJvVpYO(OT z-itw1HxR=<_MIr40a7y=83wAwq=*IoA9sS254+DC(qshMK=6QxtuM8^)WFi>{h0|9 zW>?MF2oa-9~ixCsua2x8x4PHOYb5%a4H>hRRddvjWK0a!|>Hks}CG7n&r*QC!Y zR`oHFK2T>ax8`1gb>+UXo6y3(;_BellNz*bL+X8eV^x@^i!8coZl{Cu*;ffM$uQLJ zEo!(!mRYX0WoPT8k>b3+PfkD$Do3Q#hw|gPq|psW)n$04GSi@zAGzKI2P{1;EWcJ9 zM!ZJ5ZYPOJhW-#A)-?|}<11KJj3?uDb{)wqguus-Z!*fc>G>t;(J~EOIWpjx7*by> zMm_AB3(Onm){>L4pa$Fs@d56 z1YoUPn|_L+|4ND--&D5#cr)IYpv7@1S-z`&kNWtE8L_xxLZDXgEdcV<`ajjG8O*$- z>+sQsCE^kQqWNSqPd8Y94zT7Y|uJz5v#TY=B zayUQWI1kr$!djT??Kl7e>=EZ*Llpgi>R_c_2oBg6rFsE?@YjuMbB9$Z+q9@TxVBJd z+FbVVRipO!-EVbn7I4Q zv9M5+pIFjrIdtPi)81|^9Fj`=^A2nV2}Yr`d{cW8RlYaR_`$}#vy*y_w6Sg9P~J(t zp8)ETJKtN%>7qy%|H(Ek4iyiE3n&qm?bVoU!h5oZE!U{=VyN#;8CdE%L@&dps{u7k zs77jpiUhc6+*qGx>FHNjsm*@1v@Oq8ggLvOz!n~=mCQ$^hUh1oPIhOmxES5@l@r#{ z`8eIG^eeLZE0ZBHEMC8khU_`kh;4vJ;SH+JrmMRN6+t&RXmGp^hfUC_2NU!M%$ECa zV8y?DKF{RtF=8#DB3O9|Q?dNCT@v3K>T8r^D@ZDL`LvFf#dZycdsCM1r&WUaEAVps zuj&n+2CqvEp%eg0n4C@7zQ>OamNv!U&bU^v;NVQ1cKsazG;;X9vS2M{Bu;V)sx`!v zj8}dC5uD_RyZ`bn0f1(+OV7`#Hi<)q)kA)o&Td=8)@DI`AL%&kO+okQm=qXid`8l<2tRRe z6R%gbt!!N?tRh>Nw5&2XO18@lv_bioyAv|dKw;Y$Yq zQU{K`u@KTCklLynwVz7BqSducYV+`O_w%(`+l^mqB;-fso6A*2znqw2(!6 z($MThgGE~D8Ohw!oczOPgajs1hOY}8a8DGBgZ8eun-{TjGzj2fd8fyIWS^7SzeG_B zo@b-?8s?U`M_hxj*buUTsJ^GN4>V{AVfzT&UFEj48!rIbgktJTU?=;seLp(s`H&X1 zT?l70NCkY)>*pN(>z9>VL+ufq$gw6M(tMoum?qY`Ru$@JrY>MZ21AXr{Sog_SnrDU4%7Ds zOiJ)Vz4tw(F{y`1QS+2Vexk>+HRY7YTR{iAzM`jyivN9dzZd zvA?e`IS>t2kCyC>2#!+&zEEnew7Tx*;9*-edrw)#>>p1*l4Z6@J)RfM&Q)eDl3t(s zs-{a)Gbe|1YWsD`=i5(TnOLKVu%qW$qu{5!5=V1o&U6La9t;q<$NEGc=6CXiXNYBv zONRL9Y(2g@=9m19>eZT6@pG!~?e>7y6%`%7KGzZ=p1$ z0J1TF-$LnyI-cmAq&&|4C&ULBWC8z=zk6JQgC1o=edPvnV{gtitGf#z={A`X=VOL@it%Kv>DcPb$JyFp-+l4fAPr>Y6Wk2QE zcz&nqtsgB-rzBUpfEU?2Edhe>)snz*AYgxVdqdr-*OhvOxBhU@D5k=;BH-$P8+58_ zqF`0`m*DWqAI5tfzoaHOo6MhHyTd$Q`qO546?~ZhaV(ck?oSHwv|n9s{qmh@)?taW zn}vKq@Bel9;Z3>mincq-o#7rA>BZ5wWnq7S5#nh@SwJ3_V2b<{UmGQ+L;y6wn z_pX6D4!2%9&yRQEYm6N}x<(`0oKsZQu zZEA$-Kj93BBi@vR=|30W!EP9x*BBvZI1ccmc1GIR}lKY{gDQaEHm{O2Y;Eift!32hKO^c7Wkp{napAb51BgaPbkr{g{0DD=L4g^>em8%o#|J!4%UChgucj%nX1wNI(r z54j1!g_RoaYlA3AM`8JKdMLSJ0@9iZ6#%?FQ_QS1N56~#(&6MYAi;{<9|@jFgZf%K zX^}f$7@ND^K&4wN&e0w4)Id1MIhmwYV)doQ2S!{cYKEO%5u+F*Q$ zj4V^Enow0if*AgX;huvvKaO9fUJFd~Y5{g0+m~HT0p_9(uVY$n$u(5tSv3=Z*EKZ% z)oz!)RybswH1gmPV@wvWO+S;hrCd=0I);2WR9r|V5+i-^YvwW_I7;4-_1Qgjz3v7m zN%dXu<%!)XK2Rm@9|>%U(XWV=i5#FhMAbx<>oxjB!87JX<+|7AS)xvhU?%OGQh1pC z!%cJiCF^P*YvDUY-%fg&v#uJC{LsieCk5G+NiUj*q2L8p;i*sic+!op_V0>SDRuhb zdW4W;aSEtU_aQZgl#4!YY+SHCO2V{q{XRaQ2@F!lv8m;54v)^`joPxaUqUu_?5Y!Iv?< z-CPSsibK$l{H0!?voJ232RJ57=F4Zq08Umk{FM5NRsVyf2Jq+cqd;WTfhjXwcDqL5 zyaoheZihbS2}{_0#L>Or3`_ECLiB-k3)Li zr5=%M+UwgNvjM-<+BqZrm7RWRy=j(e)1(1vg8%W=i{5@sXyKB4N5t*6ZmN-SaPb2wSL@Vp6RmV*J;*!4z8s+X|or z?;TtH_?tXFL9uvPyxrn^LxxESOaK9|MPA;c?0sHKRBU|wHHyR7N|y&}d7MG=TZ!I# z&HC7JJphPyLr`Km`TS14)w>)jN3b`z3qKOTS?=SIleU>p4!nJ{gAe_~LJGnC;i3zt=!p-zKR=xmf?yo!+?dEA9M&Kvhf!k{R-}*IM%r zH^Sdr0xvuL;OBq36nJM7ftjoZcn5huEwg*hI{;RT0jz7P#?5-u8J>nY+bgTbg`G^O-EeCUg5g^B zS>NOzXQ2U_LEHONx!1jzk8@LDGpS1{9LY1;@BjY~M)Fpidi5*Ylj5^ZHpz5*7Fdw| zAKBk|THMi2mMtwQF1H(_-C%V)`1KAUZO23eAit|SMp<4W>+K2HmbWB21OOQk-dF#b z2|KyFD5nSr&4J_OW^noep&mujzVYeX)@@3G7#Fy*K$l?OdteLq6Nbk}hm`x%7ZRhu zCeI!OZpS8J)UppgJFuG33uT9 zZw&){>tEIpSuNON#XX3h^4uWZYMA~M8qRim{wZ}m_xB%IQzm{Q!8W!DPu)d<4&mHG zY~~#7FR$2f=s(8dLGv{KnnR(M67ib9sv|hQ-bm7duMJ97iApZDOqRssF=E7KkGg3OZV z-43Yc>XAzre>JEzj{12FGr8eI1B)i$NabhPh=nt=ty9^IlWrhx6O4`vt`ceK<)vqt zG%jjHojoz-2L84e#zsKgIxU_g(pDI;I@(2-Ro5@=%S8Ho`3f(BpnJOCN?|Vm>`cEA zB+4fPv?Dg<^x^_Esf$vJ>QL6TZYFn@)JR`UFPfwvQERKlwd!dP4xv3FR_$H)nWb0$ z8snM_6O6csKZ#kS9|EQ>S;K8yi9Y<$A#38o0k$$Af4Qb1Nk|otrb~&CM5>7pMBT7TA82}*oB`oOx$ZI5$m?+VQTse;K4oJ!iWVpt|y-r`*D{B7? zZ_*-3ALp4+=zD4)vhp7%LLsg5&0~^$CCCH{2m(zd4 za!hH&4~FxLC)C9@02)=h%NtkVM26-h#^RF`YM}u(HO^9Ro)7ScdqzVftcLf1drn$y z26ZPLO=PICUGw5CrXLJIx4f7>_v-dR{pWVh8@7+OC zR_5K;L9$baNS3CxxJhaND$E@2~1;&!3lJ8QK-U9q^4P z2MMw0*&dr23|)JpufFF?V$q}AWBlb6hq4(7*d|uHA9vzl!mnG99B`bf@gk-yf`$ya ze^=0pRf_Z%aloy2cT3kAAJgw+gHaqdR))Y~&xkzYlE!U+^I2iTiM)I5b)dVE9M#+R zYmP9-h%_>wZm2lQb9y`*V?f5;b9w%ex7-1R=&T34e|tx@TLT-H{p0D5iWatS(eB%+ zU7u;}=6nAkCh%-L`^>LH^zuvuOUL^*1}qO_N<6Hg#6b@6L;*+04GguGPxs+y`Wc<> zeR^B`34oEQUTb{HMOp9lo2lv^cu@za!KHcF;f)0jkH+<7{f>rqVSm#U)+IS$>21=l zwdk9d6wd?i+3Szdx)5k>Nos@(L60O7Cz=*uWx!p)@eC7eKoY#UmnRz|F4SpD zrgrnnA!Vx^=eictV4njqQe4Lv;roMrw1<>aR2oe=loLLj^}M2 z-@)AMg&+R=x4riEbOO%8Ked$eh#ubNb~I&4-6T&exh${=yR?q}c4Nd2qq?USgl+Zj zhP(gM@u>eM{wrVge|ZBb)MH#@4N1d`nE#JPuS@1~Uf+te8UMMr_rKmix`LO*LH`7t zGra&Pctc>;S^c%Tzb?#czOK%g08r&ngvN$<(qhec^jAfs{c%hN;iR$6GQdH-?$16Q zp3x&ZfU^aTOjfr$;0npvO#1x3@6zb4B%~2+lHD9UxKe1)XmkH@?t4r!Igw&{3fmwQ zh2SIg{h8gMId;QXxy{d<1Dkbx(toM~M@v27ac((vx;n+cR#0)@%jTL=q9%4zN{!=7 z#KQpiE9!)#b)sy3QbRya&uAQjCuOrRqr^992!j;gO4^_L!|>p$-GBX)|Jo>_7}dQX zQ2ae@o)JbaX#?LZ{j}dS%qIh&Ex&>Lss1G5fFDr|_Guq1->tHz=!6Z(*u#KIg22Ax z4y@PHa_G3ElEtbDTM-_4&c;sA`R59FSdU-kxPLzlsx}2X*xTo0XFLk7KRN~g;WKaw zGw^_>s~D;Bau)NJ^yc&N8B&z(pl08F!oDpmre8IK@N@`y0iB0tJ0VUlh3>EOqf(C( z1Q~U;;{=>RZ-3civhU0NM(o~{2NQr=?66fr?9!dUk6KfQ9O}#!tzDt92=R2vVhA`8 zITm_^478WSJg-2?t8_JA0-BF8(YUY26|pYr2fJs)gg-PhUE(+3g~29;mr+2}r(n`jFU z%O#||8CRxBtKj=eUqlYEWpg|C)6~)Wiz<<9R$$uk4@T{1c>>nk7-%W-ly90<)NA%bDq$&Vt zy-MoT5g@$_#rVlKi@5K~&-OFK@0Xt#MnnX1qtgH{Z^NrP9Og;3KO-mt51nqi}1`Tf+G#&wIwKbOt*p1XVg?xVg@XcO# zeRpY#^Z~q_$NGEu@HxxIL!DLid~{b#;wZM*brlh6{#}yTbOXfRi(Hl<&nJ>`Z>&8W z!Un5jM-JraGaqj$HGQChvFz+Dcp2G_w6?aJ)@9-z(v{;Ki&zBZExh*R#SyFtt{RUO!e$f__px7+^JIE+aAgpSSVX)v{D1iBv> z`3|-oSeam^M#SDDw;P9O!1ngg@;;xIn9WP19I+-D>wFaLCSt1Y;SiCAd@f!8m9o$C zPzOJyLfrZ_#`{NY2?r0@ba@#B{5ps>Fv&@KkxM@A0YIt2Jx;M5mp>`RT>q5e3;QdrAiNA|`-@Dv0Zu{~odlL8?hkW`v_)Iw`g(r0)7i4{r z>wwYx>?L15wtTheWo5j6H=lJzgoX38CY(}CK!g<;efsuxHW3{T)cZOHAQqVNZrN3X zRx+y^Vd+VNIhkrbrQYnQa5~Y=q!{G2a4!j)i~&ni>N4`ONO_Vp<*Jia2=J#C_MhI5 z9l_0xZx!J`dD9I8z}H=$g)?C|qa7FfZoc4vRrR&ZWC)O2UP!BQlfc39`&nip#Ovv+ zhv*|mxH|wlso{O*pN7xy8tOZs_vxCH(Yag7WyeFbJ{`V&^1pP+XlgD#@#h6OA80TD zm;?vv-S60W+gkfiS?~nd73%mcjx07N+&y1~>Yp0ki+*LNVZRt9VpIQ>b>-@nW@?%huBBf&AG0r2R95tmgeEPM~B1mU0P-3AMT zG0iI2wV65Q>|h z7T&vM7p&#J;|wo(>a^tz0zAh{#kf0a3LHd4Fips+=5jnyVLKU&<5GF!U4YTO?#ut4 z77bh#Qj;Hq(cTmQ^#YEG0*Wzk_PZNMcLYD^@&JE0p?t+gg3oAq0A63Ik@=S(wK&|a zTCz2|TaO9ijTB>V5@?URAHZT@Z2|DzNiA>x2H3&7g#QZg#d;;oeRtL0$T<#D?#Inv z-jfSHtOjonUgMHR?B^*nitP0MyGpja+cN^6#F&!{a+MeoQ;>3s6pcdM>+WL7jH4XjvNvA|i?4KrbC~?J^MsVIb&oPXU=v2; zLd=fotLc}+ayS@s)ZeDt^&aH{O0W6h zyh^`b+=ZprS>$nQ@`7(xa;iHi$@};W(Q1a5=}L4)v2Ckg$IcU8w_>q14wl(p=#-b&e0F3o6fE0@Jx|&a-xw5C9$AsG_PnN_dfq$nU zB3(&vCJG!FQEW&C<8e4kKHgOo2PIbJM@VG zc&AiU?fF13)${vy328hMmeS)*r_WR4dpi2h{YsjHrv;UU^t0-<)PI2mkARo$(`%?( zk{YlVCdH)>JIxHt$9zri0$9#X-NP$`T#M1skR12vgeA$-Plv-hqF)7MSYc%{_DiKp zblqPtJh)Rw>~ycAT2ky+tNl>{xh*< z!rg?BMb9@EGwGm4y06yOZjL-xg#Sze2z;n7ZGzi7STr3LN>)LTB0%}UGq#^LUI-pb zXQa<=yN1aeGN`m1EMfS_rWTGMA545|4V6$< zomsxasv67gqZ=?msRV-`8E}iiylOKLI(ERwD&9X5GadtyWX}E8w zk_=!K=g|ZDR|C^Y8cy>rEs{Z3^I z=O@dhp`s4u1?60PQQ@j4I_~D|ZI1EYee6y?BTrbewCkq_u?+PK(5lgc$^vlAN&e6p zb=kQi8yF8C=4A&=qPX|B^9<<4YrH7eTdN5#y07eNt!WE!p%6ff^)E15z>-p1_h{uu z6A}FtM8_!7Q+j<@1eHQeY@b{kW|W$LkyRbuu4$yWi(nK=Oa=7;4D35UJJs1;C19Rzj#B;&9j96*uKRa)yQ%Sbkz} zYD5`-dv=OfpAY%L3k1vegQ_IKnP(oxoc^;>Zz^zQ*G=t-1-wyhC;q8j505=aOi0?E zza-`QXmIuV8OIY}FaEtoKLh%`3x4Kfs5jrG^I757Oe5VCFrtG#CKTo zQ*N|LY}M1L>ePYR{$XPVSWB(2>y!4)j4m=_+E??A0m8y{!@_SGc3B-CbJfSNouZTI zgrF!))4ltrDwtNLc7ek8RWIK3QJP&@4Gp!gJ1t3CKSiB3*7_0V`nNB8+*sG*-kZYX zMhxiW=X9Zh=t>moYsAM^d8vuB<*bZ$8Q7sF;^-48mK*0pRtlwDx>z*V6Y%$pGLDY zBW75ZaPu2}nW9YJT-+60f~Xx`dUw8K3{S7a);+D&coAuF23{=Nr|e|AHHn?rtJP_s zbRAu&?Bq}XVi@gfHQ(DaaP>9SAGRKP0QM=+!#t1Q+F-t_A){}|$0_5iw!J}P z;;v?J5F0*;sQF2Hsr5+U$7d-M_YaMW%br>LZ6{7&tMwqP%JYNXX8^DXBcxcH>G!yo(!dTCg5=By@4GQAE;@2*HTh-|Jo(NMx^qUrMU+J=zj`l zHD3X{LIhKDq}9EU*7I#+fMEEYeWJW8RtkOflN*Tq*6p$6+7%t$R|#OL`68wODqcCO zA|A=D1z(P?GJ}eih5~^MK><XUJjj-Dd_~_o%>QJEGz}&u zc-Ie_gE(Zj6`^0mA}9~ZOb>qSuWHq_U17Sz?W?xGetz!l+-a5KZ{VZQ#ohLZS|lWM z_BZf!q;$a}Pfi&)pNdmIY_}Iz7FbZ6C?h6f;(g(mu^-?m=y#qO~08!SlOOBZigQ0AN&KOB)OKiv?W zL%gzDhmUqk8y**4!}I05>ZotB`*4=L?suV%8LdVc-$7Z)e{PRMpKwbRjPn*vLLxC|8tf)e@3)ptF76 z)l(9*sLFc*isoQ);mb4RT0vnS0%zXBLR1Z2k7{5!q*0b#Z2b&$yl7$RW;PHQcL z>}7ZjYOTKWasqe__QP==+kbwwt_6+0Mh#e@J*356jDTDYQ|<`tRyc#oN$U4yxc*xi z{MYSOW^Ol3(6QW1;M*A9U0hhW&`aiu9ZEGvHyLT2-Oves?-t^C*Xn@2#|%YI1i)Er zf8K+afTy|DX%rz$M38(x{QVkf3pSuN$ynxB2#-0P*QmRyrZ!&>Bk-t`CDGEi4T4## z8AlR(yvz5YHm!*Etww%{!W?K@^wLI~dWG?B6*+qEitTTRFmB72_l?C$)M>r3X+q{w6h>-N}J<|~k;QGfNP!Cyj0Q%{# zbXnJBVF+~221!odS&7caxu49g4(Sklm6Vq4Tu@emxNMGPAp84w0kZG+a`+jg()&65W^86R!Gv@tvluRwx>`;5`bT=;drl z$2z1;-&)|aAC1FHg8wQbEtG=FN&Lq7WZ6MZnx+A8Y0!1+W%rM>z!5BLyL*wS%G0qp zWJYq}Op*w0Ldo{L$rJ(@>Sp2p^HD;@;`ZFldsypT*bsa??(s|Q&8SLuW}GJ*iqh#%>IDCu79dRC@_S7 zx4RPxIP_`rKCSdm+Im!z*oG+_`uT5MsHGK-FM(go*45kXb(rI7h96SFxsJKnj;i_b zI#=S;ohKs4>bB|B{H5&%E!`BuoH0P# z^q=X0Eb|c3`;z0XlIUkZ3FFMbD_g?%;_*=dV)HcVdLnw$LSmW#e!%We_x)*p*CbpA zEPUKYr{WQD0?fM-&QH2y7)~T0g!E92(cUj42>ay30Mywvf6=5XtU}$!ds~a02Keal z-DfQ5{#|pTlejpH5A-O$Xzi}(>Zu^Qcn7>`#5#_}<&9VjZPju8kbI12HGX_^cD7ik z8DAm}fIZ`9!@{;mAD~*smmdg{5sEVUWRDnwaOCB9R2Fpr#N-qe&+;}@cUTP?bfH4? zI`^o`XRrKrFuW+$mi0kD^F5;9;3ar*Ch}()P2u~2&?z+pzp=-C#}$%>nxgsLz48`S z&JHu@3+cD0+LjTuRj`Bx6YQh0=aqdY^lw6E(_%9m;WJr0*}D0dAEAj0detGycFz%p1Cyl_Ah)3j*5 zpI^C><%4f7LUf{^k+U~$FCTaq!z9*T*MZrTAsY{Ng2hiuZkxo5lCeMEe#L!ZQ$jwi zmusom4)}I(zj&M}IQfoiv|{oKDPz@>tAZU5iS(4A=33;W=IL%c%)r`Dmz% z6mPG`zPkm}&Qx!|N9-}<52hct3cYn#VpV3H4daZL*sJJi7nEN;W6EzIF6edniq^WQ zs*dos3`W9EY(6?Ta(vINN{z2=l!nI32qeG}n)NABW_P@7;HdOLIbVWw3pa0z#{hAR zEq{Km{_!12){SwSeMV6$fZY&+Zz*5Gd<|2K-KH`$kKQ!%i9eW8q)yN2jhLN!hZ16n z5}ffg*xK3ViOkmZbE86zq9_H}6IQ=67wc@mGB+ymdu!P3MEc*^=`MUJ5du8{p zO)xwDA$U!nX!`{0N@gB4Uh4S=t1p70|DO04V^K@5Aa;t71acbK6 zr0s}(3KRx5_Oxl~IM!NT{k+O|J2kj~p{AU`@@qkz$-#5yq1v;d2eRs$rnEsHl?=0S zy^)&Cu73A%rq0msoN4(2{%h#1)!aZoPD5@+AM1~=;Y9lYcvm+qEsYOJ?>bw@*4x)gm#WcFiE^?w| z&8j`J@AsR#MZ{Sr^=O~-)gic!;61M4G8kwk$^;z_-(dX)vxa4{e9zIkHDbJZdpM z(BH)%Qvb-@Y9ZcJRvmO^#4~^$@9qBa@CP=7rHf7`nwq(y-texAA!ta|uGzT0?l?ou zR@08ZPTvrlq>pPFZtR{rtlSw+r%AAGS8plKu@{s*uk>)f{4Y_ouO1M=kD1<#0S$)N zNBNpyMx z8<~qT0WdnJ;d6{D(aR?y-CUHX@kINcdM8Nc%LA8EChez+;U#MzXALW*;IleFB3ZSG z56x*xfPm-uepjmF^HCAjV=5}5_*#-izY;9Q-|?x0j!2%p1Zp)Y$ali7CqCR3j{5sk~TK6ryA}<6f_VLp> zH6qWfTDOmpFg_n*Nn&f{^EtgR({}5YEAv0P-_=oNrUc>PZWiHgCK{B^CZtD@MeR7R zeQ1YRIj*Hzux?vYmaz4 zk{~qM9_M+37XTjxVed+Bbun*KUnVLIr&b*Yi)zo_HG%mCs?UD>O9_RnT8gjpN^v(9 z&Qnt5ax%i$TleTT>7hp8(UtMIc(d&3b+-FXqUJ+tlS~V~Ce&`kD2{huSqYHECf~Z~ zb7OnyvNAG&GAXhV7#8hhlg05&P`14qUVHqCY`)j6^w9aE2* zue-vIFBXviy@9qapSb7oCS0XG+9+{~GjSnsgo_vZnvWS2`f0~_)CNTxWhx^0j2&L@ z#9OV4^FW%W5F8u18VjH(ym{+lYjFD|E!$Bd2?ab_cMzOfUfS$E_wT}6#dqLb)^C}N z?=n}2DNLfrXT*mQ9Z)`(a0?{m;T<4*Uv;Yhii$U%#%84Gt7}xfK^I4r20Lv($Rr)% z^$ke+ZwNE77({Uq-mG1t=%Bi%@a7^@Q2LreIA?nnIqVPhROjz686GjooU=HP;l}n) z8hUycLaw3GVGQItCL5MxO4=@N56!?FoQJoY|A_v_?0Og_2){M5vB7r~u2me^sT?0S zs=q5AowuGCqsSdXiY0Z1zHu-&;<`^~1|B>RznbcO4N4^-oBSP9Um z$9H-!1Y@E@teUvRmeGy(e|ns-*>PN0eWA&BR^*K~n)X!1x74?|>33!4qCcI^9=xGJ zlCB0zGS}Lf72;2=riBfH{>kWYkQMy&XE^cybTmtFCfMfa{^30a8>-Y- zJ=IsUlGom5&=sY*^E_1R>{Isp8(}YtphIer2agG-I05AunjM1@48!S_QqyVo+sI3a zVOz(0TOaVPvm^F|Uw26^>Rr*Aj03zd+m|Bmz299HC-b4&>jrG_H6SsP2SB@_Gt@kIq2RRBRizP~BFEFSzD zeXs*UD`l~|M5NtrlZ=Ck5&Jd2?VO~A1aw(Kb@!q9c9-I|ZNHTGKb&Q}I6m(CQK!r_ z@@z^#W&fYt9>n&)-gL^$2b_{VMQMm2pfMa5N^LPp3dlP)@HYTgp##Q$%N4^QLKuDg z$7EoNpeyQcF7f-qrz@Yx1b7TPNx4aRy!7l*EjIjfnOZ1BayOK9T4Lg?@0@cX`Ha?}^4>nxCs=AS#;@eX?&9`^rC&c@Eh z4Fz)c*nc;G+f90KdKs_9{{C-w$_RZr=T<}@pBEM5HzfFFr{pJj0#7+#|LUATS1aZl zRrpK1J(|C_mi>FT%{=}Yfc`ZDgSrPGJiPeMe)3}jVj4#L!Q7|q`QbA%o%W}Iz4~@@ zh+b8RkoxZgoX7Q(fLRgK{Y~YOSM}vAa(c9{%Cmo|$_&^unA&oOj1(2e`VQyk@cKQ> z>7GjorrDvOyK}t6cu4DP$lM7bZ-eWSzQ*9AvO?19y=g-&kvYvuXS3FrwDt+JR!4(J zn-fV&?VjLKx089EChkQK!dWp)!QyIUIz~_`nkjNaWL^mq0@!kmuALOpM$2?_1IJ|!K(tm1E2HHx$CwwAi%UcM z$czYv-Rl=H><}UUau|h={=B}$9$XTcUHXrvMnQZArOpZfT z_9B%sZ7KDgv@bpjestG^q8s3#Y$)1=b|y6|x|G;`o$u91OY|DBEHoxFRr#uAnU`kp z-oE?N?&5~+ds|c0Q0vkM4@(Xtnb`^sJG@vNvs2y3uU8F#`P;0`t(MuwJvw%RR?@ZC z(z`!jqv!P_-j#gGPxAyX^)`a?)P|rED)yatjqZmsRQhF#gMzsFqiPncaM`iOFWE7Q z;K#Qoe;+Vmf3GO^c~`}&>z~6X5#m`_;0YP>^YydOXZwcP=rKpb?C&qC>iVxKOV{E8mq z3b@7288Nb&%RqF|NVO6(5dz_)J$P?lU8@1OP(Xgc#dxOIv5||701QikjR1*9sxGRL zC~|j}W0s&mv9LbWFO}=kp;S$IW6%APC6|gcRqX&Ferji?Pu(_FcLS$CNjn0n(+}M} zS+36JjJ3~WuCevyC3-D+Hs7+IYlXhF=!;Vjez5>viFxdZ;@r5-`A6lB?0jx#bO3fa zoiF{($5bJmI-Yf;R|K+K4=UJLrgx46RVs*sC#{RIv%IOHaaF|{ne8oDuj<9_qAC?l z-eS1)VfTFU1zE=Km8;*%iKFNK&73{=$~O_+iZBEnS3fnY*7==# z1QcUUzj^M^H0%nL4JAdxcf~~~*yS^y`QpBTl#;XA@OCDBt9Gy)PpZWfB<)oC#`H`EJ=~&422j1{3$0d zN4NTW&wrNt?e;R=JK3Ngdp;fEfjjew`%ASo*PHXwyGHZ0xb-mtyO(}Tw&|Sj?`X_+ zHEp_*Yv#?CKvEG*bfSm0Qz-yx_9!3s-JhHtLH$Q%=0y11z?wbh>B{kY81Ri%sNAzj zA0_uN-s<*POx3&~Ts3)Hx6d*Fi<8|5A6xk7{5Cj3te@ni4TZ_ZZn!RxR0aQTMO%)6 zy~Fl-5qgZm35<-$BNrJD7`|d`E5LHM5^I}YsnKq zeAKbU6Ngeqv}|8fo!M?J))&*f5%WN&T0Y{*c_>=D5WM$Xq!2qMV&IRCSDy}MyhnIS4wrh2S-CT3@<*_yG#eeQ_8pPUq7i40_?7I482xm2^=de-OWbL2(8BVh* z%G;6u0KRp+RKdC#v7HQ;z>j680J%v`wzvg3cFR|febqL0ZibOo!-y1F9p`4S<^~?M zNA!G=-JJKB-yunN%Ua26A1prVqe5}*mM)7cnyg@YzE;tBjLa3zC5z9j+ih(0Kp}*} zD}KF~(^X8y)1UBFbz>oCmLk2uha_Hzvh~L(Y=X^gwC&S=eyhJFV-BvBSC1P}{yfM^!5<##bjpSBiP$~IT6(4(&eG)WU$XK^j8FBwY>Z6`OWYW_ zK4V7Jn@vW&bam&l*h7!&or&63Of2*h7jOcUnvGapG@yW7nRzwRD@ zjgoCXrjA=P-^tCbZNj8LmJ|5O13Oh>c60dz%Lj_8PG46Gf2mHvsQY^&(%NNMxqOY}M_fH%yIy{zFXQ(Y7334fu`*^}=8 znl@?pWnS`E8M<4Ov~_b?t?kWz0jlV_=dWo80DR4lr1$zs*U7?t0ClzN6E@gPZSBry zPy*1F!A6t*U9$;u8-KDlJ(Lu!2I4)w#66&>B?3=L*J4qPp@3%#1m^f8U~3&eJ^C2w zioM)7+na($nRtwtj{S58wsV#(qg~&2#6alw1#<s$*fQ5-$Asif`3HJO6V z(Y`XK5Z}nwaU#?j_oz~buGj{ac3m8iKqe6TTF<2UTbtBZBz;j&zp9WPley04}tuVbED4hBt!e(J!Wd4bz$KRiXB#15m783mQj02C+!$+KBNg z1hF{NQ}VMeo4|h!76IGHp-L!Vmax)*-4)?ff-A$x17hyYyZ><6j)^wy22z5b z8K9mnwvj%25@F)6z6nyCjHEZ%;_(cbGWzCplyG7`T(#oPe4d3fE*{j!5aV48IylPz zXKg)_I(nPncKSytCUk~GRRRE_eXJIrF%R~AXl7+JrW zZdU`uOAMH}o?b#vj4BX<2eMTaOFF&ei3;q&Vk)(2)vnEazlb~XMkz@h#qwB8vt z^~#9QV~+XFBFuk6P6QmvVBm!J8!}f;aBb-|spDnakp((V?C--=Cz_aPS7z|K}v}yqL_hv4o8XQV_QGdPy zQ%OKou1PzJe~ktJO}{DOVXU?XZNQ*DPbOSn)iA*);OpGD^45|fyFr`YPYUVGD6ci7 z(~TJ;`I{DLhq=`)_Wa+hDF1Zr8HW20=C7eg7sCT;zyE(^KH2U!ES*h^0CkxfqSan(IViIeg%v!&;dETu%K4LhK33w5;(@@DV!(*vZHX+YOj(D zSb{(`BQEaW@pL~eY&o39Hl@iBGMwl9KHA|3DgTL3-SE>JTa|A*8u#;FxV%285}ZVf zBgfyde>A*2*g4e>3Sq$(1@M{Oo$#n5yuk*4*7`rD!TXK3#ZLUWLaV zhQ|k=K2{u3$)@?!V)99s%sM5gB9jB$;nb`vxzfqrQMJB2jbY5xoer^HCwYzNLU2Iwyd8LcATW zy|1gF(RX2ps8SSo^M)8nBrZewtVtcA&7iqZ6fv3UkN*~wx9gZH-0N|7?|ZSEv?fgI zk25>OPhDXPR|kKvOL*2OZ|jCLG9IYjfN!8OoK}3;7DEb!jZm~Xbt-PtQphGaK>JrX3!xh!A&X;>l1?-0-J;YbH9jfhu}c;wpXX_V ze6NMPRtef24eQ1<81OxaR^<=WFS;hPKr(gJ*j64}u%lnVb@XE};6+lj&O&aN_A?;% z9h)`8-INu5n46UL3+s2xD?Q%LihT>uk5|gRecESiU*7twY42-lq&*5@rM4yp*eRrC zH(AR|^!s_mm$gP`SKo>Gc@uBm2TQal_udTKLogi`*h5MsW}q3Rtrp)-pV!fw9afMS zt_X%v1o#U{cgH_s)jkJbIQwm>X!Fwbma%7_gQ^cnq@T!dtamkR8oU@5Tc;W zd4yYc03;3z(2jhGPj`kFvl_m1VduOWTPg5eRGRcl3JU|7~ zFp~mBZT;91%{fcaI~L8_n|^TvdqVf<#P3bJu*v?sDrML(MICr?uTg>8x`dw- zo@QZQ4jeG;N=Zz;Z!;1+YcTv2~hG7nVB8Gi`S2j?iKq&aV zyEOrRj9rJT?Wetvg;N>PC65_BfO9@kipvviF8ehaFn%+s zgJG_nlCd{5KugTRzHM{;9C}YHH?CNy==4-)3*@s}iiuaJ+9e5}>SMCMeLiaSd^c}a zJ{$FX8SX0uAaS3K>W+3NvcvP_m+cPLI3Zr>OoqE1`v#b0v`&))D1!)n461J@FeJLT zY47lRpEY^scAfTpO>xz4=wo4sghW|czGHzIlx}34l0ldVug8{(PQ2TV-WxFN#e48@ zR&s=%aDi}sdfon^j-P((faj>?UCfWg#@A=xZ?Gd)rM4oNkRtDA1G?U{)9pFqvMadg zWH_{eGaw2lTs~#6{_uZ;Rp7sPgf(KV16scwO;X{g?vtS0)tkY=Ih0PwX&dneM}h8* z{qF>)TKKa;e&gf-px{mRVCmS@#B8~~eV>AwwkA4hAS-*!%hbiB>#s3@91Ik*g32}Q z@F1Ex)JDl%;ygK^r(HV)fU$JR^=H^+vAK>)_^a!bZ_^mcgb;xQm^&j>!|4bwqSB{( zK2BS+|E8H->H6gY{?lEB@JL!W(25XWUXm|H#IWc4GWWhUX2k#)S7TO;%z-OlPP47OE%$>af24?AxfSg~x zkdVT2u#K!;%dSvGclI315e@zfu7Esl0ZCB)tMgbmd7He9XxckjAm?{?k}22+V$sc` z0J06JmR2=N+KrfGXoeKQD>++(Cx#$zyWzC2lK;w3)6h`NEKl3yu*p@$si;`>DNei9 zFc)I3`-=6N`)v{rC$F_)pM}+Jy*k!5pGFcdZUBl^^z&0Z(f7?m;`B>Kb48Xms_air zPv4n~QCcCh3?O}3SZai1B9j}5_-#ZKu3V!RZKE-uYmWZ0d=-x#2v{Tw7mZsjPu;Pd z&)Y;_h!K2Fb1wks646xLc8gBC%~z${{e1y!_yy@xvHH(O_{#7Q)c$oWm}<6Riaqpj zl7C<_4T+L+DW7pHCFoB zsG@_hYldJvlcIUj@a@^?4;j6+$4_mNDf?AnE{srtW9w%kdSg(zqbL| zkHM7HTFs3sdwPeMZm~-#fG?PVDdYt*B%3_^xf*;yK-3r4EOckOq<8P#wr;IE`zZyx zuL45xb(o9^F0DMoSsmNA$7Vs7ioy`X=qfBx^hj^F@!Em36eMMwBgU}In9iRPZjSz# zxvSehH}gB!NO$%ZWxRp{U0&ZoGK^Gr%z6UK3VE*HJHU%ENV0e|aC=)lPOqVsLZy|- z7&J=i&4NcZU4VT?%qy4Tt6Lq}n6GZyVFsI>5Wvl#a)P}fbgHRc5FMA*?!0;i<>do_ zs?tfl+AL0Y3Jx@*Q0@ay>J{Q@Pk~yXJ4I zx@;CL^*Y$fQvQ7cMaKcp{E5ag!nEwCLtW$Y5kRY1zCJXlTQ^QR`YVS6qklfGsQ{8b zUMLJ8QSi~v6Y_~KY|lIVQsi;B{vh&q;ZXLkYvegI!rxsqG=evw zXFFiHIm&@B?^WdnfId@U&n#E`KPl^)Lid`7p7e~<9)WBcu@8IDr&qL61Z2ff0B!zt zX?Rj3JSMxx2cb=8k1D`YBavTmt~4~!;^^=E%Cj%U7OCBw-A9T}2bN7qx{8Ki4)JI{ zrW+}ezLI?jw;>fbp%6f$CWeg_c+5;d#)JRTdvt04a1R zITnOBZu_!ePtNR0Pi|J_;iw8A)rWOSQsk-%Px7jFZOW#h7_{fp$PPYy^3KeZlX*JO zP>&d?J9r7Ddrj#(@@^hz$P=L2r211`ACwMJS*xqmab^U`_0d%Mt zXV^GU64w9f32>EvolpK})c~mY3*FxA03+b3w1IZNBK`R zy`O*j4vEssRpuztaS2g39==Xmrv1;;ySq322-4c`%%JBJ>r3TCNg|#PNjP$chGrS` zsDrs$n4XFsmW zbYV%PnHk6n!scYH21LC#&**_!m3Y)3+tsMONA1?+qMxJ?uK{e}P^ zxgPC)R;Q2i`zEwavc=Orka{1_k!Zgg{~4~THzjUfF32}$voSTBclo_BH0x*GcQb=o zwW7=2zVNQV3%*TQ1`-l{(1)jn^3cq+uaI=&n){`kt-5T8Z!O;8=)7YLEF&U36b~Y5 zAwU$Gy8y&Q*YpIK)o!EVrxcBp>v`C@DZA~pgNj9;76gT&Z)0+2{q1p9FRBUBEUi|l zlcj45K(_nw72?BfQvFJxT6NN=v0tTtT(e4f4aR}mC3Nrk$P4Iua`-3mjn}WzB9Z&2r%u3KlrpojQ|v~?*<9PT_qha|7(mxCg|e`I z?!<<^uoghz#R|D^%-+Y)Z-k;78cB@+D9|^)BkP1#x){Y33nXPwVs;Df{Dp2V zzG3BbK*itKhLc(lV8yQy9VGOQw)Awgz6VyJQIdudIBsmlS_FCF(Z>3FK!3z?g+J3mct1=d}c&A&vY|n0=m;)c{??LiO2tZT#l7TM>qkB-k~h zNyi6O5!_YF@p!%2*}Qza8~w2E3Sf!PlgW&D6fAMH1qVwPmyU5~Nr_mSIOO`6n)RU6 zw`0?`_bqFzd`7#gS9$BT>0_X@G}Q{491xLO7D@!6K~I37=Z`@$Jz-^ET1+PC{?IHA z=Y5_HigLYvh4pjrrkx`1)OsWz$I`3^I{J3_-B^w9mBz{_s%y8e`dKL{loy(s0DuF|s-yDRxrxPmUGvJY&y@zeXg6)x z{j1Y$^#bV>)y*$YD)8h@A@`pG8}BGj5nn{)P*NzTT4u*Z10Y2Pv^0EPol~)Q+!DS<2iTaAaFQ85@6{A8;DDYrCLn3E> zoxL94zVJ!ppO!M%8#AQk@9;M1KYQWKSl-7VnEBwnW`I(lR_tLByI1|1c)vS7FxBxx*$vKM!TVGwBt1l|8#8E6pG0fc zE9og*0!&+1`MkN6ukqG5vSq2P7`&P)X2kwO7=E51qXB^W{y7 zeO%Be>Nkgqc)$M{*(vq(h33ckC+Q|B*;MHJ-2FOv>399{Wn2~(jsBXmXPo%-f{^K& zzy}B)@LhMh12zueDB8eyHP0AI3~gglcptR_Mg^7m5W3sF;-@dhn14PY?oSdJ-aavY z4Pdtaq@n~07AHI4#ImgqGXe_Agvdql&knhcHF*&GyQx%)9-_Rz3yb+qML3U3v0w{ zTx3V!AM5_$?>pBd<-pxL#zYgMs79m49qBnO?Q7&Z@b>m2=l0S4+8IdU!9bg1APwccFw7v z4D=MpuLe(i8LQ>pQ;jmLGtanl6Es;J08A#Jj&$1)oG7 ziCBfwz_KPX`ddm|;^~Af{7_qr9dWh#lW^0)rbS2ot}4mN#orf5dXe3Dac!toG`8>h z%^Fz~euqmNVHp)5x@r>%jw-MR^Zea{c$uq^{jiz)Y?UtT85sSSnHF!aht4>^1utwb zG|ya)(A9CP$GDmGeTt~A%>2lOb1>Pfbd~7_|BHX zV{%>JZtn?d(fxcs{@QU%#xTBsn*rGVvo(jenW_vhdju^aWky4&J3QB&F97*GmXtG{ zZch2_J?dp(XN2?vQ7ql7nLg^{3> z2+QwGIL+K__YVr99`fEvg`2Kx!?}e@arTc3ue>(+9TZ9bAD;pGHNtU!csQN;=>Rn@ z1xR}TX@&RoPx#&l0Zyp2QvlW9orl!_T^c7Hw95Hi8Q<0|I2&JE$9{eJ;-m8>65w>1 z^kwmZ8%B((@qsHYsI_rm9>if3!5SjNo?#Y=^h-zvyL8yytOUKGWifV3GX5n7W1+er zloW*&IHKR8H0XcLm+@)`nsxd+Y=Tgtr3eMdi2xd>R`5xRq{!($(!gclN_`p>~Qh2rpwXW{UW6Oq!+{{Dp}o%{2s;0F+>; zzq??idHeG>vrncEHwS+<6%}Ml-sMUi{|ea~oH`?|MRP3GmxY?ht#c1rw8zBkJ zYID4OCPkLcZxp;devh=Am#ZUuNuIK}6^ zH<;&3*7bXP>ClDVt5tPH^no1os3F`FQ0nDbH5jt8whs#3^soc7=i64q+oCK{MmhqC zk)i4}3wD5GkFpa9At9SyH|y)8O2sDINfhL|Zti^cX?`5$Ml-3AF@?=|g4@?yGd(I~^O0o~Xq-ZnUzgQFySMOfeOJ)5c91443q#vkj!j zwNv^|k>0UUmAN;L{z`D@_k-$={wofKGqRy*j$ru=pV4j!JhZfH6yBhL=aWHFp$@!y zJ%|K_qIl2z7E#`U%$0UWIeMxo0%Sn}@M%EnI3$^t#`!PA`v#f)N-ll4#Pkwtx1cQ^YML@*wprDwoX9 z4fPpu(@;ZT$c7`)_9UzF*rds)@<(?=vkmUNK1Z2fB9$F+QjMnN#gDspioZf8#rG|rH0w_a{ z#qWW!B;I502$8A?Z<&T4!jqC1xfHjDIa_RMZy%l$b?;*oC<|VQ|Ff2XI=UywZ{`J* zjhCbR98JiA)5ORL0DNM^cEb}v0mOLk(*l{IfT;z3zv$9}X;8M0c{ci{V~loZ{MeC= z>;(A1F)mLvYP^sJC==kXXq+&QPZ1K5M?cFxSzg-|mc$p$o5<zBRW0TFjSY_0*5hFFiWp4_5& z*tkMP-BNy+DOB^gDk{w(qqz{KMO+{A5B|UI_C+3;37#SUW>d|A*&9GXL_nJ12-nL6 z&{nXq3%Nepv(GNzbN5M5K1YNUw2=S&^rUL+;>+%8vF>B&3jq`g5Q>Q%yy^Ce#r5SV zvmCDBoql(N{A@AeL}s@W+qIs3{a~;MRAW9X`ErI+{?JeHP8H6G#U=o`eP`2Ou$;`p z1h2x8gtH#KqAO{(YvC>Zz^s#%qXD2q8j~5{sTMA$DWoW%7%8FKAmIa?4yumZ7{rn( z@0vnQua?H=cLT3xM#CE=>ee$H_@5Z9^g5&)nyz68TnE z-)na46#Y4-z_2g5We&<*xn^7*A?2k5)wW%F{W3|>yMd4sA~KEisLF|9yoZI6FK4=oa$G_GMuO&6w}e zjq<*hlUUA3(8|Yl^I+58CfL-$_MvOQ4Uui1Mmw_WDs?!*y+|Xqf41{TFXIT+qL;eX zZ7oG#%4OuT!w9Kh_ss4}N!Q2N2?lLZ;%n_DIa#0)QrsKoy^z3)SQCJ07Jullhn1JO zDAI4V=Enhhx}QJk!Yyn@N7FHD5DKUNNsb}D1>OsLeN4+Py(Dj~k#WQsh@Gh8L(jDG zem8BjB#t@zZ6u2M#FULoq!EcejwMH9&ttQ+buYRq$Jgz=uZ-YBrZkL!9qf%rRhIZ(oM1(ecy6Z0%(hRG-R*6?0z)pAIkFARlF-cvfgN%`u5_Wd8N220_lCA z?E}SqMb-=-Z`E;J<6*FU2QTbqm)%D3_Gmf5u|1z_dQrD8UQQ|IK6a0pc~8O&LLx}& zQyL#N7uyudAaB!q0@$yQ`Q^pjT}zd>Yn7(eAUk3rc`B{6FS1S+vr>#LyMe0f8yXrA zRLvXi#|30GsqrJSnY4$)#pOzY)RYOVnD9QO5c)?as-ERD{1)G>SlZ+)T!S`@geSvX zjm>=e5z=P%mUTk?m?#}Hf{l8R)th~6+4nq@Gb+jTT_ZDQ2A^kNk~H!D&7Ux=Tjs=+JNqV~xGDIM$HW;(=9q#?vsB!s?9ZB8kF<nSs^y4le4djS$3FdHraS^_bGE{cNM*?Wm-#(Nz-*>3r$xlk1@%qbgK|zJxXVP5oBoz74esi*mnF8W|_MEE}8f zu1*rQwi%63S%I7LKs|0gJeyQh){e*CZoQ#20e@of2K&vNZtEI3fO^=vxhQWwl$xo^m*zj;MK?dJ zLanI{AWzR2L4Nvzas)MN>bA17=|w#0<#%bI|B>0gl~u88GDC^)=N%N-zy5xBgoVuf zeT445cHIqKqRXz(hB?yZI0?=6M)1S;AkhJ4)!EZQ{_i6QiLKgU9AEG%{^vB|AI<#> z&sB07%4V!S`w7p)!KI%c^<`}99a_eAbp@yL(b`IIIdc>RgFfdHJZku;iP%f0hv@zN zYZz;?e-E-#ci9Tq+M9kC@xjW+(YgDRg4M2IZ+mc+c>%n82h%D6!S$MdNfbwAO0#PE_Q97OIYI1>yD>xupttiZ700c4ONaxPI*Fa~ zm}qQval08>ir}e@K)-bX$H(H;Z~*+u+ZUM`uIr)6-|EQ1D|z&~gQwx=s+w6_oCCvz zPmXsFOh=Q{-F3kkBJ2BgIDC@!&nn+;wCl+u#ouf4EopjVQ=m5PUVh@!F6*i>sj5U( zdNvWn7m(N&_e|WKM0P!Rj@NTvY-F7NoCY`i8jCmNS@9LFJ{y45lmZ?$32!wN4S&an-{Aui`w$qbU1?saTSrdgiOtz-?&Jy zBXKlZL9z9vsfvUGqRp{Gd@73LQ{kX4Gszn|yD_5RR`X%A{#Pjj&A!j5nfKNSl8CQf zz9hoDI9|qHx-AGc0BiugOK8*VB@ZIOVZ}#j>^E9DnTlT}pKTh3r5N;8tbTiO+~y7* z4`8t<`#6j z;ozU~R>a@mDR}vKXczq^h1%QR%ru?Bas zdnXCrT-C}ROTiRp(yv#gT5Kr&xCKS0HwPDUH~^nwiq1Fe4j6#{P2ML>J?;!@MW+%y z0<6tTc?Y>`;(BKHs=%PLlL#-g(?)?fBH9ICzGPGO_jtqZY)z`jbUdHV+b6xZYB?kNlf~iNE9I9Q0{*eckLL*js(N+?$~H2HZdd!+j&$TSwq6#` z4lZbkWqt<+c_r91t1{rs{tYks?)@|Rshz)OAg|kQND>dxNj>J$)Y6o*Ng2-7$I74 zv@gl}w^ws%Z=N3G>X8)4_is&h<1KL>^3trAOuL{Z?MatjH?>>jOI})=Ji||Tt0)%C ztIpkB+!ccGDtwEZC{vFC**xO%y6@J1@pK*Ajw;LYgBS>p7=rNLyBSUh?fT}_swy)g$&6rA<&#mk1~{D78(^2C&yX-PG523 zqB@qsUI=5D1%RJkUK$86ghQ3BA3Fp8^o|=;T@h zHhGIewDBCdG57EKNPD1ne_{Vz9_hoqwYI&Wl>x5W7>X;fi;u}b(3Iz%7x&w)vzfZ@ zs7_du_`_X=^n!ZJFIiL+5x`j^*$$O`5~4l`=J3(BR>ue=f-Z|}A7OXB#MQm{Dp0Z? zv)b%ngtGPq-(r43v~>#PumqL4jZDD2h#C7UyJVTC5JiMfBcO-W-LH|Ni)5PW06q)O z`1KQ9_u-*4JN-nNxtja!B=M%q;!z!#9VVEj*9e9-mc_nv(3 zcENfXV}(V4R^OWbeG5_GWUAH|uu*_}?z{&n7GfCh&7Gt@GV%AvX5)r>f@=oDn+kl# z{9b^Z4G@K~&}U>ipBTYXYXud&;NHY;<;IvHIpKd^!pd_HLy%z^bNnatc(*LJ40-qL zHh0@C_^CfnEf6csdH?TKBg5aXZH)G=VHnyyso5nfR6)}Wu!AN69FZ=sLpG5BY$7l5 zAUXiq%+u`~1{fzm9UoKu^rir^KDQ>@Q^-tlQBsSj#VosxcHSHxFLEbigL2r)e1tpm zjGl2u`n5~-zXR&rOWRdc*I}Z4#W|on%+ssvEwU{w6!LM#I!J#Oc zPtzW?zYNIYdamzOZ(Xn31Sdss43+D-tEdKI?5hTRX*?L2UCaz{nUCNWy(Xoj{!oS6 zOR?C*Cec3<0*FZPX#zhy`6J4@MnSGHp_}f}wAOa{PIsw9K0cS#>s?v5Kcv5+@KR}w z5scJ38)#y%XcA!L7ocg%h5Xd9k{_yAJOq8e(lT7a=Z-%KHj)gzc$eenMfiZ_P7y%r zt`cQR;M(RSrJdSaqV2UfKAp^=Rfb15-v`5T4YJs6mJy}*0U!%0qMW3LqPO-O2^)JE z6b70`9`{BptiZLq1g!V=i4gPcw&wV2*7q^qWGV$D`(4l5lAjiAOV;AIEA?56^px_o zjc0k#dz?5h#tKJIQBM#DA;WP2&^bByek*J!%4b!}A)K>@Z*p*=7hUIEtz84a?aHet zp~v$!`!>N2A&9A6CzN1hutgsKDq^++q6V8uqsr=wr-u=17(Na_q)iQmh*;E?ww*Dp zj~_S!0ti|~s=N+Uy_*JHBxe`lqS1vfswXlcSDOX*C`N0-t;vFOW_)x(D;#t&Fp>-Q zapa2pzRi37NAQh7p5S@ZbDuH$*4{d& z?z0EoN+crkR@^>(m;@;DEc2kpa(1WC+oj&Sl_qaX<4XSNfxPyZ1wJ6jIZnQ-G_eiy z$Sdqgy&PH{9ErH>9X$c#z6Sc54LMa+umX|NCeTPfFUAY(Y@MVso?(csM9k+g=tbo%)9CZ)5B?#?9 z9FfJG!mJh4UwFLzyrkfO1o!xj`NP;Bui>5WBvpHuGP|Zc4L2InT~!HBJZ`|Res0X6 z0vw7?*PpHte0NVV@+4nXGKKL1OHb3h8^04``s+#y#X!0K+0zE!+cMoMtD;^27j9FV zbvY1cQJywa1HdS~zS;Erj?0H2-d*Nbo2swBk%Xl-KIy+U^lls(_h$3M7XaK_P;R=L z3WSX&gZM1JLikmp{Weuuoe7b`7KW4c(={M_pLk-A4E6$hW2RV^R zxUZgc^EY$u-(mtvq`G?;<-?c<(pM()K=>z)A6Aeb^oM=LQ^yc8L$>&+3dst0UQ{f3 z*qvEPeoE12_!}oZ_q_SsNagafhk?EP+1{=Y)=g}W&sh<&yEVVH zqJdVwM>u6Bf4A9>Qn(=wfYt#;32c@(qdGKOXr}Nu@}fCNi%&Y!b%b*xh}xj^g^;!c zK4V<^34ZC|;}rM&yX!N&y_cQfXaGuiv=?kTfZgz3-HSFiU3PS8qr2+259chOmU7!^ zX`+Y75lD&d%9?8G^^@Q*yC>`P&qPf9d|7FogCtwV;?l@?RID5$#Vt&?PAEUIyTgE? zHgKi7$Kph7AtaZrrS)F{$ksY%xPPx67!hpWyXn`&65n!G7&V%^x%2B?GKmsHGWN4_+W;qYg#^_g8^%ObEE<*Q{*C) z7uX=Ry$albC|24l7Nc#1oT*0Kvw{C6D&~E+)p~FfBk_hSlZA5^nZv3ul!UJs>L~-> z^?ClN&+r5(&Q-54x?Y1_FzQ=~%^6qJ3jA#!O4*QzED2T|91*}O2a~aWb1JeZy~6Wb z1IHH{XM)Qed19lKML?!}83L-Z+i~=B0K-+hP8fH~B&<{S^R`oOtjRGo+j$r&*CvF) z2%=$-L>^CM2rtG*uB{v(SKD5yMSYcoLLxDQym$JO*p`3A1NQy@XH(5mJ1Y!LP>|8eoJl=ga_*Gd-nczgob}*plknbFJD` z){rg8nx`aPe*3Ytq4Ypf=RJG<`BLt|qbki~1h$S~x~{2g@Zk2A?!fmeI4VhM*MsDd zL1Nlje<%_EUY74eBAt@mbKD$2DH3(Mm!>d0+W32?x+B4Vko>00*l(G#L%SaW-GrC)qC9W#Q}a8sWK* zX#~G?^U8c%_duFvQ>?fYA~*JD!z91z%*)MnG2_d97?d%)h|-Se9hSWp3T?}$7=xa= ze0<|~IL>Ms;3}n6qX9vbn8SQ70bKahFXI8|?~~CHKeS9c%0!({X1_Azw;s1^P8jY% z@tKwbHY_S2qBQkpb(nKB!{`O53J`^JP_x>8pzIaPcN@8)v`wTvC3t-O2&Q#$<<`ho zsNTq9Grh#NA3E8IWGP4F*dVJR4=G|4y`^BEA3L%}HKH7VKlLYIL{Ljc1qP3w6~8nY zi*x4fBgIa$`(o8pzr|iPupMhj@GSX_fM$l^wMZel3jDm%>utl$d2AwD=8_PI9lAP$ z$TcL4W7j<&PFY(3LqNR0@YzrC^=zK$gps_WP>qGx;E^d(>#WR*gtAb25c?UX0|TLC zGxjWr!Rle9b_JL$i7&vVBe(dZG+UVI>lLbaic7ux%TBDp)I7&!%Lh~t+|3t|Crls+ zJrC(8CBrY-8d-SePPgHCL=oxVnmOZy4r{5F9aSfs!~?kJW)9gex}Uqc`$TQ0dBGuF zQL~EzI0yrFyH$`YhLJSt_`DZ|qZpK6WPy@*uY;l5z{tYc&Oc>&%y-*KmNHBUu|eu4 z*Dx%ex68YDtLS>yJLbrKrLKWaQxahOJin#oMzPe3#Kz!hiN;r}vH&*rrAw9LLHaY= zHsFgh3i^$b8lWb4dYjjy06EMF^{Qo4i=or)k>C&+Kg20p8B~k~o6(6$jEAXv-=<)~ zYj^8vK&#^4R29f^FJmB)Y{TQOfug|( zmN@I>%YU?0!PV6(OP+_?EPmA-*z`%*UwD_RolBv6{EFpE>W!^;)!M2-^@HvvTB3PG`fZn{7W>)k92azo_&A8j%0V=CA{b!#5n-c!wJ7@m?tS1pkYYQ!d1y_{_(m$R4wC_U4WAplsE7TeDNevPhm=c<68@9d;V`I<& zMcCx?O#Idy;z_T>ED|bP@l=ue zS6i^qmb3^Vi}A<3n@)N@!Xl}h+xE=Xvh%UN%d88(C|})?mAHZ`Glw||e&G@9c%8rV zY|IOFM1^1VxvD3$rSV?Wcr+j*GkUMws8+z40wp;gU+#IocmGBF+%il|v$uAsKQdqR zt>R#N6H<;ZV7K#8I6PAS@ngU?gor~#d<+6N)qizx(gg4T*c(?@qMjqHrhEXy3(VU^ z`xO%a&h|_b7Mu(HS)%&f-;6i-9|WNb^JPyAKu7RFJIz4Mmi)XU`D@GhX7}ofbAKg% zcsDUh(zL&fs(OF?7Qw53YH&Uig#b_{FR*yYmDaWHR2+#Y+}qiA*MXRkfn`j-ntFiZ zl8Wa^P!-`+gGq=Mn@y78)-3bOqoE1J@V}d)@sJV;2=N&r``x{uy3uq z?ahXQHEv3dzEy^cFY?!`a=+o~shedln>J7rwCRmr8z4!YRY)V=u_2^HiZc%atW9LJ zx-X1C>TeK959)7ug3?Xy{wZ@nW(P%a{0h?2b%axXKEQLczsgY5X6J()NF=Wys%UaQ zp+rU?Fjsig zAyyjDH|>Lv_tqm;Z4SPC0PA+IO=vD~WbqtC^#j3_iRv(!GF|@p+)f~I8Y1w*^Q$1b zEOH}VcMrm^rTa5kl^+Tbr}HPIEQ}hWx>XW3BOu5A+*7VvJ2Cb4dHcvcr>1miZ;7qv zvY!w4>l-`(igVgP&y}F{*2{)6n*s?YD5Qqe$4i`3Nlcbscplj{qTWngG?U(M;`1q= z%PAW#_Zt%`zw2@r;#n#?>vLpJoneqjuN<3eo}A~&9-=%LWX5t32(6ZRJ+vw&nSSAO z-T6E(IYU9r@UZe{Aye>{(pKKj?{++It8(l5g2RmmjPfdXkM>;Cg|pZnNMDE)JRaJp zXwvVTw)RUiS&)_D@SQa|21xA3H>6$vLDgND zKMg|O)qXuDMwuT8YA|GwHsZo3Z`<3{lHRoHfz18s?1HSh&pxCTODj7)_b%MsUOcbnvT*3# z-De}K62@O4Ty`BKqSHF4(fn?WU)evH?_y4lXc8Y@Eq=JaCOwSM7yr5**EBdc-jnuo zvP@Im#b^6$4|R=MieiT+U>=X;^I;YBWb#Uy({uVM`Yhfm2q*T!{3_MgLk8$;ixn9w zCP9RgUpYYlY~hbufi~B1#=AE&5EDlBGxV^v&`KHMs+TI(5JUTdoOyes1l`7%)7?_C zGGrbY#Dun7V>qW-?CfzH6UcM=NJp$=jy9gg+=8jVEl!p&)xW0X>b<#l1Z(-r8n#;i zaa8krAmf$(%>p3dT{TJuji;)=_TPJ`U|OjLQU^`G9m5!50gKjX%?{#XbxR%G1~!b6 zzW!Bo65`MuZyUkFc<08gu?%MT!z z!e2Y>UzfVAcWJK|bBkYpi#xrFiz!rwRX!K^e#NbyIsVCdGyL5@ch{~?L$Cs!e%xaG z`}cZJ5ufq4-Pz9N&VBhHgOtCk=WiJxjB4mr@(}jUE8Xv{o;j4Ui5dF8c9Rzi9pF|) z+ClJHQot6zoi*9F>KW=%sLh|?3GB0HvpqKVc9alUo7AWui?inI?RGmWDp;uW3G?n0 z3O-xqu$fITZc&txQSHLM>nZRks*DD>A0|+_Sdx92`W3ShXU;IpyJCGo2I*Qd80N2s z)b|;~``+g3ZWT|dx^#CZOL@=B%)VA*iSh)5C}%KM0Qg1GW(ZK>s2cw`fUa`fso& zi5aB4tUp@rX7cv~^=3O0{Gk}0>U)>T+`r@WDzByf(5+*PW$XL?xIV#S331fj?c1A{ zKfv^jGbH<=1`+1TxWiwy*6)-^F1}x{jh4$l559PX(5AvkG~3qVN@oY2gkYgf{^RDH zv%;PsMy`|ODOs5y-l)_;6lS6qsRNj_!cPzeP`qhkLp{!N2U&?g4H@nbF)S+L+9}%9 z`|aUb9N*F6d+j<+*+gCEo(J`OZG&wI?~mqCa_#-_eZq?xs41%ra%Rnh1~rg4<&{Fw@lbx)XKpV0@sQ(u<3P@&?y*BUgUeCBHa-Z70(yzbXQvn#;In8mir{NM0iocOutiL0O_Hy1K~GUv zP2r&G*%&f+(SP<`Yw)m{Ql_KWE)WH06$D_nLlPp%?c=SRW~XW*J|vUWqtE2WhM$Zq z1%GAgj2#rPpvf^lv+OfSfJH6hJF|ai-Na#+?N`4r@p>o|bvOsrE9`{|2A_?ibPkOb zRtabB!nI!wb_F5c9u46^Mie&o>{2*GCE(mzGMDgi#va$*=0kXfN32gCkU~$PPpva1 z0MoJ^^WM+8db@L=^{?McAijM;e^<4yH{Y|&96~&_E#t?Rd56m6UvKNp?-%4dG-!ET$d_st2%KDT=zNZ<9!kFQ_L)6e{lVTgc&)jCkw=OOOdvx0Lh9-EFi4{L7_D@cM0=lCE}W^6FsHlWd`a_e#Zs)+TeR*g80 z1^L;OD8`R`NCeYTbup-4+)yv&ggt3mkqAo1IuK#Bvn(z`FKulbh~h}>T}8pK|Kd_ z=IzT>Wvq)KBHgYbThnfsg@t+a*9iY4$3|g^L=wO+c@S`a;SW#xqMU_2 zezR@MB1@|qlT1RxWD2p^FH_(H*Vw!Vz{RXLej3C2nfPTJD*ijA@8u_E+TUV@;w{I< z*R|2qpFkx}HFs`~Yo!w-nra|PrfdBSkV?0m0pM@Eb7xpmZB_B)}IzrFkOk{V&j*zgwZ+Z&i;nd1dgpL4M>G2^KtUmx>3s_t(yoIPO)WAz^2Jb>NsSA>jUnZwdp zy~{LXam+3CiBBAN4a&n_zmCaOr(NH+PhQ{l{fMlp{7ydHw+>Z%@@@^L{Y<%M`2G3U(2K&O#I&P|~_~JIHd8SRLx@)3^y5G~ktIA4UT7noQ zI7)u59@%dtzU|M%TE|&cm1`I+K$DVa`&bfk9Hcmnm~xAG@II579@Kd+)ieO!V|ej_ zS~xPI!9}D5`k4Az18C**uc{F4>}tGYzdKU5q;Ce(JlYu);8JF!^ z;e9sL!GS#b} zXtjg<1OSxen(8hJpPqUtUVWQn&3iuuLjHZr6$MXy^xBy?C-AZNrra|Z#Ns)Q!EK$G z7qgr>g5TukUZ(aXxn$8F8(LSylESKNyVeoKbm`jV-0q+~>kS@4Y>SF9p3SK-j=k*$80XAo<_ zg5xfqdt}SnvO!A-TK&XqxdgeLuD%i+_56fA;jROgr2+Wpr)#c;c${~!l~080>x8c- z;w6(Y`CQS)*LFIQ{Z86!XX!M^Ts`>-x28~P#<98Z`2zUObUHf~JK$_AV4Im1MI;TC zO!W~%I3R+qFB?`FF?q|-pYW5A5#R9C^ARq>YH9JX_$_j5jm3&@ub;A&ec`TFT%JuY z81|)!hkKzqC5`AmKfp6<_M|C!gO|*F!9$5DB2zj@OS*6Fui8VgN4M zBx|{Y$9U;t#27AmQX{nMJ}g%hm97&upKg9o9gF!_L31=0q(uMR+&u@`BBJ8Q6R>PX z92Y%*7;c$7ig-Y-K+(R-S0dqQ$GwMOGwTCeq0Q#AM<&RMeF+`G&c^h7et#@BGJk>~ z^>_QTCp94WXy)8+Ov&$!_ueSP$b7?g4`Rx;CXY}iDPrz9V-bRP#h9CnMCLS`S7hKc zv8o>>$7-sM)AQN(-IzKnHmlo@9WIff^}fF%Rn#dFr_)R89@u1>o2#hu)pc2h0pi8` zJp639ON@B}U?g(*4S2j|O|Ya|s5O+9?7!z3$qJxXduC2QbrQbZii_?+TqG>{k=iOc z>QTrLOxGYB-kQQ`tLB|D6SR;bPwV?BIJ|V9_0u*O`%M>eab%>bKC5S(MtENDH#$Rl z_xYg=dQcmv$S-5wess^5vCe|H1z~P+kD%hK)FJLi*@NIT>u-}5uFRh!>$yr^HdA23u5F01^q&5ZZxpNmY`{X8l~mLE>X=#Z0ta4Nz*j?0Q~JAK zVs6zvkqM`>yX#OTg_Gh^+UG;7SAw-cFEc{WA0$<6@-@+`&kg3UzU)L)y2S6+F zZNFBMP@S`jJg`}8lx)&lxg0%QOn+(0a9KLWndxj8PSA2aVNx2>EcfW9iWMtPWjbw1az6?c06Dg^J3MHE@F_$KRB^p^$ON zSs^mh_Q6Y}*ag*|_q`4hN!))BLB~o&zdO^=Sz+V;!AN91eh(J<%mUHUX-53+J0(7C zv%$AW7@7NbebHrERlokPNSz2!fyBl-{yt{_S2qKf=>k5Uyolxn?; z6jdy{J=?UzCg%#-pu>p1GjU(t8?j?cxe!{DxFZgx%^EGX;{^8c7-9VRX0N4Q zkR??V`We$|ce$A6?(zLIPy0ojDSAyR<=T551U*bT!Kpt0b#6nhhW*#S5f2X>MT?cq z8K1@3+vR#{B*V+B@-MF(p_xM7ZH{aYf_+QRCM$A?gX-PVzi1}t<0y9VKVhAZsj}e0 zSV8&OH?3H36nT6yu+8N`7RtH)Zsi0&(t~Zu+>K?oLCgJvj1G6;^Nh~ikNv;W;XSCi z>uwomj(;|W8CupGpcWe7L%;oB(Qwt6V?LyeA6Ni6;RAr*~qsmV(}RW!a=YDv+_N;fvHzV2|hiPhk~)=5cofCs^a8UAXEv=2V0nWmQj zz;XU-m)BoUa|7PPy#`$M_;r?wN_c(M2gckd}n z3U>3Q1V6|lV|)tmGsYjIt0s{epmk2qeJDx6G3+cl9j^a)w-WAGB^xke2z4$Nn|>+( z!h1rX@U`R|NJLyeraQg{L@?wbwfIkBmmq;@y{hzu3`rSabCm%vxh96z=mhn(jv=ut zH3+y(zX*UztRI(k?YUyF>UOK|VY>;2@l)UVKBW%{M?|{qdG1 zWX`8_oRsN#D7}&v#kKhJFTU0}S!O|$U$Z~~XcDO#9MP#){FE^)O-V$You2b<*(1$> z3SUT{!w+OoD-uFHHk$WW;S_Qi>NdLgwfJ&_nMem2B+r`L#|UGHsnvV^p<2JG`FWSo zRkme|RW$@7-zZI@10rcy+pU-#e3}6~OLQM>4f&y#yM-6ioPxyMp6N_LrB_D5O-wUf z0AW?*)aP<}h#L)n`*?nTB@%MgpPg_EfU5_Sc^{(s*_(#Qc?h2mqUq{<)S+?ymbyDv zyw>8xB=kv+y+n7))cD+I{l##@@O1z#T@of5ka@DW+{HslPs_<*B|lb=6#Usc&}Y3B-( z1FYXH$Cq+pLKY11lpW2rCNyi?UET*ncg~}5A+c8V-DkyR9K=K1CSu}zgoB9NMg5*U zC!a$`VJ}jw))jrGY<>8QdP}l@G1XbN7v-I%nnw2QD3x07USLAskGLbDGp;eCjLhJ) zZDT&T-0lvT(qe&h8ohY^6t^~Mk@*X|JA&N63W&x+>$S%qC=Q!{DM!lASj5Y)n^$$4pI~uJ*XKtiZ=F8F~4$_gYw+ zF+qYs5j7Arz*Dm|M~3s#yaq8EkVMkcdi{Rqz!DVFxoK4#jVRz3z-yR*DaMd{1EQRs z7q_LeCRexyNvIR9SANFEPr65S(W$dQrQ^!DS)J+XM4@Jf@FD)n>ga2DfA@Lz0)M@# zJcck=TYnW1UEPu1Y1<7i88r@&4l?e+Xh)t!NgCpt3^!q@f9N0K(}45Ur4L9}M5|j^ zH1d-i9mG@-SxW`ou+%oTe6@UEGuqNaw$(-fan&Fx^AFsoS6IV_>Tzm}l}`9Y3dd9_ z=tJrrX5J$GBO|bQ5i46l1p8~q@2`1SOFyN%pJ{;X{0U9gC0&a~fA_ncg3X!vtucEj zvP{=&Anj^5A8v6aA+#Zl<^j-Endx$-$-ULUoGvH>TiPJje6i>3k8o?fVM*$Gj9BF2XyPQ^?GMo z_ZuZF5c2_^?TN@68Kg-PC&qduKtPov5*d?Lhl@%0H9RNgukQDO$SNaY$c5`q+@V}d z#uKTAG2=t=%RM(dN${>6P?=z~;?sXU!$7Xz7Z=(l(8>cjpN@^Ud}E3CJE}4MrOzkg zGGYD#dA;daCSL2T%VtnMEOMD=~A{%rRYE|wWV zqyki#)x_y1ASz# zA=NnxOBrmvEug&J_E1W$+tUBw#~>N58!^1eiPb^AW`sqzbBe*C(pjk^-x{>3ZGt71 zsJ5T_wYqBSXSV=0Yr9B%OXyDIKPgl2CzSz=34K$Jh1?A})+GK-ZuA4dLbVAX85CK6 z-s`)o$ELcRN7tVbpw+TS@%cJ)l3{-}7y|hw2T7`!@NIZ}$mtJ_M#n+t|XfS3*h_lIAqq@zWtzFH~c^+y`s5(;%}&#t?bCe(~bM5BnzL-ChS-h z+bfEMS)<&dC?CU$GcUyNk(22*{7yIUZBqhc-r=0N=EW0J%|^#i4^Xy0K1YL1oxt#$ zp&yCM=mGEb0O${D`wlCX{IhJhrGe-593XXY2m-J##PjpY*Fq1pggOO z00~O=Gr^~~&T_61?A<$B zVp32>#%Z(|k7n_Wb4ThNN!(lC@B}+-Io;116VuBDY3Z_Lt7}9n>Li_);}sYtDj+q^&LK|iq3ts%y+xx6)ZVgzX!~= zX_TXYQ)o$v*_`$8tDI0dGK@sL*19B`=IT0}CO_Y$#Di*%eElfwH&%L^HKx~Om^&e2 z=3U7$R9#?0SGpzH*LRNLuhtnvbMr^4F3>|9=_isk@MgNt&qcN{T^K5HsOhE>JsQL( z^Lrv72ojlOmz&Y;ty_Ggt*<$rU1N*`IM$dhVY^}g$BCI;yt7w+06`6g)$ywK6ztF^ zXbzoA^+77nFCrv7*@bZ%yw*Pfi+qGBDGUQj%-KPF+sNoz`Zgj5-L=nZTlpRS7}&HQ zjkQIF!O>l)I$8h9t^ngf!k;J!tj_0=7L+!nWJx1>rW;>g>ZOy$mW@niaiN4TE&7mq zC%Bn6GaDvFT2s0MQsbEkQx8JVZc0g+RfA&btn3W$U{)810<=t$P5h8EkOHDeeY! zz*xxSzn33~45C4?f^RVOJgA{P}sh2z*JuNRn*V3>keaC;UW@PU?`suX$CWG@`hvH9wV6!tcC7S>) zr9q#Eg*urK2aA#KuU)vrzhMNBP7QJS&WE}HISFmKDt^j5q)qS@%M6Q z&ZiOZm#XtU!c3X{l46O2Lg43g@jet~`Ei!CbKt~DNQe<+HHqznlQV#w&O3D)jU|NM zCBh?Dh!T!yAPVP%nI3Wyfq=gr7~E!ACxKYc^#=bD=y}nNxusKpGW6kEF=b|U_fQnD zTWv|dX=PA-{T7_;^!-o$S(&n7p4I(L0mfEL!P;SOMKbb*S$M9*oU{@4Do*nG3uvKW zyHe`%nS3?Y;%~-c7w|AQ@zVuhDo%T3i_@{6?3slC)?z_^Jo~Qb)b=SYf8%3;nZu)# z$AIL@o#JL`@JZV{2z&oTT0nfQ!0ro{v#s0sZ%BG!O_}bQcaDHC>&#PRI$<}4<99-! z4eh>}CvZceNxP2(sdC(c@UDHhu2J2x0xjGxIy`L?=46Nn8CY>fZ1wp(j}?k;X-lY@ zJKd4`}ot$P$JTJgtChc`aj%nAn!ovP+~NKi7h`C)Fb zH@sBUy9W9L(3p5VQU=;wb7lkeoC*1~O@x7|mfmtGP}iRAycU3M!%MrF-|%0W$j?tP z{whgGGM5J+-p|mOTikWQ)E!>9Tg>~<+D8C1cl8ZUHfxXyB~?d~KrhJIUW2QG@tt}< zx4Rh4XeY+M2&ZZ5>Am@HQI`PDNZf<%6(fpe{CG|e6F^fohTu1?BO|};qdEh4VvwvG zO5WrH9Cp8-eIN5{X{X>i34%@&FY3LzujArYm}J|w&3i2O87Y&>(bW+-84}m*oEHBp z;O9TVC7<+Ub%GI^xLFX;Ah_o_E-}VDY`FH6%Q-8j$69io{BoM0i@bHyK+;#8eVWg< zh-9#&>|Nvme=uCMYfs;AndB_h5C6jw#*&uEha^ktsBvqNft{cs(m9UPtZY7HXxEV; z82ozq1sI7MY96}<2)-v}n~f0R^2j(kRp!VW3F*fsQr*6F8@rott$_lER>oy(7j7EYpOP|yQIG(#97UC^xcWYr&}to)kpidt-l(FDay155C4=`z@XX2 z5E-*Nz)>!Ky-t&i=o7b=l0uw;nEXH{e=oL57$8=(RqW`lA3SDtF7pz<{EmsV+38ce zHcAf#IR|s< zHZiyV^r*y~n{RR5+CASYYHqHxbU8sHJ_LVB<`H&baSd$)NPd!^H8?Y2+E%Ua=6B1! zcULVD7|ZN* zvZ6rKtQKZS#hCAD{C0}@XHBFS@#M?9t-<$6J$gWzeD8P9)7%?rxRZ5?02~E!R`&r5t1W@ z$vFb6?!nU@2z5%j%;A;e_)H0@1a@-vo4~HT=XEJDZnaxxZF8^KNcH@s4c}YV6baz1 zO*XeAyn5p<_#rMYTV4{dYQOTMK-GkG!){xPjpcw2)K*5plT1kl3$~U1o_zYtFR{MP z_UKjJbz0zjWAS8XMH`gmari-~9q$CDEfRNv}W~_RJsfd{2u<|D+M#YfhkZ_|pd|E;mK}8OvBJ z_bE2+J`=BMx=JqqS_t8_w>lIA@mPuEvix0cmCO{E3OzcQ9(?#Uy62tTw8h~+4|n>` zK91WW%U%@$W&Ng7J3&oBn6$r|3SQ2~W+6cbtp3&}P?Cjm0Xd^9#-;x>W&AcHRX*%N(?Q^`1ph;2ZaeEf93uKUavs&%a*gfTQ;mOHts zldEAB%C9@tw0olVhWn+U)S5Ydmr__Gk_R`8h;{z1>iP@)bspF5kk9FM7Gmc8Mb0jE zdz_t|XuzMKgw$I*TXK)%w^Vuj2yBHkMF!j?|*FG~UU zG*YBXkIyRbq7~LAGXg9k->uu0zdS?PxoOq+eh_g-E#Zs?s!_h8g=pL$#LV;#jL8ZPGixeIb4MwVAEYRj1c2T1%z=!~r^1A-?OeqdI^vg6 z{tE<+hdO@lJu{Lo(yy^s_)MN(Hg1^9_FcWrtqb#-W@M2o%x2@E6fTJH5pz_%lQ;&RH$Fv?y8Kk1WXfW#H7b(iA1u-Tb~KV7)fRfTYcqtCb%yzoiy*R0nCmr zBj{Y?Q0H6`|9>Q1$F`$N5c?n&mmHQDOwJj0ax=JdP@B7=Z z7ebSqtKU*V=3NLvSP}R>i7fEbaI_F0{p_!CXUY4!uAVTnYO6Mp{yT^a^hl1uKHmn` zjQSUHZpki{zT8`b^{Iz+|D_A#`vYENf`~%dvlokn%`XIuToUt9^*_zN;QLdkMFWrA z!=eEz`%&5shQIV=GyZZ+CibO7?BW#KAM5}c{i^v)gx&VejB}ci@7Ik^&j9ECBdMq7 zDFQs3@G?)z(wAeD1!+5pHO~Q^XAS#h{9*!mB0{gaGZX)TMzxkdpIs7_kotI++y1DL zobS3UMp|DeE(<8)5T4x(mrm?rkh;D7m{`FKg-^iTJ$zkY^sr79Q#` zxJ2Oy8Xb89i%>SWH{%qlL52DG?u*~}EjU+D-J(T;x7qwyZ{1{@0bDdsgt%PUo1}Y1 zaMG{H25U43Lqa#v9(j(UXErPQlG8oUUZDk$8j5`dk2Nsbc-c+tEIb4KU~wh>@pv^H z{cml~?0_uM?Z6^tcLEb}_s^Hw^}%N4+(?TXUnSGhYQrx|WHXVV5g=$9lz2jYBs08? zUL)l;+NwE=vwS+h3p4qyh-jlb*;!K(fz7wx+F)gxypWqb_-BN=P)Y-Rw27j8BU1Ipb4xxBSjFzE&~{?aRI( zdfX@}$HP6uq|fk4rII;{Ed)5PV%8Ko#J`?13o%y1KVWkxhSZpAXsfm)K}0}MxBbws zMo<(@oT=@}hq9-FHD?gNx%^dfO+Cs7ryj$C<=41h7!btq9%dzS7F&f%GZ|2NdC0;&K z(rH&i+4*WW5UWYzzq{=m0+J+s>-hHq_ZFiIxn{xz?iJm>^f?vhXz>VLi)cIE4s$%> zlMXf?yOwE{jo*z(R+_XYJbt|>6hB4o8~)qL7T}vct=WJJb|s92T2#RPspgu9#N}m zn~r0?HuH6qAKZ&2fTA#t6%mZ!a8(2`wdH*zJ$-NV?RJ+Wp6;4vD>e7u$v!e)ep$x3 z-8*{CwEC*!JSG1o=kJ%2i>S55ZUL`?>SdVdyhg5&m#;w1V_M;UgJ{{m+&Nmr#`{w1pN+;9dg?7tz@RiXV~1VQ)W^52)G}472`I)iw)(jn-&Yb|h*~eu?9pPN16aRD}4d+-!S=6@F zhQy=apqUH(4$+6OT*J+Lz9^>WtOK>RkZ9fuI&~t^FY;SHo3UQ$Sb$4}xTku|3;8O~ z@t7~W6eTY=7bm>H|8T|6#59apt&ogur9*{gzTcu9c%M(|LczEFjW7VmMyy7aXl>fBLJ}#*n_v}QSPa{0Ea_(fe$ng?s+hJ+DmQ&@#>CR? z-Ia>lKkeQPl{=p=A4g&8H0g{b-XVt0H2?jyR5LcoM<5y0jVgkARii7)}RiQw`zlE&z*GK0|z@FFvI`#w&W zG#RV6fCvQ@SKR@(Pm4b94Zr!b&C=`Y{sJMc(RFAVuMsOrXZGCkZ|{-X0;W8C+Gktv zBQKFf7Dwm3>w{<52v6+}EJn%0fH)UQ+YR2g7fuTa$tnMtxjTRfzM$`H9?E>0kBKWg zD4n}UqlOwFA9;7myo=$3HBr8n-BU4*ymHa@FdBl;Zr z`(Qb0R}VrH9{Wf0WA%43zE6y%3xpSMhjRSY(dKKm5ZST1?R;_fbUc@M z-=}WI{i=Xj`Md8AFipnR^P_@c~WY9oefzF1h(QZHJb9?7&=oo@V6baqQmw6D=yXB25M3 z7kbTb-q-nOpfva>k5ToX!>Ne&_nm~P-z<%r7-Hpw=R44IjQTOsscxHC`{DV5Jwx@- z-)xRn0)-CsPVfuBhzINV zUu~*GCMIebbhn19qkT}^%FCKLS%ZE|TvwPgE zxRc30II}o#(|oKh6p%Ci*LER&LfBW=pCv=nI5`Zi@UQI(C2Wf<-)#`m(Gsr*10REP z5LN@ml-7@TbjbQc;`Lhu014*jboyf!SnXx#FKepfn(@Z8lhR6`L#(? z6zKF!vPL2q^oy7p5(O3gm|oa-)-v8T_ipfStvToT>s6cm(G0Tf z^Z88B5(6HEfi8jldKfGTTXyIOF(HN?HPZ);d&^sfH&bt@;$5~ay-=|eIv_SdekhPz zeY#)tR4JLRju8$_1!KxKV`6Opz*iHERm0NVTDZ`Pq}Xj^23&`?!oOF~)H}Vlz85*_hCh>S8!xTS z1D$CH!SjuN3F0Q-&MjFA^iPy*J}|wckGQOf&rng}RkeT?nM2VA@X8DsCyF9Sp>>=Ehfh?cA{Hf^xPgK9B>ec{{q$MXdA`h}#;$%ZAwky?->jjMnCP|?4vsrGYInsk{?y_47-bvoLzdOU1WxzM&o z$rF(NE%uyxig#-4(^BzfUU|6d>;M|}+K7gU%}DqZ>Y8Lb^?s?}BsAqdPD6jdUmt?E zjV*a7`}8t{WPZ0-bmm(GfZ7omW9VJ1C_W`R*Jo>(f0N$>!Bu9mq^LSco$Xo3B zfMa_z<&N<;uJ+OSsJ3vnUdU`?*(PM$?0)4e`usXhRcl~gHc%#cr^ZP0?^x4sUc%YL zVl$>79VsT^Nw61$=;H3;38yUybgvvj!^W_Gp(uEUx4H7OxQB#q+t_Iu@-ZHq2U_e| zUUp@l|=7?rGBTUWIThfU8QU>qKL!nZ}r zh_KkEM3cD4%t_NI0xdvu@{+fp^3APeIZ0xuyv8*&1!HUrZ0Ax&YE#Q+Q{e)-0xg#_ zsRsAR>?c@5dE&IAWth3Rc6`>|$&(I$B&)eo_`2P&+FDskZWxSz17|-4!@4S3I z1p$#dx)<>(L%o8$h4^gzE;0hZHKNTUPemS5gL za*c?XpA^`9RMTza^XCn`ejJ|gGd%eXhHf)= zRp-0ZI-^?}%%AlbPa`K8kgK1MxBL(XR+o-fBiQYpss(E9p(fBox(%^nxXUP(9#`3Q zHGO{^!sh;_$jT2kTol}IIyRO3&j7Og2pOQqWwE*Kbk$?}r9SVA{J*0>PWf>n)S+aV zxqdGx{1EiRA`HMw;h@cmog(l`Np!2Wv2wo=KY#qYFt=+~@~b}?8YW(yxuH6}tIQ0O z&2-t098OFVN}ubD-+KI*;(!dtbm|JgHHbr48{pYkO?xnFA~N>KDfls!*k8`KipgA^o2_vetSp?lA^SROcDnYBKDIn9I; zzXtWiY<@m>6u$Y}BtGQzLV57aIwrdlU$Du@{Vc*;AzscEvPxfkaHjJ@YiM6UA6y64 zI44HrSb?zF5)lk=8yqB`YZA_x^t|qnbk8tQa1X6m%@KmAewl?Y66;4kCjr~pa#L&` zldOSoq(p0JBWG?hp*sEeI~pPXt&DER-m2GY?m50HM0cn-{1+Q~UB`2Iwan_-AL=rq z7~ap9?H2=%xSt#l6jq`IBu+1jdqC_mC#1-*-wD8IC3JphMpfq4n#qUd15~>GI*Gq@ z($Xb6o3WGK?ebnMnon2%-BThVt6{jl>mca6v@H;4P1%U_yPb@1_ritJsgD&3_gzZl^m+QhkS&mx%hEmWo%j&_)XU-} zD1YwHR7LH&XSh4@fgaZ)K|4V@T3F-cMS0k(UI<0j6be)e;gZ@K50OI26|ieL{eVqN`1_ap{3Skg$;}ueCh>X9Pr%``~1xHXB)<< z^1arta%f~4_{+;`M{T=;&X1E_zlROoCITrtc{l4!Fy8PnD9PInTy;wS`OC9X7~LIg z{ED^D?eWa@TWxS}EyAt-xlkN=b;EzaU-LAU+TG{fu0{&0a~g87w$X5j z)+d;4tL#zt+x(rvm2-tH(EKETAW`B!jZ49c$G&N@bHi%%;n(6==4B)=~bS6~HRFJC6B z)s8!1WcjIe^zvqCA5rnq^cs~(njA0h@{=MGxG~N&EyM)PTZ(R{zl#Z?%!jbBf;G-c`bI3$k{`G+YnpyW3yBnb4hk{ ztG?@-sE3!UUv1LC>R+;C>ql9%3wL;1KXc3wX&2eVlrMG16)YT=GsoWZ;?tlu-xiVt zj)XNNfafa)Z1MKZ?r>cIb_)FG6k&~BI)Jcz)w>CyYjd&lntbfynZToU0_86v(s?rO z=4cQj-)sWpiB<8G8@JW@u9~BzLmEK*^>{`@)P*QYQB?o9*N+55Q#AQvuZ)C+GQ`WC zMjvV0Mk!NS9dH+Ca$T!`nJI-oCW9;S?sff%#^mwRcY-7I^WdN*@I*7K9)BvT?N9e0 zEWl@^k%EOPAvZ7=m|&`3BK>7hMbaF&=j`x?_GrW*)=4ILNoqRKwA>MV1t(ij% zeT}StdV30vh||O30^mlPhz)_+y@hc5O=}>AF%=cv6aw=;Am4w`P{iH~fkvVV;l1t1 z%0KW`rs3r^&cSS$#7ZLku4tBM1gtk!eN&Ry62Y-n9qs}HL0bCZU3DhZFm49=e;m9c zL9MvX%%NADn?>g3oUnO1YiYJm2klnrzur)i5}KDB{;jMFtMd6OUyk zyAo@YN8rzfd>KC%8QOGYJOc3=#h$>q8Cq*idZzGMm1yKpV;N!KZYMKL)Nsg3q&b=(Fz*#_BZQd``t0!r+A5o)#zr7;l2K{>9L!? zqBJcpESSfKe}xfDk`$7|Xw`fyfB%_9PJw}Aq#L99Bgw2 zs2_7TUb%`7idQ0%l~;qoa66sc5>ZWhJ?AB`rSCq9yr!!dMfxPx`WENPY6PcgGkr^h zCb)zombA=}BhIy=hMctPLp?7}I+s9Nnm=q9mdQ4dK3J`Si$Weed+GxUJ?C`KHWrTf zoG!}H0{u1NU5;y6n{m+awW1!N_yT2}xx$dVK;$dHPoNHg z7TZ~&nKGx3Z!=t)|5>u_mYrTV1XqXhr8dY3=unWFLh@=y7dwIXGL zFt7=7Aa3Xj=$OsFS{lW@YE*&;j&TcN{xU|OrFWksfAsgy`;!3JhCOh#c;XQnF%Xs( zDh>5n>6pqZp>K|qd=;CAtd6tM?^3i}-cGZ4DY3_6uxg$yS#+~Ps$aFwK99| zz1;fij^_12FNc{7H=I3Ay8T3=Qnzx4Q3YFq>vRY6F6i;JUrYlvB9?8Z-|XEs-}zQy zn=Tf=`qoo=?~%TFc%i0P(B`P7X1930$|XM+_rWR{yI>fcIwT_@{{Otk=VittsR+9f zhL5S~m}g6H!h;Q7FYVV1u*vgE>txat%JczQL-v7NFb((s7U;*bbG7Tkmj%2v-EFBV zz7U!0_CfiVJ?l6!N@#$mz4F)le#1)8eI&(jY(fFc)VJUBqkkzrpyG5MZ8`l=L=iUW zX@h%Ga_jTYwL&*-`52#d&t54$OZ+NU!h~^a8(I9BV$$>n3u~l}SJ^;|wI-(pC%?Fw zgqyU#o+t+TZ^tMZ(PWTGJkiM%s?F8tu6kH4sWZkTC}2|@bzvkDlp+kfP|yi74)>mk z!;fwAy4uu-^~Vk+@A%WODJG+5uF|OGnPw{l9uKGv0pdp>q=TGLjeYd{r}|)x4Sy?em>6@|vN| z)EXW<>%Gs+%VRe*p>lc0r5G<__`$pEEhlT4By6_Zyd(q(Ld^u`LA;@Y=}Sy^=xL{4 zgV^r@(Zsca*r2qoy0<6(nzin*b|?hY0*rX9V`VHejs!ukXtio7PbUKv0!K+JQ5|(-k)@0>)+wsE|yTpHGOVr@>Bje4Pp;h^7B{~)eGqz6!!?^XQ zR1X;b*`1qqJegf*J?T$Y;lS76+T0OF5XS=DsjOeV=?&pzz2fWl^EuN{d??FDW}*J` zG?RRYAh)01$`-E2p(c=f9ctAzZm=~B8&RTWPl0>Nl+pwCU9j)Gl_38rJ9koBeMxgg zfS|D!YnoRR=mL@J?l5yc8xDcJqa% z0HL$2Z8&z-EtJOD5DsnE_(Ck+*W8;g_4?#GCNBFQ8`Q{oP2>xl_=K7uK=~>fhb<6^ zznY3+i%h05hGx)=^}8m5B_4@n30so<%2qY~nY(Ifryp+Sw5gEnnPxYPsz;7fgxEt! z5%H3a+kzigKCcmj??}{BDvnEY>l$$JSLrRF=7+HDDBBqg05K}>GIdJlJ-xk4Vc_Ry z1eAzRJ52r#G7{O0Ka+aISq2jogO1+$S61`VD=XFJFTT^is$RNk?)s#*V|@FzN=ukJ zJyG+SB1^>M(_#~H@?~jRwi=q3r)O-ICLI5m1)@@)Y_a(JvHmi_^!)5quAP8FY3BV5 zZ3$kJz5v?!h7o8QSq`%b%_|tszDpL*ys09VFX*jOF^E~5uEVa@q9`Rd-2f-8DW?r@ z>lP*VY;ONK#jgQzjO#u>GfP@q66%4-6Pa&*&OD)?=#1~?c~-zeW4tbJCLTyK{COI9 z#v;V8nY8DB!qNH5Q}z6lCnE8ZT)Isj=2jNsEW)C`vE25{YM-QLA7vkzpLe}%y>d{xy|^MHe{??q+(emQed|53|7#pN6{c6x1iL|%e~b>1+RH$IWkfq3412ne{$Q(x zFkgQ7OrGvt(c!L=(-ATH99s$hQ+D0U?@@f6NoMb2f^PvD zC^i5IG$NIPO=Ba`;@JD7lBl71Wbyc?{&}aQRAF* z9_)bxlyi)1e`*Wo@r&ZToy&yb78N%4L(Yv^(jA$u9jdwr)Fkw6L2QvHgK}cGM4*Xo z^c+zld{&jh{~72`%j8at+CLbbzM?d)fV)5%po^u6_@-^$tbgz;l5|Y4`ghS6Hl(cg zHSOXQC9*9+wy(rKo>D)z)bSdFgg%Wq?sg)V;kte~!g_^;O22~PkKb$30KS?J4JkbM zX^WXrDC@N0_Un$X@HY8%=YC3?p;L&N%_MaxSNC`~B*FJ)#H`ZvT|wh=alXoDGyp#` zAkI|H!!lp(-bbGY)@3={so4}b`dbftoVKq>5GCN?tP-28Nca}rYIFosPBQvPf{9LS zOZhL!jXgY*{JYNK?u8Gsc=3y4XyE!hKo?Dg5toivc|m&eshit_;Vezv6GzaohIoA1chIzFus+VZ#bpvwd?^f>8x9SY9(^Jyp zt$&ZCXWM?Oa3Z~5UP6kYFa&+VSVaq>^2LnB%YLI8wpbOq4t+-5dY(-RpfevQQfjR9 z8;UqTe3&6P?A&!gJ!UQWl0-6XLvr&2=#Fzm&p7tWvaE=`rAd8BUHO}!L^cKbB=Omo z!@Ah%y;MS$b}Zp_Zu{bwCYtUrr{s3X1sb=4i<;GZ%bUhE=TT;(U{5t- zQz6?cr~L&Q;ye+JPX2Q}n6L0C>NPXm$R#Ud{Bizh`epCT`ijJT6?I=VfrLx7KsYC2 zBx3*5Nr9s_r$_3WLt18^gE=E}#-S-v+2E|8`CASM{eXu@_iigqjZ-Qdl-YtPD+mgB zkuysd@&BWZ(sQBaPO9p8n0nZ0T9B`MmsJTd2R`FcJOgAOR2qZ?;0 z?w$#t%lGa#9xA{Bk2z;N*O)o8B`aN*qiioh#NvvTIohMA&(CIrx_PL%FalAYKl87a zn>H19$7OGu<)z=ioOgW0MPv|Cizh_o7w(orE-j;3_S4VWh7SP^cB+sqp%)G>cg+UI z>P}9tc7>)|IyPCK1bgmYp4t-EgL?udc{Pn|6zhIV1~ah)@5|4*t1^8gKUdv#gndQ$ zqeagBs&t#sp+d!7yN%yxFY*%y{lNW1%JQgbbP-@1k=HghA0y#ZHD-zG&mK(6?oCcD zo1Ha3GVHnS_f-b{{F3;;I_{3;t@56@RC@^Y?yGs;4~5?XwF6mYJG(ZY=;seji^l!< zLFx>Pza+UW!t7P9R+HNC*<3%OL9CxZJ&`03}8l;t~y_`O#=-jbf4m zI4{ySgyD@jHM&$A0g#SoDtn{&jgzP%A+wwK1B%UvwhyK~(QF(0Ev8?-g#%6^)(1(C zM%N9~Jy-Ovo>BC@W^=2+k5eGVXuT9#lp%THQq=ILjSMEI{=#kYd)~;zKUWW!ZOD%| z`Y!6)UC{~oc%A1D+~nA|3IzkEkvRcyz2x(kh+c1ue{EuS^2#@3Snrh+mVXE0`Qm_% z?>K6rR9vkg=+vBP!l|bS`6TWsHg8IDr>p;=D({so`^gR(&Gmk)rAdDl_Q?0o9}8>> z)%SlpSZ&w`;^ji+21st2^wMMQpZoUWwmQNSgJI9aMov0=;Vwwjv9R9rBxxoP4vUw2 z^%F_6+BWmgoUsa5P3G|w{Wl^%3kBgOe{rcW|HrjXn}Zir3xH)%KfKud(NfgX(aI_p@m*tub*=l+^+!hG43ZP5jwFBOuh#k_tIXsR*^ut^gU9xd6*maih7)Tyv*t@ zQKIH!YdfR0Y6i}myI;SFcO2a zI=WME|IJEztZ+>b5Ehk-U)75J(rn$oeppqH%}(Lpg9`6OThRa)J;#cs9{-GdNZG(e z3Hr}(7uCiy|7oaro8(3rba`YhC;KH4k}u=3RXCN zXXqE#4YBKT0a5|6InOA%`D8XP-$_<3&ChO0=lH+e{sQA?{!H(#Feb-Q2I66VK{@vPG|S7MGta(cI zao21UpHPG$b^QclRI@m{aMF(6hrpxH!6UDf?QNU$$=kY-k#(aES$!rH z5@e!%G0A0*+AYfQO4GV7(q1l4_?9@!$<=wfp%kJ#YX;v#F$5Yr8|`UY7n+nC4Qut2 zQmL1g8#*+s^W|!l%;qQ!)HIM$x7lT;{wNlryj>Yq%9dSdN0mf7e3ern-pM=iROe*Z=VXO%aRWN@by? zTf$o7HB9i#lboOJi~Ttob0|+sgf8`aKAL~edXvN`83flVA$*AazY_)d(+>hF;)qme z2!nP?x^NcPp9(%Wv56~5p<+$1+@;Uv$03VZ1nr?K30_?6&6>6qX#+y#%t^%|#Y3+E z{Bqop)UHor{;(>x%ke-X9J;mN(>f~W!}q=jB?Jn!a7k!Mnoqafe|;wbcwcmGVXvm| zBY!?|B>fSdV7z+yvgeUM*hkz);MnknqBd!Y2_`tS_V!0x4YataziqE0WmUlE8CYr<< zgZ)jh^Zoln7|%nlu7&+Y3j2)m=rkzM8o0u{Ioi|4B&)-vL}Sp8dYrQ8ZC)R(aYAo3k1FfM#F5J=D-!Ry*|1{tMIoq;W(0 zH19Ovr1)g|r?=v55Rgmsnux(Bo1QqAcaFk|YB`#)ifW(MMz|}zGf6)-@4F~65WJX? zLi6Q^1#7n<#m&#UC6?cw=A?79fK=wEx_q*F2KuEMf1enz_$X)vQxH<<3lh&=g(5vl zr~3Yp+gxACbVf_ni6%|{iy&J%<~In(nB~%bRR@yhN}>p1^5IM#x`u>vmu^v-053|b zW6rOV%6W!^0J(Iky?9Grak5nD%Whh~H$OqEOuTmR4mfLK**Z}RxFpdy$^JW7{Zlq^ zy!m^AQJ1D19Qt6G^XDa1SvJjRS*hdW7+fB|+=)&Dz_SL=Qs~t*G~(7BLgmJHqN<~5 z&Ly-UvTLkt;kJSz+Dn( zcb^jBaN0V$Y|3&4XdK@$0v4w?ZG$@UFy6IoSK#y)#g<|4I}Ub}o_x!)wPU`aV`AX& zl)dM@yA6ehL$Icau&l`y~_53lwFtAi}hY$Gv8nGzB$?EMmslz zqv=om)eJxvh>KREb&W~7-sQhT#s>{?EkV|PGUICQ9-&HnRatHGn|Axe%Z~uPB4}X{ zs2jD7hp@yrT zMj?b7C=D7{*-|IfO&e@%-0DV?SWe8aGf*6yIbWXmZP72g-Y>DK+e-4)d{r~YT{t2!*?=Fwm#kS-sg91 z+{gAYxAIJk-@{6H~M1q9I7u>LXGz!TZJZins^a=@GK39 zz`apg3UTop0ufL0q@C!W4W{|8ZDgAPy`&Se#>}Y58{Nru|6o;seJD=w4@vr~E3|=P zG>2Es*c2W?8j8w}&1LbI|M&-pn<@Cdu-Q1-XB~EBBWxXv$YKv` z`>meP%j*h2d9`2>9`&ozCFJ7qQG-7?3C{8CeJ9Ji+&O=GN}nF5Gb^FDjZrdcW%P#a%P&mq zK`d`a+w>B3l=BX*Xac_jw*|h|q$R{Zl|<-F8;SP49=E6ENOogwKV&dv$J-TOk`Sv^ zw`)J>?6vJr(X!=^ZP0C4{7)!4NC!x}_#zj#WQ#PR-+B=<5O}cdZ!b)*%ejJA`?AoO zk4Sup5~ooK*wQKr(o!v7C8G_(BV^l0m^=}AnQTl{7T)xSU6ryM?y}d0_myHsf6Q64 z1hwpV%I@c?Z5=uj*$mAT4vmMiVj4WZ0K$^vTlUs$9s_*OZ{9mWyj3%YNcV(?S7bYh zV<)PkNl&i!xOX8R31@NdVT0@hdvZu~%46Btewhd(cd_gD!<^TEV${X^?S^$t8TAna zW=G=RjMJy29Aqgs}CX*s9 zM^a19Z=9g0`W4W_YOSmbhqNNDQY0R?M+WOr74?!cL1;Cc*;K&_A2@B|LP58=Qzid3 z-};|qOJ=k}{vtT!euERcT!!}w7ycv?yK6FBHp}dQd8b3g>h68gg-D@cf!?jA0lB=I z1{R3CtyixS*V*Qkfm>ili;`z>x4jkVTM%GL1IOV$-92NeI6EtBAzujHZySFVs6NUa zNj9$*9{*wolrY20x9mYnkZ?60e4GXK*Qz2{tB{*-6qF>?1w-XiAYmhr{kKM;s8(Ty zqsm%1xYsz#6+`(n=`Dc^`Y(>7zf|m5KYz^RD9N2%>LZc6IV)`o?o*%->Pmw*gO&1B zyDu%TABqfdW^YdkIgqc>Df}x?N>tFyMXV#hEnM*e$kP*D0?d*J!x7HXEIj97e2Q4tZ}Sw7^C)|E)?dbH)x1EJh|8MazuINb zt%u%MT(MLq_x{tTX(w&l=hDGctIBf=koPCceQo>1XF zcwM`k=0|D}A^IQbix24&qg;a}VDXI&|q+Ib3;izQz} zicl{U`n&tWenE)2#L_2Q=5Q`U#kT=CL#cxkfagzXAPo|^RYP&OcZWNA@pJWQ*|wz| z3nMGHXl^Dmia;d9McE#U26^%`e^H1-E>dM z*1L)SVG88vuK{NcQJlby{2%76GTim!EsV+RkESwIuIq`Bd zmE@Pm;eII*IxgJSCvoNK8_+Pi$Wh2M0cji}FZLSdKrMLh2ze5@@=Y@?DK9xjc$^_&a-eB7a9lP8WB7yKWB~gI@I`-*3l<+} zI$dP^P}dG*Pw8m6;J=-=u`YrVoo%5VPz!!vVk|ace=8#5MmLB#12rjgU&YSs_$=+_ zY(%X)X*5ny2ZXi=qhAWAk`Efd%obl4+I#`9NbB}MNy0x*0-Je1mkXUD(~t*4PTw#m zTEVKUTPdJPNG)@ei?+Pt8C+nHP#sd!8MtqAYzUW@S;|hXvh74T=iv58vBTT=*S}1& zi5{^q!@tF!B>t7?NMA=p{c0ohNKvb-Lu_8vI|K@z{b4=Y1# zxFB#fO*eRwFS^6PVCVw*e}E8z$my_X11?@>9!iYWKo%0g4Ve^}5_^}voBSL%>m_7; zWp=I;8%h{491wE9)}&vuVWui&hzkC=v{-x6DOJj)Z1+Y!-*krKth8SRNw8nlPBXQi zA4C#d)Ga30DqA(j2Hk?CnM3#CDJIiK(oM5GUaZ?0kiGsNN!PLE zs18Iwh=s{vNiaE?3_FrBImfSm>Und{%%aC`TT;~xNUDn1YilLP1EBNOK*X6W=7=)? za;vZ5Jl;-y&HIQG(nqD_-6T@u6OzGPUIfgdmP3;mF4n7e6F%XK>3Oh4-hM>$ zE;Lv#G0tzdf!4g~O5E8^F!T9Sv+r%xEer{%ZAk)tk=D!NNOow08zDdd_Ieq^&6=;t zx4*yFyb*lxh9wHS!oud^IXfhE(ceLmt&{6zL@kpo@c=uDSF)w;8~dAPN15@f-$C?> zPW#&B!mqE}Rx7$?xS+{dW3DS`*AyZWvf)#rDO46Vr<-aFpEv7dz33o&8 zoVPe-MRGO2Fs>4&)L?Z-DWebbh8kaC|4Jj*D;g`!SFeUs5=F0{sz^AYyaY3$o*2`& zmu$L2E-mthOz`bstO_tTA^Wbn7D3!7j8Lyy3JpNiz6ta1lAB+W0}u4*21v|byiV>i zlPAFJ4~=U%^juTEJZf`YJbs~Y^+I^+one3FjPqpvxI`>ufD94KK(vW|(95~2A*L`q zND({*`EppamqzMbjQ5bSmE^v%#r-S$!QmOmEe>u@MxOFiuco&FvV|dWhmij&+5eqZ ztoH!dgc(AB{{WhzbD6#yUH4l)BwYX1N%*WDVx?)q1F5P9O+(bB(XF`Xsc(1p01PEk zu-)`K$eTU!GYcQU8(~WRK*C}>QxX2W@XGsBy^JjXS{xNnT>Rqo^I4b?dGNBP-3#4q zEbs1C5oGdR^*aZNwAk5d5s5EdlvZBy`Or%j8NRTWweMe?fs`ecGUtbxG%SI|wnW;AE!^4(x;1GHAzmN`CkZscxl{C#5dHgJxRkx60fN3uM~4AH?g zYXg{!aneUB=WYY)q{A;prsk-(+UWY@;bRPE`J*=^j0{cSy2MMGf$juf-uT8-;BPvIKF&A6XTZqcnE7IDDygb+W9*R`G&0$YG_2CpPwoPZs=+>T6WSSB?t;nAu;<{P63q>M;Li&PS6A#>2m&U zgy30<4|~{_HvbD0={sxW?&;+iZeBD3DE)@>6DtUs?7XzWBjJdkguj{Z=4ar zfvwZLvY=c*em&{lh;0yx<>eLl&>~{e<%;!yIq*Dnr-;jkm7kYqy%zj7 z$RANd>hIFca_-$eB#^ttOdWlUlY?pbzm<^P@ujwZhlxa^HGcOfBNWU=V~p~PM$V(X z1!?H3O75@;<3z5GuZ1nlsJNMhnzKX_y2gc5E!!x|ko9FqcS2-9A>;Wz=C>383d^*# zL&WrOa4zg!@fX$G{5%rsOlCz)^o?dLEUth*W&-HxNsH3K%pu=f+T(EIopAy1fip5& zNBWyh2w`RsQS(S=vni48>muPlNlkEG>gwAyA?W%YY(bgZgiCm%Ugy+7@+O{#-3SmG zyBs8V2`;I?b8n{s5V8$S0$)ngjR6DRD7QHNqRT-!Atf;d+_rPJyF6W>=ZU_4eo4Jp z$$?CNnlNBlS zv=PV!Mv1S|3+Ky$Kn5s1fHLve$=Q)&oTfsuP1u7y0Ez#138>8xZ6z;qAj!07JIeNb zAs;oNa$`r8W4EUZp3@fLZQ~w4IhMSAWE)jD)Jnmhny=^(tI=ABXn)hwwu#_(17Bx0 zvb(Qz;z5GDn#(BuZKIjFtr2%3Fb4g7MN1PZ!kYyF=7)rHEcCgkDoC~Vuj#mZ&CN1A zu05x5Z4DNZS^p_z1f6ATjHh7@BT^z;;S)pd_AAPs-;;H zU`~H$Wu*4{>v{nkMmtT8fKJ>|R3bhHx|BLO{fDe3ThFhwM-14zjL$jC&zEMCS6EHj zNjuiZ71awBoq4yIf=eJh>*db?SK(IO2G40(;%PR-;ZMi{39q8ew_M{rz#i?IRA=3T z{+FetIt5L&E2u7WBp4_H1CBnceq1sr*XvtVZjPBFn@E=WXF$08@0QIi``N4(vwJic zh-hDHAjt_VKlt_^^tGu=S+sdx;q$OEWWQ5$no*QYd;KV7b}W#=CNYa~!$%?Xf5if- z1x`m&8$GoH0#}t@Q9YVKGvB?rSFf;zU0;1`NopU6=)6j1^_frjbb#l85Oh~z6q}!I z3H_pd_8c)Mx`cGez-O}C#fTFNI9wv>k`)ewIS&ZC@5Sp z1K(&GA;!7|*h-zAXFNCCeT#j(wb}C^Dz0m&`}>~md3nI#KQJX$>)(rG%4w=wC_td? ze|1fi@ljCj6Fl!+v%VtkAWJ04arw)_^Yve1D{e=qc&wzP-B3cYpY9nlujAW zuNyIV*kD|I1@-)S7N06GluA-L+g|(uXTKl6r#gD#Hs*mujpY#YU$tTnk+cuIyp@cv zRN=NDEr*lUvxA&nnS3X)%SNOK~gpo{MTA;LlDScn38dz__a8jT&!Qf z>`nS;p~79@6O($6DhLM%^YqJh%;C84z*P6n+fS}A-Nln`IF3SW*%)m98?KmFGdE)S zMa@VaeYGtnTRCy3p;m}M1H8g@ruH?f>Pmn`-?P;Xd{7Udar*(6-oFZR5;Nn|RyMqd z`{wTn#1no|2|+k;`RtK_GdyqSZ({+~V!EM0WF5A+_j-)y3f{tOaSO?XZPnZ!J_JvjHdC1dJ-M(7CAc}Hg`1l5=!^#j{GsyL=itlKmK>(z{|j_q_P7w|~!lallWUX!ID(ji+w7=%ovh1b~(+#aKroJ7zRV>iN zX8Ib+0!#m@gMW;&(>9vC_R(2aBLM-u{dJn<;fad@_K%?!^Yu=kwDNs`)nBCJrLt<0bF%(KgL9l2E_=#kR66 zdBKYkvq@Ll9dV_zUjDAlPf^MvjlZx3p<>df8BBMDWmIz|&eL-v?;eDmee%0^-0jFi z%38#PwV#k&K@9AYP*;|J#mB~C?oUbrOZqDr81x!y5`Bo5Zt#w%dVys;m>i`+AB#Ti zE>;@ZvtbH`1b^%H`x@I1h*HZC8uYKi@cYdxKn42FJhBo_znZ^dCNK48BAN|QyI%Vj z;&%_l4=b}K7a1W%`ON;VpbW9JQ6k!+cAxLypT9Z?l4r<`N-G^Et&@+WvR7YK zU!GIxwH-g&q^PnFb?@}AYs25aoQ%USK*X$z7Bli#O3*f+_DxL^SUPTK;Zt2>`8&J{ z@N)dY=1-!tT>kp_g1d(kvG~r}!*1fn7csT5te2=&OK`U+*BB0fH>Uz z))#Azred+&qUfY(n~ai=oBr?_b|R-(CU$w`+#*g5pCNUErA{w~z#U!S&uk@Vx*k}* zM&PXQfuSv2YPb~#X07W_cmN&G%Tj?FjI|Yqnm`;SQ|<;cNbc(IsNvs^vsxH?f05<4 zeGcFNyCBx{kF2oo7#~Lj3%>gpxE*B|{V}2Sl#}j?faK~Z;`eU#n=9(71pM!X#S<@Y z9YwpECJonlFXuh%+SBUcM#x03@{Xq(hCY)r%@d}<}J zGnuWH3>P=*$Z!YRwMOd*2nP=)m=)O|>npn0C>OW@#4_w>#Yf)4Wb+2MsyfUF>!yIi$@aCF&=_1Rw)$j zwx-UdHQtJPA@b;@V?7UNHhDD=w^2A@hiCK$gBr3?UrEsypYAJUVU4YrHLsxV%M8 zLCkM>XVj&4KZ-l^=CeLz-G}1Eu|YS~@6)d+ImTlkQbPT{$)m)^$h!_@$%fhf4IleG zG~Cia_offW$l|{HxUA!7W$&gSO_W7Ai}L>5K(vh!>~TfQQ+gqgiLHO9Fwtr6c#BW0 zk9%i}Q#XFZcX80aixl^1Y1HV=Pzv(cww4b>ZFB>}d)^d>`Bc8f{aWnzjNozvDR!+d z;X-=i^k9FdQjJV$*&uJGQ@ef|f~8(0V&#>1)LgxH@8xa{o`xV$ORD`|-q+4PT9pI< zid@;o!26=;B>|FWO==8#;0|M|lv0dyy?fk7w5apKa|zs^>&eIMXn|EieWrb~Tp)w2 z{9B~{_k+a0rtPx)vOE$I4e!0<-U2#ve{Dj4?swGo%X~cXk~dX79avsmJ;4lQ*O!uV+s`v?$ zQuXKwnEz@Slx5t2o`d23>rDabSSVyN$A`II+SHZWUXN(D-d!3ZNFL6pUkn<0)mLem zecWKmOzuC7M=4AsatG!0hJWU>&1EVDc+pWY1Pu8jjERU#rs>QkZqw_~&b_qr+%OFZ?GcbKA=F7^{xD_`@1EXKWw zfVca}G1`79(#NA5|0F;oq@>U(W6UT9-N z^mbshe^ZuGnUu|1n98{c=0%aWVTz9EOlgJWO7f@A;p&{1GttbJd9yu41BC(qm>yig zmm~oEE%*Idss4Tz(-X8A`}e)iHx&Pl>0aKyO63jYLP($75D_)Wz%|vV{f1g;e7U{B z1kzT&WD0v4*7llR$ZzPoL+5e{_*q(+cM0D^$Rz)Ca39aKL}t*#T7*l@)9;rjwnIx0 zPD(`Vuj0V+JmgS7fkbn~{=45YB<6^q;qXM$eTo<{*+!xgmZ6?s9{{e`Ji^O zUkU6}6;RH@sGTEPI&1!|(>+8`x+E1-X?5BXZQnOdN~qnV7&jEqX7|Yjg_xfNYtKmK zBs*NB0quL&SSGd5Y#kFLZ^ownh`!wWb1Fhft7N_j|r~E;k11ZbPLlHN{;T<(MfmqCU z)0$gkvu08d->7&2Cd6#jxM$JdFEuDnsl>J@@cb#v!rvSFE5?WD228qmR@!^Fr%gq7 z4AwuA&O#CUw1_{>yWb2}wJIBHRSz)31q=Y{B7Dbj-T{*j?SL{fz+!VB?C&%O2Y;_( zD3^{#Fdx<$kVV4n4>2L@g&CiD;F-tQx@5z&oMHq|1n|ORZv!GJT~@T@)E4Ix0RU?! z*V#?f_n^}1LMpoev`lO9(_<8<>UL^%lNZ#skxUF^V1T|V8}CVHk~(#QW=Pg8rm6i! zBAV!tfN`5_DSZ}i_HpbdQIvQ#e70#p^W4O*v=GeVm##7=nMncDnuwHnMB&zJ_M)Ttv)4fyn>AQ^+w0bMWHtAd$i>=^PtKeBKgqi;6- z;jBeSWr|GJh)-LXbUvZ%2-W;;xQUyZaTwcLVNf=}00);6Z~G^}1m(fR0L|pX_uTeX zMs3lHSw8(9{?3T-qA{DHHphqGun}y=+B#*hW-Ze=>X->gsQL9tRERoEiO z+2Ya0nn`6J-fQMB0W6a11HNpBCzYBUBX-+rQxgfD)wxFW?SI{+tkQHvr(ZVQ1g`ir zS%^E*=!!K4&5PwqBrs#zOq#run5B^6p795qd8h8g;gV&39kB`rei} zEOxjDn(-&8AK+xd8H-TNC#VZoDBs&7z-+nFmv;jQq|gfL9nFUz@?5n(40Pgim&}yW z?8=KPuH~p^h)&S3L9LGT5)hKK=iUZPS9t31<g|{DTv+~HQ3Pf36D5E{c7BV4+5(;5yf!3K3F_0pnUd5uo_8ac1eQMLh)Qgau z*%iLCP^AblZijRT(oIiYvU?kb(ZbC)cTN6Mp=$ZMJo3k?q{ziL=1$Ea(_cWqL>UHG za+`8~ZL(s`t0B#gELwlnj|HGFd)LJb=By`GLluPAAKAhfNmV&5I>2FCNbaIQEKhEiEIQCh4+SsGHn@RT(x} zL5jIrRALstH5(f`D2!r7X!W5nT~?Uywn5}{a#oG_+ne`3T<~38*TdAYp%BY?+deRi zuxzqmv2z!F#4*y^R<5_3LY9a+TqnbVSFy#}ntj|iR=#57K{D)z(*C$L<(@2x5z)0< zv<((tLo=rLteR1}r!Z0IsBymu%m=nki~-aOr<&go0A2Sw4TcLo=1v7;@)Se7N%=mp z>4;ImTMEkQ^8F3judAQk9blrGID2LWr2|`<{cjInTU0rFr46viXkGE4P0-95Sn=@s zN#tCw0r_soAl@c|gDnFj@--<}48|QlK(r}hI+M)AbW?*u z1DQ!1?h4Et4>?EidvSlA$lBk*@>o}}AI2}%Rq3w=pg>FW?=TPAGC-_mGuiHmJoIfE z+peKWO8_6hTyD`*w4tZAuYd4xTy$NrSSs7l&*tBRA z>p(8I6@-hCVk34s8-4fEhv%;X&XS5t=o!hhVM$jz)hLynxp>g&e6bhJUgR%Ju^;r! zf?jPYyX->sK^16RywVA-+APy~Q_53;1~VM;p;K%XXt9OSg1C=>uQ(@&RuKx(+FLs{ zh0pObjq^z)j3237FTPTIk|8(Gdo+SW=Mv{0ziSFq)51M_V**a?!TF8qF-N;XNvobR z`|@u)z_!>Y@j-NZ^{7o2fbwb_fk;7ntazzJvAmPSNchp{f14V`<8&8o5LgB>M&Vd~ zGw1V7^whpzIN}N0>yVEiEdlEEsTL8Dk1NXg`HpErcjS|Ln5odP4~N)34Gk|j+b9_c zK8=0MNKa72gPeAM-MnRZLZeSMyG|bBz7JzY%6LAHLex(D9n{XyJx~*1bXh22oCQ*x6nPJycUaQFBF2;wo0bz zG9YEj4Xao6KvMcH+3go2{4iZ%7SsTF{*Ljy2igmNX=Jn>=PB+nrXy8ZHT!j^@uZd= z)$DW8e4lxys<=jJp7M~`!mUg8F>wY)qYfagE1>U1yj*q6F+W|46xA_Y*|pVno`vCI zfHC=@_`|>LD#9VWXXp0179RXHa5RwQ zBY1KMe-nnLA?vJo5a7Ef5CL{r=!lE;TUK$aS+L&tb-Jft1?MF~J1Lq52*ls#(YF@6 z7DbZbSlTs{b{M2~zdqbbQ*V8&&(gl%+1_~qt2#t7lUmp4J6#ARVuzc1OrB|Ki*;e| z+6*4(GauY5HCn%2jT!f|ngbqDCTJu=q9Eg;fo2xVIyqloH_3trWRYpUR{M~_=nsX=)C5M)_w z(`?soA54(Dz^T8H{O{uO3b5d>0(QSf`g?Q#_WI|GJ7_i?a^~rKZOTRipRUHv`ihJG z%72G_W|vJu!L0qyVAma1B7n;a8Jq3DPP7wwk0ry3mp9m=ndCuW{A8~loabxO#La&4 za!W)<4LWUWgDe88tJydF&#&F|PPq~PIVq7PH-_x5>*G1=jpecSI#-@ge`raX!L;6~ z^#VR@igm{GvYsqL1!xZ&M+U?%QfdW9RB?g*Cf^lWPXJ76kWKjSTG?~`)tGEE~0`t(o0)jtZLZw zU-!hYqT?S2e$(Gm_)3u9?;dAoXMpEKmx}HdZMse=J|K2gI8Q`grP2f$! zn9;FMCyJuaqIL&@->M_ymznP+i7czrxqGsg$V%I7(iEx%Yf}U0HH!J<4dlD2F9QfD zzi`N4b3543+CuM}RvvRi(A7o;gaTHMnceMq7l-}r+n5*w*{01f&RnNgM7E{6FNktT z;|POs8bnNsEm7v`Xy{T~uSx3jiiWDuwEXc5wbwUJ%n;ddgw!~O^Y1}QB`akK=N&DN z`vWSNyZUj+rpwu}*=a;JADf8dv}Ir!ryfwec;SC#vdxL21N&oYClm0D_E$^an2me3 z4w1bx>Nxr)E?w?P#sw(5sTwJB2VoyCM zU4gPWF^}<(Z2-@JpiphKxbLl2jf#0Z@xy;MRc)b)^5G#J<#1AtBRK^y13std757mP z`RJ!uYP+jpiRaW3G5ea`j)cre9Sv}$A&7rEc9hnWj1`bBT+boN*SyPWJnJ!~R<}p2 z5c!J#ef^qUZ!S-ij0}Rdm8knf4Tkn_@(6~wHr{OU1g`H2LPux&X`3MzcZK*=;%)u1 z&6kI}sKJzbEdRW_Ifwsh@cXQKs3Z+DpD^S8${9ElLM%QE#DF(vxcmE@w=d=Ol&wqS z?v%(WatQaVnZJk7gidi@||vo$5OaGXAu=gx&W#nJNB z_S<(QT>HM1P@tT{UPBW3xt*1QC9MZh1&V0;RDC|~@SC{&@&Qrs*A!O}#Dc4l`0vK0 za$eZPh(`N;-&4mt+>gOP*ff8|OezJedYYzosS|#SZ0fst75Y-O<*s=tVEfUeq;z_Q zdW!Kx&RFNTmyMBVD~+??1A-Fj8v z&isyV4-ylYMBI!Bf6wSt%Q%rmhKaA)D7I`OTKEUhkA3-o+%0@3Ugj1xszs$AVQBq& zZokj|0PEfNn?l3~KQ=Oh(*ZmIHQAzjcnvk&Y<)64+X;+B_3C_ooh^diKBDZ2URuNa ze0960>Xd9Iq(?PP25p&S)URHR5qg!9`gjN#^TK8_`!|#p>wEr)e`j+Sq1lJ#0W1&9 z%{K)izX4K}C7O8&U1US(*@SJsExTY^P|T@sAU)nptv#EL$ZnRs5o~&}CZ}5Ca=c-J z=OK@b;8Uc!we@^OZjmw)t)AJY9WYE;rEW zspxx(bz#U-vQGp*gqz(LVnIg?H+%LYVpvUrm|$bZgR1HF$}@6+gQ?u>?JEtmIY)77 zOYIf8EOSzDrV|iJf9=b&;FL_YtMl*TX&btY2%^jiNJrN>Id|b`B2uzJ7JcVlmwyFw zQkM@z%#`k`(Fic5JM~u!P`&^+_1o-A!^9}!T; zmbm6@`}2^&rz$zsSbxfes8@yf!Nf`NrK_u+)jN%=3=mY>#0It3h=C+_ako=@+5u_s zd~55iDcS90Uu2t^+8Zn}Qa1c(gy9O_GQ*oUF(;uQp8r~)bWN%6MN7#w)I zDF_{gS|Kb0)7Y>xl^n+}JY5MDZu3&k^Rkgi)9OVDP^{>Na9Rq2MW)ydS5K(K8g*GV zmtQQvV?Fikj7h&mA6qez6zJKq8ph76S+D$eU@h0`(oZw}n+NyrJW+6?D_HVPuNkAw zbfL-3Q;%6=?&_Tzn#1|77n%kT&Lojs zPWlfvQYuXP_-hC!`%UM6jpBzcm5HzBT=a-#{nNte!h37ThzHeph4(wMpbJyAy1PyQ zoXX**XXb9?Q@6X()EcUO{I93Ybs+eRq%jRk;V1j#xU8DatufFvNh1`sr1TS&K2Pkw zCfM5JXsXBQL_!))CG7|7T`JD4F(qF3XJ2OeN9<~%A4=ZLpW_q2avkBvCq2Z$F^#oQ z>g_Z>9!k11YLSgBmS6jg>?y2$u_2yK3ti#TkM>d3H69L&`R82dR9JD8Kh8&gPkdJ= z5-$K9=1U8CebYgC4c_SMS;ba9OQ<*tZT`nVu*)L~ubEAtPy7y}2~-o9bd7{EkeUqt z_L0p!VX|tBhcJRyQ011Yk+MfZ{$_-}b8#<43Az;&c?tL~$cZssRqHD2V{nV4sJE)Z zvsRYVDeGS*VM%S{N_K9yl_zmjFs!*xIDMBCKTKU}s-dsfiWi-|pc4x4=8KAyScYwn zf<+?Vjl#*B=LcdWVWzq%h>4QAeptl01jr#=_r4r1n8)!ZhfQ{HDHhdq95K}A-`HBoRwa^mz=zvm=BYmikbZZ1jv^3B$ zo2KVDmaf_)I$Z+vXC8T&c+!`n+}Hi7PWzsEdRU&T9%JAK6qyG>`3TC`_D0)Srxb!g zbI~Vh(HAF)I?-20eL|Wg+cJ4%#>FtM%%BL zsK+~go6dedzfVa3??VSI-TwAvxj%C|7n6lU16VKQlEpzK1GnGN?rB)4{k~j!K6(;IKk7(q_j@Jxc}ThXLDf*f5Rhg1kO;Bk;s5q`!DeGEXQc3Ee(7S97)pF z515|>SVd99nYz14bm~jlzBi4P6sVf5EChxvxcw*mZKA7zL{81maoUbq9jvU9c5^FKT#znc} zzS+$;M)PFm0*$z}?>@5BbLu#a=;tBlvZqikj}N}4J`#VkU+n7+rLIy-;N@iZg|u;t zOJg1_hIy){oq4MwgY(U+E|TKjs#5?n=E+UJ#%nf%34z3{jxW?$4)9Uov0QsSY))1ODJVsd%; z+>bJEx~PnC;;%bp&n8cCZ(a&7C7xKd%{BVDL5SEC=&%>vJXi(LElGd0aE~{dWJsQEgw& zjMcWE=&yi4hFy*Xd-W3r;zE-+2j%EQbe&mlJ7&Yh57mS7ewA` z^Wy>sLMACDOx-1$4Bm-{5N#XIxtw18#gILH5T61IhdYGZ{OGpEGH9Q41Rh0zIW}l{ z3a{?&62z9m2NDgVESYc{4nJdNo?inCo+X7N@!3u&BuR1xC{msiGhHVAH(<#T3NI3| z;Ie$3d>2m1X4Tu<$lC>za$|q^hEA()&*N9Id{Q%%KTdlKvTbS9%d^>H`j(<t|T6hyGA`2KYw-EY0 ztL>0UtU-NGnki;dzJs^04NW7@T92yi8ZS+<1hO8FO}81EvAR!p1MO&~Xyc_>mNZsM zDs3FkQm3k9JCJ?5d@kv)mU8;%mH)2V!iLkul`-Q|zUF@gqlsv~Vl+U$Zm+?=5*SGf z5mgH)rEWd+CgZC2?lcvn!KU`wLaZGS*;BowH@|)B`pU=7kj2WU{Mf1PY^2wbN#8sz zax@=0EdRS4IAn?;%kV5K(U>FpH!>Im@N1nA6Q6fOrkbC>M(^{OV>*Qr1{A(_il-j* zZ(CG)q0y9DFrs}RaQ`wX2v)W&~yv5I5F<(hAIq5S0SuTuA zJTKPriXqu|Lr*z~ta=|A{9=F5<_=!()N2D+!bOdG=?(6P#H628HQ(#m);`~lzZPH^ zD-@3x70(?Z0-J|ZGBTmqPPGCea$h6M$R?X6ti#(BkLj32acGO_jf5y2Oui>s)%UNa zviANJpUbw(>F?yc+Y(uuVVvwZkQt!6Dv^D+zZ>I+(hV|=l^U~PO;Mw#fbaNg#(SX; zO}Afn!m0N$=*#71tKDh;+O>%`5=V`AjtB9qD!O%RS8q)dBq%jR%Q@DI?*PHeSB|RNBXB?8CQS=#pqeg~do* ze4e!1NUF-SVhgZi3L}{%o-5$%;jW3AVm3g9^^dxpHK77`+9`B0L!sBdD4HR2X+b1~ z0wU(ub1?OtnZKHBMAHHT;;3Hlz>f(kkbqKw`$h0}(K~@IDe;+~M#;bPK~#2$X>wS1 ziVzXQT~99n3cOLdru+_hf&o^opiF$G%`_X+)GSJIC=>Ao>}Te>CBfZVgy-oG2bxNY z`i^Yy2IO@zeOy~p=nCp_fHOR@nOeOk?@izF{>(X4Ia4}%m0)2W&#f6fa;P@*HErO? z12!Of_q(n&QqEWLv#e&}O9lMH?LX45Js@r3HjmE_W|sSi2F(qWBUQIC1$WMhmZuC) z@tUnhE`YCT1ZotlgbHmokV4eS128FTGE@_DHL$)CS}KoCM>`|S`*2q4Br8C`RQk1n z>u%1ZvFL~YE|-6x>oD)%Yh(je*6_rLnKe+;PHbOgRv+KPRmhw|Y&)+ZYPt|WSB__8UH}q|kKPw$vl}C#aSdAf6ITVb3w5K%BRJ*5g z7)EK@!ZnB%wqXiKd;VUtRSAYVP~-CY)xERqx6Jv?Dg>{LLZnw(@n-OGx6+r3NEpiw8<?w5_Ri@bW z8ZEI;8UG}AB+Ps&M6^w8E%sZgGE?5{Rbik#w}(4{eZE-%K=Jqi-3PGZp zzxs74fQyMzrEmtI2`+jO|2i3HJ)a&$-=cd3H$?pn>;7+B%2bC9+EV(ek+|iBL62Yj z@2>%X(SQ*7tI6OMm(T%??D+NRnNsxQ;s%!8%|c-z03#1eiM_S+fvDK{-rVEwZb<1q z$Uh(a?+h?{A+~o!MgTG7Hw}-)#b1I`iJW)oDxKly&QUB zTkeN(VsRr4?jZKrcSsWX){qWw!~5K~h4nHR^e+50I&Qxy4#f+jor@p&TN(g)pe>XI zbyVB_$I^9dxydZy2hw85VF^OyoMA^6Imf5JE&q3Xj%Pf!8L6rI0^L=$CJOS2fM}^N z{I3ZqB0-+lyVo**kMuhIi^|4=#&^Xs<^=4ch8s%fN56jJZ-;rr#9|xWUZ3G5{X#|Y zxHvpKoq6;1VS!ug_R$9aIx5nD_nBX#9Kz{5h6c{UV~kotqX+{xXgv}1`$sV=w!&!k zm-s@dQj7FA1pkP)T@0{nL$tXYB=@m%F-GQd75D3D<&O@cmz%3}vr^g8#KpJLjN`~C z;Dh0#A5`$lboCYUtIz{B4nJPEdCtnT2bSPIW_LB{<6k8m9MI)UWdT@bS2Vv4rJ%Pq zJVUg24kP1Fx19#hUY((?WSgr(U3dM-22G0Wu#=BQ>3OInI1bExn0;WMpk?d*>G4Ka zE>2ACrCs$)e)_4>JRCsooj6r2kZp8bHiMV>u(`Je;UGGH!hit^L_3WA_leEqXG>p#g2Z-G+X!Ji zpR^oVyGWk$3F=_#kxR)i<_c2Vf8BLVJzIjSeCF8cp-KW4$ybX_z`qX7uQ19vf_8?M zPuJ@o5JhwX=W4a3mBwar`sGm`VNK$_nkDIBIMOoM<8=HMxWXc%@fcDqe`MS5kucFP zf~-&8hkkK@2VyieopCqt%bnbYHaE6=5 zoYHcdv-w0U-ZJmv-cuDUNTFtxd%^MegiQNvBJEuQ5s=1W|-SD zU3qTY4H$!KlJ#w;0ez!3k?kgC8!s283U}fgdssGisU@ojqqOV^<$crj_h$K8*@gq?msjJurI(voajV zE|(~N+T*=^w~w9Scg9He4{Pc-wPw6=hugexBIy$$4M z^i}`8a7ew#Z-%5X-|n`maKQ{Fc>y_wI%_PL`O`u|-R>h{;>ZOpZVjctm`m*; zk+B=g@|L($1&7e#;j~%peii!~Rd(9teMlE3U|lc5wrCch0$DN%@e%s;*7U%qF+ht@ z>ZOHzZ)!=E_|qK}Bdy=pXtL9c+IGgUWw|GdhWw`1FStA41Ehb5ZRV_+UG{wR1>U+p z6-W5IS~y^32g9yj(=7Fhi%`rpYAGSF|2D3p`yHLAua!$t~ZS0|3Z;uaGaP<*ne{^3{+FVo>@ z`Vj{ytCQV#(|0U#v)gX18G&}TjbEEbe~Dp2%q6H_d-x>wUu@UE&=$dK7%K2RF1xKNrKi7dXl8Kp`TT|lhCcO$YIMauND(;H zBF?1v>UdkffXmkb@*%>QY?Qf_=bz3^dqa%K+`$_E=$YcZn>qLmE&fip-=z;@OjrGH zsHO`|C|Fz3Nb14z4Z8o}yVXh8<@`}AcsDuG?Gjm-F+B`Ps8qYNGZSqV+oR9eZFV{A z{PW9v5C@!+{Vvq*7+PcX86H!y2Huy?Ky7O=6rFVz5JSX~K)yyfv zcTcbJ)pCI1%rDc$6>8Cnqqz8LTo*BHUfJG7{Zhjnom1uS0bE3@WmyXYa zQekm9E3VIUn*n|FN6ePZd&Oc4;Mvf`evV!#ESMJb61c}#KSP7gyzl_4m8~c93KpGE z3k6RMc+js8^OoQJV`5%yIo7uOYx{ytZRK7#GlPtyd={B z9Lz_}VRl2QPN3KX9w)OpBm8x2?|iQ4J;8gVZhQ5uhbeO-&<7LeUo{n^tVQyx{hyrb z6$IF8NdFjN@u1Y|k0}y{oitr6Hrnk`7(h&t;D%E-VbM$I8Y~{*HwoLieJpv_zYWso zOZ45~yJ^8-AzHesY@ zAsao1!aOvJaNjTUcQ~on4HgoBnPTn;*lov!yuZf(P?tt6+b{?Tli#h+Z#3cE@e4~> z84=09WXbo(q$&YFBG~>4cm`JbY%hKHC|K+AK>v^izP?oRZLA3^rY?G)K#1Az0EBiN z^O700ITxyHmd}v9AauX|N&POn{6+N`b}QrB7qsnoumGX?*T)0$k`mwDQiCZMcl(l* zZ*+SQ%6tMm7qh0$43tGz{b>L&ZFGKS5bCZco223t#$)JJFU1$_D23`5*adh)^iRs4_(^ma^>b#O5h+bH%869 z=LwDR=2v+KHVUu!sM;XiHcXu#XKnX~MQq%#{2G7A>{>Yo%j%b^wK_|AbL$T^2uU|v zS63P3TgQ=lUPSc+&A$$S{$PztBiv~@Cw}tGAhIj8)a>&&+0^f!0WaaPs~AM;LL5#L5d7pO({tpPqM6w1fB`ax)VflTK_%*IZW zs#y+!-bSfU2+9aQ4I(q=cG`GP9uVo@-6QFzB&E`G7M**dw}y5HS8H^OnK*3q${``V zf*D3Z=i8h6ie0*AU9bq*0@#4duwM-z9CCMt8AS%|z>_0Zx%fb+s72gr{HT_1EMnJh zL%{1sirQ~RT+0`~vsnJ`44vWOlig~+<|&Ky2;JM40NY2O+<0kl6xbI!yZ6v~qM*Fn zr4w(IR`jn4)Zw+&#?nwq@*DOxig#y{s{WTxXnT+x0gr_f5JqACFisXd{+N@Y=Q>Hk z7!7Yl>^&@LiXfHdBClY$81Ncxe@8Q2em3_v?Fm|VD`>vJ9_ptyNb?lh0+wYHHJTYs z#93j>f-9%CR)+jLuaZ4t08|3!YMWZ2UEdyQwthf0v0I&Ary6#O?C!OJP!0g#8+#n< zW7VZ5TDFGk6EEof+Vo=x*4FUXO){NU{1Q573>Q?fz+N zfLSU+Xg2+}TmSK87?!izwPA1ciV*DqHVMCpd+^NurjNf~T4Oxxc)Kk|_H$z!zhc)P zX*mJ?oiF^aW?q{am_phAIlUlX$fSQsW+2|Z6R1A$D4kzY$-Wew!CRwgz2|%o#8h6K zY8AVy_Ss=->7=(4|9c%b&uCKmS4@ycYPDR<0*$xi=(<8lnBSU~%vD#)4J>PBq=}{*2MbxL+;F-%h_yYoY1sUX%iO%fj zr-~P4BJE9KH<#|>eWDtzc#zpye$T|L3&Ui;D#l6v&GbBhK=?h(NcvBu)f{4V)($9=z~cz8ta1`ojpQ)*k#$uVtCW$k;g&tv};H*x8$UC5jj({DK1 zEYMdB@JgwV20(md;(i#1BKpT@3R-e2y-lyhLWPMaU@{%EBmHhuN+lKLgxgXQi<||1 zN7q~PH)Hw>n}J1BderP3h#W#v&j$TT=thVz(z^$z%)v_#fz~6qLG-*zP=bo(K?17Q z(h0YcA5xhaW&*zHZ4mqS@pX|YvPKV5C+jLXYEJ{>9Y+=(ex&9M$8`JQv&mQ5+_u)% z{;N`I-b^KmsF{ymTTC^8GB8U84aH90b_&h!L$g2PA$lfnrzP1;t=>UKrq=1mEd6;rkSpGSWMM=!r+6RcZ zk88VdANt0>k-xzoy9iB)i~`XE)4OFu=2fz9p6nA!^B9`FQrK0*JJ$UQE+vwlffb!~ zuqr(J^N=P;nw#dJ8=`C*SzBvG*v6gO3@%~uigBU=zLbehHtdp+K9{umlZecZjpex` z>Z~Het{2<`tQ8TarxIv86!FQk*el&aiP%?Q_DAZoW|)V~1qCY(_SI6>;YgqfWgyKV zk$?h8F-~BN{Am3vL}+ouZWzBN%)7#Uj>`HwsAJ@fZSAr5n?}oE^?8kd?|q~U z;ew6G>L=ja^c#lR{tOa5kAV>)}@iHg(LK z!KXy|R~S_>6Qg-dG`+#^vQuHxZ=XYc9P&td$YZ4H+{<7Zj~%Czua)}OujMYb3_7xO z16V*4nLY;p5YU;T>6`MPrH?bHz+X2S3FOrNvR#h3h^DSS=5E8@qYxF+3EGE_fN2O=spy^qnJ)wU-_|4pDz)aqHJQY&)Sc zB24n2d-%6+FJJFFG%bwkg2!|hm9X3#dU%3u^o+p;o@ha_EiDBf8N>V-72Gfnf1xqd z%av!~Z@(q^vmJNRf1oh<6U;9HQRZMDT>m|9!M5ZSp&QO=Uo(@bVNah_^Jwv#BO={Q z74>xMgTtCvs9pP@O=S|=w)jT%dpq85a?KbfYvGk}b7b2%Lk1F! zNc?Bh0&=EELbTD;IDs7j^09in+J zjRGVzQSWkF-;y;kWHf)XDR2}UIe|)nuz>o;6-z_lqPj#bjVX%kGA)LBq7!{3F7Ykh z5TPv!`e?Do{l%*OYp(ObsT$dhkCBsz5%Y`T=xZ#QnJ++GR~x^otUSgl$_L7{ypFN> z2KykhXN{}4cnsBF7pnK|-Zzg2j7;;Vk}KIDP$Tl51TEHq3q53{ER_Mm@o8Dv=66D> zZEOY!qZZg*d6K@}s+6gbppx`26B=>8akA0%#2co|jl8ap=9;WQj<|Up8T!sRKp6nm zanPXu@iUv9e}xU+JFNXS1N~lrpCKb&sLr%Yn~2G2FqvrTYKD5x*qH?7Lesm7y=r;%SG(5h;wqpxJxwtm{NWK5zu80e2@+_N1ITx}3?NTXgO7qCBhRP?_|4 zXuB>qUF+ol+R+6e8FZ~RUj9BB9G#@D07t&t=&Vs%r3ZC9D|z1QLk9As0~}`l%}EFW zvi%fj$26M6aQq85T?(LbA)aViQqrR?R0|JcB1>y=;V=c)8TBeeR$z6IuKSGw@;g>$M6=iw6f$0I+uEw*H{wukg2fmc z*SE{+ROlM(hiuI2eBXAHHh6HgBK+H3Na^nciQV%i^@o$zSd$zv6Uy~noQngwr@I8x z)#Dqj>~YkzMHJm@@|sk!27W-YeB^O-!_C{Kfi|>@lpbpb=H0aYg7gPF^&`Moec+2X ze?FE|k%IL1O@T{KE-Z(R6UW5!6>g-Kkn1tS1NSocl!K(lsY$G%MT)j_ZOBcbs*RAOn4ehp#S~au7A5^z_#~7D+ zn&be$nC^Z1`@C6XT0}GHI4*nNxH$E#{2+@}TS#5kRkgjHPheNCXThgb?orV&UC|kk ze8bl-YSbyazanZQ-y zlg}H~e7{vA;q1(hu@}u$$~4G)js55({=6Ntre$8ca$hL0t-EceR;=KiK(XUEJ|Ay_ z?GK|0Hk+PGJ>%rSOs~IO9p7Fkmf;%hK%a))pP1gE$FIxE#B#5U=28iJOKAx?9pA$v zdk2Gx3(mL2mbQzcER!b15?OvDV^JQ(X)<}`V$($kBAN?Gn-Ou7S^itgK1f8Sj<+2L z(*AM=ae#``ZpXz=`e_+z^#$SP%Y0v(K0SXU6g(!p2)jHY4aS7OmbgfiIP9?8t<`lqo~j8F55 zJrt6tefF$x-_JOK7g$ecUJ-<;*ySj=>R?``-{wS{$)?bhjY3#1k(FiN9Cw{6o&c&~ zmZ3Kkw-S^l)85XM{^)5`O?Ul}(AaPs2m zXrT1_CE4lR54OH)kHL0_#Tl-XHtclg^_+CNZri;x7)mqiP2Hl+aRpQ5Q2A4Tr#BkR zHz17ecLc}TWdnQZZ!+S8QO%_tPYL+b*2@0*Z6+l8?cW)211~R@+A>TwgI^S%Md+k; z=+@6z|4$jGbkDFGcDy0llCMTTi4~BSb-lbD(rQdCfZiJyv3W^l>S+mt~%3n0&do%dCXkF&Ew#xr8p{#xvrU;OV& zW6^J)A+28&Y9TThzXg`9x|4; zMJHJ-Bp8|xdx@54MIb-$49)9u3mPIwCs$!0B$xjV12U6+tX18 znq^w`=94E)giTY{)lU=+@bv5Z{`d83p|{tnX?VvCyGQ4FPB&$lf4`+RuC2-p8PaPT zgxWSy6w|q#KjdUxawd*%pvy@=0HnpX=Hwe1)R~s!=+(cZ9o1SE(HV^=L%Z#+6*Rsx zxEib**8>B|*`#(Z7qGVM?*R=|!?4K4pRBh9iNR!G_V0w)A0aKA#EnrmKa&+`!&mc3GyW3TQ{8bJ}4~j6b{KjSQX;ALT_HI7>h#0deec7R6s8{XRkJ zqZVlhe{g}UWR39~r%Lnfeqw5KtT$r3Opv{(LblKXXdHp9k~7j1p|6nwWRxu1-}yER zEDu7NARYELmvA7R5B)3mK}^9Il6ZXWF3>f`ya2@Kt2Z97V|Bg0_`*gwU0!;GC4i0h z9xe9->qq=vp)OtZAu}6RzKDJ#yunP`Wis4eOw9sVI6v`g7Wu!w1;Q!fHt?BhbFLnL z%{@k@_wX5H)ttf1>TbX0NV8+gDI;WX58z?gQX5AiyD6{MH})Z9TP01PT=KDj%^jw| zmaHydHSKZfx2-oA77^IMFKdt`ZJyGh46m7?1~Az*jjvN zh-?D~QP#e(lpS~57ySuZ0N%k3Btv% zY>nm=lxluE(K9sAwEB=l#d%X#Z!{-jrj2f<&@y2b={Bfdgdk!Ny*zkbYHCauu(i#J zbO~D7SWJl&^PLLkX-+Oi>%RX;UVUd(ek+0T%CMX{0BN~%iQHJ!-uU&Gj~_IG)B0Qw z^=#(1+X$qP)SHwRD6l?U#tq#L6sMVATX&Pc5%7?<&ZPVO&33ocUY`?cyYau`uT8Q% z+TgENyfgX=Sove^j(rh6^E;2Mv+Qyc1EYcP20YimUshn+^fPRMsOd{|1M$gs3au<1 zgA8zzEt}7b4^SO|fYM5H+h6l$(`d=n1yOrFzw`2nyC(Xw@bu@3WoM#iBDp9R$=1wC zrjecsZDPeAl0g9J*SN6zn45ukn2+E0lVf4X!%U7{wvSNmA}R5JHEGQ7<7| zFGQ|T9EA*Hm$~GQbEgD?OdJ>a^pmbFd?`hvqvhF$<9rHOpwy0#`}6VlK&;7NF*MP;qt) zqfmgMS^SbS+j~g~JwFWm<@jX9DCNjL0Xqwei{>{3UkcE+qIyVI+?!e3&)IfDOKflr zv~1_j(lq?w@`+z61o-6!m+)`TFe(xS!iD;yr~->7tWcEP-^8 zjD%J@ye-lvTU4oTz|FYbx5i-TC;oG0s)vJfVK1U?f1AEXGUJXwHjuX>%)6X`G|nxX zP8=Qew_Ee2Uonsk*YrU;0=x%JVuNR?9`^5?l&G`JfX-f{ ztHJWe2x+8G=g!V^QSKK#D58neN_RV`E_tXyH!^q`e<~swMDDNx=+B)+9ya#_XNlccqVA#_N>4FI4^@pU4UO#D z3!jDR)Al4AOycbS$?^MccRbwMh>!ZH^IsK^r{v$s#Ukb#qFwGFC9pYI`quR~bi;vI z{-o;7kTEI}*n<3FCBai#8rscY3IW^o+?BQnPj0H_Y8A<={M(wL6=nA32tO_T^iDL{ z%$*g8=obadFhTwCSl}0?DugN_0*-_dZI6NV;4pj#@+`8z>-kh0_|I?~w(hmRhqP+= z!U?-?cCh-a`J=bxD8-`6fx&Kle7M?hoJ=0@`j9VLX}azXo2Jdjm5Q<340-b%*}zoq z@d4|ZUBh8{c6L+uzII;dEF8AMDE~H&b?I^b>hjbXHW((O4_}khi!SLTM30b%T3agu zaQgW*)Jq`LBT09e(*E9KNw%SwtKiPZmxA0AZ+HFl0z%uwx{FDKbr&WPiaplKlrrB*t#357oZs5Uc*2h7ql3v8|OvHPm#j zWS}PW;pLXWA84^DBk^ORD_&tvOevyStlYIvLw(_e0}(sE5#Uao!Og}^M#OhglUt~p zL0UlTf&|LFN94}C)LK_qR5n4t^&8B|)Kz_f@4-XvJxnkwZ=salg%Ph;0C~)H^S3>~_{_VF z?xYOS3jOx8Aipxm8wDz@O<7aK1pH}lqKz3}xQn5kzid7s8o;O6TsH=xW4KW?e# zN+9;lbnh9bXW99<7&*j4!JP6_(MrFkH`;ovr0;Bn%|xzyBNB|hG8iPX3SF>nS83ap zwVBexy!B#vopX8tah9O?w}tM|4ZTw5LA!q(dAkFr=Jb1qDi%+@=Y9b`t zkSPt_h8}|GD~M@=nLwA$8|S77w03cvIznlp4q{p5xd_EfLxhshWN6XcOMLZLq5`DD zc1Ve(&@x$5B4wUtEDm1A<-lHO_x8gw)gNzxaUESlBF1(^NSzGnEF zY#WfDMdY2grA+d?{C8a1a$H^R9DNkrp>CU9Z)5jvjRF^S%ljP{s#wyA$#QXE-cd4q z6ozQEE77n43!Kk{e8U`=2>`=?apaUkbhYh!&phn$pt2ZeD;p7@Fg#o~NdJM&Yog3t z1fEHl3Z6tlKaK6aur0F{=YlRWl&&Bxxo!LIff@}fUldDp)lyl^?s=hozh-P8a0GIH zPwvwr!%H;zC`vkivNt1tg2kU6Kw_(p2nDS=E!lg&jICKvOVwCG~-DcOIv@DvGgcyCC5EPv@hW%|y zS(>*oobRLQd+8^Pj9>ZR{pws>m({(gs&pC{UZv|@jJjs~6INhDEU-|vw8PKiOs4Wg z-x@PZKEPC-f4vyTxOkVju9*RZ)+KVN)eYV6GA!pSL$BEU#KD5B-yHF(MhvU)5bRycOOnt#?l{BGS}DS-CXpI1bRq8`-oq12E$9QAbuC zgZ{S1ICR^IspI!_Ojidvx2p)AB*p{KW~<$mg%=NMPIxXxK3bH!>synCi_SJOUqpup z`o7(w-koGCg4eu8xdK&zr%-d3a-orCYFT%J4BM; zQ{TPuBP|rhz?54zm@@?Gn+?t6QX&gJj)r}I14JJt622f}ji+nU2*YiYPEUp3QE+-P zInmf5ep@sxW}a$*W>&&w?zHMP!+v_B*KT>oxQMnr6WRDzd^MsK6a5b1AmZ^P$0F?d z=)0wT8~+_d`@;zRjefiBp8SvS_qEL4#Gp263Vz6|Nwmb{H@G|vE#Gm4n*rB;oQ3h9 z_HS|)ZRi>nBZW~`t9^f)@bt}k1-~}mKeeJW0$nzbm2IYeKKi=|R|%l_AQOLIS9a`1 zY0IEeyV{9)AGVRxw0wp-T)c#5zUlwf8iBiw7)s*SgYv5=_tnDwou^Th#S zhGxBcSF%{5?{_LoKkw?zoKD&^Ov+woGSpifUMD-iy8V|pCLLtO2EK|*690QwZr<2b z{T9p*bw~u`-3e8>N#PorSC|(NjCevAaMm@J0G+-en(hzr40D;`L}6 zh}W3Qf4sjn38@?540;#Lu6s@4<~v`5*j=?a>89CSU&tt^0~!Czdxjfd^sm@})_ZC> zlQKp1^Bxikl^XgqzIx>IwxP6(HfsZq+R?t$N#Rd&sJ4S?CcULl(0oaI5Md%b>u&RF zW|}MkT%~(3Y_f!NqWTR)kHMF!qZN2JjqHo;XN(*s{iQWrT;1;9Cde)3a$zG3!Ort zNcQ!isShq+FFsrH=e+MvK!x4KRI`pGWBO|ZwuSCwd{P~i2HzXTh5rJ*>OJIe^i4_n zYt#@*3<$>umYj9ZDWX|;N+QvK6vttqJNfNO34;YhMAgib2#+IEti6E}Z~}}RdB?wL zd4V9fB$TC{|7!`;*-ql3LJzM1R~^zU2ETy*fD7z>-xp+=d4<1@E3w!wSQnq8y_S7Tw?UVuCaF&C?MP5B zw%KHXYY6}Qr9$_5A1)kb$cz=aECcQGpi=<2$FXtz)c)l9kf`_#LB>gs8 z)X0g<(-=l)tX&o46!10UQ?)cfkS=X~_jE_Ue{17J{@1oB;I^6oO+d20k5m9^OpYcltOaFOBsQs$$Z3lG9TC*M63K;nYb(5x=dc;gpf|w* zNgJ-Z9KfHfn?578>(3zhRS67H3uAPde05cFs7G^Q^=IgB)RmSdX;K&&B;viTdY4o! z(e5d%P1tt3UjBxSwtdpRERYN*`Z>GF;A#f~2CIj9xLP3J1UC@4re^UyzvmX0o(2f- zM;7HaMsQWsi+Kc62Wc$GA=lId_*Z(VhHm51JP|8-)K$8fVS8mQkPtz^K=3-`2l2;1 zZoU9oM_TJwqWzuX`HOA*Rn9gUgz8N*5jG{j6;4smymy&bV*!L^Fe(0RY^ANc5OP&L z4^P&ierxVS-Ysjsm}YtD(Z9}9j^XHAJDUQK2@_^;&@2NT9e)M4Cdc8xpCZ&YNCE_5 zI5W+^I)BckGrs*WIpPezyoy1(K4Un@)PE(Pyo?GqO+abrD*n2f46p0FzV?kHl!m&^(M$m! zECxUDVd%l%_oscuNQ^tMxdmnB#i*jwxJUF$u?L>ts4@f7Df*eW&1{G4fFJ)Ygt5eR z9e(ZMtRP?6@uB#pVFYmUaN=^7C~Z%M&VSn=dJ>!6;x()NQ~?Tf^?ytMg*IJbBr>T?2%l6tV>6dyf5I z4?*+=`yH^)2yoA9Q0jk32~SY^{S`F?z!x~bp}4+Qw`A8%+TY3O!BQAL<|-BAiL7iE zvm+H5qc$q-B&(H$3l&L|QH_jW-Q*@5%__pj52t0@^c-@5+lm|k(LffO_KexjX zm>!4U8()3+t&=tS8BrE!TxDRN>a~>~Phtec6~gvjNup3ro!IvJ3;^+D5~B^WO!S9* z{kHIf&TaBx#K7miCmP7?N#Fd^ShwbK=%tUNPHB$3$JZm>Wheqrk+H$C+g$&8*lAZV zDN}Y???df5ax|&axQpK1n)}mC(A6-@lLpKCG6pNGfy)PJM=h$%QYT1_UN+f>^q~mY znogvtMZ2z#;^agDC~pAlyj^EN?Y)b}2NvN`&2+nOyex6&pg&_s;OX<~bYZ3F6>r_* z=p6R@Q$-rMN%_6Wp?xP;-s_3`ZX@;~g!Y{x49b*2PyR*a*9M$DR(0z}hz3~R{oo!$ z|8`A({eBttzd5m{?u_&-&+bQuH;2-;68S4JGvbS5k$Db<;Hxari zIH7}QmBe8ejZb4|>qRq){#n*eKB94T4$o7r)NpI2t-TeExFu-vgq`&%D${h=CCR0s6}wT0F_H_LCMi(u{r)s8 z2#U*=byY!vHhcBK?bA)n^MnZefyuw0%n zx%2*}ohw;XvB|p?z#VOR_#7-koj=pU`5R))5na%!C(Ngy#qAaHmlHkIXu>m|o~HS) zK_9K<#COIg7BTw%VC4=@D_`i&CVpibojhdhekK+bf76WFJv4t)i^1~aB~+lzk#>>q z;aSCeayJ|`^h6KRb;CV{Y@`6mQ2X?hBH|GOPm~GB-~(O8wLamG!^Kwx#2-u7@w4nP zs%!)*Qw+gjTnF3}G|P z1Nmqw8wU7T^2!bM(C+lz6}~Uh-alAql&4;J#Gs0w>yuVgY$!9)%xwp2kFFt3MkkAa zd)GGH&r2`hukx9_vdEvCpc4(9fGf;yN3G1D`0K!`w66`Qh9!6W*LR*H281PrVR}yZ zO&2Sp{Crp6^ch~l&7;C-r3Py&O`o(3W?ALooGb{c*kY)I6*A@`G4h>mkHripJq1LI z1kBFW(xLgsh_D->U&kU5_JW?vQ<`kg+R_=8WX!0)$#R0Gw8u$He&esT)U%4!3JGEM zr~qEm_3!&{cmTB2>6&Q!xZjw~n6onJnWIhYkIEPUuys-@YA-vN>9v0z)LHXOqr5Rq zk*Dvw$j$G6^CH^le@tE1j;qQN{UAS>41NTYb53t0V{*p8*Pmv#bal73G}1T>G*z|M z>aKu~N-S&R(e;n?xjbh-sm8|Zqver5^QX{l_(v#U%PW4&Un^Acy3e1zZ>B=wEBOus z+x?rTMA6>If{>1jz^`O*0YDjdO*GIzGmHp_X3AUhcJxy;(&0DNGav7|4^cOo3W*-k zU)|`bIGct%UJ6r{wXS;2*UgW6MukTLmdMK?_idz9{b;81_0{;N+?~qQ7m8k1@YkZf zL($0v%Y#z%XHsa6KHM9xTj{U%qy@`_fhR#g{K3W~9F-l_Gmfs+o9)=Y7C|PvN9S}g z-`f(t8Wlm7zF%L5w{>h;&quFE0Gi&rjOdt$JNYo-*D`mGj|(&t;Xmf^05Q(b=MSgs z{`WvOIX>2S>_3mEW%RJsOLHS*VVx$}FY$%l1+s_TID&Tow_dZ!^NU>w{H!t*lRl^l zyI#q3_w$}(1MlD%5j^i@*ZQ4&I#~B^p5wi~{qC-1Iv!Q|=6c?~`=>S0Q*#Ljs(HF2 z8QWR39K=QU9$G+eQRa5#eLuEsc|2nzVM;;+kX@oi)6tOF=O|z`;mzDKl|4Bewv%pN z8zo(l^}2}Ohiv-DRiukzBX8Uf)}s@D9FP&srxyVIY-yBFk+G%UpAwa(p}=~Jj_0=3 zDMsP@($8lb8pzD|#dorNIZlEq9~I&QtDY|(K~|X@1AXSG(TgKGXM8zAQ*w|G_y-v5 z6>{8|$m4LDBrx6YkmQHUD1y?y-^PLI=C@>0?i<@g>BDG=9^r@hB>D&+D< zOy6q-;4j}CJQHOx*hikFIblyuI-pAo=kB+q2Al^H#CN6x9}GgnF=ur1QE*;Z;FZTc zSm`6bNOKOF*RDheQ|As_mUCGG?WRo!GY_td81a#y(!qU)Sc1LA$=SMVnlSleQCeHw zU7c!x@A|L6fa5Ue-5iQ{dtLQQyO{z86M?+AYb;oi^nk3o%O@O&zS}lU3h-GOS=d@P z+Ft8WMbTK1KJIc4*`n{c=7bRqh(&*9&*y25ss}P{MH9fs|Gvxe`YgJwqV8t7Dz(Da zg!Dn!)nIOqM?yzu)kfvwFg5s2=dYzyzlxRl*_cOb!yhO^QunK3ZsDTTQX8LR)Diy# z+Az@DX@S)xJY*t~1quk5ys1R=V6|C_lFCVviRS z!S~^&GYc_bu+8)f#W=f*(94vcX(@;Y{FpLVGOlSN zrP!11uu0@M9ARGYA9g|R`4798O!FfxFCB33-x2u1G-c@mjXxm2@QdqOR9_~?9iW07 z1kq5|ps2$ZLr+=N4BU%>v~d9hwMa2kI292uCjCWcbfV<388mN7eA0~%bElsr4A>H- zAT01wBMvtB5Nx6tpTe&8UDiGB7ERAGuYQX+pLu;4=I=Fe4QY*uqn))(hcf+g2_JcM z4<;tn-%Flek9K5uJdOlRx`P8PN=vob77r5!SLSSn*Bi~GqM`|?1%k$AU ziI_AXyd#7`@Co2HI(sGE&geioP4`3w5CCj)PihkO zFWO1ZVmFxb=O7HRYRw_NvW+cL`R?K##4m6pUB@`HEGfF&>Y<8o|FtwMe=2_ zRmr@ho7&)`d59R`6$pupFnGhT#FRE&y6+b|5jCriGJ3LB^OM8q$0yrhNhw1!-F)s{rgR&8AB|iqmwO?V2r514O zh>MPg353JztCIHR4t(}h*Qc-*v*Y`+86l$|x*NO&YX~b8tl$N<%P7DOBZ#l2n^IAC zRDr-z1-Km~$cQJF9}gY{Et()dssndJ^snr-t?^b6z!Lb4d;|IwIzZhPsH#DbB3^P; zkj-r;$WyT!(Hw)XnG$`3_!|XpuaU_WmzUQApYWf<0+~AoirAyf^`PR-D2kU^RfVJN zzpCDj-#e*t2#^4-rymg@%ae}BLKuZ<+8Y{dGh<>;3(}wz4`fBzmudFRX##)2Ci4^; z*z`%%OOb%uM*S`yI(%z*0*b}Q4j^!=_1k4e)|{#`r#JEj{0!J7x=#LO4n_hXCvGD8 z^|q@{mq@iXloa6qsQ`6!eLov+I(IL9Zf@EN4X+XUvW!F0v5blEJU;o)w|8|GA{48T zNY+cwkm2;1I;YKWg6CLm#ZJ+_oNmxU=5qplALsAQnGDBi<>_u85$$jSc6?atNw|CW z^U6n?hU>;_BXnQs8VuQtywswQw{FVWmrhW`(IC|lDbilXL%@vou`K)8YJmwo9{t)f@$O=Yogd ziN!!BHWq(nYQ~IwW=mN0;Rp^0HJAf`W{XgQ2rR+Z2t`KaB%R+Y%clXT+zBZ&>BS;Bqa) z>^%MF64)5SsY|Yz$typPqheYng()@)7nV=gPjV;BQ*P)0C-{IcIuwRIXD<2XuJ!x` zL*lzt_NlK_PYt*bJlHo}_JI>MwS=+* z^U%SYZ;b98Q;x#sH9-|GYHsVCkMH4SFJSG$#{{AIg+vW}onOCIZ6w5c+il%i^pmd4 z?bw-qhfMPX`OelyZlOK}P0dq($g_s-1k^0^T4&j#O_cYZ9XK8=L7nx91#sFbvX@2P zV{~I#)ukYn&(D<99#HwMQGHI7{aVg=YA=Q66B&xWu=40^_^$Bt+9P+aGsXM(s_8Ss z#L?4f7I%f<5@xn$z+x(Q!$g|9&nGrOMjkJezLV>BHG1YuO1#T=cyAI&Le_qp2pgdY z{YaeJRy1v_juR}4LyoP&qCjF~S$f8Z;;w>Q#@P7ki;%C4u_L{;5SqDc}Dmg1*`rz|}wIw<9r z>lb<-8b`H5W0>`m1D5xFgwb-yy6oI7@JEGy_jat_EjNK>0;BW@F3ps|t2bk19RI{) zsY7hQzRreb0p>z41wwGRz`1C}6x>huJzZ-ew@vSVNmXa&J)U+~RJ?=hgS_Jigq6v$FF9W$Zqk+-$U2lJta0pB}u3%s>nMJ{95+2!w| zMoAPt5qw(5^<@AbR!^fRn(=(0b`j&Vnp}#Qw@1Bt#&e_w0>|h5Q}NgZrNJyf&~889 zTc7xBqPh5~0WZ!MzCQg|9Wr4vofi*Rj>O@}pwT{|zei;hyH+^Bn)N~t%!_P-CthWF z0_c|n_rR8v`?%$kQYds0elX0@Pkjq8HF@W*ItgS~`BlVyffA2iwm+%9 z>>X5>vEm6m6oh(cPHdW)pRTham?Xl~FbP%A+^P>)ew>p6>Dn&i4xgCxQyH6;w4>z_ zjL&zg!|9QJHw1fr$#*%*bGFS2e|7z?L&&oRE?(GVep01xZ!@+(Cqy(uc``gmALpNP zFv*yw8>R;6H!?-{%(vC{LHYvM~p?-?6%sT2)kS`{7JJ-j890Y2Zxb1ALa zy5x;v=Iw)2J!K+|(AK{rn)w(7ie4+fKkbQH=;!%+@8O$@_2}yr;b+Q~A0&H^0&pVm zeuT(;JwAkVaLf^~z?a$5kCyZ9DnJ9-9Ye9>1|l-Gaa0f_7owoYd)xyF$4TM0Qjt9n z)>lIH$mvWHkB%wf)|-xTgX*IF60NNtL1h3HU_Ct#Q^bf>YQmqA)r?SK zbzCAG$0efk)9S2~G}A2Op0)SdF~FKH3Nd=;9b5C;fxWW8UMyKnUI*tA|33QUhy#5F z66#1wb^nN+RXCm&zl>tX{C-5ICoi&krsMbQE<$WlPyT&eo<%&udSfw*Gif3l$cOL~ zWU=5J2p;Dbke90py$p=fUv_p|!mV+MH3_)o)u!v(HT5t5lChA%P8@!lM=sdkXCb_m z)~OJvpM0T%NJKaNoz>B?o#YWGBW~WP{j$R2rTp$V5s3Nq!jY6pqI;^sXt@rYUqMo# z{8f=BCLWU!XO-=!yqF!>3$s6kuxrPfTora$`>eG_Xfq`);P?a3%fx zI%(oVrN^PuCH!nyP|P3m7$*1=N_~gU@5Xv=Gc+w%Owu2xNvka8eNt2aR(8fTJ2 z{b&o#z)8}5DKPk773epC?w}~yyT4g(*heI*avsX}pOQcUNSN6!>Mf7Mz4wbfZDHLEig0;L6LH3Gaq@Yjxq*gAA&i8mK-v(h86(OLi7n3R39K$c~ zWZ|YTvYuDM+H`ccYU?$QsjX^iWCGj->RuH+xDt?+>7wMtD-_oe%hAqWMv|@t+lvby zP|H?Bx7EMqfpr6%PY>`x7#A#YW4Zyi%IM49E!GJLZFBmCS|588*F%?a(2{C14O!@bGrOX>RYz7KsHaJ=dN5a(Mgjo8o-%yNEQwqjL~sO5_3l`FN!Uh0H%{`U%Yi)MNNF zmVA3h)|2=5A#aC`m2RaN7ZNlDA&}+B0#jZa6c@HaY_W9V?Jy!)oBZs6xZ+sVRpr% z>yam@YV9?ntaUXCJ08!?i2}KsJLS<6@C9ql=oH4g zPg@0AJ3J?X5-Ea8;DCe^bjPK6CAW9>DFVA=KXte6*0W-;3ff&ja+lTZtM&b9-fLMc zMWK2CHZDWL=QFWzfWmHQe=~!4;{cYsz2Yl9PrI}Ty=H2=CPv8RINcz+_-z#F>k6KE z!R_1`H%5&nbJ^g|A`ojj!O@%rez?g3P^{emJ&To!-@J%DZi06Vjudc2gtJLyAiDcp9Y(-!3 z#;CBazdb|9sKzgDjYnU+&STg%(f#O^J|fHpFuj*Q9C8 z#Vnz1tjDK!=`^9qPMfyPZLazlguLtvk@pAP)nA?$&7!1tjzxiAj`h1-pkAxODq{l% z5^S~@(RQ|3IsjvkNX~2N_njtjA1?z`_wKe-_fMdFt-l6Y_c_l!rhc*0(S0tB}NIaL!7zP{b0Do@^#oenuDh8(AekNWdu0 zbMYIcG)$>;}>b;UgWcmcEPmse&8(l}{zs=;j_32e6Og9?`Q?+@p-g zsn>lz@Jpa}mxGSZSk`#ARO>03;KU`R75HhxLn19O)F_4~1t?`Jy$i+rosU=)CJ_bZ z(YqgDKM21p{F9Im8Sg1cFrC#(=Vyt>N~v_Hc{I7HJzf?$SGFvqx1X4p`@HJw{x&M**+Ei-9I-n8~E3%lm@5 zoIo=`^E*#RW%o8)RM-TSx5NYl4`SwJ^=_3Hfi zkH$pcVf)$EmbHB3kh>cZx#Cjk3s`HpO<8ow7g&(HyjDCLBFAw*zolSauBQS~f{kM! z$MC1zE(l-4s)lLTLEww~UBWqKnEVscQXJvAe|dfF;7TyBD~OS}WWppIv|S(QYL367 z=2|_mjHlhRN2CQZA$KTVKz?|C@8~zpV;omrzbB;xQ(g-eQ2~ex@g)W(gGxMod4Fw! zqVb1(oH^sDB<%~e_&qQKN$jP`0hWKh&*@Vkj!Oc;V;?o%cgz87d$HS%eFP2`4yH6Kb@Vuum;wpLrhVovMp;GfBfYOsW)$-T*Rk~sQMZSRt3^-*<-h64j+bL?5ukXnUefK1cPbDzDFH7Yr1RF>05EiOaaL{n+?&} zFic;@cJEYiW1tlp(0kbJb(*a-kLF&qYx__8<@eCB9CknrD6=k0l*P;i=?(&|??F+E zdGXphUa;eE9nGH$S^w;L4hzm}i^zZJ$mT@GUJ9_shEc|yn2 zrF~ctZ%Q}g&nyO#guu)C+b4$M$Cin;gaGb;NdRpdMG)BKk_=#ejs^`avkno_78$TdFxFG-7m+fU< z5YruXAKWrN0hj)#WodRh?tyummZyAmQWS;^!38}+@&Iqz5;*6S@YCB4|9OS0WGDpt z`(iV6{T@qJ6K0%zb34}5FbL|}oXh-k%$rB0!8Q=&unp&xFswv;_W3^b_t)KS;_f23 zc)pZ}GX2_-b@%KgwBIRPaj`S1f;Bl~xef~v+HfJAXZFlR1~AS+{(Kn>tHSKVu5i8A z>9Y>ku6=Us4`z}IJj`u1s>Cx(NoYR3#BO^?6m9P6lxH&Dx~hOad`N!xyU5@hAPzAX z(F@nVTroYq5!sfS*6!KETrZcOkVRlszz^=VL$F$p8$rsGN|1KuQhWOmoxfc4Xu~B zrwHat!*kcyr?5LZL-B-&W&5n8$AJO1V_c1`b^)}))S1JkG9Ue21@VwRTwk`P@LDI~ zn}7%0*<%$jz`FEPEAtn64@f0Cz0HsD*zs=3ls1NixUDHOFRC)ZH(lwPQgC7*bTN_z z6dmY42)O&MV?NA=CKpTx!7W^zAmtXlW_K|?k$FG9e14=Kq5ri*(EbQ>czpLv-B6T= z>y$#LQhuc39(K>8eJJij0p7NfLK38ouRP;!!&>ev*(S>(S)RY{@e2HQ=wV~>GS#io z_y^=o!1s8=PeL2J&p8mZ=QqeR)ITzkF*T~RBHJl=abS>1TUZGY=B782D70*HMKPaj zA~_}c)@jIENshz1Nw7b2m-w5=b+zAz8TqnWXS^STiN*&+`6BXh8^WS@bv-^i#cmb; z3;&AMN285&j3e$0$923g;~>Z!=bK>%CD(>8eGxW{9N}#aMACf6`2uy&89l!)oFbEe zK**z8<#|((oGkQmcIb?Tx$#2ipYfaZWJDr4K@;^tN(ZZe4{O}_uc&bG-kN>xX68Gs zeP!FX@lUo~H_U`a$p1xT643S`UIJT0{v#{y-Wq8?=zRqzB!H?>G5kY1Y zJP@)Nk2Ak)i-0=^I;D*jQNIyIa18afeFg#X$Ulg%B3)l3C=!J>(;_j3M%d+L=pHO) zt1Z$IDTB(rN(D&6nbYpMyH95(fEFToICcdkw=9unB!+k;<7vdQkZ-f7>pCj(Nho2E ziXVuOw1Ije%!j2hgD!^4T{m*Cwu@2=di7z|W^XNnx=zyv+<9JKIxtuG@BlqaoLz2@ z5;snx*vQ!(RRt<*=qt$*(5~0pYDcPrKr6Xr^B%|flw)!TJh?90 zhtJ5TwThae+2Dfwrb^IKejl{994LFY$=0jl&39V6$?MtLEf5Z`LYWrr^=O$|BH-vv zzEDaQ;qfadS=Hzz70(m7|MtXs2(#$Ld9=D2+Bxg6IhYHZW4GY2^m|CjxE04K%F%N1 z)hB3i5*Q@+=uVqN-`c%B+D*e6uY&vIGoNi)tOFW3o5#cLsCBSWiZ zHkk`M2wxrYBC}6)NU|$#zwt8yIESc2oR&v zT%#N&8E>G;kmBWz9Lqr<%w>6vPOFENO@TZhT3y)GrG8geGOy!JCC3K2!&2dX7t&Dy z>)*EdMyo(7jwUk4;a8R|Al387NKK>6zpqSt1v>huyNcZ|W1rYi^GAhv1^ZiIPO%BdKO?ijP55&+XN~i%saeTp9gbQHnks^7+SM-lHH) z6H5`|ix##<885wl1c#^XnJ5A%kw@B>Og?xe&_+Sivs+XIX;!kkDQwpJF5$bMuVp&B zUBPl_|8(8Bq=$dca8R7_E;zEAXzWtIWDXa@en__0loH5vjs!q=5LC^>e z|M{ZlbA8cg`BhcrV6kE`O3^5H3JB(sP$->)*L7)gt4Bff7l=PJelf`5Sxv5yf^=#k zPosQpzm6pISVbPh8dZca)6%lQ#>#Sb4w~kcGr+ZdM+_{9CARrGk#@UK-jca%jk_1n z=fdudlUjV~sa-J983$B}k(Z;zlViL+hZkrO27_On-DM&2T#U=vZf!+$-9LxYzEy)d70-E<@e~ieD}uW` z6nvs79B*ZS8%Mqh0`*-J+dHGouhgkGsJaPQbef(Ii-O7LC*I5sYz#t}VU zfYKyLKAO-#M<;fi_uQIM+1=hn>nnZZu2L(TReXNac7BU;2;gH4r1q}U#)oPTM!&UU z?0N`^W1j>W5b#O}Z6U@_9>{fGfAmhp^ z>F=H9rA4yMvxcT>ynND$b*<{vq)kJPic>?1*YC4QssKK*TDH@UzR4|Vx(|`JP2nl> z!Tcq460v7T@FVTH4S2|E!|P7>M67^BdCxqtC7h3wEBQQWA$v&ia|J{+RBI5)8J9Fku@pYQK@UPO2toIzpq8t!=%*=e2>*R0B@CoM@@i5S zlO@$#smU=nrF<>W5l2mp2#j!{vT^r8amw$CMlS`$^Ay#_G!R4M%INxg5T(y?)bZFf z0AN-BJJUcu{Em^39!-u1F#SswpM@d{N(oR7hKp~ZuFj&Hn`%<@`VP;a{w=N~`g>Hu zSAH4rA@8y8J$^@!$!_}5YA2Iypyp8ptFwvynNwof7zqRty_OsQSoJ6kq^nz5v6Wu; zNHtSrvgI2~7g>6XGKgVO=)dz$)z>j`e=_i%^==+DtB2!&_-F-VspbF19pdmEBO0nda9F6t+j6{1{kPtjmj8^8Q-4U7N@%`psIfH; z5$O$4u)U=vv*nQ5n3u1`Yt)BtpWK3J8-He8>F4n54yt&5mdAjsBt9`aDtsFG`AsE( z^~`pUJ1EIEe>1HMj^>^rpe8Nj>#&I2Na8!u>jDJ6g=d}2h@6SePCBd)u~f44dt!j% zJ%vf^bOPqVdvk6lfBEOfJ59R?%X;|`>Bpq{^&BfcY19(Nynr7M*iBZ9aCnOr-ZU4z zh)X>Iyrmp?taRNp(JzS>ZMHibn0ozC)8mmlL>4>}WSpkfRvQ1KPB~u zT%K(@CN-U zmeEnmxpfP4zWaKNtNZw^8oyw;ht&89T;X_4(+c8d*#PspM^OoIjEEfmG66G+9Nw{h z23uF{SUXXTr0xwV_FXggcj%xD_h|_#aZmbFDr>;wW^xsPzd`kUG$N%;5TPAUO_Z-1 zw-{Jg?dekL+#rmhr@}CY$Iq)r`R@o->_FNdV|>-f+{g3xM7(ESL*eKdzka-2XvN>i zTl0H&w8shZl54uA07tVr60;z#4s&*5i4gBI3PF-Yz%Of{uo>yxqNj$GDp3tq=3y+a z)0d?$Aqgw9pkMXPxGAV3(+BR{MO}L_R6o(A(X*{zgp~o<_ zlO@ju)~tXdREEHuY_&aClY0UT_^>R{rGpOZlcqFw=&uqNnrWf=_={y^4Fk+hHk58x z6A~iAa}*Tq#2@zdzGue|uo@PVQMG>S=8&9sXk1GjNeF|Ei}$d1FBN%h&JKJ{8{*{j zhJvGY-U{?d>tW`|1QyjM?)O7}MTDmzit7}Z;<{U%f=?5$n!b?+cF69_oP6Om?}Z8> z1g|9qa4L3}COrJbkPi6FKpdUvwOVYca-#Q0FGKxa`%1jfFeq)9k=Ip;2 zxNcZ&!)W~Id2PJw>fY5bo&NXiaD$BJ(1J$NJ1t#j6{e0*>1ix&U3CkR+=U8nzibuk z_3gU9EC=`Msr_%nX5KNwYgC!XAr_sZkMNqgNHer6-q%?!c$jXJX3=@HgKAKF_wt+< za>ME$-%V_e4UG+ylkoEJn9jfjk3L%l zhC0!fYf=`8{1&-LS^JhF$-%0gIgTdCzn`GN4QwL!+^f6}sKRa685KpBtP^*+i@WEQ z^9gvAXC7PESFe(dXmRo3D^8-rKR>0W&D3Ej39b1q6LbF7O$LE|D0k%fn8&-%asA*u zAT9L9kAj@(%25H-;JZ!0M;0QMI=>o`#~MT^8e`Hyz)TjY9E}gmFhXQbdaEl72$~Hl z{U}ErVOwF!@CoHcAV@N`@spN#DnrQNW@E3CPkdvSM3zZQ+Tr899y0o|__ zwl|m;;~{wH)hEn5aF&HJs@P@sZmTaXhPpLnuxb|WwU}B)S{h^jXeH^3iF3eXjwY8v zP(x`J#2zE4k$sJ7SY19A11~kenP+#G+|$&lmwmjTx;ICQ8IvVa$J!i)4#Y_Wr7u5aSoES>b43*&Ngke&W>YKNfK>XHa@T>OW%_rmQd~dHR}qLX_MQb zRjBiugwTt0sH#~$a>H7|QuBM0aW%d@Oai{*He2QxcgcPA?PgR6sp)wnHs!Ri6=@XW z_579^CW)zTv=F7H55Ss5Y4C$j^4ZE)-2*t1ivm~OUXE}Bd%1Ql>*5bRCF~r~-1a$C zo|B&wx?*Q-)UQEEKox8lC5{+A--0xjoEvV7smG~OjTO)cJ)76S0E*tY1G+x5OM9yj2XeJjGk-J|KA z@$ycV|I7B^nw7Ub42tn&z;8fnFS9_s_0W#< zI<-JHVckcJPe{2|)pkGNnt+(;+$`DeyOeX}@^z+Z>QP?qt~OM2R{E}r{*t{b!^RL* z%784O<*3pzzb?g{@IQdK0x(k(fv-*wLDZ%!(4B<4r;0K%84XnB*^dZHl|5>r>u$}Z zOJ{LFl-}0CCey@}S+UQ7bG%=S z4|JnI`d^-`P8R4zfO_md}I_0D+G6F?4O~zb8 z%`MZ@%BMIxLx{91JnydO^g6KL$7$yA8>`Wd_|1wYs+l^rnJL8aiLF_%m)Cob?oRS3 zygm9N1$MyNXwp=6%quJRarr>EmwC2uweZzN@L?JKY4&Ok0{NQGxhn#^FN(s)=#pqLG@2?+odv zl}9iNBmvAiNVmdmoghtz(B<@d#B5WvvHl}>^o_q5ba+6ah*(J9?>Lhd5%pBJS5~aX zI)qKjCG-*)Oqt)Oo%w<0GaCVEj>SMn0hDveJkPC}k#AgoDdxt0lh>^sR09SUVg)Q~lJ|P0cNOEC0 zOgg%;0&Gg(_(CQx_madWOl0r5v&i$9`AIa!6-lXUSWV<3wYfUQ?yobj_d;+q)rf-` zjB*S+85z@BEI!}UB=vMR5TE6h|rOdJW|tH72oa3>AQby-bccaF-% z7w?$)nDR-};YUVZQ-L-?;~woWH`_-4E^4)qtt9!BaW-fyh|cc-2x#C(@YUlzI)5js$g;RGTH$D=IDA~w8D8#27O5A$KZyJnsq7vz%@^P?vs1 zem7N@2DEWgU65%m({FEeW`2yUE3tDF>IP)t8dl-fPS%>AnP&yC{^TSRCn@gLETeAKe>qj(V6ea&G4bj|fj&K}Bk``ODE}-kB6HuW}eCZo1Uwcdd10vZ-k_ zC`akQ-bHg|7gwm|YK8K1?J2_=5zjzk?~M4}kN1;06OhhzWLVH}O>^7_3&twO^topB z9CC$bawLv4$<5cY`|s#?MmINGzur zSqO7wN!pURbN-?HKlc{geM0)tI1f)Vc5Hp2FNld^=FMl?`c2%?4^MsL%#Riv3Lqb} zq-z_!2}1>iUSUapE+RQ#3AKNFFUFyFUxT(_rv@!mM?=4ez_k3E&=1>$u1&73k zz1QAImazz#$tOS}Lnwn%rZ2m)`k~}0vDg>@^JeBel6MMTBkbvYW{sIM$o6-g8>$8* zF<9;!rOr5j`o(^Pkp4cgO`xN1X<0^VrS~d{LgGb=ab^&_aZA%I-==L6`vz4}errHg zn?*Acly}iVnlN_rD#>KIWn8nYFnR$)zFSBr&jr|t2l=Qk4Qe1_HjHOU%RDXr_FzOk z62{}S9^Ibt*LrhW8vEWPyeR?2^&ahY(H z=Pk-BLc(l9%?AVhA?af%Zxd@}KeDMxW(U>ys$lNo3u>}G$Rj~M)HHs+X^#RRmG%u- z$FsiTk%C`~N^nJ|%)HMuO>ah2o@C!wNQVgx8k+Y=kmm|Ai<3q3YJP8|By>zRALN0* z1q`Bcv@XhZ>q6wd3p&`8--z+^=kfc|#zXV_V3=01ZI=Xyw_fdRC?C^8T~)-rRSf-4?0*m0kD&G{u7 zY}E$&-C9rNHRz**w5zSYxz5?{!>+yOVXi}4G*|a1r*8J-13=H z_)*6B6J{a-c>Alqt?r^5zy6AwN|4xCEYoW8%zOI?sRY*wE`DG2e!d>E#__us%_=nQ zanccq*Ya2KZQxo0#*Fq@QSys&V*6z@AMou{tjD_cD2_WAS^t#$yWxoZ{MpujZ=8R0 z#~O!0E^oB)I-tcrJESsAxWU=_0h9!)?~LLWn3;@GPmJv)tSMk#J01WVvFxU0axvlz zxeG{UvB;O3IBk02-Doc?B+Stn?Gl5cRkNNbOL@KMeNk*O=mHR+U&N>)mOt7+-g zrk$zSK&{-LJT#0|{!JsZcQeg)%%iVbDY$DeiZ$7~0IHwNdXnqUXey7011YuY(%*S1 z!VgykfmA~rI?AxnM%Ay-ZfL*ul`TcERerP#rOmlsecP`3-^xr}{|Z&-z*OAG;citM z7Io;p8-6oEYE~?osj@u-%<{zPrbkDC6ub7je}Ms*2UXW}Z1nQ%iM?@jY6NC-T$gW^ zMF`6h9jLCk>NN1m7pW8HLo0X4HFG+$oBk7#6t{Qg-)^k^d+EQ&0|WK5U47H06%SKD z_uqycej_-fizO5K^x-IpE-f~_cqDg`yTyBS%JZ-cGItSkl}t{o3}{WUL;xKN|7e_~ zJgd(>&u7i=hzy3->K04@u3_Dn_)l;Lj()yq!#wBqwI03;Wvh@U2QgYGg7vH^2>G+Z zZ44Q^8=m7b^;ULsm8$SiYjPKwrm?aFdm{&Gy@)}BtS`hGEC z0Ha5X$zaq|LUD+^X9e6kS8<$HSJiL{u~<)j_3Y{t_sCatq>O>4VV=OCQVcj@Q4r|Y z>?Ndw$~i4j`YF|L^rS)Cm91KDGZv&o-uTjSR#_^^J~}f^N9*27<~5EwBv-u**?g)q z%a?emhHt>SrCG&PzJnEFRBHw_^?LW~*os$Wm<8+Cp-ur43``2Sn4G4AGEl&YG$G{M zS>Rt}-6=L3PFiJH%o%7cuMX2oyY9CBCGS=5kk%@tth>|sKB(FekCAg|)f>)oMj;@v`?d88>Eau9 z@MM`C11F(^wmvBdpwZX&^p(o1Bq>O%c5{w=XSOH&ZIbLVAayf3-<7C^&3-gQT|a1b zWq^C@&)c-NL)R#Ou&%4PBpMvXOZi6VygEzPJ4dU*Z(K4TacN`#)>*RfzGJ9F$Rx1n z?&6u(fqD18kx?bh|DGr=R?QGKx(7XKM#!X;$wLfGrBZm2j1tCz;>R{e*_S5TNy@Sd3{oV)$}alSW2`B9mIs06N-RJ8@k{(n;+hLxe_M|C{7EHtlsY zqQM_dR-%}%tqaj;^%b<&>xkh~Jx0>?1E^8n(!^gMXWc+B5Wt17yMVE3wv{0P$Esyq z&%8+ipnIT(?06aGp37ZMccwk{<4bLBUkw<>;r&wrs^9kY-(%aK0le^Q!TKFMdWwv< zBvEP=(_-T;9 z_NXxcT;|$rIj%q?J$p-EyJGEU9YIUgyf)oI>PYhqw|M?+X((1y+*e5cKbNJ z^U>?I`ui-pfiTxidM<%2Se#kVi*N&vzl8UW(jsvB^lwn0`D8aJxCE z;B&zTLyAVbrUNUc`53E(+5nVjW(NM*x}9{hPG>`({R)!Loq2#AP(-(qr#qod|G zyLUtNzBvQ~1{;0IZp`k@nH2;FlX~;e`MKPN0R~R+^Hq)7Z4`Z;@~KkL*y$5Ywc`If z%$5j7G7d~W{$_bn&w}koOdK1o8jwqem@J+EaKa7?R{EkQxH3~dRyQUK1@Ss@)rQvd ziO1p4gZF}uJj%c`>c}vVX+#|xE7J0k?&Wp*@+;1AqG$v44p8+g0Zmo@3@W{sM-}v+ zPy7t9?qq-rzNJ0qgU0D+y-IYQKvs3239`)a_Q z(ZosKfp0(Saxzk-?XudR61={xj0^bOV)bJD7)} z4^~n+Hyks7uiJK?rYR1*hbH;?$9XI3+oes_Y>Z=c!s$2?&9ZJRF<%t5m?8BWPlB#$ z#WXg1*WEZLhW;i~$@l8T7^h=8PJBtp?_D;?fR;*C(n}MM7nd&lLx|xguWLbRR-SHP zz5EvWR=D;}tvKh{x&lEmbCQonw}`loN)UdeL2xdri_4FwG~k7nQ${HsBD-(CCeb#k zQS0MudJ*0M%<~?3(cof>QNJ;15}Rs36iZkq7u9#W572!J4@iUW~QS$a~F0%V(hG24$<*6#f4A{2^b zg&M}xC|EDP!RL5y@|z*&S?`lIJ6-PFzfNA~M89Ymc!P`0R1Lak;D8i1S?# zqR+}{S&<)Fo$@!1V4%;Bt7mR(qnBkqVYROc2N1_|kv{(*x@v2&rXG~dV5x)og@8;f z%kSlizK*8y0{62^Z=Oi?NY@>G-}F=3=n&(UNDZWO87u_6T5*RUzQGDc`@>>=)WmOE{)fGk~Jv-dlxzeXi=iU*{$*F0;o=Z7Y<nnWitM{q>6{03!X8GJM3Xzl-HNDe5!~^8X>%<0o z+ry#A+^aWk$c`Fq(d&HlmZnsFaeBjMpsaI10Px`WQwMNnAm6K3nGevh&BvK#!Q?PU zYf5Dq533k-ZStD7x<%P6x`$_`imG}c=_;+kBbDz+EWCPtlRK!?PLI+XBFDJEa@gza z!SVU5Ze-mQ5gE$z%@*ppHnCw>oup~JC`SmN=SWWIuRL;+Qc9;cr+mkDZDo!!txE?) zU{;<&_(becqEE=a?Vlf8sD|8GRianMdeJ5=p`}cUt0@*v%LzJ2SvUHaL8$g+S(U>x z{1b_j0+?TQ+`k*RML6u|tT8`nfyj^a&=j*@Ms5oP!#Rwjrt1JauoshNV|{6fby&<~r?! z8-P7pPazym>@A<&n#%IZ7m4Y$nzfD8Kx(N1xZ*ExC2iGsrJC~%uYzuOx7%CuY$vFL zYg6VEJ%Sa@W0V|Qw!+8=zX+qJ3Xt)WjZfa_ofn7pK@4xX-ECs!NSEHH!UozT4LzkF zg&*RxhC}NIWxv9_mSg+u&L$!JE0(*~0LHpZvn(Q$L562o!UI!FHv7<3@gehQ%6PX1 zbSfhm$1=C_d6_lLJbh>8(O7D2%Lh|>t=>b(;6oXDkXJ<#Ae*+_ro23Yr8F_$pDo~k zF%!*)nS&`}Pxwo`p@}IJ=eHs06KiWQ0#*Vtlz&w}lv?7jOTHR#%2UwTVxjRCb$zz} zRgQKAVH;s7(V*A|>wF2Kn)a;c-#o%6#rXy*83y`0)sp1(0|@C$L3NI>106`OqJXR3 zABVBO1>$3sQ}Qa!DkQ9(X~Gi1_7Ag;d=WC0qK{z6*blK<&D#QrugzFuHZ@$Xfl9i) zs})t9Ac$A+qw5NWsg!$n{KMbH9dWm_Ldnjn&a zj-hXBWdUS8_MLX5J*8-}M-ZjB~DY8#)q9*=1&A(`Qna zAU_|;1HH*Xyu8M|md;u}2=h0$)egXL$a!JB41CFFLVI)ER%2CeZD{wg@w|FMIR0pL zdzl;V9!`So;im${>0ur(SAqI~D4R9$k%vb01f96#9@|mCk`X|TiYMlZ)U*nOltle2z27sqLPd6=_HDV3hwOVC` z%5|{#cx`wSPn$Wt5n%*Z6iss!sG`&ESrO2a-f)C>r#hNfsV4ZcvX7NdPr8jOCt21G z`a+IvFR>3RhpCjHBj0wMQy^Aj575zaAV)u2tPk*FL|I$V&~#IukAu{(%wvxGE$5A9 zeCQr-@|PgQuEKo951QN^oLu(k2J5v>$tYZ3xov%rjp@tAg@ydqyWft3`=+S(>~V)> z5?}e!{*AM!0(?W6chXp;-qbq#*G=?%tw6FkL~zTT_8DC~KzY=R*}flr=Y)Az%7tJ& z!Wen)`82@zX=WS1q|$mqR4-FFtLv?zet%D&lkep11zNrG%~T^L=ME25+I@7B2Vrvq z^sK;h0xYvEYnM>k0PO}D??656)2RaaLM4XYZ1Up_Qbl-WVc1QSGvkqL)=(+2+Iim$ zrRd9fexv;ii45zwZ8FA+^&<%85RpZz>g(jG+$BxFOz#C|cP1@a2NrNKqu@r=E4)hc zWCge0NWr^PZgj3c{oa#`$16Dcb(7+#Mgd5hp3SlbWPHif?4xLDNhzldh#p{S8XIRE z8@i#{Dyh@-9>r-noZ?dg3*K%*iKz6~^JR&;OhR>zKf2Sc+HuOU+@gFL<@)v&`G3u# zJ%#)7Z{#e5T(ETCRw4%y_%p8HdFq&gJ|LX!gyR#IL}%`vklkaG6;t>Mqe61{fvA(; z{cgnFgMFM=p1g$%$$2}z&Jr*5qpDdfi;)K*8vS|OZyiF!*Fw?vi}&J7kn%Y`yKw;E zwKkM;aM6q;0B(fjSUmQ&(VIN;d1uKVxj6oIBKb!UpB`X+<4PWxlDZpaFz+|#d5pX0 z9~`1D<0KP(-@WNH3NT!C2T1U*gHeH+6DNISIo591<_o5V3&n)~&U=FZTvhC`^JoMJ z#IDw;cB!VoDeD6nAqM_!t#-ZH*9BNk7Ahv>wL2SxRvGniMmg#kykg5d1Nq?lllni` z5bDSB0Zd#vNw}Og(zNIaz5VF)so+vx{vCnlW}zLi&<7UsR6m#XE%WoTM03deu(*`~ z=^9y5qpKL_@y!U+i^D~GzSJ<+w$-D zr}bL?Twf#Pl*8ff*~|dXDuQbt;-c$hP)ghb5D9@q_3$+^O-_PoSGu^Nl(F@xZntXS zn`COQ`*!RUgv>Q6wed&o%{rp#Cx7y&%x{7KX?9b5&!B+~FBo>OYL#B_p}F7pFOJxW z9nEq9x8w*{44-;SS}<4wH_tuY>_s$DAEl9PTf_Lh_>YX0(_^!fv2a!?LI{H5dT3T% zKOME5oLZSWG|l?lyw+^+zE&FEko1=7Mf}agV01HVd8H(~&q&vL-^)WF4Ml8NGJ%ne zo}$-9$B2W28=#JLUNy_EE~1-#yTY7*)QZ_b8pPHRU-Yo<}<*s%>3rz6J1J{En z-}L>6!M;F9L3E}_z}RcI(A%F1@RNUAELVU>zhTYR$W!&Q<#c;AmNrHoKSOz@cgtC? za%0qC2h!Nfy}vGSpSOz(fg$%=!`#$d(^1V+Ng;%t-J89ppX*}QO<>nlMPnpnNq4qFX^az8xr6HUZo}i1Xn`=P% zcTT|XC}>T@<2`}*n02ECrB?vhr-UH}j8)5vc4hf6TE}(WGp_3{_tI%FX%m>_42E1s zsrE|wH3NNrj{sOvXC8sF+-hFaY-U^zoaO5``;L^ueB5fzoWy(e&MeJyHutGAXCgGq z(g}~xoA#xMfucHRn}#c371>lnIZ6@;ns(CY*x}Gbl67Bd?z2`ImsK)?^PcbIZaK{( z40x_=2~Nh{wM*JdRcGgi%1+}6nBcg)%RU%Z;l0|cwgDv@$%%%%nXM#@H^-hLVgZcn zkm8PvszbQ!aUGRsH}8|u0X@23#)o&I?}FyRcBfj_@x5{MM$u->6ivaSHiq2jY7X%C zWqp-RTX0mVVWcn4q5qPe52=eg?x4U^e%Lu~q(-TD1gcFEEQ_Co%Ai|?gKIZ*Iof$; z%)F4OtP9)4*~WN@{W{M2=%ICoA$*f*Ec#7BlDiPb^Gic7*SPwWbF*uN&4YfXXeRK#wDKVXvMPtUr207pv|_SUem#6+&oh|Hh*CdKX() z=wJ7HUywE+{ME-|XEfMn;vuA5z%(nW`ErtPzu+6Nh2ITaw=PM%5&pxW2 zCE@hyEeC>p#@Xx4#_JUa&tfQO-ERr&!qUk-j_{0{jXXqGoCF~QzwRych7XVoT=pXT zS;cNX4PQR&8sseq5c}(c?fEJcbm+b$UEfJ#!)w>N?!t0rT_lbador#*&K=mrkf$*k z?_^Z9y?EcLZ|P{A1=p?-kWv~s{ZEX8d&sKF%X6c_utvc$hhz%5L~Zk`H$Qx8Pol2$ z7wKw{xGu}3dLO67Ei*Q!*wJNS zgR}>sJ}MPq;HUbH{$(FtWHhevgFTKQxClkz*RAi5u>7-nCkVb<3{1da9i~MQ5$3MK zD)7rHspAK2#51C$X(A*^v#^5P%<)G@_uafT!Eb)TbUNX%`tpg^x%MJ$Ls_TL?SOY4 zj6RpT&Sp_Uypx&;q8EEiUUR8xIrzR)NMtBqsvb>}? zmz!?|jOvDW-&^N1?88;+Ns>&*d``ZwD-2T5r{)gD_{DPG&cX*Z%@&3pvPb)NAC+YvZ+{W!U3C3y5;(J?`D!gFxLSMtEJX!?HCqKG01RY0+abK{rf3 znu^2wxL*8gd?~s2Mw$f(UT~TL*&OAW2xGv9pAT{F8tlN5kxy)Q+O%1azKR)^kqOQ$ zVHDa7+c*#k9n29df}PyA{3P2zcyB&=|2V3|LT4@ddc(i&3+>wPEe)g&7FJt${=ya>#dk-uq`5ljI0re3v)W6-Yf1WU3obaCb^`enDen&ps zBlCsd_EoEhiDP8G#1S4+-%Xxis*~pcq<{{*;o9~!ScxUT?5OiXNy!_&|7*yQe38aP zCpr01ozm*+>aI>HMknSPIzs~ykcJf!yRrc@=sc^wrc?WEC>aoy8%p{cPhL$~JSvLb z;rqi<%-<&6wP5c&6^d0}6wP{|!ses4rAZB-99=@ZFA(<7^d54YPC|)QPJa_%ii=*~ zS^F7;Y4zyx&G}|_)ax2BZqE+cFw@r=LVt02x{Q4dS~gtOnN*clPp~2Pl5?g3E<9%r z>Pcs#*JHoI%|2n?P=Zy%u2uoVk+B58yh|$UOP(t*`J_px!d~>qru0!8hv{q&fVIw) zTw6W1e(jCe32KUMws${zzO%jE>gUgbyG^oae-@*y^He4W%zoQFbT<52HoMx>AO?BF z4Q*SV@pGfqj|Yuc<)4FDN&mJWv)|1deoltJ1|i;7H76DBpI=!~COYy3>^Dtxos)zD ziA{~F6Wa%z@2ZcL{7I$V>_wp0yuQ0VZNHPltNcKiKzQxc;qqZ+yc-ltXCl^TcC}`4 z-2U&7&rgN6YNu+hy-M1^zgqVx26?G($~cX2R(h!1+gVC{zCOpdv!M$#O}$n&c%hXq zi@?OeD(}!GN+yAR)iFfk&X&K|RKI#F8#kvXQLwmIzI#9yJ*5+zas@R+NXY_(c zx&h;Zj!l`^%UNRpPmI17-ToMUU)S?yvjbG8Z@Q0CAFh3iC#RJ|D9`K9>8|4^E@|ut;2+(>uN~q-w0HS{|nB)_5b`g z`Zo=g>C<1+m`54^4nhyq--ey~H#gX#$XNRv<@*tFe?O(#k>Hkwo32w|bGLOd;dju> z?F@yI=Lzk%F=+et3Bl}lkfHnYplRSFYN|73`2v7;)&Kgf zg+9yt`w^gp(Lo^8Km$ayCqus{z+leTo-8l8a&i2kSV&JLqaT^dc5d^VUC5J^AUz}C zF@nbF-eM9y4YVwYPVTO1G0v&Dfbc1~On{UtDwI{IHFGBIYeT-1IsD>2q8nf*dUFTy zZb|IX)Hy;=z${q0A@E0I@nTBr5V?Ld**LH7Z%>#=ZaeDF1LU|rM}Er8 z$22P;^hdl2R;2qZDAU?y)8rRiOs_7`pIWzdL0dM4d^3MV)>3EM4{VZ#xn~ zeV+` z=}Mr7t32VsIs4V3ZH~HRBvS3_MKqO;%Tko)ET6Uj_0CaQ(DGu9+C8~pcSpI@;eA-+ zg^UHKGsnL9vl!~Gd%2$-BI$10JI9)A0S$BTJe#8ZSitmA1?WWi;A5)YR(vs=(2-Pu zT>s5?g9U9JP9Xu*+i2|ABv=$IhGPC9%;%&+)~JZql5cDi;XW(t*`-!CoExy24EcNY zfJt|_ClKdq0wu>Elv7R_P|J=jmOTR|;e|Q^k|1f{8D+COp>hA6GD+n?V?est+E!0y zp07dyy-Kw#N*VJYU`6LCvySPP2oK5(b=$hq;9lwH7LE3v->abYGED(==s$yF+j-Sd8D6d-Hh2b{ai|rX=)80G)kc3v@_+ z9m!eG@917@tEb4Mi^nsy;cT%JIInq8I}w`KfIcKcpmQA8O`!nP$^&i}iw&|~KR@yo zx}NaH-zG%!JtYrU&2RikYP}sU&*>BmpoH%2XQgF^Ys8fEG%T2XH;N4zN zm?Tak%k|i>Gw$Dh`+@cDUXyF*LT`Y;C2dVl7@#e_B|#-w;Pec(c^8mc%XZMA7>%Sv z2yZPsypD@52gC-i(tKVE8~StG%;lD6=t^u$mgtnC`G_r}8QoU=<;$*QY*{s$t5keG zYRgN9)jPZK$Zl`KcUc^~?3z3T)5z^5hm~pX0-)bWw;-Sf#BeMmONMFn{^5wXVmxIW%60c zD8wid>~5(ESJ-t-)^J7Awvo&7%P2fmxe1GR(_tv|23wh0<*iLC*epOOC7i3dx)$}w zlzMSK-Q`}clag@6ilb9LbRBRO@P4#^>;O%^@a#)>KTxX7Ty(l92r6g@XymVe7=PoY zFP+}L6oKos_#k()gR}F*(`Nz*Er-M#SxLfL6(C@91OuvhuI;q?^G zu4l_WZOUmg5f{ASxo>MFO&nPFzo?oaNf6o%;7!;4Nsr%HCCe<9fZSyK%5Ipe9mAq~ zG0K%S+0)ZD!u6vUtmuF5e)s8*PJlGSZt{63&^S9N2LQO1<}xqJ!W2?1%=Q%a5a4#O zkK7XxP|pLPF%W0+;$mtt(2o%OHw`vIo?Ogm4-*BlM9!T}M4~8qz^{osaO6J^qckBl zJbskpBau{f{ah%sA;tFudFFJq4fQVuQfkh{E4HSveo^k!^*dpL z&;=3Kr;w2}XnLr%_Oa{sg2RbcF-``s-omiQbBv~m;YO%P!O>lta{!wN@GRv4OC#%q zJleb&QE6(Je2Sxyzk)9H1`nE_JA-#_7R;EN?|fm?u!S6kZz3c%xHsci8~>!sEIMe9 zAld_`=jOsItu!?D`BZu(G^{op&xOOmhtohPzSfVm*cUEceLG~m;V89dfAn1rpI4cD zJ4rqHNgC%Xr7r6!B#uWQZzJFEvY%_ryX>co{2pah2Yr`MDHmF9rv1vNDE9>4Lbqqe zy>3)0&aSiltVJeZX3|AorLR^t)`L`|sq2n?_6cMLRbPZsWEy@pZ@*4hUx>~0fbf6= zPdS&GNgD#uyv_cR2Te38SznZCeo~nqe;p3O9bH`;`t`{BA^BPSF+(#CzVAS0C)g#5 zOnlw-+dpSgqRA?KjTyESyE1+5?*q$}Fxhq^_JYzp6ghUjS4ZvdP?^oR#mVw5bkKem zgcle~7DjKQjCJdVmaiib!0wtFeBJXJGzS`s=B^V8GCte81BT5&eolWQ^fw5o(hvk= zMQXMV5dx$Xg(T{^Kqo^qGcAz=`3Y9{AjfF0h1!r>_M_ozsTk-ye1xq@>|sfve4d=7 zm3UUpAXGR|(S>Fn1V+X5fZELl;yjg|Y#VN194@B{+vAq{ax}b`<=Z8{pAnjr_13JO z*Xh%*psId^K%B|^>NPQKAg|Vr%q)6DL){Hm22k;s@4H3m&df`%k81aa)+aw-6G~7v z%%9r#n7V5es)ta>L+tE?BTlOfuLJE=LI(a!eV;%s@)+S0uGZZB3(@#wyp+kqR2=#^ z4eq-eS2YjN7`i^!Jn!7EOCKJK z>V%>GrKSPiwFczCby*k|3(U=gOqx8g?)H{2ED)Ycgco1*!Z#5vYcrK@FHg4}+P=1) zUWr=3`XPQFIiH{WqG^3@*~dX*x5T1PPD`IfX5}+0nczHpeGh@{5()CTjqQGSW}>E0 z`t_aOvyPJc4sY+JofG^pVSoGjl%>VgmBo{Cud&+s5l0)vM*A5!z|j`qtLSa&qytU4 zMZvdfeOK5{N_*0Q8$?7_r+xH9IXEV5d_qL@q!KAa9U23@m3IO^LIr5LDQ+of9OOg+ z1RnT!jiQ{d+@?l?SZq+pa^D*x$S07{*#VQYULUp*;LY0x;H+%6bxCDF#SEDP)%AhM z_4_D*EB#cY;~MctG*wX=C@6hov1kFU6M(y(IYmedKwVU|JT_w}Xc?HUDTB0e`$U!! zpoT%a=7LsH<+WL!u?A}m*;S4L@in7b8dT2*C9Qpp7|REenqAIEGaK=;v}5 zS!*}^qe|CDdKnuF&$G#y6>QJ*fj!aTBH=>naVvH(!>tl3eq`*ym}D6?r54EdL9s#4 za1AU&OWL(Fp{SojOKVSFlg;GhDq_Ie|0u-088bFk>BsDF-CrC5SU_7!GGrOI;s96$7)w5|B0^R;Gn%ue_};)2y%aBlOs*BlUzZ}`4ys`_ ztvYr33Y5urV5`&34(ljRj;IWjg>Sxa{O6c+!b}1^029_7<_lU~Bi}n%D+Aqyt^!Qp zx_cIz%P*t`ywGw>Csu*$bMlqV7+|Um#KC^)@pUS`?cqtcXbS4V#;Y>i+7b=B8|Q$B zn-C3zPqMns8K0L>pcQ|Vf?!=7^=`xEU4G7(P-C6>*1JiAJv;f^AV>!;MD^Z~3cC0e z>PfIC+#!qjewcPk!NWt0w;i#1h+s{)-D5rj(57E*bl>gKo7?O8P%{ii!5!fM(p1e` zAx=ML@o$2dVIC1WoYnfcfBV-xcImtA^9_qgO*81{J0Tz5Pb#N%lUJfEo%rQ4?t`ceEqx`} z=Xi7kH)Gg(5ag_;s56b@dh-Vp5M5aG?Bzy))o4CP$vyeFu~!OYBoG2PBpdcAOU-00jf~dI(RbH0z#ea zNpkgF1WJzDI_vI?FePvXYe9MHlX@ukvcv*183=7YX()w=%DD8eGaiT|N@MBd4t9dHrhffezg6wqlfXizRge>W&h0AnTnDG8zZ_W`&!S z{AKZ0FM7zVYCug~T4+WMIbr6ZBi|YjLn+oHskA~V=(P-J@@ov>U5$FXSfiC zrm>_QRO;otW80Y6xW*@ho_YC(=5eRrzo$?^ysz6AOQ**D9IzG}X2x;RQxr}#f~$GC zM+0AF$d2karRuLMz>dIc=YlV1cX?r4+2fG$+{rZnUd8!m)*1>FBddkyNQXJt-Zmzr zXcm_Pk<+@3+0rCv?1?1YH;;%Y%Lk5O!y+=5E7h}%^^EYba|6lL&6c) zu_8=?B|TYj!~<(LSKcdwtg4P1kKk(ZqpoEvXHQ>rS7k>>KM=q+`=K4F$i8&fxdwW& zT}-B(hBj$};sGX*(1q)73VX)AoUaXRxV_|b|NWlO!~g{Sk{+SX$=i~<&2B0_GIbq8rsCl7-8yDXcJ>Mcyzc}^u`#tUaaqvT?w3S&7 zP3vrfR#s9#Z<#)D(fmdS=>C@Xr7`B#n}*VEDHA@|yihbHi{c8V%{fdCEFJP?Z{fC` znj8~euMId`7x%&U_m#S%S_X|!L-3DG997D{JtcY^K~H2=7${#TMq&u>E=EFm7+3OW zTG`0n@})F{S%`Qas-$WFRhpeIBEx+#>&0ZZ=eLuy8yRiD%Js`5UIi#Yt*>J`>j$ya zvHc=V_g!+kU3J?IV8@xnvI$?U@867PcnWS*tOaGq0!F(n;!QR(kzJpA0nEE=cN4p zA5G`6=%}`9(GQ|wa%f_5#$@P`bIyGIW7m1Ny4z(Fv^3`cElEN@ZB7a7Vt4$WE{Jk@ z3g>rD6Nx&~>~_%8(FW%Q`6A$JucV-PkydNuWj}a+nW!uFDTL({7<_1jw8tM)5a-fm z&L(j!mqu=(wk10j_F<3S_|$pgk9P@28S(WEt19jm%|4cjR#^PnIA2v1CB0kuTq&~bs zKas2Oz~OwC%8{xzVj0?j-dqX72x(ezGp5w>f?)DJwMEfSrTsL45--ztB{W%US(^@x zr|pN5w0Mi^k!&0|Fw)7Zk0WtvWUU|VAd~{F=*EQ}kUPF)R9j!Zdku+%L$ALTU|t?s zWBT4dBYyh7eE@}GI6>j8J=~`*y5ElWNpZ!K1OdnXDsOk~_24&`X7geTYSx9fMy1gD zE|{_t{KpxsJ#2JqM!D#>3}noS&&zVf&t-Hk3oaA`U4-} z>SYks!92{)08oKCxB&R#POO8n+pecTMoOeY;lnM0`CkbVoJgg&Gx>qtNq?( zg;*o_y_qlaUwcEN(Kup1mV&A?&a&p7`UX(|5WOc<_sSFmx@tj!lu$1fU0)8N=uJ11 zbD8`B$!4BD7E7@0AI*hnbi#jwY+mN0N^`QSI-j0jL-^0;9Z}VKgFRW3+BK`^&ihEL zo1b0kzi|;aVp;Da5lSVH5pu^^o2gZ5Jmm3ng}4-fn1XHjlJf^kbsb9$5c1 zUdc8}2f+Dyc@IPlr|xCuzUe$14XyQ77`N(6s=dRYuhU@t6x$MkGWyAQjH2Hj{%?nI zkQ<&LcPwi&_0b5f0@5#x&JoJcRbBke}~}6 zYm^eFxp;>TnnFq3z6T!J#&7$u6VSQsU>#YNx{;ChU z_}~8G=WlK;{l5*X!h=kIlly4)-(-2WZa0wsYtl;#uKRgEnwWX{?UaW1PS*J=FZpYy ziQuF=-_b*k@2?5nUp2R0-{ew__w-A%m()5s{Hqc9n|1?}6cDQ~hbz}*gRAQdChu71 zv0u#_Ilq5xPp4yD4+`!9`!oH%%=E9!X`0YT@*YWxa5wipn@GO;H@!z@DfKoa{gUl% z@@fDf6@kcmESKMWv3T|EQLtdz^d&lJ>^Qn^EhP}`nRSte^x)8Y zXp?*0-`COpHT~gD+2A(jji!Q6+d)zGXAD#sHWFj}S-7$-?3Y`yqIZ|BKH_zFn%loG z`Au-6{&{04#g^UWwx;X!8{x zUJRKvYKZTjpVk+U@37U05AVQ_?>>B9NkBDSyF8y8FHKKhHTBG?maU}NWd7}Pe~m;s z%qc2-Ddle`yS>6O4ZWAs`m6K!+uA-|eaYmCVh0%(YcLoYV04;(@1>5LyhH@t7iY~@ zRUcl33$yWoM>@0I_zj`--ZOTs=PQH!g)Zw$`wA7JyGe(11Y(YXVQLk$ripVP$tTMXU| z_JOplV>zK!R<;~gjUDvDSpG)UqY}k?Tni*Wyzo8(P!=rg5vCB5^AQvHz?yL>ON0`wDibJyt zdrzU70<&H-`qOFopG|B}f5hh={Qh155r*(uj#Rt#$f=El( zs{^sc&@q9DB6cBwx6t8x*R&mDx>cW$T4Y%|qof+-?DZGatc9QiSK^PnGYb+QC!a1Z zCi)i?@^Oa5*IHa{wl<6ZCck*;esfVdYJ`Qd*KJlEF@m+@ZN@0@9;xYrlzlhw>-_ij z-XMuoB;$uG!spGX$X4u|4H?<8xD)zFDVCP>YQKpJ(nGCXnXPO$>g#y$X1%`um$)l) z=#m7wU2o7|9K^vr@%&3_=l5Y87RbC*|EBmpn;vjR3 zXe5|s9WRLLjnQt}wjnw*xrgk2`&-cu9;c9;7-Wjye951U#J^9Bd1z+#ye$SMIxbtzbQX$v7%=g&qnD=|yYMlQ1)!Dz{rV!e|(zKZf zkd{lvjK2*skCrcoaUW{Fx{j3C^6{An)x__p<+qp2f^IYf?taq*( zlJl_e&<(o#lyjbE*rF_z)zx~VvgNU!G^zOL@;HcNG+525%#0MLka20c}=De zoqixCw82=2@Tvi0fe~%)OyW83L+IS7}L9~fz6YtXg#$aXhJXjKUNrqP#UwD)Hyy;yPlGm=~REdpxeKM^UB zXP|z2l^jwAw#{=H7ArFtcXQ(5(YNuWRDkcdSYG@#eiSP+)4=Q6-+$uS}2YWn6dO zgkOyJ!h^qasrZr|5dz{SIfB-0L6c7$ktdsU_+dL8M)=jpUX>G^3-pFc+-L~V=K8Da7kq)9BV4J(ktMKwv z1_grS!Y$lS`0_gb*k6LS8MxGzyShmDL?+kF(I-2v1_xGuD^1L4BPnFPiNAvZ*7(Wg z7|$jM${kGu0-p0FaZa-@0nNFZ3B_wh6orTx1=}7>Yw3U`D(E(YHQoT>5ceGr2^$q^ zyir3Z;KI6XD?@1AjCOElMf^eGQPwk*F7~S<*UTzK%FatJGn1L+SD_aDH>+2$OY`+F zI$WZA%W>PCupgCoDrtZfo*Gpv8gS+!%C{7tomUvf8T# zH#Um?ON%Fs-s97v%iQD;oW2QIl8GH1BERdRd!)ZFxGU9vyXtgdOpL%wSyRTr3&acU zjdfdAy=#B1Y@6<|W6#W<>X2Y`+I-rpOFFqg z1*Gw+lE1*?hN->!gr&@<+MY)Dq$~LGieR?%-e2v?NFkduukWX0AHQp?d98Sf*S*;( z2U6tk9eHpU*Xgv{EkvA^R~JZi5EJ>1Y0QogBm`WRzAgt%(ub?u5EYyYRG`Q-WqVxhL)`xY0DQAhn2XmYRAo$uFiX8yFNI*U1ZF@ZW1TpT0hq`-8L@j4f znQ*S}*QDA_EoZhih=#qiG-Ft3ft=r`s%8&U`aM3lu9~NW>HIhuED8NMriBm*N$h=G) z&A=Oa;RlxMS4e)g)fPy3qHhF4g}Zz_>i0J%lGuG>pJ4UA?`@BI)9shO0^}?pP2|lb zzn2{xqVwR||85KIfn?|F2Iwz?_A>EuTONd1>hfFQ{5ndB*uQlP2ALp+kar}zU?bZI zu45>Nt#TWc5jlh542O+h1=7!Pr5AXW+JO!kSMI-ru@d6$cw(4;_)Isr-Af5bi^%Ph z!m_z(M^~F^wx=j>?y@aGmVeV)VUK!cI(@%af<}6U^82g&*XQ>@k2n5(9Wn*7)l^#< zka|REDxNkqZMx#Oc;T9O@7-y(uB&UUI>j!DOLNbmAW`j)K|X|&%1Oc_YzE4dGInM! zJ%6Jk5j(Q02zd2}mG}DuTolEHzqUL7-e_*$GEZYdE=P!KR{~6#0u0xgoDztSo1{G`$so>Yt9ZA4LQ}#(t z#Qvf|Tc7e5pK~hu_|h9wzR*5hsOG;=RlG0JdIfk*(xI1Ih?yG)@J-6muWFGHBy_l; z4mY&R65+kdqG2$$fW?WS23TWjwA2{+dlC7%E)7g<$Er#cPqP>$ot{(TEyf@)Whi2( zfGJMQ_%_t3jmmVNhqmxn%wc(<=%PKPj2Lf%g|U6SZ#OFq-Xt4cWdQ*p{-#o*x zCAO@i$16Jgw2RwMv9tvI=>wL_pn_pgUPGb7OPeit<5t=+uk4*#!@rt=LA@3^WQn&!> zgL3=EW@-xvn{+jK5_AzH;NyzOk?=Ob!r0$aGu=43_^%t-+J8~BYZF2ID{7lMUs8)_ z#NHT@Lg?m`8Tp$zb*0iqa0d2}wPO2}vNukIfOkxW71tQd>rM-MK`K{(AYb{x*-#`` z=~E};r6SI8!n|*H0F5NH$pV-~b_FiM-!)_L{Xh zC_;u~v9#}Y4$vv2*?n+?MpSZV;lq2uF(cUILZGcsRYtaCCZ}20JX~YpZ7iS0Io*RE7xV;)yxMQP z(`vr&f0bhF6XxR`L?C=Yv{wDDAe7yRXujXCxcKkyPSWYXUtX$-?rB*ZoLkSW(HjnN z7yG@oFFg?NiH@`{ZWs}MI~W2)IT&9@8zUQx*O}^~bs+I&i)n&Ppi9qL6(wfM0P=CL_cRqmAjK=U@GwiB$W6B$^!(`hO!-osPvG1ROrxe1M= zVm)=Le=Tf`;nC+@;u~Vm_xrwC3IYO=n@_NrnyX(h{XnnO;p-s{?v5LiWVnW{L=%~U zFuCjq&DSY4cdn@p-#4b0YWmF}48++pb_%zRu-xRid$pOVg-iME1kL?8#D$r9MZQi2 zeXfE2oKu)-JJPw%^fJLqa#;g1WYdv2D3R+&ZHF~Z2FJ%z$x{*Q1vOLy1%PA=xGV5l z4Xc&O7z{Sw0~8BLP~J4-*jIui*3VW@Q9vU)L9hjC&@5-M?6;}=y}QT58@q(mzg5DF zE6lqzS>P)0hoA*L@>z5aKBGYRDOjooo8fv2Fm1J>q%OF`qNF7^BhBh`P)MFK11^vG z8Xx(V#2H6|1S@;_Zu-*#2$FVoO)D7IRn{|9sc)J>hn1*f!!5E%RN7A$OhI=eHRY7v z9zU?oP^27hJgX(;Ya5tsX$J|(s|#Fu;&CP^jk+djjK9vgl$s+XxY$TXsIr70>p&n7 z6|cWsSPt)njVh__-*O3SM5Mx@)w*}+o}IdH!|5^0Ac3vRL8p2A*&}Jv$>MIk(SNT(L_s9` zMu#ATP(5Z#go9Js)y~teh*Lj4hxsp?On&`=A6foR%oA{c*;S3;C*tJGDNIs>DJO`g zUXBDqV3zU@&UyRi1@PL|^ji zv)|I@s7UMi3D5=2HwDgQ-V0p*wVnreYlcDwTHG0f<8)bIhG%AE_DiYo%j~EJBKC)O zw^#XV8yTwJ>;<_tie&h!DFBHdf!RsBCx+Y=GP$L6Bl<^YCl}tA-Nu4B!BO>VFQ}Mf zLY2t3y!@~Qy2tN@trO8p*87@|`|ee&HVbGuA#3?{gr%m00tp(r1v{97$Ogqo(&*23 zdxmQEebFLdFlMkw9$V8Oxz9_Y?a@#o6rKQ=dE9L`k6`;;?wV0bmJp0oFu>nmebE+p zLllh`g1GuHtLR~o-*0|&uy!Mm!C1#6s9H)!-su_33%W5!yz9O*h1$ znKTd)Zu9cDY_=;rXLAvxtg~QqK3RNx3w&OrNzrfxy){yNgE2PYWRpp;h}9bJA^%JL zw44>w*6NHL&e^~!IK`c8rla?xQa# z#zwfk!Dg74ciMnNz;@bWZS3p&+&zASc2HvL-l~T1R(Ffa{bajsG@k-uyeq};KzohV#}7NlPYasPL`;L+c6|SmubKHln<*p=!ph4}+*aP)nzGrc zef&wi1sfc?ZTO^#husxQW7IEgYVKIeg`42A}#B3gzP^46?uXT11io^Tq87T1XS!+3dUr#B+7J)`$24Vt(Yz<}_Li zdt3Y_4FSXWaC;qv2FRbvJTV=0js1#)Nwp>XbMTSpHd!+TcWUy`scUZXvdl_HYOjwQoiP^o9u%i8I#V@@&Ykk!DP-Sb^|_HyV*1>6aJw z?j-&wQ>fO0jyRZoBotGSmALLR*zssFWVg_bKq^vwJoCE-Ra`im58hbefrX}`FJy}X zjr{68B|Ui}yL0WT3GB+(F9?4P!)Fb0A;ahUs7quMX$DE27uAR)@Fp8nFb5;1?C$3V zk4y}Mr#vy1NOK#`h_o_(o7N6}G_2Lx3`^ymb@4*W+}bTUxBCG<;Ui4y*R`|5sM)0vaFRl8@YQ_QP9`xGK@6Z9!b3ZuM|n)R!zUDf|K29?zSl5MRn?g*X3p5M@xj)R>$t_;SKNFL)yhU6juEg=(M)-`def^ zKS<>(^eQQ(k{%ZkG%Zf-Lc?>&Iyx;9{b@T6Tliwd-lqAc8>p@moU*czp%fxA38k|B zUJSO+LYhRsTDgT;L?J(TXA-;YHWnJ7WPlby-ah@8>fuHLszu+g+40km?POO~P9keO z8!hEQ;fgv}jyGja>1P*}VP#@4QO{t15>pT}k})MPvs6xVzASQ&BVgFkB4OrojAw`c`vGBKZ@EG z_fq6GV!ZC}aCq7_JPWf}9sZ&-k>kT|^W@ zMuKVADCg<_-*iSJWkC~lTV^F9@xBis)F`iXF8X+LjO6laFs38T-?82`>gwM=;!u(N zl~E2)K@C)Vy`k;Yi>oawtbwKKAeQK&6Jb|-_j~*nU-pQhO~1d!JjLFO`ro{x*}R`Y zWy!cEJD6Hsh=>5QPe`0rOHbydtFEBh*0&jZpm}baMR1=%H%b{ud0Y%!n-$(veOH&UQh10x?J$8{%FN(5e{;%$fW(6buR|@v8az_2p zi{f2vX0?Ug{!LA@OM3Ez*f$Rcr)AZKy8*4OK{hnn%OKH82&adG)%n?9F9}h2_zKn; zzlec$J(y!Q3IYsLw^sVCck~nVW5MS~aMY?VQ`Uj8vaIl;L(}y6^-bNw2p`lvp&w(@ zy%nqa*O4T4{%XNr&%*tc1Zk;@{OgcH3nQ(6Hg~lXjNVaJz8)w3o~JoE_WsSG!?~so zRs+K0)1CjOSCP5H@Ln%B&TlLnbmmpwU5WjXNz8lzy#*%ywy}rYBPtaYm09D(&$-C^ z`7*t;QTRI1`ogh>GHg;fgnc5rc^8ao4Q|*+Yc>k~-eZSHK&PMt&%jOB7K&w@V#@5q zi;qLDdJt{rBK2}3$4c3G)5`Qo)F#NR7kiA{##?5-&#U@gS4lHRYSMjqBKzyV<6_pl zE<3G(u&{;yEhHurTF-@n79?FYZ>k1bC`N_X#>^IfY*FtUzcL?a5%s>W zR;s|zuE&OgHtb&&-86L^CUDpg*B3h#vVZ|~zV9k5@VcB{WjR2 z_9s5mNS31D+zL8e3F#54qy7a!j`dAMFAIbIv?Y7pdZm)_(uG7Xpu5WVdkqAAC2MvY zo})}dgnh(v-+IYUcvtF?$ z7%@rn5|onNN@>x5*2C$|h|LH$|; z1dED4CO6rKwIO3ZeP<)#37Op!L-;qF70QAK7AqJ6sl-Mq+jBIzleD%BQ2}kH=6jm? z9`;S+U^kk`SDDS8;dI3>vrUB%1~LCFLV$+D(szj~GOaZ;7asDFn$7+grGc>OU`4OC60Ko+_LbH&I+xkOw>zGTIOID6ts0!BlrK5s zo&SK(qtEIt5Ef-N%Fq-9aVPjF{r)?c9U(FjNmFn^LAgKLh zXKrZTjWmw`VSzQ5lEWn&XX)Xlm$cLx?t9p62g3dyZ?=s$?=xx`LyAZ49G8>T&1tA( zu7c3PWZ&1s&ELRVm-bD-lPn_JASku?tY2_h3^#Gt;MSZ{|0~|`2^%c+6}(z+%3vfm zMsa@6u=0M7n8WGU^3uyxG#}|q`B$>D;}U*v*oJ{k?lDPTu3a=HXqy+?`Fmo$eBqqi z6V=_6$3sUrB-MplaZj^ceSfCh%f`rq3OQ@iV(pwmfdI$eEjjL~SW`N!kiXhzcGUW= z`^ul^?)$`hz_i$YM`%Jdol;w94=*=cw?mPzy+$ie87{DhVy}~6_0J>>Z}4oY4E<n;>^u5zFD6ESI{gu3`Y+i7N({>p+ zOEiqwTOuxOLhr&Mc7-B>zYZSpvCcLnB5GxGYQZy9L+R-^?O^0iz4Dkh(roTBDL@o8 z_$K2oe`C1On1rXFeoA7+x1F$!!U3EyJ{dD))u5!@)KXhV;8&vb^H}MmU~|qK%=6MP zM{ScDxP03l-#!tf)0m97Co_PFOosZ1@sv-+c*-O$=#}te8_do(7yX#!y(S7TpTV0_ zs3YHdlJkuzzUDOVFWcH1dbo-1!9p7U*t;?*6BBGk;;fnL@fViFgJDLwh=7fANbS)b zh^*uG-D|8VJfiWMR$ncCx_anVxe@wi4O){Fimzg~1B;7$P&Hyw)9tVNqDAl^xyt^% z5OE8loX-@}4mK3-V@KsVR}>EwrTgGAjh!Xj=X}KjX)4mruo0fy>yw_(&r-uaCcU`3O~g z5C7icP${}Z0M8#8AgMPDF_+_v>iiVBwZmp+x7dw{^m*YP=8~3I95ODBg*RG-@(dpa zIZ@%b5pCKC<_2>Z-gG^^x#t>pt?XVGHJg{Q=`@JngIIoo%-A-=D?`Foem9TMK%$E6 z8=ecoqFvB^R8~bj@Nuf-e$Lv?W3c_3oZE!^z2Y3qQ3*>948h}#@S5WCc|IFi5L4h452m@fYPuKaZ^t~*2?xei~`ggXA^gVkL1kVJR2dAJE9S! zi}ExiJkXc`In%-V1L7Z&X5*w-|rp=_oY z0p06Jw+Z__`q!z?gVTA1?i#tGYP%2nwDfj>Au9X*-o>*pFTgmrDS2?! zOJ>mb%Jerr1_HrRxs5#8iDryUH4s3ZidVeKPDh(hzlKqdX5O{$;3P;mo7(N`&C4t| zB7YsrMQCcF9w0jjvFJ47o#&SzM_9q=zp z@K1TRy##`^2idv!d1H)au?;|f<9oVr&lw-5^^tZP zxwU3Abm2-w`rlrXuIU~YGp>Bk-ebw@INztZ;uAf!eu^((m~hgHk;(eX^++V-9EERc zY=jgMUhsn~+aU&JeUU2ega~NXj-$K%UfM|x7wWGR0b@7HhJ?f*_6n9k(UPpN4{)O^gAyi97w1v`ef@j1_6U7lK|-23k?aQw4s4c zh3hSyb-9neiJffcHxWmLS^(OtV+8avXZvvU5MTSaU92%SUkCfEo)}(yAvJ&}oK5usU)YAWi=T%{TgNWXAWxiyqx7&!276p=5!FN=%R`X;E;L}#m3Xyt|1vp?gB z93m^VWaFx>mJ}JNnF?DER8V3qyAA0_?(z@b&N84x^cG zqO~bo=9BvyET~WL>lgk?R8^+X3TPh|u5fjNx@qs@(V1zt21tl0Sq>9eBoOm8?xh+b z3}8{KUs@FTt$gY}aE}U;-ypDa_NRL_#K?jMK+(e)si2=lr|0Qq9lALro1m4$;q_G6 z)&{y&S4Zc)$_ov;7w<*y7v=Rx~Mz={_P(DDa%2ey! zyD9OxajVHI{ii)?Uw9J;EFi7NlHBxR&aE1^M>TojX^JgtxwXP2o4pTcfjDHS8Z^K3 z1mTe^;EB{gT7T$)6mUSM*@BA-c<2+~cCzLap=fT~EtuFEbwT4AAj`V4Q)!vwR*L@0 zG(SxL_^Vs3=ntz8YyxxJl_D%m=1{v&NLx3CdC(cJrwLl^lnDv=M&Q6&q508wrySX5 z`#P-VwIjjnUqrM77l-0}3IDLFEVpQDNDc z35R?hx5{_cu&0@V9_aYtZKxtm8!jq<$f0A&j$>>}t%#}%$dYUD(vBh4r!NfRw(T`V z@`CwXfj+H3GXxn2-rpep(KGFtu6*F=Ydg?vF{+?jX7(3oTPJ<`uF(8YTe#P)0K*Zu z@qnIt_9IOM+f`kyKSvPgc+{)2L0jc87z+6=T5_*CjpGad$Zw$Y>wa2M(ldIn->(dM ze~uN!FwmhyWf_|x-5?3iE@QtqoqzGTesZ_oFlpL4V>9-sD7e zg+!?p>mKC`!d?N5)N&H9YR#*u78wk~DAu3d#owc&bB?gz!8Fk7=gR$CjBl!Z^W5{g z-@yCiIl0Dkta@ zjSWHozHMxlIGhe~i}5;sBtrv3gHCXbb;YZ-W{|%!UR3^p9^eb(``Fb`3_*tCOSY^g z$4-9<5i~bnY1jINd|4)#onS2r>7Bh#cd3U7Js!bvQAm9b+Sl`1v&}XVO)2wp+PS;V zcl87Tr|ns3;9IFS8%V%N5GNwM{^?prQp3g|m0kbv?;ot8u@o!~Y2-lVM32-MBX}`4&ck!<#rWpeQ|gv?UA7AkpGp8pLo<_nrMwhT=% zVX?F=JUKnPg38p4&zYT~NV_?T8_wR+17?Mwy->23)OXJ`U4o^V4(^qgU$GG#`@fXq z^O%zNqp^w!)l1U!`6Y%y15_~YrQeYaSVTU4ppH>n69(d=gg3jrm|JG-6X?Ggt?gu< zkq{o3{wT92bWUu6x)G1xu$HsS*T!=tenVJX^A_oJx9B`Sbw}pdLwQ@P-D~9hvzjIF zVvlq_^sAS-Ab;46#2^8LD_%<2i2x|CU_w|`)4#sWN@Fn6nC^6vhobygM;+Xyo)i25aRjY+WMx59OWk$3x=Mn)&nw|YQ zmZ%FgoC4y6bdHLMq%iW{B_+**rhNpR{+b0W&CUs7WmoKX4^Z+MFQ&b8 z5D!uMbG)TxX|+qV_vo5`h0f!EH8_{JDYa7<@=%MGObi$MiCV3oCWH%NC4><+Hh&N_ zl&W2@Iy8eu|JQ&K1}E1lpFF{=&Po}Y3{hg}Qd2hc zrHj8;t1m8|6iNdx=$W&d2)dVp^O-NN^{A}G9I<#q zi+b)49;Ux?drzW3Fcf|>wqZ`7uA&*laN#eWOaPP5##VefL9Ux2ptpkk@Tu6Q?UVY{ zO2*hWFjgxO^lB8)IGvqS$fn1}jiPj{)gDcQ_JOg+QDg-|jfd4{e}$@2_>U{TuZJBA zyq)kwv+6iH^ll>NBdmNP%de1HL~ZCDE?uWDZo!B; zEI2C%@0;f3`IAmAibIGe-jcv(R{76PkNwsP9iEaH>RSNaf3IC`k44DgAd|ssAmEsQ zVybxv10r}G0TKZe#l@jhm&3-zJ(KyIj*3d^*w==qqz>@;{$T}-{NfwKj~=XhZ516zY}@>Z(ME5<<`z5)nr?~X%J&LS z^(BQ(rhI?qy}&H0{Iw>3>+s}t6V=Sy>kVxo4dyG4nWjugdVm)!&^7Idh~2R$w#4E7 zwu|u9GP5d9eO!kD5HcS|toHlF>AechoSnD91kHW#8VTVA3ILM6p>0lp%O#KWN_iWW zb%!pNMsLhEH?)5U`Jf4ypt5*w$+GB3av_a{^;&mkXF@7lIOqIiVW^G}achtB2!qsd zn2Pyc>G56|ujB8z`o`DoyKQZo1_0et~_7q1^`hXU02y&-HuNngw3w4}DZU3CL!38_!1A@ZVg%pPUBYo7iBcMcqaj z5N6hXxElzYp)Y+W>sGrbA!@*Y^r`o|0Uj8b@5_1#uOvN0tPb=OZvv?08@%SD`t7-v zfemxET=B92R$oTeZ-!xlJvKFI12iLz>`XWgfGc)&H%JgPv3PDT zch)|+w|ESl{*rAGdlGlx*M-9&{HK#Xn47YM24`(WcX4L#Z`H8@$~ zZT-%(e5EO?L5YNpfis1y!^cP@t{1LBc@->0`HR$k>v}SHZfnl z#*=HeXoGM3IAQMx*U4TR1=;GBz3vMPS&lhj)9zQz%c>O<+`IDImuKxx6WhxUb9v&$ zU$hjx1HUp_xQn&Bw}%6p4P-|UV+A9L%=FYyf{heT55_n4p(7}(*t|noM@VOC>zcs7 zcHpi+!iKgi(`RfIJ;UI_5O?!+kWTPjLA*)+&RVXwMG?_e_Z&L9UQP(urrfGDSe+DW zIm|d>F^gfjD*b9zeJEuZ>es?DPaB6GZqKqCtP&w%SCT&n2R6Mz;Ct$EO%5e5jFII_>r%^ zm>^;C9+Fmk6#>wwBRmEgx4PChY-{*lnyGvNt#e(7Nbav~__khw%KjD|;l=eOPyQM| zX7_bFfo$Ip2)0BQZ9F6W>aT%oHd|>=9`YTOUm4sE?n{SD@$; znWWALrwvX%r1=*42f!0EvI|1zye8i0of7=S=J*@cIW0b6yV!Z~T1AR>yf{AFExKDW z87=TaW@;`W;W;_;l6f7aZ5aH6mIe$gZZ<37XLZ9-8`grd}H|||MN2OOfNC>YV{CZj zW6cySvb?(9_D|1?!0+Qov!^OW{4j#j9QC%GO%^9ffCc`N=RT4-)wnX9xjYxZ$NqvJ zp^kOi1hN~KH#;qS>^1VHtGj3OG3vo8QS#}?rY(95L2*=w8%^Qs^rbj-dC~H3`}Vel zZ}ow}9V2wmfR^Z7Mr0{T&f`(MBegq5q`t8Ot{=)KB-_w-aFORy2cR;uE8ZFdnn+vsC zRDZT}B3`X1yg_ZnVz?DPvlB6#^_3CHoRj`NT($k{_3JDD-dNDaDuD(N=I_X|1Mta9 zN@Geku)DTgdfY3|NMVjH)eOBC=m`x+e*=%K`S#|)NsK`=SQZVRF%qlg_ZM*`QP^X_ z9p`ckh~;NfJV{J7p5OE1uMb+}+`;KDwd6aTUS5eBVPtq>Fa?`&`0%W(#_HTxpmf&3 z&K}%AjKBs@S8#LVZeQ<#6kdMnJrbWQEtuv~03gcD#-VR|37ec1!zewXHEgupxPRC%OD1C@UP4$feHTJFcSG$#a{C%G$&p1AYsxM##AZJ$pc3;9>1T(6Orj49cSyXU+Zs~V z3&=lXtlm%v{^ah?7v|0e#!HzowGrK-{WU$oIA|k(ZPzArU-fSk zwBpn+#q}j=yjapNK8^hwc8L=r1NIvq1l29)@zcrgHk>b92{t?AGEo&eEJBwF!jaK% zIK6Zs7Z%o2gq>Eh2^;4qA&LRygM}x6AH&Ur8V@B92iu2kk7%{P_!TPK?!R?F)p$fz^N0;4{r7R-S3Z$s*V!{&7nFs@-)NYUwJUY z+f#!F-`9pFG6I9AP>Z9c`Iy)|=vh4DaP;IixK|J{>BsD2DA7dZLF4uA9;)FB(MTrS z{!*EZ48ul9&RE6j$Tszwqx&SQM-HlyeEDl6h!QZ>_s?cl-hP4J@kjWM#9)1iE^uU` z2D!6D+;3SDk97e1Z{Z9v~w0ES?kFi_#j%<`Q>xTQwV2Jj1)ewf*&#B8Y4{Bm~SIVnO}_Xi&25vFjj)4TLZQ@%r{{X`&yao zj)5ar?O<7_WtJ|{*$O9##Dc|TZQhq?6ZIzPiq}AmqhtRpKBgwm^{Z`LE+ z2Amm;*gRNVntn(3*T0$6(49L|hKrEIupTQlftc#7C_k3Zs#^Vj13TCKuK#?u|NS%F zj#rNF569d8CRc_K{d8*@;hxS4RU4!^O1$rDw-k-SGb}Qle#b~D7RT4`y)g0(JamQb z^S7?Qvoak%onU58u>!2`8T=f$EcCtLh!S23|SMsY|sqRkC?QzRgeo8BH9zzwYCmgH4SKu`Bqzp z)#<;zxJ^WW#IS0R$|ilZ%}p9_`^o)vlB8nCZ}|~yGlF~U#Oyo=d~4|ZJNf=(fzC$K z#H%HiH=819li&H*_-txGctawtdC-9LCO`?)|%+gX|kDUF)f~E%?dXU<@I(^ok^m|8(FEkG%UatWKp%i( z)fL#eJB_&cKeD;)76YQc`QeM=lQz7PZ3ou()%{u@Y>#1i;LC3+KIzlQpy1fes2EOR z)kAWLi;#v%-h2ZlKuTDbuX7XxI5B0V8FTe!8u&DDpf0rtgC$xwziK2SD#98Tp#k)w zqS)|vBA@fK6-G@46RBdMdwv#4y=i-a;5%XN2V$eRFI~)@mJ7jcgo?wj>=^`3r@xb9^Ct(J-&4qIOO)lt| z`OPrZ?RRQLhXL_#SRj*rqZo5IJG@$KCK5zjm320L6W~@pDp4=ziWS@=U$4I*-|_n9 zm)J{=^sTF7Gp5X+sKV?1I!!EUeUtOE{Ugv0?nO0pTS?D-by-5!$bwKk`=D`6GVzj4 zX$+ZZ%qGfs&QfGaK%TQV(;Uv=b+!jD9XJP@io>Snmr|Ng{}rYnLAxX?{R=u>v$mr2=+O`m3N3}}~bLg0NN3V?E`coE*FjK4l33y}+snyIOr zz=STP#0y7}`O6-UJ*jDL1EE!e2a)MkFP%?ZYaf z+?;i-&IP~GnfB6^%0bTdkeDOAFblPx&F@^qVP>FYEl`&xF*-`))m?D{)b zFimE~qS7H1`mv1rS_&|3I}}P_ct%1?70r!8cpt6bT+ilPLKYu})ITglE&D7wnYPMF zPetb$ieF_Qt%Op5ZhL*0{aWvsQ4epi4ZNpQOXnhvCDT%TdE+%vfW9#$X(IAkRE3_x z9z+dFJhrYCZNvQN2!?;x;9N*0`$4^6pyUrwk{V$i3EJuO8cnpeQx^AfWwDl>=)%7Z z=FPmBzvV+SI#?IMARM`rY)LM<-;jvLvEF$XMHnBaAv8f}uvVQd--&4Ym`#0#$W^Xd zD)RYK2*&$5&JW#q@VJr`N91HLRvbgV=HH38<=*vTCFi)BS%HdD8()io)^|;3439Xnz>1lF>SH4*B$#Sbfn|?yKd!_km+QJ5=!>WfE4UtWjW#f-uPub zsJ)^E8b@sC+S={9e}IMS@S@6pm)i3!;HRIsW}xQcSE#;csxkyr22Dhk$PoV$`H48T z9m?LSU+b$8u07jZCe5ZAKf3(`%!ELlFAb;JF>WQ&tv6h{>z=kNtdRIdIhhSc@O~P+ zY3~%77jN(e=&}<1)$Svq+KA*WK2m#}^jZ&SxzDcq?qEW5nA?Ud3ojxQ<}R=HwucnI z{qQlu&o8_|O@Kw};o^nv{k;!2zB}i!Oq|RY2WEDTP2N07FgQ0gvi*IuW5T!)8^$H6 zBZ_wCT{`E;DJLH9231rdcdm4JGZ}U`C|lQrd$`aun$n-U~iXGDKOW zEOsNJnUn51-|r^JA8dYEIFke=V%g4&bn{>cx7L;@4t0ppE!5gIHqqpI^sQ~eW@Mt# zb=wE_zq5RD){UsZ#Ur`*C=AOd?F&HEg9gdxlF2|(E>{S>YEd5~4f z-wld?7dX`-YNRwVu*2s&cz7t>LInav@2z!qEQq z$vVM;*bCrOt5-E$YoRb=Rn-=T(pg|{F2!)I)GadK3(w?8OC*e}T!NiXG==Xk<2|;q z-!j4&+G_-#`H1kX@awyVJ1**i`1+FQUoZ6RZ?^XDhcsg|sOkY%^FvV*)ZEBQJw-+8 zJIVf;GARG?Mr6^b6^O6Yk{;}Jq+IYWQZ>#NS!#L)4-uk#C-yRQ`UE=#3alY@ZQAdJ zHf;Opn{s8LM9SpdSq?7o0dv*uiIo7o{CLPv|0vg$98sZ8Rx zG0$&k_d1u^3GQJUE*A`(C8pxS9$$@BSv7f#$r>M!VHix@0w-nwke$WOr;Y;m;=txT zx94GxEL@U?u=?y?xb?am13g(@+L~hF=VDPE{P>x=s{%B{GWI!4v60LrD39iIe#H$U z%|}fy+=Y){_r+d&QooeY09y{fW|IS<^YOc9(vgX9PQlhOL8Nh(Wse8`8``12#F6JuVgc+lLKRgFbw( z^Y+1bG?PInpk??7`Tp{jwMVl0xN`ldhZOaUWB@Ke(Z4!N5a-HK63f?}opAKb}^tA&Jk$sO!S@)Rrp02YNtBICZwEG1=*u?)j z&U0Rv7Mof&Yq`^y^y3j{6XSn}PjH;i~I>cV*P!iIWVaiQIQNM zc%rubpctTV*cD0wo3|tdi2{Rb&VRlmS$So%e=k2nMAHAJx^K=%{XuhFoiws2Klaq* z_J*b4nDG*U)DEu`taV1tgOgeB!j2;xXLb{){g<-l*7ynX(3G%n+_W(~;+O zzu|FGGA9!R_0ocgPG(~YJWTCl#{gSep+^z>&H`Y44qHnz#g~Plvo+pBG-eM``ayI# zyS5}?&(#Y@SexNrk-k*Y@G9cwkwPAMh&&<{+aqkmf8QMyO4OhTPI?+!&u9V9G2$Wp zckIbwWnQP`W(42g9hcSwg>*!-DTpAaGyDccf7jlf?x0dK=!LMBjEb_PI8q zTX(k2fSEsNx=wG#8_4#P!jIY8<|8|sr452FqcpIk;2^|UOAMZqj(8#l0H=N_Xh)8v zMxgn8na@z4G|(YT?r1{Bhh_?r{QKd^{=lQe$`zu2RpGG)d*)* zBKFs8Uh?bjEcVefLB?$+XAr+E+Np4@OJ&OyN9J-IGlT7M^$J}#I7N)Z&ptC-ME7jdVl52Z`E%RxzUO$BC00wd9z5w>*2V zH7-r3TO^LTm}6gz_D^oEfpo(fDlRT7>K0+<+wKm%-Xq%>so-fLY?6pM-{@B$!vw2xiCYiRX-BTg$hkwY@?eo7im*=KX&^=Fgg<#m#I zI8dlU%BS&ir??JAu-b@HG7ir}{ceF?`GEvSjJW1S+exGt?`wi^lMpBpXUUpge8k?o zEt5wif$Q2xJ<`!Qf#+mS#OI|sG6dv}CVgC6r>z4I57n#-2HhX<+*W~Xh^rYf7I%Ic z$qEl4gc#Z5)d>!^xp{$jXO!(8{nnsXWqd@t=y&;>p-5kP#OTvDC<}r_IbCn3BUfjC z&1=1>ClP)>kRq6A(6-(voHW4V9O`dIeA z=d9Gb9>z+)fZ3*+_Y=!V^k)y_##kSTS;1K8n^opLfJQt5sdFL{rKG}lwjHNotnF`@ z`Hko}LjD3bsNm%HB)--isw6Y2oXpX9Yem}Pr6@YBH}rVNC6!Y~Pd!$e;L^%;A=l#+ zIuJI=n@hFE3sjj|N4lyhKk26t`*JBmuU5RS!T>2{VeaOmzKqfvzWltY@9c>&HBQt6 zV4N^I!|`Ho-TRkMj_qGt-9DCou%J0K`ds}ep;I;~H}kXFQe6DwlK_*Ae|5UsJ!7bY zr)@O5>k5edn=y6`%<_6~Ux#1K->b)1bYWS?79ic^^}0nY&)%&H#tpt0qb*VFMY)er z3Q25!ZpNg0x+QiYPmtz9A+#^@B&k18ou56Xb|#OEl5HBI%d-YfXQEz8kh1HFGa>F~ z`6Vk5{F0;B#9}C3y1BJk)J_HjfPSpr)Ml$p^Z|{OgD`GA7MJqYXZPa92h0@c)#d6&GJ}aY zUcb(;=HGaWtL3O3Uzdyykcj9b?i^)O7AUe>7u=gYeoe+^#XSu$>|0htDz|QhI%u?;l@ypT-EXJYAy+5eK!R{8j_)o$ zi9Wn`bL9ZXe?#TbA0&Dh=s&IVctC6$KjK?{-uQ~UEB~nRRy-Db7SjLP5J!2JH^GP2 zyf8ezFLoz!7Mn5_+Hs6ENOz1U_7V1mvzKd~)9iEdj`!04y9PzB>)NIY9$!Mc@JR&N z;+s(CPP`<;H-K*3KE5#a)d2KX{@Uy6rY_ymT=ma+@E#KejBLP%JuY3@$pi1Tll>BF zM&M|yg9z&FFjIQ{qh;8AC3wHhpNCBr&b5vXAg#yuv_cC6`T&=_2fIwv-6MY#h&QJ$ z5jCFY^}pURv@uTepc^CY86QT3c%u#8gkl?T>Ud@x(#;3io0L4+OiVop&CTdiajQ(GJL#T37DwolsLQOcE#8ehWs_bgz=hw?(lom|?2$ernI0*%ES; zD(}rx_cOWN)R}6^eL4L$*~&+ol!;0`wjTEQUC1lqVXQ?CDKKZ}@sDXEcp1Rj4V_r| zoVsw?%&lHye07qzQIj8{ANkiL)|E}#G#_>QVkv;jtrWX_ceL6?=!_90h5#=n|G*tJ z#y35Ts~B$dF4+||oy0Zo-haO7gWnL2@0$wQ#EBw$Rj7!FSYIa4gQG?+Mx4Gl&&(7g zPUl&y_))O{pMEg11n?O3s=*BiNCkEbIygn#Pcu8d$OV(X*b~%f2^_UXd?)9+>i(se z!47)%8IP+YH9*$0O*Z+Uy5XO-v#ioqa{W4@yI`gF!}gm19WpBO=j*V|cQx=n00m<5 zmA~^+DL_yzAbw~`B5iAMRYm9fF34AC2OwY&rR7?DlQpa0Y|t|Y$=EmMEp%poTe^w< zy>9q>pjk{VU~@U>d((3RS*PW^ZuV=91lSfz>as>ZxWnM2Z=p=psOVR ztR94I8=hN;HTz9WWB?8W5hdl!!4IWqJrZ0ZJfzg~Pk2ShcVOa`IgfM?u`hEu3pvm! zBL4-t>xZi3=cN6dwLk1|HltZ9>EHhS?Xu%~jiOOK6SqwcENK8Q&Cu#C@1HMH3upx{1a@`4+Pe6VnFS+3YD-QrH znftF%vj(Gm>qqcuKry`F#KL8Ss)zS$Gk2p8eZ;g+p5lf5+#h7RRwVW3l>nHI|YPGgpFKL^&W%D7R{mvzw zkFT6RzEHJgX~Zo|_h!_`-nDj_C;d4dYkj);fU{2UKzsFUt4em?~`&pI-(rifqS8S?m>{Q(M5n%^gWa^q8zslQhX^*hw~y%(OTg1<+ZK03U~}|dir+Bd zDKn+}>rK_B7TpR?z1i@NLJd~vE?g><(~yc2@;C|6;KKLl9wI;}PVhWYV@P=-n!30l zy!y}2ucRiXbVlvT{8%Ty)Wk8d!u^!-yCmVuHQMuc<%dtK6FoI>d!lmva$iiyl^(qf z0nO}km46L8^!o-TFLg&@_LFRPNZJfX0cfRG0QTyUvfO@zpIZ?**dBfn;npo7TDg>I zFXYHr^tiEfk!7@q0g(M*b4` zmf*72dE0|9iCvT`O-+mhfRMo*f6_%?K#eD1kiPk>;c|@6hd7zY>_O9}1N%?wqD&0u zL7L`p4CGu-)>wFnWj5o9Rq!1$y^j21QjHbPklCv%Xv0B;3FG|?ldENd(YE>U;g+B3 z!>@i|rS|5;>t`Cq{#rQaVLW<+``NCJn!waW;|+E@$g&ioc?tW6(>QhS4og(o zzxRilTe{UZZ5;>%h(khG#oD6k8$(r|48H_AXq!B2z@{Srl$nL^H)>o;%_lZq0Gb_N z)Ac^qv969f=1jk{vqw9>kACaTzU-iKwDHYdv7$&cr3m1DPPhXW`$B^lbFN zjY@DxoLxh+rEc0^ry@2~Od9)w8*t^q8{EC-6>hm=us0 z;^Yt#n-CZ4q8OFJ1j2L=4(JLt2VTP42s3oho95uz3Hp}w%PUx}PaGk&w#nJv3&Y|dzm)NijCqs@TS`4Z%9Fzr@K7@Th6E{jomS8;42F)DsXFhj3|Mq z&2#p$N<1v&y20e?PCi~fddPvyD!(r26Gx=KQH)$0(V-H|jKLH%+r_MJ5lMk$ z6RBN=FuFGs)8(&m>Moo;-^m`p$8=wRv)MniutG@*r2-fugHv1$?p1HC>bw;jQ;xcM zx80h!hKyX{(Gcr>`E$ra-A}2u*88GdE=F0tF+F|z=AMnp2~WQFXez1&Ep3=D69Iqm z9IF!G8zbIY{3y%w0ciLUGa4R~w`SL~H(4jWg}mMw7o9Hw2`1q6nLayR-D82#Oj`1_ zK2u_VYl0KX&${J3`8I~_1p86`H7?Vn#5Ub&VnVqz>Zhd4G@F5(5|2aLUt96oPKLsP z#{3o+2^kM5>Lc~!d7<+BYE9Oj2wIj?DWHy!GVJH;yQ=thDYuz3mt^B_S7F;H_rdMv zg;$!-i>iVzQj~?2S=&;__YRi3M1Ecct7%jmV>EFK(H|cm>T&soa|4cv1`R%Z4}tKV zpHO~o7;0beQ?d_3D0G$Q8o}JYUOu!?O zL;5(MBItsw&Ik(4Gmj0Hg5sC-zs-gqZ$rZP4E>}qy3hZkidFht1FjerY>kli_Ldx}9Ow?fifY0rPNQOz~v9z*a9OB`-f1B*nA^kZ6AKOj( zIIH!yKk~65)zM#h6hgTE+k4o0DM-r>)TpU?7xqxu9wX|~q-?G5V(V$_zn7gSO7RUI z6X2;a_jrf9K~2w1#j5X|M)IidwPp>vlP;Zdz_GJ_i&1I$LDKiHfUu?*Z#Y;sTarBX z=}q26v>yGFuJfC|3ODZhTND(SET1x(ulVfslF;_T(H&ctxA$+J1ER}TR0OFX+)!6& zQX_SvQ}ccSav_m=AapQs zUB4a`Ap3ZRPkb8_?mf__IPXB^Ir!?<+RTRU2T37!c)d;bB;$QWH~mD?N7yGY&&{J( zmh^Z5)W!|`_qC?qCCTB?zLM=&tAxBC2-j1`OU%LSv=e>Tu#qgH4y3e|8$ujJh z9!h@Crr!c;fBa3;gRp?qeG;0c`FG9T?!ryZ+^}-`Qb!c98=4TFS770xz@6Fd(WVAi zica6tm^+jq}C*)p=w14n7^?uU|4G%Y(rG zO*;(HfAiF7`D^FXYG zgVYwZ3S8$C`vXj`OSF_d(ZPA>eM$%V+gSup*Fan!e`({~hLg6Pt^GXw4VxWr+#jhi zlziU(knp7!UGJ1q7Nm`TnuC>z`EoZn73od1bVz#MzX^>m^2|~qx~_x-bx)l6pwks( zv_m%}nJPUkOW$_Nf>Yac@NMV4;W+jyDCRhMf6b8hzj^Ak>YwaX7XD7X;xAFs2lcQi zZ{zG`X%P65AXZ054Yb45tkfO!!y`flt@H0be_i<*^ z9i<#-^jwiFa4-0)*ouZ>OL(gwf8wb`k7DSW=^^ik!M{T3NK#-B5eYCY@z)A1-qgf^ z1K)i3Un5@#4!!1xjRlo^5l>|$+Y zmvXg@m;hd6axS=R-8I_D+UVe5W2VszyHtY~9V&(m=lyF;0bZu$w1l*8ZFrpBU*Uw_yIllUa*-2S6b_f7D2lnQTR$dp2= zeE5NzR4VfP`JJB?MWUy#V>SKzzPyb<00*A(VYspiYweBvwz!1jH8p=1d-M#c8XMzp zIB2?iW_E&R|DLO)fOat63!{DwM64k_`@UQ`M$tulxEpgUUlg?T-`b0?5~!BhezfD| zLfskd;kACs;jJJ#@PKGCd-KXUR)yCh7y6z}KK(tF{BKlR4)!s+LJF+DEZ8e}%QRdZ zp3MDVG9Jz!9ZkR;FNav+;I+Fb3Wb3Xs)L{cOy><8)=Q{KvTFTzJ`}BZgF|7hlE%Z1 zD4#}%pLJ{sTfknc@rdbohDwSlZT3^vI9Cgo3R&A{noTH4+{d&Jo3b?gS^O~5cbWR_ z()}pyE<{zyiT~P&X5QR20^DAH`z1I{06C3z+VFV-Q;56V@vqHLr^#{<(ZZn#JjweT zT34wHZIiKObThd3ViP4mY7UIGH!f)hTBGmf24I~PUHCwCS{AJ4h0#6~!G#@Wdj5Ma9k`->fz z;Jb2Lq;Qh5Q!A2qhH9h$NQCPI+m+)Iv`7yRaq(=aB?{}WRwsAmq(%%(lar1|2U{)~ zy*V-c{C-x+p9}6lmaQOdg_G7|a!R++@~GE@_M{7wZQmpQy5#<@*U#0`k4&_gLM0^s zq5os)ytSNVmMHo_1j3sTg!jTjhWFm{^q2Ymn@pf34bW7dIvcvHeCl53iM6Uo!}mRD(738^pWjs;`QCUCZZ-CZa??!*a$rsaumf&A@SMc&}uZ z@dbhC3|?j?3Y;i@*QpBCP(%XQgeup5_xTmZudEyT*?pG0@!K~h-b~#_L!ad-O~2@( zfF={v$w{F+c*9WQyv7e`L(K_Lk^52328N#LBiEU-=19JseQu>|&` z_1p5Fm~jGwp@v5ME&PE<+AROxv&6i)h_%kWfqs7?l`5WlN-! zhv-S9NoVRh6!_}lMd6d%Y$AN65ptREbOxGt@L?9=4bB#UOWbcM?KI;jVq)N!^*4#h z5TUU5{2_`l735K7#rFStk^tkc{yL$+D6=XUaatMmN;a%|#qnyN zh~BoXZ{q9fa* zl{tOs#YG=Ce~{UiSy|!&;k^KQe3@d{oVX)93JZ|S9pKP9dnYZa6^t|MZ>Me1teOis zgUJWP7qao8soSje;L~OX-j0`P1qK9rVcHS!S;I^>n}^;bv_iQzHVU|-LmlBW`UVpO z@TXJ*DftD)`1@Y&^|hryyn9y!2pp$dJ~`BTgZI5>7;WD6<)vcKjGr9q;->6=R5!CjhZ zz!klHg@V~8rU>HEGMF5E;K#Dz@Pt=pW*>@&7#Awj*C57w*tG0T)R+k^UJu!5epVo^ z4?T1KCd_c)MR%pE(7HKR*1sj+=e9PVm&0E=9gDMh=Q8_Y$$WZpnW6`%j(zbae2uZ| z;h>1|HBotH2l^-8sD79FDl=x=>U3W`970Irg&Cvi7Hl zCD~bX#Q`Ma@DI_f^kr-2HwgxIgsQ904)=I5QJ>t1iP$Ewq!3b64Jzeg2O@+wYx`&; z>mHbc9ZDE_Gr{fIylmJVGpuhY*U1p#-!J+>2f1*haiLHk@PCmMKwk5)=f7ojv3NNJ zobr^cvXR(hoYP3+G;Dsh4E3v(C@5m2CunI27;pE;ktMtMy~+&C_lH(l1y71O zck0z6EZH2**j+DqmY3$+>d4Hk26nmX@scdmE@H1EGSZns9+w#~@D|8crE?qT47k;r zUzclq;OTjIGNMsRspZS7W!^|Zf*~6IH-C-k@yqToQ~yESW*!5G0O^;*s5q|>gzE7w z30hhle6qf;meTJ*dR|}o<#rgd+J4d6fVlN}`MsRh{eHD@QLiV&rYe}bcDgOc7u2usPFXP|#0b+XS?$_{n6f4F`N zT*7&kB;eM|RUQp{VR34qMpoP;-G7BEGULDfMw6%L#KuyK0<||K&)%!X-#CNeOxv6! z$39>e$?HBQGNZdMo^G*Y#=jtpfsM_69LK-x`nLgtQj)q|M!SWHK-H;S)fEt=$)gS3 zW;FBJyEL$^vd-mR#sNcKA?8Skyz`qg!>Gt%4YQ(vi8EvW^)B3yD45RH?7A;+8X0WF zi9@H}9JX)l?0y4NIi>iy<*{6$Xe@pG)u40H*RUuVZ4`dVY>uC{iakmAYY0xcAvDv` zy}wm5R7~4OHT%naf`ox(kU-@`TI8=ilj=rI4%MYr=#P#pcPHSn?s)&@7Q@+Z=f!M5 zzn-CKbjRbr#C;MQ#`=&k(#jq)nM7E zVQZN7luW7{lCM8Xk0+@bhGKBY@g0$rOlTrRT^^TS59}vgl7Qo%q}=h(9%Yz2B6FI> zbMm{()(d___keS($ylPvy!@0ZqTA4tf-a@{21e2!wkJscR|kJ>=r(zcG;KS%_IeT1 zS>Q$<;mP)`(0FM`W*|DuR;Y$IDD`2#>J)P@P%o=kq#26&O#sRF)E+J}r+$xq^EDgp z6lcrJ&J&0*ik8`@q(8JNw&*noidrNbk&Gn^Gorsfgl8&@5V6mYXC^%}kXR?}2lL9F%lb4WYK#}>FQ;3SC)qr>9h>!^Ep+U+3}7Gxq$um&dq zNJS}H1(UAEe8C%0@W)W=>o=lbX`Y)rH_fVArgl}EQzNr&z>*s5d?OS;k@%tY{1AM7 zv3rQnVGH?qFM&3Y`CTemBw<_ASLSq@dJ%@&evu$b+h!E7H)XJ{s@exfUHqsbw$C0| zp6pDa#o9Y(&`fKf#U))_9jRk<{TcqN7ft|v*ks8yx?KZ|c=%FqV8`!uayte{rkz0%sDVpj6CN_W!Dgm-Sp#vyjq?W4_=2l$|!{9w#ykt~n z!AC{agT;@Ej2-V{Kc8bWw5w#6qjbMTK({`eLM~wMU6PB8hSn0R$mdPLF4y)6YlLrJ zhZ%TN+I(oPRwC$bDoqXQoh_vJIJ z3$!Xlnwn242RLED^@ztlD+CMpR~w8XsgLMG7j<)@bsrV%1u7`a6dQjDSD9vKKnsc< z0h-XpnWzc{OI>;@VP5pN&@Z_|qHNmYuStWyCW`l3uD~fsXZ3`J&nba0ds{t^nK_D8 zU!ohx1Gwh96L8=;ao8N-O)jxwWJJ3!mE=UDl+eFoGd+8G8SAN!gREg2j?@z(zu4Dv zIDAUM@ z+&L6%G-sEM{cX(DtuWmWNsCWQ+~%(t;~O^eQv@eSldj;`c56z)$gaQQ2JwG?`*2RU z+h^pzejQFgxbyH|w=tUidqf7we~;rW5gFgPIQsC-tTbw*LJzt^o!xovf2|FCLtmUH zY&Lp28hMM9Zp+V7!dEaHoiYqx?TfPGB7&di2K>PVS1^Zv)9{)^w@df#x;H7WWMG=F zqo?E3;Eg`4Ueo( zyaZLPe4vjHgN+q3$)tU;txMNR5}d|#3vALZN(`}2V@B7@!~t( zZTy2WhgE`ua=}OCpf_$!M)w8xg8_!enTum7*$)n7BFX}icoMrxxFQ+lg48ufz+PP$ zo#WNevXN#A5KpkC>igX!+oD)EYhOW9tw8L&Ql%I!$N}%IKvdv>gnoL3d4i`8vmHEe zpUqCmrAsV1ng1%U*v`D1DRcnje*4{l-{EqGGiX`TaP8-E(Tf;lEUa0lOyB2+VVdnbz?cFM(td;X}`*;k8tAsG7Ec292SHp=*5+b*4Jg{aWX zZ{?@@_*?Ggk>q}vI`3no_~t`w$qJaiQp?7-x{pP@%+X4BJtl|T`^#t?Kuor|1P~h^rR4~wsRxOVad3bL7Ge|H5 z`(?qiCzU)txgF+`p{v1SI{qd_6W5>CyCDs_?rFBi9XnXs72rK3nStcv4=V(Ty@2Jb zgARX?$_>9x!T(u3)Ug~&D_jau367AN`6ahC>{e4lv3qn&0FKe;Qpw-J7`vqXf~|jw zJmi!YvUycsg9Mp>NJq)|?j8M0SAtm)3vdKn%XicI9K!DZ)xFOSv&ztBL*axF2 z-$!Hd5Qhjk`tXlUDJ$xM0=ieKFnS{a%Mq7ZpRK{l;#n*ZK~G;;5!UxUlRm`A>jUUH z(43GC$w*!pZ0hyBfU`qxnld$p8`%pp{CgNECEnD7HRbCpd1)$3f0UW^Uy9+Fn3@2| z$<;b`K?t9k#BUtqyQ7JaD)6UDP z+GbW@BYKN#SsAj1mMPCWI2IrI0_)$4P6*B@C*Ai8)z$e+xi=M``bI=EOK2a_OmzDY zC4io}0pxuoeXt(@MJ+(RHr#mxjSj;(EngvwRu3j@7C!Th-iXbYY`uDO{t&o^MKRr# zIy$+|Tqw|vliqeAvCvG3lRpK!1lHML{+M_Tg7S^g#QzLOV%MtiJ+WB~rX_1U!-1_% zd93%+LmHqg(?AimjR;Ph9QCo6Cv}vEuUv0S9O?8n*nsf`1F1UTm(l7mSo;Ck>}6I% zu9L*PnOK(>zrnlcR;cHzuLI&+v+w|bIe9_wyHkI=afiGr{X|1b=Pyoy8M&zcXZ3!5HG!}#kyVLj4%w>4O38kyt`E!T#VC*IqIpZYsy z3Rw2ahcEMrUTEdLQ?4rVx`KU&5JGb8)@y4X)&)|%LAl?AutTBP>p0kx6cF>@^Aih0 zfpA=-UA3$4Pn&ucQc^}-Bg64Hf}s=&o7I1W*MH#YrSiWvoo%2QAvP)X1bpC87}CtL z0LBD)!`9*FYIyqEcpI>D>~j33{dN{;zVP|?5;b`L)Ag^I$CR?W2GV~E9i+cS5bcN& z!+%{}?Z2%#iq)wZ^P@in;eD^ic|Ug)`rFFaDH`q3|jPO*~_ZgRvW{kOQ{{wtsS z{u=tSb%*2-&12E`uYapVOwhB&>pbyB3hC7Z>n0IjShNn%sN-nZ$=^NSmGO6(%H=U_ z8BN*i5xvZwDBQB6ygj0bhdZ)4-Vsv62cFhg97NrKP26RcH-#Fpy)k^ly`G(=a;5Ae z;oRvyM@RaZ|KItwo}K+Q(B`QtY~7AiY5bjbo#B2-pAIO*?Z0YG<2Ce)HzcMhxxdB~ z{T2zo(@Np_-$-qyt1^U8+_G3B=>1zt6xfwAqqWy5Dt5eAI`2dTn3(x=?#MB`Ckarl zZT-F0oO3~aCb|@Cr*v00CrlIf=2+&i1AR7;WoM=L>pftCtQj+t{64=})pG<0WxqmB zYft&^bEK5j=&7fQWt)o3^-cg4Q1@AM3ogE{LXF^T;1jD0RAyE5Co*>@7+PGH;NNSY zmSHr~kpzc5@y1zQ>uIC#l&4*K0xA5k;w0;}4RZ?TuX8e2W&U1*IeQQJ-t^8Zw6Bx) zHtH^k(_H+nh?5BRCK?a$=TLfVM6dQ5z;HM`-dmy)_Ulh1X3_>>Nn3BwsAiElR(uoz0=Oq)mE$o^44PCTxKuWJWj-D!sR zMqa`iA7=VUuGIME$0PolV04JAN?>CI;YMEaZO2e&XSUuG-^!(W0^YL^i>~V?kUzb^ zUqu~YvW>r*o-WiV_AK$t0*V0xAMG<>Qtttfbi))diuz24IaN#Bk}Zp$cUA2LPXjVf zu2jU{UBp`=bTj{v^P8WzCIG)`Nqb%#{}hjF7~jbDT&bFbrC(arV|Cm^^N4ul}qup~l-uFKwhmSbzha*Uux|E@S`T4V*mS{lu0>z`Ji5oJazzx^SSH4v(^s0W_!0%FTfmIIUX4+j|pqqNFvSBr1;m3N=ML1 zo@ShxCOCZcrmMC|_SCP|(vd8_a}8q8uCQ?H_26t2wETj7bzRS9>3rJH$-Y_tjTA`C zI%SXv3F{Z3zkCpaww9VuqalD!A zVjw7KDL-Yjcq?VLhwm#8laVM~*>-U+!Ru0mWjL?UFJJ)41t0tEWmfGlFujwradWVF z(vGrs+gm8MqyO67&k6mC9$(?DCp`Q_EgY)5?ui)VG5F4v)(o zo?+L^YCHLL!z}C$xJz+bP1MyFn3kU}5@vfzqV(5pmAQ7eF_f^52Faa7v2q}Ks_h}Z zV0vZC7f9JBK$1LV^>@Pgq=X1dEm^@bJ#l1GHv&fbuz76ib3P3W{~-Z>thFN-uGEUXT^4{^Vb@GX9W@72*L zl}#A0@V*AVA5XuIhoixrepS7soHqkPL~-Sj!JD5!8BAElWGl&EuM!F>bHKFM+k>=* z_}V>+%BJoQOV~y>QRK~=oz~%aJ13*J}m%3l{r$_bCC7$O2c1VJAI0el^a-lGOM#&TCm|p+PEpwrEBrJ-3DT^Bu^Z&lTkFj4=s}4-IpMV#7p> zh=+UD!oHfjnj1toy*m4dND}fmc3gPa`rGhp_^&TJTE^<~lond%c}WpoAAZvj(>|&S zwLv_GheOXu7w)TR4TW#S9-Wg6jVPLv-=Q+8;PTSoPtFGDVuPCp!=zeSs&)I9>`VK*={4m+h9)2+C>=BdB)!<(BgEAB`E5ruMa=#{XfWt`R4en8 zg4k=fE1Oy&LCe@xX8^%35q?SHN=Em_x022yYq&%*jYC6{`%J9|9pwjga-PWX&!!OM z&!@=kQ4rYI(5QI^tUSnIR`47HXQTvo_yiGR%`NT>KL#dzZKE%W&5q59-_W$I8ry(} zn-K5l=-LgD@0X&N;8|B>aBGklsoU^>C5Rp_I2KihMt#7(QSMkAe{=9P9%E6!i#vdy zlb_s|=9X?BH<0nQo33i3w~*ywuLT{+=zb0~166B;__HvZmcNda7P|&EQK$vPqq5L} zv=OX>5)XytR%*Y0OO0UD-ePBhq|YH98x&RU^wcoAecA@xslW1#wMnQ+oT2!}L7c6j zz5z!hhSTiI1g1$!lyhwU(3(wfqY;PLYv?zLkfv&zW-%p`YR;K#QQ;cA*SIOt_{gYf zo^+T%eT#D$pNXnwXQYU54u0>4WMiV9D=WMWiKH`hZg&F!oZI=!sN!FxBx5Qdi5de5QpSUBEj0Lb3M$K7y9q34PBiIBALSgxBe0lv!xiY2Z19sib3p;Pp zbrSkW81Rs$ececyUZ-WRzZa=0A;$pHh}Gn`u|hM&u+VU~%~!nzC$gAA5Jn$_fv@OG zMZA3sA@%!P*fee8Dya&*g-KlyPI_ML*DGlUpr_yY(PagwQqq10%uS&71uU$s}p}Fbg=tboMnlFhKqge(vt5qWagiiFmqe)IloS?bJyA z_?4%q+oSrJiB2RFdwg*^cPy&2jkqeku;bOwi{SDDTtFiD>r>@!DsJS7uli7^*4hqhz~Q`+Sa2JX<#GZ%JEgz(AwFV5lXKaq ztkOso`n_QfhQ(8mAHDvw?F2M+vr^?e&Sw6J-#K5b%N>)gb!9;+r@s(qVLtiL5)LS@ zE}V}jCwPG-Tf#k$Amo9-cP|y)HI2b2uH{=gyk=DQcf1(;5nu(<^!+|=m!5mhn>&O( zA4D=Zf;L|OMYj*HNjw5T54ASb8+~NSk9f++yNnWPPxj2%hW5zQEVjeAW>c4I+{-73 z{S3H~e)KOMGv=1KKVH#ej9?t`Vdw$rJi&9Tn~!!;cJxN?CwNQtYYyy_I&DuX8Yy-= z-tWGq%EF^3Slv|o4g|D04-U&I_07M@jp-}dY}~`sjsgSzxD!0V z?spVoKgYWLTXG?WQwEgDC`Ec7)^@Jc`kXacz+c#Vr8@Cm5761xQ8embxfE+7aCAdq zTd`V}WzID9zTmQ65v+359iws2P?L*ccD7%zb=i&Mh8JWkWQKey7~4$~e0lmt*oPm; z^0Vs|CY)kd6nc7tYoqzG)ysI-^keqff;=cH=bU)b7_(wK1aC{@OD=6ZTBY`fofaT2 z-aVh^8eSRln#p2V{5H3$0xv4}LBs9{`X+q-8tmiOi@Uq^55asi(8U*(x zf8KwU5}o66ZMC+|uGTJ5Nq#(WST*pra~mCE+pC`!g!{!?Fjv8{6UOpatg`vdu@WPY z4dV)qD>v8aJP4mTQ!5NeUkASy`a&y)f{4`i1#*d`BoUB9>iFyYw}+j;u(IQDxUneu zl{7HD6SHn%pc-fGop_letq3`-j2BY%iMs%>3BZ%*&_!SJRXLAO$*6GjzD?Uj-KTiYK<=4w><-ikkM9Jg`)Wvg!_guDh zS~a1$>rHq`*O|<0gnSOcZ%ogxA?>0vVkyYO&}p5Y=f(^_1gO}C_)N3EBNy_y+ z>6oej)~uGZiEvRxx7SN`(`zrE8sW5sfnHfkdbiMw@w5o&4i`>kK_G`dKYUdy#2;>C z%?&&ow-aP<#7}a@L--!mBMON>5vjHJV{s(!fD0U;eK3@)H%d!lMbRiW>=)Z5P^vm3 z*XOMSwmgQnT9Rev;7FQ*^_5{mYKNREwedBN-)MARsOcEECIR@~h8Uz8b$Rh# z`Y4NS$S3<%B(WCwVY;5ICwSZkRbLP8R{ugt`oMb*WzCR`XE<1$opp{ZSDGv_2OPXAhIim4aXsKPD;1L^#+t*5_3#^!8t-c_eG z>26PyWj)M|H<_|d1Q1@oB!dy-n|jM77<}NPR#?}B(`NXLdr1J=HD28%WgE(jBf6|B zkG<^$G-8#`i=N!JyM2cewJQtgdM{DY`pN1ghdyuMVm0SJ(pukiajD>JRLmZ#Eo-Kf z!c4d?DtBYNkg9Vr55o&IE%bab;t!p`q!SPo8aX`-!v-A35DWYIZ;xHbd$KhBy(*{Q zql>%*Cb!gRbBJ*7f5yQ}9}I$2x^j4 zbZHw1k;T1j&RSafukhFWn>;6e*MEjtRuWOz97AEDdiQb8$8fthu^ywbP|?5 z)`qa`8sDCglCJ1Z77c1~#iwB+=3|Jc*aI&HZ@3MB;Lrrv8sSjV`+AZ*ZqM$* z3Fcr~=9?P=!cz|7sKCefsPGSI;|M<)y`U2B0j%0P>w$DEeg3ACaVyib!9T)QeZ$u5 zV8i(2z4P4GekS+x`Ut#S^=Xc}ZAiuEui}FD;2G)luaV-R<|O9r8Ta+H;SGzot*w)7 zXhpM-&iIkAp-Ix}Ed0^aoj@DLGyOz2WZ^Q3gMqWrG1RUF2ls#!5>3p6P2NTB;u-D0ES(8wMYSnG+lo%#bwM` zG*0kgkKP>fIndj+?taL&j%44*+XQV(w~|FyMKC-h1hA!Enk&gSxVQvIwjR8% zu4VKiu?Gqvn(UpSKH08`t(`w54TFZPJRgsFxn9-&$1cOn2HMJ4PGWEA>pkY${FxlE z`mHcW>$1j6c#mw4y9`MI@3r$02=vQh3^ra(>GzQnwaYcPnTWaQwTjt+Kt(TW-BGdzy?CSLj=(+#=uELoHz?h04TvZCg(QAF*U*S z4{s*-5+3>cNG!;zADSs#4})R^qy}TD%%L%?eVGU!`)nT;Ayy9VJWr-~5E!4!_w4Q^ z9T$BlWWl;~cIVcNcH{V=cMK18kb+K`Pl8e^EyV=IfmX-CzFB3;X4N1z47#N4dQFdo zr=QWfmDeX!JLWn!SS2Q#taQKQgWnRn4I0K|H=?aV8tC4K|EVDKM;bqbOplA!sW47t z;T7YmMpP+3Z;kYx;ZmJ*5NB{TD;H7WHaf*#&{2Y8H$T-fA#>Wg|CpmgdGSON(MhQr z^#a3TCfN^Xv=B*wEt|?NtfkBG1dW7D5t#(Rpy;b7;E6oCLF_KVF!B`BHAFJb0QaOy zfXXy&L}{cOxTTE0$J3afGMp+J+PC*Y5Fpx>=w$Sql$EWjR^%=Huc$&2K*j_00IB&w zwK(WzfD=&!oU5ibUwg@%qUH}e!KJrgaYit(sTwsW6S$;FUKw|5A*Zs;Y<`+})o9dHTJ*>V(VpxZH4c zHi)(yuF|#&rFc=)Ij!;Y(gtN5Sqhr&0DW@^poFME06jp$znZc!g?!7M-&#J8-%P+1 zccJ2~3xI89KGv;LHl%`Sj+LSS5xpeNdsdhGXZ&V?#Er0Imq*NR<@VM>6P#zJ-*N#v z5^Q2-_j%Z4P(}IiP4(4{W+~Cd7of(7J!0(sHb%@(m12T<%~eZ1{T~-?h8t+vfW~Bk zLt_-i!K_jJQ(Tte$-uGrP+o8f0r@;LN4B?37NUirO@ENiED%1A-M@F8_jBq(x<|`j zrCz0#$T_c;!DW*mZD7>u3~N(JPuOQz%>p@|hYaX6_0 zW1RJ@6hn%+M9e5Cpx|v#z#Xh{3zG|{2qJX?dyLkfq36ivUaAeze^v6>L~r~xfrSB* z#!3Ct1P&53YuqH34~&4Q%WcMg``CC*Q^<}uzt9!sjRLcNk?V=6J^pNpf-{7OVP4a`mR0o8oRn>np@qO%$+1V?5@9lD{n8%o zjpn~1(6@twBV=R1EkN-v_njdrx#%OT8sdn{<(NjYYoIakov{Md;b$+!<3iMb=_d7X zlt33yJsAGPV%@ppJ3u=dbHdyZDSGX{MrGKUQ=E; zCI@|@S=f?0)_~dO9iU5w>SdKUerHD#xIB^SM(#_69Oi@RAKZ)uo|}6%_=0x0b3H*h z@PVG6?{cmx3&1A(=W{E-T=7VHh!8KDp!|ETf)#j>GD*kNVPth7r3kBAks95J| zx-+{#tU_krpR1Vv5>9^o!wi%MoSR<{*gq~7{*o7cEyyraPegMtITO?$wk_JId?k9k zGT<2APhKD<@Vb1cAn7aK#suJAceNz*Nx}w_GPUU#nj1dCeWgY8tBLOzdf@N`7EXn@ z9{mFO)liuc3{T?I&+?9``T2I3DUr>eNlRjVdH_CcOp@kzWKZC)XfH5U*R2uoFnu!A zjOP#O-rvkUgspxWEaD`1HOH8oz1jBKzgK!*x1{$&@&&sQ47MHFUYP*QwngI?%Ww){ zZrohA750k!sx_3Um`XsPunG85uP*ej+rpv7iMQ}~f8Zj$Zq2zD>U9C4o?HtgU0`=$ zgV8DOkR_Qaw)#c#l)Nogv2pF$N~qE%Db(RIsZp}8`y7)Or7GR5UjL-guBpi%V?E)HKQlmJ4d=ovPEj9_VMe}xUX0KmhWHH z7@pj0M^}Os2lrPH%qeekJ~JwtS!`sBJdw4P(ZneHHbwo=XMLfnEmVEQZTVj*L_Fm^ z0f$qhnWabKg#_!%>Q<(UsOz2c*dR8Xa>)71aCFwcUd?-I{3-kq2nJyoqJ*AlNW@XwC-yw?5{uW9P%a zjzV<$(V8<@YWTyQ^Xn&!*yji-Wj5gjXJj)at!)Xn3#l9fWf_9HUJHZv6@8l*A2n|0 zM}Z9YPWl2yNa4O*3O9cNy6#K*VCrV9zdT)<%g@$!9zXgSm-oPk_n8wi0Bj)qwRo*k zT=ZWZu{f*rK%=VWaP?SQ-9&G>qOpIlozxkSzFr%v(rEw}R?N2_tT9)$!@1)Iu*)uQ z-ZetQ9f@x~=o0AkTP-NMf5$CyQD+5|la2_ZU{PKF+1rmjNs09gxpL?V{T}{u9jB#u z<*!mwzo|L=EeGbp2R@*?*?YZfXZ7BNjP&u^6)g!JmZr{lu@@l?d3S0w;jUMWdi^v; zBGh}&?DC6XijK25B&cDsa^Yk1gGpKxJzr=S4CuCQ&$>KT?mmA8GBqu zd&TM{NW#q9*kh3CucSn;^35MR(tJk2hUNwGU&0;0JLm`>u`zRSBv zii&$o(V%gIgtN)|36&r`?*~zR0p9t<6193blz!OeXEGiU-uLYU{RcagT#AwgAPC^2 z|Bg`@zjZpC;l$^st#Qct#1O>rD`p_iGnqM@- zoKsna#(;>|zbhcFIu^}7LB6$?k?rSR;NtXB#265C_ue)CMbq=0wn>iT-PoYZHWV=w;#y12vC{mJuES&>k&^E3eX*=>! z6PICV>Q+$4SB=2UtUMZDY;;v>>!;pLO0m!Vxc!8FkkaW|kK zDXvdy*~BQhTPuDSK@0iR3?~VU8cRG8FMm8Js2ZDMc2&tuFWJz{iXb1hir!)HZhb*( zvS$#_S1j(p>19W5j1hGFYv|DKHrZC@@BI=vV+NJy=n(rT!saPTSpk?t`M0R1srw|2 zcW~Y!t(pK}P5)_QZ=x?(?>i}w zp{{~58D9z;4`rQOVWqUr0WhsOrG_opKC@AEP#Jg6@>;}-+~d;^sV)a#GmE?y>-247y^_z zc}g0$xo_{))uKDD6W37S-ps%D6>2GvdQ1lEfT*K`=e}-^{!~}J>Ri@Eo!5UGSrSsW zax2?`=T96@1>6sTJ}}Cv@ozXpKibr2BaPE%BA`wCVUzEjfxz>*aMmVu2&n5;!Q-wwomGa;H|d1POjJEDbnU z!jy6)hYC)a%>syowk2z_0PjzCTZ4>4*$Ntyn&1I13M+BXZQ9(4=ZYmp&^fk@MfF)g zzl}F$)d0YVZDyuUgT?FBXWjNmzq8Yg<+y9tPg=~^{_tHR*JJSw9S)3o4rTm zp%%X&0Z2GnyiJ|adb0$+_*?LQ7Uz5*CV5Pc^wYWkvY}WPZKuGrE%e((_D-{^E`L?a;9j~$*y5uc$#yai;3r-J z<2-!7mqg$35f2lDO<;W@)S)I0Pk&wsdgb`M+4p;AO!JU{`)U2_kbOvd+iGq&o?L)o zWM$T+H8e>lV_bNcl1~is(Ecj^llys^Z=#KY%l_VD->bix+hsg@^`yRUv&o{nCsiW+ zKbp>CIZ|ec!Vkov@n#8)_ulRBjraEHFPcB@Y!soEMo?s(JYP|jSv~CVm?^vrmIm=g zX$sWBc4C(=KigvK?yq{%Afn$z=9$Os{%Rq0%1o?e6;TPTv);xZl=0iJWtWuhtO{*D zQw2CgXWofVETSNl_IdPCxh2gSEF&`yhx>0zUJ5|GDeZft-r)ehGW4)R4fw9XBSg^R z87h-s+@JNj2#CjLLg05;^p102%!jmSv;Je+flt<;c6Bj6lIIFvSnpBGienVWq7+$- zQq2ulkM1s>xP8F4IiJ!mxGs0_RR?8~{1}){7_!;mkao^G?yEzRkg2NRsLh8@fGkK&Z+AJk4Bm8DH{IZa{w}wyhC5 zYRJey);~mH4)OECk=X!OM#+Y6Y%nc^izr08F#T)ZX}4`}JSp{ssG(wj`J;Vl1Vyo> zS-$S27I@5$;!9Dv2O}(jbeiF6#*^+ySs%`a3F?P+0#FR7SPUYdX92 zK|kN=%VK?(t_`And*X8QJ3P~Lk!Na|y?*>_XRL1^h{5j!^-veH$%&%fa5gLWIk+|L zMF+Zs=Vdpz{0vJ)GN#2I1k@M&XfnqSIp3^a(s9fe*5jDI=g z`Oe2nq+yKTK&&{s2w#-;Vh{ROvX}s+{TqbsQtXdGtpp8kZM^xKLoi(*8u-HPQ}9cS zr~bXF&NQZI9`&KeWle8S>edPQdr8jnzxQuwZ^KaTTmz)x=R<>IP{Cc6+s>Lwj+uEU z8+O0LeA?B}SH8^eoh^`1bfGZ1uBoP%Ym2x0syUGjyY)KF~i@9UYkZBUd5GehiybroRb?R;APxtW8B+F}w4##Jv&b&z2v>VQW%!THj(PbDaELGOPX2RO3-p1$<% z4{ZIk9%;_9&l`f}iwsaYGQgMgO3InXXPD#~Lq68Ya?Jd#q~*uXVUJ)7s_J3`FB1@8|aR;qzl^xuANDAIxIV>9J|gT-PxCd8)5?A^P}I zd=eLhUj(g7L=%cODwYuII+GO8m6nGz$4TY57r_Tf17})TsWdG^$^nk?ormEi3|#g^ zmIw~(=?o`m(`ELlfV3PypxA&Qoum=7wY7Yz{=GqdutKpbKw;Jox&z|&iEcY#CU}vH z*^-voNxzEm5t6fP*g0rk9@butyF-7n%D#hn>wKQUEDjAz7c4OB2e0>oiKBV;6F2|t zMzPgqv|V-1q6K6yxz!e*!;~wsBZ=|QSW*0<)W`D+k}Y)IBDv)5=fJLdu{#7ViFf+M zQ3jQkYMPMLOm$1}n&k=ig|+u0H#z-U!35ZoElY+Odj8g5N$Sb&O_h~L%mR6l`c~E3 zcEI~_$f#ZU{Z*Rgq)EArc*o?On)B2O`NwGaO$hU*q34yL3?-w^Op+k-_@c7kv;#E5 zKjlGW0(IaO+$Lp*Is>`}Q=0e1D&t<1Q$s+$BP_ouQ43_laUppYe#$we^~D7+3rIrFOoXj5-#_LF2uMbkfjLievh8XZHLzXu_3NOm6ZnH8z2757 z!%~`w^e|2xxiG<=TWh_ai@j`&Qpu#CY@%^5NY_*E-K)d6nDzSbSDB7Le@98eH&y)d z*0#4PEmMkvkei8scU6@cgL9yziu#fGkFb9=711pyXWf-{$NdiU|#2%pQTZX;eBIf z3wby2K~Vd#*>8seT>(qnl~Uwyw$ z>CbZB+3L++;uV2$b>6&>#L625uiik>V7UFD z{Jm|Bb54Cl4oaDr+g{89nC5*b{fysyeVG~d5oWX>T!pWqGh@YK?4$LPby`Q%Y=5e3^iDnZPpCrFv9a= zHb2L4^qnX58QGjH2`Sm~_bT+_pq*pUCu<sim$yxJc7ePc!vHWyHy1(Q-n+6gu)(^({K>ldmV_Z>wyDelqj;*~aQMiMtd$i`O z7&%yCvnxl(_VTZAyZYX(^7S^`<_yj54{iII3mro{mw8-L;5tp_@Voa4^?{|89i9`0)ZSNMp2pDb1uGOp9RRxQsN|#KiukKs^+(3rm>vxVPpppEm=mzZ}Ts<(fQDcBsUHDE- z6NlQuao?Z7CCHaU`?)K0e-ubGBXp*c*Vt-eW1lyAo(~{R4D%^2BAWNee+Aw)D21t) zupM5ZLt@G3L`|xjG9DTuv73J3GIfWIE(P|2OBZM3>tp$$AEf?OG-_C-vrfKrCT@W( z0yoK<8ezj@64RW8O5t#1n^qZ7C5tyVMv<&FrtdVaHe^yPhKbIzTIIpz&CWQZPhW9F zk4(YzLNgrrGHqACtL1#y7z&m$Ne^z05u(@$B7LNc7yR{R1L(MbWnvHpiP|sa6RlWo zH7(O4irM%*EQVsT{dj&HvWB|Skp!90?7*J2ZKWe0AI>{UcQDLOdI))!6c zSiU--D|UdVzNA*jruzCM0pa(WbYY3^nsm7Z!%--iR7$2f39+c-2_<0qr2r{pJZ+{KwZyqDZ%x?urY`KcM4XU_4{DNlT-XtIn=exO* zKl$hBExO#UzkG=e*1URuS>u}!nI8W9`sLrn!>n|#SLo8HU6Moc;tl`KIv20|Cur9I zyeC=QLxt$1+q~vx?LG%rItvFZNh3!~>>$|#<7H;|&j$1f9!zgMa3FOnQ5+h78_q{Q zb|ERmL8uuv#~-`w5mT#Z;?K9Iu}McZhejuIMCbhbjJ(G5ar?MO;0*Ex<;pfO6gUXk|cq6>eX~e<+vdRXnCe<2-8r% zF94;4!O#^YRP$lw2NWcEKwQW-=MDDKQ;ZOVUN7JpP$>iUBOuVIN^vIFbnAuAvaEFn z7e>KQ;HQ_SEWb&ZQnHm`_wv+ZQ_0Mo3K15kJUkUgu_i*o^)_!f7MmJ}RxQHq+U^W$ z?WFzmZ{mzeXiV4|6oX zTpQjWI@sLee_gD#bUB2rr(auYI!nf*HznY#oL>PZQLL(rj#R!tLrw^_GSP-?N+smfOzXo3%kM#Lp3r$s9*k?3?YH) zhkT^MycYZ=ZjscxkqbsOFH=qWe(q*KmK%qRK$sP+S*syvhgshWb=_DE%eCr< zW*6fKT(IN%;TOLI^$eS>PjPZGtS(RPg{7%^FAXfAuW~Qq#l0eQSvT%s3@sO7+7?iR z)T(c?`;zpMzv&i2WKQVsnR1}{_x$@|>Uey7~3@&470X@#!7T zaq3^l@;W!aF(}6}&{^@$BFLf-?X^S~Xg~r1p|ZNo()WtkJiDBluI@Z;t5&R#Os0Xg zN$-T?zfIq>fVIj>D>IN$tP>OE;-)*jw$Bb1HkCAi{|x_L%;GSIB1V7b?w58L z_g9^lgPN1;v6!g9%BEs*4=}uL<>Gm;EoQxLX^m4OqvpjV=|ZNGX;J*AZvRTXSd2lm z(RF#k%M*(h;Qc%2%J?#|wD1CjS+n$TDcjQR6t;-M2GQx)zJ@TiEh#@rXhb}?t{5i+ zJD#T*+qNzGA8_z|xJm1=ZLs7H42~`~q*^hhe+of+(dzt#)c{qJly(oFwUg?MwG!3$RLa*Kz%a=O(ST{V(KDY4cq985PNnH<}k z-irN>CKOSSbo6aA==3+F8zgQ72KQ;ZUR+sx(_53pVnpF1VfNAkj!JEglV?DgFYZ7z zh{2ewTJ{1}juC&&DB#JD_^d@oI;j8c@|a)*8DW6><`tU~;Kn!oLc4GpPg`O}C z&HL*d$Z17z4V0o*s=yus({IXU=~@*v2}{BSI$sH{XXa|>6{7A~x($3zpaDrAc}N{e z$Wy81ICk%8aKO++=0ASsyOTo1keBI3y4F9mAVsH;^&nth`9AH3V>C*G;1Lj7;b*Dp z40=tNxPW(0ZbmRQW%#!as0M4#T_|Mi z_4Mot8=vCnSG(^VZxB?_45r9Yd}Pl))60n-VOnFnX+Da&Tojq>Oq6|AYR|{Y6&$gT zFHqx598e!ktx~?XbxtK#DrPK3dKM#85A$YXO~`e{&}_@9!>4(?p@OSPE?k44_@N5Q@=D z{Kg#Lm)Z>&ywX%Y)-1>+9n0ZPsuL(*VtB0%!rfRRW!SgbhSzD9NHK@EQ26nykPX`w zT`~R0aW;e3A-ckK5c`DZOLzs4>XqzkOuTbNh{BXPRF{#g!T7V|3E|MnrtqOioe1{ahm zT|UjHX7zCigM#+2>0yVo()cHlYwgE951_|XwHs&Ziu49RaMhp}l&W6vvT(5-)kCuIJ$S|JH@}_iJCm9$VFH=x-yNsIO;^{hPQAkV39cf`x{hsbkCu7%01YGT%(x4)stg_;_OdQjcg3h4)AfkZMyd*bquu z)hdyuKvp>xM|`i4rOVv#P3HCXJ~&h04mAnYkbQuK1*b;Y>y1>0S}{TC$S~ZV43IXv z+n3!Sh5~D~mP}*E-md!wF5<;5?)BnBAHEOC;b?w{T2!<$X!&{ruVEB3W)t{#Kn1+& zAZq);9>bJg9B^fclh`L4-prS8gvyY0DN&@AX+)0p@CSAGvp%j@qT-j99fGv_a{nTF(=tt)X>&+Oq$_&!`HYBP7A z(yfuq_e!!J$$4i3V+n$K?y_lR!e754AYGiFP0HYzH!FjIew<~^Q3);8aPqk+l;qS1 zXbC*O1avcle7%95zf6_;`+}o;yg+)J911}8x(`;MqZiPk)7-;@Hpa}>`?Ph*^LE&{n>k^o z&}g979H{!JarrcYZ82AhEFT-A70mha->2Bq7*e_4R|5t;7$eR(iL}Vi4;u8I-<9o9Hv`zjw7c2{yIag*ExRG zwuQ_BZmBMR2fyW7?G{-HJ#BwaJxCvGD>%2Matz{cBa9(jRa(GC`OKTH>e)_imqxA) ziWrzIUqnB-9DzL+z3-~~$2z)6^q+SCQ!U@v`rfur-U zp2W&&Fq;`+l3s|xhol$ty!@MlTu&B!VMoYaqvu~r3&uds*lPcfm)-{VH{+8**i?`# zWcp$EetLajl$HUL-S!Bw;X2oRk+rFnp{TU{KD9`OAJ%NbcVx;$0P`!kdR>BAvs*7o z%7F8Hf&ZWv!c%K<&_MEWcEoG{>T^085#66&IktX{u-K`W;IA-}e(c4Ah*x(|X7gqT zUV+yyr~LdIGnj^!arPtZ$p;Nsv;3SCgpsyZ~TkmF?eiljvh-LjbOSB{ngcR0- zi>MGv%1nJxKH{TJLI1iZU7>DE6qX`9b!_7$?5^-chK8bIu_wZJ%E%V?+c&Cv@J(!< z_qPaYk>ZELaVQL5&63f9%OSo{e670sYriBh*-6qaq@`fbOf6nht4{3(4sdiuvF~R^ zOflP2AARXiP7lH#hpY;HibTe9)b6#@VT62|kEdtVnmSmP+ z+*waFTuW_y@ZCr3$NkMVr9}ms%AWq>KLuhmcul~d`q_h0d#5@rJ;<9AF#!di*KIkZ zgMn{61nAI;ZP;oP-3W$Vk(zr!l!D$EGy|`r=abWgGbtmc&}Ch9P`cy1f~f#hR6U$0 z4K=cbb$L_m;E6Pv;;;O%UZeId1+|doyO}ra4HTl$hYLW^AnNtQ8@>7MMpJjw z^rP9$%Y1)lOB%p1nwLWKO%+*Dq=xhrv6coxA&aSmjD_w4L!3$iQgG;=CaGuW62iC2 zY4?21uQ0;;Uzyayvf>vgoi9tgV0L`HdBFlg;MM@WkK}a(6bnfHkUPje7H;%orRR3a zxJ%RBO!0oN%*y}~_rvBxbiz2|B3ugO>E82_e9OG_SG}V?&IvRxbruerJ+as8(fxe3v#&(EBmr{9e8uQ(iK9Yn&b{$$gyRg*bwcwz$BqJ@Q& zms-X6y9pDIkHDBm+vh9Pi9x~=6FWP2TbG!L=?K#rhWmFg@$_^YKuKJ zwGw8rDL#EmhKSyvwa`lu%+)mlJ9g@56$1fVUhwBS^SO~6qj3a{Hyv#kkk2E&6?Gh9z39tTJ zOz#~eQwK(tZkqA4flsGUbE4o8%b&rAnG4VR{gatPOr_~@_qp@qN)Xt`V?~vN9RH`+ zPN_Fr-D_Hocd&Vp4V7hTKQR+T>0k1OpPvLw(u9QGUui#oob5#iBL?F_b54F?qD7) zxS7tCKF78^axL}JxLYAX1Jzsy#5@1*gli;JC|p-+(-)mV(qCzsgVkbBkK!c22WQd+K(DQ z1zv)cTXxR8zNM~dOF2YqH0KnBB8l$^J|`2LEHtke7mB*T)99}z%2O>R(I=TANNekH z*IE?Qjlhg0q}O;Dd;03D2$FX97V)=;l?&3zHqZpUX)sa_+pb3hZ4i|_Hj75E4_aPR zqxex(PhIDByV(n`Wm6jI!QW(6F_U0Wfoi}cb|0gL&8vCP#g^-A2ESZgYL?Ck zYy0HDD7uJQY3$8L6MKhTtDKgts@z0JYzy?sbuQ~G;-6ozpl^(=%aQBi&V+Od;AK$F z>Pm3MKBS5Sf1n#fK7>pu&zaBFjLN1*=gN_s)RLL1BDMh7k-6C^z@CiSHl|h?4Nt!%XW@Db`-Pd$0_5g_&Ua zQiiq@8bn-kSo7K-mU-L0-MY6F60B_gb~3*JaU=K=O#1_HMA;8!6d%zEkYDjlU2(S! zFje-9BSjK_+Cz1oAGs}s-iODAEe`1>1lM(Z3NJ+2!7Kl_1y-IWA(c~|h6!PqgXMWX z`^o#37SW5*c7-cobyjTJ^w<5Gf4+Yr!~fU76(0ZkS%XCV_r1Rw(CH`n>-=tMf4Ps4 z-+%;TzdvEi^jQ60b7_ki!wnJh0D3ceMOR_6%s;;u0SNyDGg{q?&Xafb-n&l?hhKlu z_SbZc)x7uhK92HwCx{%_%U_G4iwIgW-?v9i@A~T+x3!$Gv9rj$+x5ob(`-`BGTFzF zoNR5U`Eb{(fr=@qG;1oLkdlwDw@|D28bn@UTY@|X!Gr@pk{(r&uIs&m`{R7!h8(3a zP0ZdSy^z|nq!icAnxrhEY%eGHq=u@YKDxet!FIL1Uz?J}7UnV>n+l@5l0J(?g%jUzhskZp3p3gB1tt0(eiMvwZ%x1do65UDDCIi|HGc z#LNTTJpW|aJnd%L{}nZ*Cj)B6u_BIBa2d&8P}yd27EZts#BJ%k@Ob)7?w6w8L<+#p=U`4vfDt=bF4UeddlHQ1R> zyI&jIc=p^b3bqu4N@ZskmuK!199fWh`X)rZr&0apw$-i9oqHmGyJB5s|vv@Pi2urczFaxCnub6GfJx zieC#@Ht8AXfN-T-X1QmX(BJu1A$gI)D@1FK)E@jPot~p5S?(iNbgO>9TR;J3{FerG zvZYBmvv7>qW@i6&lEpF}>xhOWLL< z?hS;7bZS5}nk@VJqQ90Ud_NVk{82#T6)9Mw1s^i+Spm}q#qej=XmBo_gC+Ie|8iwi zp)}A4OZgknG4W9i?9pvww<(0a{%e*|%xa^E;+0`$2)_xsUQ_@-XpMpW28ePrE~)^M z$?_AMA3}P=oyb~JplmqzJv7Swb=wrH1>SV__%sn<*#Lw4#BV|fSFCYClQZ~L=SU25 z+JC4_i^}P|M{BGwZ!1l1+&TBdoXj}wu{2bg=R*~a>Q!sDN+V)T&=xu9L zCC+VIl=_AHtCi$ru5(i0l?{g@{$fx0@ku)IYGQelWG$rQ; z{s}c9kMwDvZ)A&DB+Xd7xUaWK4FaT?^W-KHnpBJAU8_yrU(H|Vhu9y|4)U2lSpG%g ztXW9db{>t;hld52WurKN`_zlK2@O;$0+SY2Y{yg8Xr>nvKKu+qM}U6^g9c@p7?HNG zW4}ivngc_pU@dp+eIGzC$qqQ7wl|2)8%X9~dz)D%F7U$d9>-lxduI8UJ(HRDUHI$E zXWn~oBjNh~5teQz2uGuo+CjhC8E9Pds`?_g zRn>sf0zGJ6s3{zvr6+IEwgjXn-mMh-k+^EPc`fHD>N$hzeUS!)L3jPH@NKEyHaRq< zhf+vFKoi|zQ^Y{N5aQ;;ns4YuT7^P90e=uR`PzK=`FfbgfZor}oz*7)E^Ust&3up96BQThQg!hF= zrZpqZp-gDsS#2i=fQ_^7jGM@1FFa&hrsmuqNB_ z5ZZ#f6Br*5#>idYj(!oG%7pj~$KA^*)!lwGMe%VLF?IBv$)24z_OPN5^kN;oe$TZ%LJ@<$+)6|!^BPGL5GU$it%&s^b_m4dPPUh; zuDL3PTWSR+bV1VK;P=^#LVZaCF4z+{Wn(>aEc)2!+pRvDR!~`)eApc%gP=w}zvlAO zZGNo}xC7}un4^T4Y;o$DF6#0#h4a!K5CV|+yfY00pv2JiYm#P(KYPG{*bRv@+al;E zj#0J2`fgEN67YM6`f#X{RB%X6k}iH=p4@XX0}J{z*W%0KP@{?a(lungNw8a`KJ$71 z8i#7pZl+R|-PS%$h?^}dHKD-vFk?(Y%{N(BQ{80$ei5Mc0n z#lvXT<8&eFaa4IQMI2&n7qisw{mZZ^!Z-|@w`v$)N*KqL$>X)mQyc`0aJhgjrW`OE zK|*M|)4wWW^-VV8^lA{2TmNj{d;tprZf%QAnXer| zC+(x)-7n-4HG`+YC9EkijV}W&D^X*c&TmBN<>t1Tdn+O`7}b^t;BQXp)#++?w>vV* zW$u1>6u*P`nzWI_1OvTzUUx)&==H%wvp~Ul^&7 zUoxfj{gK54b1cEHMY2*A8od)gq3#4N6L{QiUb^?H3jyhg);5|PU;IT*rq%bCaD({_ zYuVO_&?IVERYsexhcP1JPLr-=r>Fk3)QDVQSlyn%}r_=jI&@S}>C0e30pHFIfKK5G^j8 zJur_OG#B!rx#?d2iZS^2AU@T{slMO3i|Cx~`wIJeAKxMcq>uF9b`wXC?DWBkjL68C zDnVW!N7hd@<^Qc%{@&aRhSG0V<5W(W6CiDSe!i;=1SA{*{dN9w+Kn=YK2kLK!S=b)Y5a#98`GjL8=d*;2GZoFG2x}y9u?BK3b<;Q z1h2|%+>fC&40j^ESH;jG99sr{pLpEoWep&VV)GbdUE`G(^IbUNrp)dW zjDHPh>WW)f>a*GJSjD!tM$4;9w(ZdW`p7uo-_p^)`iHs!f;Zl8r_@t$oAI}U{DVeL=acI-O#rJE(w|i#BXTToIeYOfCj_$7yjBH z?Q@4s_o7f$p))dRj#-Sds9Y5mHLDV>tSCn)_LB1USi0HN-7~+@Z#T$tn!Ooi1C@B# z(m|r@W*eKmiDo4d1cX1EZfWFNug^(yy`;cK>6*zAo3xK47apg{FjF(%)M9syb=DRW zRU~MQ4W#gXcwKm!hQ;Knk!l)O`t7sz%h4jKP+|*`OjM$# z85*VKlhj|GBOrc-(CkLl&{UC|S;lAZzg^ujitbwgfylVAvM@3MAG}%0MA795yUv}{ zGH9_xqdt)7O_WZmPV$?%Pq)*hL}zIY7rh2ya#?aXsP+N6FFm&Vt?^p*@B4Q-3P%sL zN^tmaU;^GQQ+!eMQIMO>o3`@r7amg>s%m?YsWJNKZO(+@Pg|NpJ7=^z0gg~F9t9xw zVz9Gw__h&z3GlAjB(}_)z1w~En?G4t#(k%GvA*aNF`cgP05kd9t%V;f&Yaf9H-b-s zC6kgJeJD$8eGcJXvuu5fgOY85lYqoSEbF06M8}MbA%&L_3lWV2w|k>{yTGDhb{#E( zyteL!U9T~iB@N}lrtkA+S?O=-p8TETtC&By>uGwn;cw34kg!uQJpLj8qFJbUlOjQX zoI#1jdrcySKreSvZK9NSuh2VM@}bjVk@<;EGY_asX+h3xW$ec_kqX?vG>0!$y?##L zV2vO)JxkYo-rV(dwc-8jjEv;-!nN;g`xzMP*9l(#e!n_SE5|Xkj-CXBEGSDt;g!AG zXhH^4LX(gs{O`+fyxm~HDVUaT{ZOxSPLakz%wzmII4!M;a46ZY3EfnakmSSEVgMvy z=C!^}3h+5I=hyV6qePvy!<0ofhL^u}Gg|C57+a&iVvtUEcV~qex8W{M4=x_Mu{Bht zpHU+O%^6Vx`oU5DQqr(I9#Vf?$^eN#+$eR24>k8`l5D;^Y~&+lBu}(dpZH0Nk9pD(hi6CzqR{`_co9>f7|QJMA`&( z?dw3>Mx+Bu3&|RC2eC!om++}M0YBYp9j3r~2}9H#3tw+0`MtT#u9${p*2d(eLWqjr z<@PUq`_XI(f_n|TymAYdVd~cQy)^l2>!E|L{w-GZS|Mi|yD?`*f^ZN%!87PG1#ivS zBuP9Q2dsB91n1_8r9Xez{ZO`9$S^2Z`Ri~bo!Jlznq5zaluNd%3a8&xnNLi!c{Zf{ z2HC7rvspt-to)3DQ@$kZ-%~1B<`g!dFdTEST(FlTj1Y&gs%ikNiHMo(#;=(J`5??O z=fkf%%Gt+TT!dd@Cd#`=c zo`TPBj8c=;a`~Cme`PG%7czp{ufOFRSdFOL#NPt&Z^d{vujD@i2(|Jv$iOu9Cw1DC z?SkJGN2AyLJ>G`U;4iXDSlECRXP?7R%6KoeFA7k2G<7WwB=v39nrOOuA~bl%OoBX8 zjmxjEhq79)0ncPieq}%r8IoVpeqjPKN)S978Od(JH|q(3z*2dHKAdsfNh6A}s!D0V ztfzf%$7|U59ndnrpK?e2f;9}VJDSD!S9wseVSf{U(?ws5Gx%%cMi#yjiJk1JOqS=z z9Qm}wy4u?F)Q-2aA1+5ch+s&`{CdWQe>jQqH<>H=T8aZkRjc&(ynYv3E*$H~sY?Jy zK)Amw0?G|X>A*77ZIJ4MXAggL)TS$k4(F93kHA@(~zKp{xlsDMaYV zRd9At@bH3-n!}>i0X%O$U*P*cs=i}OTA)Yvfmk%&Eb-oZcDTZOQ^3;?_5E}2%=D(Z z`xJ*HkwOufDT#w+nZkjBO13K(L#pZBAQ3hC1&stajkuhm)zx0CH8E4y(>)D7&b3s3 zrDclndXbZm__>Vh8FB>XD&|y^ir+B}WO3hTnFgF30{`kWpd3|KBOrsQ^x&{cz94EXM;@Awip&~*Q{_0O>=;XsKyRyRy$nD!$0;uA^f zsr(6JjfA`DvL(JzL4PxnrQ~hS83@}I&c9-`yY>1!b$;YFDPFDqxA(6rZ{xrm-hFW2 z^d0ga&9vyt)xW96lRoZw3YN&3`YUhzLFh=ttVN8}Zk$64asiIy+516HSZ5`8$_pVgR8qeq=0R`+YuHfu z6>|Yv?0<&XHm+$j1}ZTZvdjPx`m_-ogf*CmsE+_2a2fv5awN4Uo6r9Gsq9Nibp676 zmgXROH>E!P3lof}1H12THQpLUQrP%s4pt*|!2x8Z;bxBnfxSLd|kGy zIo|uH9sN67UNMy5Y&qD|{BCN0YC!!vM`%nul#joS5=Wv4Y5jF#70euDG56xtzQLk8o-BNn}v#ILY2&E5vF#BURKskhf?dGaL}GBWt8E#N3N z_tzF$uSkYM0K7Swk^Rx6(-?eF-m?u8`!!80Btvfb`ju*>2?dMFbpPE4ww=LGSurR; zGT~U-p&oqa4NH`1Xhj##T#+Dy;PrS4%<_wE&2*;>ip=OIu- zh2Vw=oBNbeeoi&ED)fLm6@a=wVc)5e`2CfMw$I+xi!@=Ta7hDYeoiCzE+n>YCb(O6 zd)XS*eOkxy=TJoQL_(J0x2+HTX(n=`)bL8i9J5w`pWHT4s|oovXXZ&h>i%#?jsJSl zo-GCFt%{Adt+^i?hMmLjavtafSaY2qg+p5pFew%n+>{e2b#ELMJSAe64>}dQJoab-x4F{-2}no}J^-i{Xv;Um zfC0(pyE2-Db*zdUP80!RoVl-0@d%G!?)YodI_@F2LPOs!;J#H~+M`lt5B0@V0~jWB z15aipD3yJk0_zO1*9a{$=+(6@?hz6kK#sH0#oBI93__oRngC&<+ao=DHC9q`py32X zY6e;{lmhgCx0-QWwSd#h&9|52y29NPF2#*6+tas9pH%8=hV-l3NkcfY;qUl-o03V(}4?KIIr`%dvYH=|SKn3!_@ z$8lUW|E)XyD+msZPYCMkCna8k+~!EKD_}ALoL24%uIleQa$`|{U$q&X8;znzldTY= z?^xmke1>E=hXk!9<{UcT585echL?T*!;QXv&5h5~ez0tITYBSh-J*SO>x;yKl_vqC z=fFjJT~s-`x%d0;)5jmC7 zsaF!TsLoC#aP@KL87z%gYE|fK{v<>nsCos(SIQ*!ofn~5vp%lc$u3iP*tLP&Jk9jE z_~}R<`n6>&BS|Qc)M-SU>8-%EUVMc9{QNc&_@e7*u8M^P{F@n!hNVAK8h7g`Ki8-T zM$nYAghF{4>R}Zc^!)2I>GQn4m5`9-T$g;+b|EB=$zRFas|Uhqa;g8lA>4Bb6Wn%? zT%9Mr&6GAj^y37JGre(RCa3tLGv4$tocvKOxXn#drPVlq0ldzBIaBZe6Z^^FLD`Xj z@x>h3fiF*v6Akw$_!UXte5hE7x&-N-J~$Nk#1KRIk>w$w|8lGJ`FCV{{^Q>f)2cVj z6OlR)LgxkUfEqSrL_F8s6T>}(<~@M1A7#ZdyJ5m4Len`40IIJY{kS_ zZW`ATxUE}FEH|1QvKUzaXDN4=qu_-k<5a7}2u$uGf{=GE^n_05{k>fIUFIzQ2O$H zVK2P9j06!}W92*DIkyIl*iYB%IteyecCx5RI~>4`!S)*6s0u}aj~dIMyV>EmM|_Y5#c>X3iL zo#V+X4NOAtG5+8WG#_e-sabfEA8tBOOkLuk&sgJDmKr{=YG8l?E3IC zOanxF!5G3koinCVEOw>+^4vV&7eC4RYdI9$DFFV%>3?-yq+oQ7iIvYS;mpwcM58^n zm^tO2M#Tnn%Nn@V2hhF#NJ@VC&-30ezLVS4%Rb%PJQ6y;tH(Hwr3^-icr|?NYRTY6 z?^eN=Z*vPr!@tB5Ze6D#{=STy`VNX;Z70qaFP`TTe0NBTVPjVX_{d7>zT^+_(0Ky% zKH&wE)Q4ti#3cVYc}X_x>F%Ww!HUcPF^fU~0W|TrZ^``q2nMLAcwx9gi{5eu{qRNOBqf`vG_=>_N{5BU;_b?MsP1X+*<)wJs(c$70Stf#0Cj zofm`(AA3%;;Xa8dpj0jkPA#;}$(r)K`U?+_TvDL{^FxZJ0{VoX=7YN5be{LW<&|^S z2x&<}HalU&`$Hk{+8Q72Si`f7hpSEtLA2RW= zZ!VrrGr69-<=L^rC5&UJK*oUfPE3YB0|cEnntyM_IELU2{0#QTo}31Qho->o5l2no zy~BRlh0w0Uh9@*Aw95`Lfz>=eomOjnmLZTo9f&RZit{ByU3kF@(LOsZM#Gh~GJxzF?~^XP9JALSkPqaI zv>=|o(=r$_7LT+9&UEgpI!N)K5%T~c73t-UBbG}YWP!@`WfE|n^Pq7a%~$9{D+L%> zk-|k0O-k^mg@O&Y>%e28(YZi&_ctFkhpXxJz%D?rYzZkfQ-~Q7(Vl^@T@-Uk&CwEJt{Ckf3|1+rUv6+a|R2 zv-9xPG?DRw;vRMF(Cw2vLs9x%9GxNZs;n9ZVs@Q|*cvWQZye^8x%L8MuKkH2P^|AZ zAO-|HfC27@mz9nf%a1feF_Fq+iz|Nagho;McOZ6Iy?i=*M42H$bg{h!UzJZn{!-jj`wePDbw$WDH+m`?ouc zPIzG{N}D&?;cF6|!OYrxEvVroiHBcL#mArtoB<;E3vc81iOzz9%;%z}U6H>|$G3~+ zmwS?L-ev4LFCi-!d#w>Q#3-r!WIm2~U4G%}N7^o}vV46%5UU|qu?oVkUj`;_I)!@)0sd;|PdMAHztQ?z{2Ok?{z$R=uR7I*O#koq>-X{7 zz@)?Fvr#XWf4+YUPjwn>!U>V@yFS`9?5L(*|jT#89Ep<01^7(B}9gfCr_ zV>x5Z)WqAq8%#@UE%ncbytJ6*pzXTvt+mQ;k)a*570X=`KqvRfZY1^VVx@Vp(KYoe zVyH?wk=dm)wFdHhPiR;aH%+3Z38=3%MW2QXPJ{v#?f&bWN}wGMq($ayp6($#F=WQ| z- ze`V077+d-xii0pH5B(GFEx&mYpY9bZGHvDW1GQ>$(2uazn&~CR+m&qH2)+)4 z9?uc&_90^m+o_$if={3`~8)bC~LEJhOj3nO>nG2wNz7MCzFm2 zu8!t^)k=)r4d?m3t3Tav2k2Lmo;Wb_LWU31sUwqtz`6V71XIXN-SCLy-q*8HM85k_ zbjHyLiK8S#WMA`W9_ok!7KdacE3yFS^2bJnp`L$%JfB7V(UO|<%FB=t}2 zFK`!Z!95?;^GYprit=#^r~D3D36z|_ zFAMESLX_}fZ9X4&M`EdZ^;*vT(8U549BJ5i{n5%#8vEPJbv3Lprk9+e(oK2SbA>5^ z*JybdKDb5gSBJ9_3)o^CK#JAL_Xir`V~&bxTVU!7z?n3x@aK(-f9}i8_q}r4%%>zk z2`%ln-U-prC_jjE+8DV7?Xw21g?FN;7&g#-K)+5s~4X7@Ohm6be_HlC^xn zLb3}kdSwR~J@=ks+HYae@ANIbZUUtqAy?Y%6He)&(0(fk+zY<_QoXSM?|6t!@9Ol7 zLpLMH=VQ=A1@n}d{wpjvWWQ7p;E%DuiM@>Edupl-VTi<7bFie|O~TY+dXQmNy=%fR zE;3Ii988eRyvECBPb(oa-GVHN58lk|*6-Cz(>5KW)I`#kOswQU#`==p7ug<$B8M> zTXlw-p!E&V6k2cyCwH(v4E<#YU)eWL`$K$Yz$lf{AlmOINqh0E&xf)rm$9EnMg1+u zB%$=|M~RzV<>lkXDGp3wwiI7qEYBEi3VWqh80C|vJ@H?gm5rlPH}rD2n8fmn|KfGD zIo8Wj$vt2v7_i52Y)0l5IjweIAPRz+q#s$;=nNxWBumwyKlpDqcjS|ih!32J?Cg9rGg;hK`yGZ@`--W$bU05n%OH z?=wR)uz0{{?pFZa(U0uiH-8=R1luU^&UcNAx%%V8&3Ve1ru}pKX>7aZ;cb$Ihmr=BW-UTLA7^n+Hp1~Z1 z=OZ9xc&9wX1B)@djRL{eg7OoYFzI)M5JR`Ek9c5_Bte1CM?WSY^j9V=xWnWL*m>ToVjc(8ugYq5b#UY-zMYed+uLChaGvR|pV>;7gdEHao5Y??z# zLVqEV_exs8#nwBY)BE7#-1oqPq3ph!-H#y<&qv~ezAv4?e)R#e#eeQ;o-LREv4p+y zxPm4-n)TA9C{|{ispvQ@`-X{Ae}~vUm#}V578uz<)DcE@d#(T3d2pY@nd^a5yiuh) za+=+7lV02n-}ZLs8~OUYuf$);8}amI`%om&0!30>D*OEvss`?yNSdY>pQ<7`92yV( zK=v0RU3Y7rbw3EHBhDUA7L3*OGZS_9un)!0NsMxQafY33mLYw)Syy-I#e63a1glqt z@;W!lOE0(qc%@e{WL@!Vyq#o?;y%4QXN*rLcls-q_9(?=daaPEe>KH^{)`oScEcd+z>U2vMD&uKpAtM0 zI$une>vqJ@`I)U$oi|1^?H3fJH1Q7&UpSu8dg%8;+S?rAc`f2QM}DyMuaxidgbCYhB>SN#p|7%W4}k!-uj+2LDGk^GTN`7w9ojkuguIM`)0 z?K6Vsjr*jE_%TKKX+A@HlFf(l+PCZ_{I=K%q7uq_W>g6tC>frev99zjkjZAc zY9GmXYZH!;mJ@OV}sPtnb;W4a&ks{-$L2yNWT06Q2toQaz#6VOCV?$sa5Lv^jtiwQd+l8g~PFJf5(os`J~s2ExV=;fFY z_xE$#Zb&R2nX!7)Tt*OhB~7g*F!bGWk_i=vB_LefQ$(tl?+MvD`7Mes_Eexa-^Qaz z+C=*9iM4cc;XzJ{IHk-2z;}NoOrH1c?4Ud!r!zpO0h*#c=CdE8%Q^ct#J*y;mm!w& zzB(vIAqGp$YEwECen*J&uo?r9lK06fz!uF#Tc$uGnz`=1=w}qfA^(lkql?q2w4#E;BR;=7IegxxK10fa-H?U|amwv^j3r%Cn>%PYH z9%UozKsS0Z2u3K}Im~Y?ri`SUbtKxq6H4hB>7Tp8@F4mXq-v^qx#;Wih%%lonYN&L zm;Y@dc470|QFKB2r9GeRueZdLOM(NXmQW2WCE<7jAvGF(uQjv++ag7sThZ|nPTUl} zgf@vmr&rJ@Z8Im+sdZ6|c1&d2uS9d<{h%HN%Tr>wI(7y6Ocr_i$2x$P;v8T99`rlcyX(+^+T7Uz&q;gkJ2*v>Q zL|>?&-Haf**A+SGhl#^nF(`7w(bV{sI#93jIivh}L(>!lI2}a&SQa<=jMi>5OG+&L zwfIEFvlS-_R=DA;1AR;g%AnWY?Q46T6PlN2Gp7eIXt7;xJk4CVe8!%ny33N}gBGR2 zBOq_Lbh59m?`(QO0VDA{eWwmPz3^+?eA&!-7T!6XaAM~{!pna4wSQ$JJZ@^g;9K+v=cP;nHHpieem+1L z+H{R3jNGba%`mIc*?rCpYtprR_UrzJGE28ASW_mQc3|H$^;}3PR-C6+x${dRb&(xe zl8<%l=jifRy$N&M8SZAQ$M=oQ@cYgf+Q9E*ClP}XMg4@U0h7>|Cg=xMUJlIH8Wwb)1 zEf%4$ul1RFMg2Xip61_g=d|f3GOXGf!>37*?{u?o46X%ZPL!mTpZv9MuD3s73&<@v zzupplovL^>=B9i17TAd_rd`9d!`BHK)6jxGk%BuQ#yJR`FA2XfFFpr-&BCiSO9om( zqzX-|biiejV4Z7?us<)MA1bZ|aP;PEzt2ILcjQYCo^gG=M43=He<>n>E)@lITO8Pd z^vjoSd?4SeE|JWXA+Pk9X9Z%k*rpgyj%Ovj#6Vg*bXhSp^wKam%bKY6R;8@6nhqE6 zGFRJQ?wYsOYg)gY;18DrK1`T_WdyVKQZ>sGz*@o8bk14?TTGwxlAs&QGW%#Ukqz;v zC`&4Wd)$+^H-`yl;%obZ2rs|QnK2~QdxU|6XvTd5k`!*&{EinJO%D%Dd;&|Nv?g^` z?IUMbC3~q+m(qJhg3CEtg2|QPD$$wzB3-~o*-l_j(1@X_RY>)_6%U?sxLM9ouuMteVH_!dBwxGtBJvAL2FC*FR%ty>h{Fk3dd^Fx7KQs8>7mXPhFx)8E9qq>>>%QcDf6=C!pt|F zf_4$ZQ0lE@EXqftIt~tnc-^ZdU=VK>!}E8pA4R`-$(2TUGV^s$0MCOAe1$W9b;Vj{ zH3YvPDh986HyhsU2xElFFo48_vs!?z;LMe2mTY;&fKdwnI;0a{YeH(xrudDDpvOfi zi{Ip6=q^o?IZz#6Ev@IC@Dk#jmyE2q&>AW+5I4VvhKC{2d}A;s)jUSmCog*9;sfE; zJlhx6s8$=&qpUx?hZd1REylGVbxUX;n1>U&+ha?OwZEhITU2s9R=0%vR1J-;@amW2 z%SJJyBAMdnv_DenAexaJ--my4^%Hmy1N22t285~kh1o!5__PA(izK1Dd^uarxki?A ziSl=M4RKko@-df!ZJWtlJ@6AqjW(4!=_3gNWk)->3ss_HPL?6n{I;R~lfNg5CZ%|a z@Y}TcE2+(gXCi*Z%vJ{RVX}xlRB3SSMrh@*fw%Z5a1N1(Ub$wks(L9_=+~~3FQ^1f z(iVvRas_qObW1Qj;{2}@>C5-$E9{eWln+RezRM`DT8z%eANyx%Mt^zDKM7k_U z3oI5a+qBQ{qMSu~D;|Yr%u2G~xBsVZpNm_|8Xse4RnX6#mAFqEdHCoD%0WIEe63M8 ztIH?#E^p9oP!l{Ba$jeh*(8OS2wJ=i#wE-e^U6}_)x>l;<%K#nq=kS~s}ykE95JXM ztZ^MQ<<8iu$S?kV_Yqk_vp^FzPE5;x;V2-WRT*V?Aa-8rPRd#@-{Y)uzoh`ZQX@l( zO{u5J*E!`kKnlIVP8_@rDdm<-@DifJ$Zu9wQ4lYbRa|EC4DJqoJ% zYsR#>j-%f!+$9tewWCjTNuHvvV^uZ>su{ofi9(alGi%AiSZCDz3O&1FaES+o{1teOZGdkU6aB;gk zBwiFFbwx3DnIMyb@Fn(>Lf+O z@M~v=9|-to_oQx=4^n0jAZMkKkJ54x!RRlGvG(AL($Pk>W8A(pj%)?9nD_cQRg^|W z9TGRkLJHVHqCK334e_J8*u_9*MCz+Z9Uto>eeH+HrE)vfw2KoVI z?pr*~7UFTVsIIr0Tvzxj4Zg+o3oRn|Rb|DKFD6$io}}>J(BiEq@6M?5WNCK_-w>96 zZ5o@m;sBSMHy1)cUer#gI;<@jsA<90nK2uNlRQkyL)1EK<$Nbk+45gz5zShN@2i`F zTfDlesXNE5hx%H;?>KENOl`=^yi_dt4qQCHH#Z~KQnEf}U-mVCju3#bqMw*b^3O>Ac zVj?_MY4lGX$6tpag9hJ!YO`FT;IFt(BaisH>kIgHFDo))a5IcR+i|f^Pmk)R3(zQT zH#F$aGt6DfB@ZH{Z}f#QmTe1vriQ=#ytZygtkcI<{qfF(~Fgtdw3SV z4EYc+x;a3Q2dMW7Zhh=`j3@8sfOzAPMy9liZXNeg3Wszph;Vel3b;cEQn1Zu&m-U& z^X2Gci>wJ6@?)CnF?vkB;(+JmrO|Y3DOJ1=uxSfXI?boEH@C#rS={CqIfqAChat>& zsvsOGL-b!~59W>}Pt4`UZi1{<5ggvReQzY`)1;`sHNR zN)&j_k_Vy+I<>!;vvT*x_uty&uFlu6WU}T(JPm>{0y~_Z(8$=goz{wo|OGl+&qUvXf(Z;CUb-0nW*Y>cP>L}sR%5U2eetOeKN{<~E`V8v(<)wGCvkV#6))k41<8!uI9;a3Mf zib^iCzO6O^y9>y>eQ1#{XZAjCxnH90lK?ofrI^X1@i4!^MeRc`UI}kZzc&{au4RbO zjpq=9?0Jl&!&bx9*c#r-n1uRK<1(g$TW4y#h1T6L0nutr7aq>!!-An4N_uP7)V{7YfM^gRpDsFXq0j}x|qZX*hK!Dhl?+!1H873afkzm4n?JprN z#}xMOWbINCNpP=oV^(2T*ODsO8g2k4b7;;a`{BmuBN+D$FW*_*I|!DNuN{asbAgI{ zh0=O-=RB+~yc4{f#2Y=eFLP}{BHSbJ01vIaPVE(UXp|{fCfIN2RiCKu`goVI9GFcn zOj{p=VTyCz`i7g`S?TSPe}@EIeS@LW3vT$4K((&%z)11r6B)aee_>+hXc}x$)aOwl zHDATfR>&4~O{Zmn;*TKFoX%feqijn$R{A{YlZ8z-7~i@z0YQ*~9GKRYaKB9oI>Qi0 zoNj}msdMMBU%LlMHYE+)YGSFfPTUjvWX=4&X4ok1DE0I4m`!^-1-9T;b~XhHfZF## zhtO3N4IuuuuWb^j1y@4xdW7@>4tPb6vJ@bN6?eofOAl+YAJfmvzle)~ZP&X^a-i8~ zRAk8x#{1xJvP8PrRPXP=)%F3sQf7IUGri><6LY?mukWwJTDOD;a6LOLIxjVWNwACb zY^x=I$rEYz^YCLR%+#^9c?g>MP%0<}Nv6hvJgQco8&+dR=GZ!B!Xs7oz*Yd+Z=w+Z)*clHF5WG}a z{9$-rm#X?NlD#wE9J!kSpCOyu)oXLNZoXYEWfdIVCsl~I5S=^0{lv1|Iq~O z$p&(rhov)Tv*iy*xvK2Q13T^qMR`2T58|McK6~KI@AGNnCqM3p?AE@b$Ia~J!s+z# z_Lwj13>(P{ww)Sx=GusC(pEU$vYGVzJPy8aAnuyydAHBl)OM=MU|lV-7@quv+hRI@ zJ%-vZQA?IYJ6!7#LI-s1xCk;jE-6pdaOM#f6MN$*;OCCp%G4Dc=8G4H-6F26^oV+o zvQMJF<%>%fYdvXgjMyOwoSH7j+L~iV+GEMSNrwmLT17!pHtw+@41=MIFlQW_1$@d7 z){~j+PgC0`u}Ks=7ebMpDK6;O@UJxCy5RPn(UpvCrO!yJyLqYJE8YzRfHbTo_n7RK z6R|yTCOD2S>&p$MWQLXGopsj$JJ>j{QGEqc`tO90-#Fdckv^Mch)M}zS2G#s(j4?l z;N}ilGF3Jtctdc79E2UCjS>3Kt@;Cno2HA=NfQ=_sqJq=aHyx*?LVD^*y(ciyQ^Bz`0ZcnKlS>Pn;JZZ(e&idt4{+NQwGUVqCx*L5yQjBa~Pp58UXWT7Me!*xc!(^+wLqdPw zurdefEQ{<~4V7E;u@jRR_UEo8*&u;i0rMiv)1~H4-BX;eoyT=Gm2<3lh3Cy~76Sod zfpb|Rfc{O(!Fqt-#oL+yxjf1YwJSk zU#B8R`FeGl|5vif1Sgf97ipm=dz7-?NPOc!2I4b)UUgSq&UG}45M|_QK!qP7!Djrk;p2x8`1pTL=S_txfkcho5EEcfydHPB~C!*%bDm z1XO&v9sek|IXoNfv%vbibwW|C~Oy|6@G#=Ouj|GXJq^BJhch%Aysn zKXWXDu$PF9&(7GV9T@Dod`gwOr7@0MLnw0)H_eryZRv!%iW-^U05wBrE^9VT=^NMa z(1;8(!Y@EDrj@9(73gj*-sXy?i|zYRUl^$Z+l@tgmqnRkJSE{|*rl!6QKkP<#K-~4f;IzwZie7Da;sj)_nTTHv!nAY+?Nnz*zRo1m-m- zz9AasMR-?5K3q7$!?I9&iLWXCQ8vwSpRQ)V1W%ny`psDCSi{eU(S1ea@E1k|#~ZQp zm6FG+BD{2~ue^XmQDcTGh!OdBY@kAyU7v$}rK=WNl!RTg9+jum-matx6`~^{yT;OpBi;5= zK;;dC8`$|u#e6Tp05OYF7=)`5Y)%r(JmBDdg$J|cV#VyW@k*rhlgXCV_K1Py>-{K(cU4J^veNAlY0B1`W;0=fg zd%E4Pda}?A&U|Jf%Hd%4AlSUq{7CF3i{0fY38p5c!ApbE`j@auOTS9XZu8sP`Q@1Z zjNJSswbHvFs7rqytwO1dlRK(fY*tUS7vcY^uDQX@yn?+h&vHUO^W_ItPw$5AfufpV zO}X3udQJaYq2&{YtI011q5Tm4u!>D_Q9mU&EIBsctsy7!(hu;$x*z-1lX5VAziMB; z@1riz&6BU7m*opKv`<7qF$9GdGd$Ye&N>;aRC%)v7*zNXrKu)M&XcucMQ5HDO@g)_h2 z;Od3HddfTD2SV*CIlK6 zN{mL#OdY7L^c2B7$58a~L2WRDFj^z4$$tkR`f?l!fjw~VXdw%|{x}>4OZ0Eo?D^cy z!ELQu=dgt0{@RF6lS!b;??3=gL~nk_dXf6oaVMej0EbA~u)*lkel43x@4jpxpLyX? zVeqAQmV5M|u-gD%F3~>bLz+436l+t}y#i=}#|lC%YJx~}w95_JvGq@A(;RP0i0dU!Da-(Xz9PDVBTXq7k{J(WW;buMnX|k5-1k*k(61*xoq9K%bNM znA=V(d~E+!$)49ArP0J!>!4C%J=UtgmGD2S4$?ihaSo0siB;>2aog&f{2eNY4-bOPN)X&j<*4kuZ+}qiq5$!-8MKkepx|Ft^I8Q2 zgh6Io8Y~|!D)E61bcpjcrF~{s@p@ebndU#k6@!kvgDfT_9>;6{`g|M-4FQ@vQ@0P( z%x-iNZqr|Kk52DO-7o~C?iF%5>H*2R@g=i|5(<`qBu9>}ZkWA5q6%nKZID+ZbB^*k zX6bDNv4x#RS5Q8MlbQFI1;E?7#IQjqz=VN4fb|SF&~+7O;Q?_TuddO6fw|zr5*@Io zf%YG;1fujUC3^P@&;ZQduit|Sz{?<8m`FjrKSl4+fhkMGV%SZDV^d)8Xev6rwg1{+ z&%cI_+yWrv0ljoWPZ1~72lY`Tnq#1yn9v`KU6eL$2=KbM#X5=@F*I8#LgxNDjACBb zX|3n#^!f6-2F(`*NORRFZB37t#2_aVrVsAPKWZz`iPHV(@xE-`%m{B4pytk^j`BH% z4KfhN$(WE;UcO~! z=LMs%jAW<6-)~)?YQ`#t9Hz=BY`j;LQ*-1CO)s~m$LzJEDW^v0`{xaQ;~8pM)O%4I zR$a!D4nI1J^qbpg5jN!>U1~!nK4Y?48P-?nkJ_MOvXfO?)vq(McY1NZ{#lIUXSsFU z8n5C-mpuE4^RnZkIP0Rm$=z(P*XkC&u$JVNu4JS{#5*RTr?bji4(LR)4wsCFYDoi< zXI>eAoMtlf)X)t91kk^=dr!H(1uv9(Q!YSwFJM^Ka3I%dJ)-aRLO>vaatVU3TySDX zih}TR3V`Hqr5qVS`VjKbgs4)GPpb8mfEWQ2+vL|wf+ z@AIK^zt(33h8BA1QfsKk|r7Wh==-ofIV6`PSvt zuy^B^VRNV3ug)MFhmBm|Qwk<%_q?gVzXb1cLB5b6WHj8!NU}5#wTzmas*Uj3yWS(- z$UdNq9IyXZfU{&y5ETnfn7)XC#aGo0eI&{}pGW-Kqn&mix5hA_gy^z+ZF`^_2@X;1F>4iSYY6U=q-T%6) z9ZAz`GboufK>95<)v`|PCpp#foWbgj&P9u)WxX}i%}8c{k#2}iA0IP=GAo( z!|}k)Ud2mHwyg@&q>7?ZtgVMPtH)0=xCb8#BhlvI&zgq|cnXVqUh%HsyLnAZ9BQ8y ze}vH3I+N$CJ<)CxDz*e;zjkWMh23A;>{+|6;mVtIJ@mA*ranP{6GH%gs2YeBy>2b} z^@g-fp;cNkEqr?Bt2uf4BHfxCD!-;AFpc<yFXG>V7d)44;D_;y-gA( zIuL4*r2gA(QbRYLUH6P(X7CA*euPlH@FWCE(Bu%ugQtfzwOFj?4ru;!&S5=GKCC=S zY2jd5T53|&b|%hF5I@Rla)nm>uIq!sUOmE_5JEI-+!Y4amFN&5n)`lFc3GngrEFdY z92}KuUVc!EZ?_daH8`dGkgX)fEQzzE78BAhe=dhB%y-Nyp1_e`y1Sigy*kt0{?l4Ag}+gCqr1{BHv#2&V; z=>y>Z7gO}4ldg!_S7>G9dcF9MHWi<&lI%KP!yCR{X_Cq^o@K8e9X)meDkKU#qSdLJ zmx;(rG#7SNYYz$)NaenpkszmTLK6_XzJ+^EY|K~jwOZ@daD%zeP=B3H0&Q953;H7s zAw|a)&pYaG`+~S>C*twj7wHLSie8cS3V_6Sn3JQ0x11k?)ikHBQRnwjoD16|rJ~Jm z@wCivN~rY4^bg5=!-qax4~2(5HjCF)Q-6`u6bxP!=Uz}=?BvyamVZ>jk0-C4)}KTW zfi`*AYr|n<4UN(^?6*35>342cHFGvgbQwm3udCOyN6iGNjWgn;IEMSA-`TA4oBSYb zWJ1S$q?Z2G7n4b>kO<6YRC>|xxS!V!b^c=1XCh22vYAqVA|#H>MP<)ta++`cH&VaZ zBZxfH^`x>MLEdrInjZ3?qge8?M2DO5UK8p%y7n{0UItwZlm^@IWq-i4gI6X;D6FDs zcNZ1&KxU;sxA8iP8qwvW74&W`AoG1h2G;lC?;H<)d%xKqbj(R^YJyRy7EU2 z?RP!>JdjR466df+Dug;x6w;tHiZoUe2P><7tBIL}VDe%4U_XbfN69RO@zCvz^gYV5 z%nLXRqHEe0B8D*A!o#b-n6%(i*TwJ|i2*xIqwo^1=CRL;`Aq{V+T>US3@;r_4Rw0) ztmhE^VBJrqD+wRbNA%?r&qvW#T4((XMf^}w;aL0tUB)9Tv!BN7=yDH{_-c(dN*?fx;+9Y- z=r8+ndH91bIlaaiWE(Ma4n%*CbkiT9_u3{1Pn5&?eB>`mGZNrcRFzUwzg8ekb{c?k z=_Mg-MpLIjxFvFY8o1DHm+DKtC#8pP; zUyt$nNs9pAZ}nK0iT8?TSIF!<8hu%dEs@tuwt2LYB@nvBNV1ks zj9d?+Y+CF9-#Wp6^B^2&twrl;!CaGHHxK}DK##u}YieQ(uQ&i$0Yut{Rmk*1}vE9;koyWNgSyTPbRf_Q8)nd?j?0=*HhX3v~u+`{&QEQd)ic7NSI+N;u-vRIJ zTXy7A-G)5o{5q3{-s zxpg~UUi+1^ZLD$tAz0?*ycJb>JYJs*R#=$e{BG6bkal@V^j;NUF91CkY0oLy^l?W0 zO}0e>y(a6}Xn~~r*`M|;BK^^UQaoli+Mw)kJURKV#zp_OHzWVog z6U*hV6~Mb>Mf5%5b644!uv%~%JU1leY4$WXuFpA7&-n9YIU#5INR$s2$1%m58QcbhUnBJm&Me!s&w6Gy; zyb&=iSCjSJX(=^l*KfqM>)P=B>C#V2CEo$CKt~7-3xJc-B7+Ma?|rWpUG>6-aIOaB;QWvJ{j3_645FCXvsiTgeSq&@=jXkx_iwyF$vUm?pjJFW9@pzZDPhv-XuUuuz$e=D z1SnZ)ERv6?Bzs{>^Nue6L!xU{y8bu*?~aG3Qd4vL7<{){O7$>|{w*#@%eK4rKWBcE zdd<_Eq7;x;EtV%eR)FirIva&gFeV!fWeY>VX|~&Hr6Z{rN{8=MW3q(Td+EYd)}`S< zC!_VSkr(493!asoq=T^mm;>KU8|Tp(b`Y8jSwj+)xCe1-)6yVOkjWDjtWqzOSrTK2vPZc*vrOIGU%yk)Ut9uZ$fp&T0xp8PVm1!=Pfm^Ek>JzgJ^I*dfpVSWeZ~Fp{ije? z0~$~@EV|!wXm(T;acpnVbS^t`u==_sEMxj)VSUIE4Pd!kR@E*iR~IGi27x-G+ab&GJNce zfsdsfl%N{vBGMRzRaYqPw>3ZznV}!JU_N6Lq_5#iC$5nUr*gg7*2OAbLGYU(V(>nK z=$%mf&~7AHw5sF`e8F}OosvNMdcQOWoI$o$yek0xy9eGf*4ci3bWOkm@f{6d0{O$9 z@^6MTgQ+-2>poU}BM~RJW-+6+IA)q>9h^-!4;P-QXl=MZ2lAz*(_Ww1e^<6tYd*t@ zg53{E-B<&{+7D&~aaXozNaPh#$)b=q?0YU&YvjLk(m0VmEQYx?HysUn1NX?J*i>J; z+pvC!KP*Et_mtTY;J~wF z$Un8DdnT-1cP0J5V;5)tSK3YgI|B^AGbz2@fD70ou;Gy$Z$VHZCGAWfn9`1{&I*BL zeOv;z+O{in=Mn`p8O=digBMjl_xc_obLiB<0V}N5n#QjlCfF&lu4`^RnKppyZ-wX) zN#o5y^f+OmfV$TyQ{!adO@j4Bj;go*Kxca0+>T?2Of1%Eizk(p_COkz0b6eiw1b@)}?q=`j$iYDy7S>++T+-P;l& zdZIqR*a;!P2GHPneKu-fytFQ}+zm3cUA0)ky4U&)S5 znxp`}nr;ON2ti{}N4)w7l@kz~m}}m}n!>Hn5sv6JT2^cE$2LyK5u``i}!50+_KBk47h8_;c7VCxC(~QOBY|=w*CEn4Xgm({S1$d0LT0&pwb} zhVDbp{gDy`JiBrFMJmslb^!V}V39>Rso1xW+E)-Z&&EdfPZBTBOes0p6CWxq0($SF z#Q78L+?}ePi$Mwpyq)~0py z1HtYF0X8tLxooEhK;tgcwUf@XdaS49rHc3}G!Lhp zE_hMP)P5G?*}fc;+uDL9yN@N!t!~54eV8>jqTvM~GTBbA*A_Ni^Ev~&PX#tGoP=s$ zF`;9{l4rl)yPFAW>}Kp&_EaCdW*&!44?jxU$1m)HRXg3D;I~sk=#qA+SGp8N#lsDU zNZ4>IsB;R*0%=rzH27XlZ4`-=SL8}Y7={sDgSHxWi|6@-z4NdPew`1!(^Y-a08ZD; z)H;Au-@F;5)N})B3LW$Q=j~Oa$t{a}daWyqx&XUatW7^yoEF`#Gg}cn({I~pmEudZ z)i*G^AN~eG+v}&cYI2kX(WT~j%uUJte8bIZJ`g2?qmtHf^RgKOHxd41jfHINLp6NtC(~%-UE&ehxSfWB{s?H|lHJH4Y~p2_SE*iX$rClxdJM)cHJXRAejo=b zK!o<>G!Q(pd%Kt#3x;DR%a8n+YeZvc{y7MkZMbE7GAw>H)o{{A&HP5EcE0=yfHy?V z`gBFO+s$5btXb)9hoR9p>AN23%chsx&W7hpH{q`K(M+{bs4OQq;aj81am$9W9kTZ7?_vb*CCD; z6IB(fe`B4Pfj`A$fiCaf76M$xQ;`X_f{Q$kcTH0|r!m63NHCo?&Ygzjak7NraS2BQz;2*%y#aQE9s7UtK-m{^0aE zKXAo8-_hGieOg?@2F85c%?*V@w_>&@D6;X<^eK)sAwu;iwcKPmPaIYuDj#QOdKP9; z!20D={Q4SOQ32$}m6#*c7}QB@oB?_h`cL~xXV!e$u-6Zagc~GVSfbUIZ2E>E(S1wq zXmSFtC|C*duI(gXq826Y`a(@?OM&5Cq z&Ob?Ike^#kPsabZy7vF8wl(zOL0JG&`ttdDRe-aLbB}ufNr_g7JKndNs|^aL%O!PG zb>T&&Cq!sWf}C8H5cq}tF@#sWXdr^p+As#*4FBb2=YQ9Fw|8|(VQT8RdVL3=593n( z&ypux$3YSj$kz*Yvv+iA-u9};!-~60ERZQ^Ds3Wrfja^>`{uh8-J%{sRsIdG<+I`b zi#fkH?EcRjsBF~J4WNbjP##$JrhIet+*_3syund4yIo+>pCC`$RSpOEK2%7#((pl% zCgrbLQ~uACsK4UL_daP2{$r~BzwW*dr})>si9&~lyA|9?^M6`r^G(d}u$O=*%>xb+UPptE zwaA<3Q(lh;urTbkA++L6Hq2{ZA&@3d6THfmDUB@|QPv{b;LYR~J9TBU|Ldxtxs8L2 zBwZ-t=0e~vS^#!9-W}foxcR#*1nWX2A>gO}sp-(GnZFfVJo?!Sd+`Bi+3a!D#mWQR&R10FVs%W~JMfaIrP(eM1Ef;6(j1M; zJwRLVvq3Pv=xk*2$o4f_qvtK)2B9lSqo?up8l~6_1#n``SenJEA6~3vG{C3H;5_{1PME56#xeH!kQ7en_yW(oUWsont_#n<+x(E@P zwyB|RR_v9s8JbuHOj4cfTg9(yYj?g}nMbzbmM&QdH$sCcoPxZ6$8vA7HAKR&uqMv) z>zv``G-Dsqv3pCo*)YRrb#gkEJIkCzI3?~4h`>;`*8!4TgfG1RrB#5nN4ZLP1h-17 zWbHg8$7FwbB5D0=i+Wxax~FMqUM-qQGkZH;J$W&a1)=eloxmoA=)Z|e!D3DC1?+=x z9h7DNfZm_nQ$C(AK=X1KSWO7s_$KBp4t)2?*Hy36!GqXSNFs!CH1@I0C?e3Wd+M1r z`rSQQ;IBr>#5#VE9ItC%GwcTB&IbuC4=C~SRX={`2r_IKJXuLD<58R8;WpfwxLwI! zShO$&vC<6`CtUtLO=o@XbXvTzly$EZM9?_HOjGxN+)6RyMg&}f`&(A`e4=02RQ|Lf z3a*|;{RV-^0jgR!-aklUcM|;|GVc@N##6+6I9P_pleTE^?&+smgwH)b&xUV(JUEN9 zb8{6_A%U@*e;t8lOv0*`t>AtD9q}CPc1N%M*B$uJE1}Qu>RzhOMbN$ZewMOR{`zn6a3;z6?@O(ZGetaYKJ^Xmx zU+!tGt7@B@_q5>4(_(wRGJw0G6p#vkH~^$QwX^!;QG3nj_Ek?x!c{F=fL!vdKD*A# z8#UdN$3b$w|CtR8u>z+C;oGV~P>j8Y zmcrb4G_x)=v2GBiO@59ES@u2nRO2{5&hG;=wMTAd@7MDS%{m-9KhD1-cj!iKkWYzh ztvP-Sn4ZNy2|Sl+!>=oF2F2XBVn>y_jyT zd+%+8uFBp%McZw7{QE5dwr2p+#*N{d16aE!3&RCsZ>zbH4uNngxEa0`0f*t2_{lC+ zg7n`;aqQMHgYIybPN?HH{T+voK(LWV7jI1HJNRtBC;c{)O6hYnKUN5rlRzUn(k#~N zaj5~hc3WVA?TG@rHG}0l^nNv$lTB2!2VL@>D3qg$y=fA@wAktBQFD3czp>tYGLH@Uzkr3YZ8 z{sO^=M4Y%X@%U*5vs;L|7h2f*S;-iMu;0q>Orz}N-Pno`YY^XKqdDz|Y!U{QodEG# zS)j9@@D}h9`ZVesz^*e%3S4#HZH>zFVA9lg)L{?`VFXK%mEOX_B#B1{NAShUulOpf2j8`<$g3BOeHMdHDmGagoSc_=osNoLG|4q!g6}qdXUCHCvrl~j zcuud7+uBu#uy6mh+-^ayK15H}+$ZkF1ZMq-d)&%uQGZKzIm?WacH~;$leEphV{AFV zB!0ts-q%n*qVTFykd;44$*jzSa0x%|R<(kWXCn3W>&Q?3TWYsPgQYlIRsmc!4`~H& z`UtSm>aeELkfm0vnk_nabVW=98XjWaH|Gaw;^XxoafYv(==qChEj%AYOaUYU2oIhO zuXVK>OHOGa`4LWIB8YbN6eNZpN`pPF(S)$9#+#EM^K@uoj2h%CL7yd36voHWjRLlm z$>>uFyZT?>Ey4cd#Nt3n+xl^Dq8w0*di2EP=`~pnG(-9<6RXKj zK#?DLVLX(lD9p(I5R--0in|^LC0deGRjxJoQ z8o*d-YUkD_1zR*LMIh+;^%I4FtzlVohThQl!qs`VRipWQxVWZRm-}8=Xlf#MZC)IV z%@gHpABGx-bxjgOcbdMfJQQ%Jl0gX47Xj@S&iX_Sc+z=QFa7rvY7j{ZjdG3Sc#j@+ z4>>2oG^EB+a^UJ0!BrdE?OvLazk#5a5eKTd{-_#3z~lb0S}{JY($`*u34~2%ou~Mk zoUR$oD79_>ge&f&5%*N<;M@89-I(FEVJzV++OqnJr-J5-8IrBb;bf^upFUZYssTfG z8(pjyYfEqn7bK3ZexpX;zj|b#7ggEq$eO;NF$|wvzw;AXhXDdzcCd0rMIstBSd5KQ z1U5?N1){hP(i5_8J2hu+@Oq*al=8wszJ(=uCC!xjI?_V%Y-hZR!uk7IxsG~SRJ7?j z%+T$@KH!pTKE1CXH3$Z9y|zu_*Q;ma=(~r&nxgsHrz)eEdZ&JoHftL3mE?nuXNf!I z>(urpt{u>yKvGAhsLvhmLXNJ50|= zcZ$fo^*b@&2%r@C9cQ96A&~yd_Emmg5l&R)n`}*Yk3&`)wOf)nsmT1PD+{T;u}~)b zE&1>=T}`-CL_I400N)tF=MXV`67MDrEr^T>leoW6dlm4&F4X^x%XZ{-qwq}f-TEqI zr=KT|gwM&ED_x6rZ-L(1-lWhf=WHP933grj z!k+-TcGF6|*^Yarv_iAy&xIck#h01wv>3pgAm!UPQXMk%Y4^8!&Jr;i!e(hj8wM4` zUKs?=`~1ezi>5&o^O&5J0#-~u;mkgujIIqz4?qR74WPr_`XS54YQJGwHS zqhTq4V`X{ynH>+1VHYqO+n2SsU%q7%V0$+zo;=O{A%1Y1WaL7O3Gx4_&nQ#7U?iuj z65bdAfgM4mf+UY<9^6eoT(TUyXH@PXJ>h#e*rj|HnV@@u?Ul;D#$U8-R-oLcmChe8 zOVM5B^JOvfSV>X(YGXUv=M>`@ksLuR*IeemVtHO4s;81xdytIPM22M*6wO}N#;Ic244Ftz>p~H|ljMBi zX}(&Pk1y{dw|tR_jIHw%p6&PLRkm>g2GsNPJ9jM2|0q2J{9w4cQFS7Oy2MPG7AR#} zj+12ZHY8&{?bEd@z1&%@;dn3Ue`HOD#fh4&eNRZsn`ob&&VcO>LTk8T9)te~y);L& z*jX}o`@AHo_(bMv$3FE=OKdi))q2&k^tkAgBOq2uY9r}ai6-xMwDHOJ&~)hIgo>45Qp4+)TKcQAK##!eZ7Zo3JU*n#z3C8rq# z7P;lhsf5-(0p7GfXOiCU2zmJyph-etZKwx01bzISRQ=Cbb$P<@zmCX(QFh?@&zOWb z!SiOtkIJ$psy@As9ZYxq=93;jz(9Cagv8)$SACFutKNq}MERVztTLe_#<5bIjrnG; zZ89HaQ-Xi%E$X)B<%jw2^NDfNYqf(bnB$@pe7$$m%{lr{MR5ZB|6ms7vd=Gky+&MI zl5(K^f3-S~c2xO6Dn_+7I2YXox#~W2mpFgCcS(5UU&{iGj5TeY)3G~QZnU9En&9za z>Etq1BUrXy;LYpw?#?j@0Ys?;WZv`Mui<&OluZGR?Fe0DnCVQEYn>7ge34RE;B%bA zx1Pdgqz!n<-|E_^(RH27M=0W|72hi6dY4WT1i8zxZ*L@wnb)ynBa} z=Kt%0PRs10I9v3RlG-c29M@^vJ#o6f4urqrFt`pfA{RQr)yWSveX1E(mN`c$?Y2WR zfGs2h-$l-QTkd$ko|OM}ER=xWF22_%S}%^)4t_xbZ!sBIKSsI_nWGF~8P(}rZwxmw zI-&=0j3mf3jC)W8Sk!_-_MbR=(~e)Sdzj=q2%)Ezm?^4xc$5uh8@m+e>kxto7*3Xc zlyxDaiUm`?b3#q%?RhEA@StjF^1AgL<($=RRjnzwt)C0L79ypsau};y1|T!W_1~}8 zUR1fvkqHoqeP-I10GWJCVf@*-jfzmpo2-&0F@_z<;M zXA2g|bNoVC>ylVoz;;nwJ|QxVwv&7;{%@#AZ_B@i#gDFgQLyw_%|D?x8C7&c$5J)Z zlmMa30I*<99v$F|;6mzOy6%?D+dL*wQW3CiQIWSpVXYmQPSrc^>8@LwW;^h1!}U~A zXKzBDxZ1S@)yG{7w+zr8FFx1XK9+%V91ux$1f?TT$FjT+=rwwvz z|9;F;R`Ytrg!+sPXcXj6XgCROu+L2@-Whh(%p^F4-^pkbgQvpQ4*!eytB8L=A#Xys zDY>LeujU{_(eNeGZypgk-?ejEeQEITjbLU;ZAu0cM^ti#SxowkZ%P(@6QV&%kMEQm zKbbOrWI?Sx;_d(k>kExVqGakmox#a6e6*IWn+D@)(SDdu0fw!JG(pJr6 ze+2PGECY6t)n?{w7U45Y?){5<3T9RBusm_XBI+thNjR@lFz}U z4#pT9$Smf2qXd*-CY$dQx}tjFP{-Ef#{toIBsHbMIKgFZ{_xpx0v3C%)zExDSzTay ze0QyGvpo@7*Y$DDd&}9N&c|I+$boltGQ0K$F)IIqm6_y$nNypyfc#dKJ zM5Z+H-u0Jz(ln0g_RTZr7CcutnUyCdT8lS;H4I`T7(egu;Ima~a!LgxTv-;Er>){R z;JG0;yy+YC%sRkebvb~QuRBZ-ADy#LtSN?vZ~1VOkCXnAwU*NVSF(}k6$W5gTKTY8 z-Edl&(XXla^4e%+9v!HN12@3_L28Y~OA1cdkz1}Fk>W=ogNfZ7|#hMu4K zx{F%WmZU31(2EV;jZf-5;&_tV^Dq*oF+ld&{&UJ6``d#XUept$#uKRz6ks$0iq&kc z=}E>u@>O0w+2yZet8F@6?lB3Y_*p({Fs1-K5D-yve6xO9389j&{*zEEo`;~asA(3o{62+e^lq@gFrN}7 zxu)xDZgLRM9(=II&6W#6YC*7^c5q-hSe3PcEZF@5;`NqsAZCb8>j2OUwJfdCL(Wg2$D>{5HpU7eSSHx;2)AaC zPX-OXv`_NsLvGUS>wG3?)dq=Sc;7HAH9CtAU+^yp&$kh{r9VCs{jqMMFUK7_1O#70 zy!o;T9T1=S{b-YFNl?wui2;k~CO#TJ^V&p3HGCs^*!6l?5FqY2Wc*Ek8Jivr)@^^@ zi*kJd&^HXu7fwD(;2mP^$vqC5wh&j&V_A8h7L^1seoUY9S&w`v;}o%HFpEBZ@g zjNhW4UdG=;>az4pX1*m|9teXW5A&R8DzEZezq0~gzP@O2(6n*Y@GammZ7*DX;EOSW zOWYc$8KAEliGW*vV=LLlr=|Z4R;#9hL`LvkZJ3=yh6L%zb!81c!MqGpr6GS;?8-r{fTANxqL}Y=(=>%}?0jDapcM{_;AkK$FI@ z;w{I6xaiyreWR7T`(O@lJ+hEL))$4Qk*02@<>QpeOuP?ARDMB9r{a@S!U-Db5N)q` zv_83Nlwx+Bb$XDsl0HpZnN+_@0t*A3gf93tQWddnbA=}IDnpZ-ggv{sF4>>+t2h}` zA@aV;^%!T{pJ0bqI+^qxPLKeG1X?n zTBpDjCZiA#XfZ(HKHg8rM?Ss+YEFZfGw6*I$qIfn&pPaux3B; z4NQBE&aeF?m1>?hBYoG)p7PfG#&H@Wg?a0L44;i^Cdm>MX{R{s7c@~=1$)VFQp0yX zrbr_R5;pkzR72Er4k0ff`&tjR)@-k{_*mG8uW0-#Ylh&bHjwn*OlHOxzU;Sfq1n0= zrmQ*o4N!(3uVWg=7~HExFg~IgrYYOYFK+e30&sw|?*A;2tggf=swf{z>7StV& zPA7y$Mx)^OhC58zfJBvsSp&nFX-WuTBdF#Slv42MTci!HyeGoW$``n?yTW?1#&9^v zZiBXg52%tCW4d$)^$`O(IgT0?Jp#OX%R7?5>l+gNJl_s3ol04vwfakI5B$-3p6{m; z;I{21kF?|v^Gfertve91(h7W}E2F&9-38^Ri6s`&@czFv0^4xLU}n zW>QLahrU{57qsRP_ZBEBD6{teF!dgd%`#2X=nv9DL?M>sBytk$NFwLVuRnvY`+2{! zv@P2(p}K0OyE=i$yVQ$LfLdK@hIW=OglXP_A+=a(A8BAfuqui=&@2PFPj>F!14vos z9KfNH&Zl?BdoQnQ^Ke7AQpZ+J_2Kl_x>Wp5+L>-YsnZU@(J}ZSLyES8%iXQU`a$5QCZ?XxGwQL?FfRG_Z>q)(N-6edc|HpDNWDFv^>5*s{=Y?+q#Y5 zs~uHAK>iM%dbszQiTsy?_g&O__RuLiY<&5j?buN+GSU{rUy|(hXzO;p0UYOUme;mdaSW5q50%;pYWgvS9LB{>sSg_jM<`X{zAi z(_NXzUJeUyb&9#5{m2r{dr{?LgFc103YQTTEt)@{3B;=p4uqRg) zq@N1N$&2zlfNvfRCO!Id0W)dYx*I!}cU9$qy9)`qFGVBQB)$L1<@W9IWWgen0e$MW zJFBi%F~f2mnj>*+`1{Mq3q674=~rB|Jqp75+Of0n9bBH`Vph!R$%pmV5>5=7`)6Kt zCi^plL@-%coCvs4wTT1ag76RxapvMi-_1V$^E8A%m+(|_jEsUjU!L0acza2<*aHva z^rIiy-%13Bds0op7sm^tY(?rU! zeoVE`^o^k6Pp9pATh#3RP7jmT>lNs);6P5j7>2g~a&t1>z4+|JA|8X`_vycgxx>5f z=0Xp7Xd)GoOe)0?gxVB>KN2*HSY1tS>pky{ZIsLdeg4KMK##^WlHpNVN${}NIbb~ z+#$t6rIf0qANGB5p7Vgz)*LHZP{qalb>gbREgZ1FO>-AMPUGVl;%cCLph;a9dnKv@ z2k}5EkJmU>w|J`)y zTXA_MyX4{EF~Es$D&?slWXho9G6i+5>MOapRoiW>KwPF?M`i8i_^!Izm*U;2yP;MG zL*uQYo5E`l7hICI${QPpk0cyrufMP(rz_5@oJ+gAza*O zWk71uU{7%+MIU(nXuhOTA8LR2ddOs(=EwdwRM?KEAVP%2$F;rv$=RoP=+_n5I-g25 zUeBMIS(Cq!{rbN}eoYwl4l>pt0?5YaY4du9^2PIt^=wNp^7XK#6ukBJDbgJXs$L&2 zb?53rsWIL8iY#jl%Mzu|Q1~;k@L#w7Tx(f#IVP}vs26PPgZF^>DC^~T-RlKEal`Y! zG(8!C%~<6n%0!J%lcQ%odA5Kt-bpYES2VOV@~BfqN+|RH9c?|3=Dm8MR&I-)POPLE zD=x{y^5*1%78U$dA24M^I0XnH2;XD_VAT1`|3m&?s`!1Z75}x@6{3cp^uSU$^z*8K zWM6QWSLXa~f1Idl4rgm>*;WouG}+2ut{e|Ud(bbp`}LatYGZqx77|V{fH(PF;`SjG z2(Wk4(PWig!XyZO=XKc4r}1{MCRg9xhUF*#|q9glhk<+MdY&$G6Z z%J*O6qn5(d@-5dDB){Cdq`BpSA*~2I9dyIanM1&Sx(++S*FY=4wAdp5*~4yuNVnvV zHgVfxK;%Eq$2Xw)g6x%y(w2Zr9`N7`UD6^CQlZPv5&=O?DK)?bmiHYaCjXTk^Y^LF+n&O$OtyqLRS+wssL5WS+y>HX(ih2F})F$jG)#rvijYCC#9Ep zy>~*U8TJqqmFDOzA?gaHi1ibVQl9y#0^3f)g^t-@RyiwqH~npOW!^_^rH``JbK?tu zvp%kSPQ9WpQM=Uu9&`ragrO`%F_3B7=9_HfiadC)!7J*x@MNP`=;6yPcvseJZx3#s z=Wl$pAL|gGM7@UXN5_GDscV>V71fJ#hzJRYk;(*W^r(fSbY~jruwBoj|KBmIj%Kg` z#LfcP?6$dF*#G;+olN*LS)K+j$oB(ySR0~v1B?<>F(978$t-PJ=OyW3XXWY%pPG;e z5ev|+*lf+W-*5XBtphTRyS8;S8=QkoFlz$SVQRs`YmFmF8L#}m;eNLLU&WeW}jFWk_LhaNTH zqj~!_eHFr$xKy$0yUJgKRa|&tL!>ZgKg^i~s=LyEFGNfj5$cR3(82UhDHj>Q66zL5 z9+Em7ysIwF-`J!l(v}KyH1~uG2_a4dS3?p&{nfA+-4{>fLhJCRDY?LXKcNwgW4(0L z%LM{2Uj4hFM`I4s`qy~Vw0Wty)~gOj38mvcg5(c^5v6ssll!rVWVSlUh-UHX$ml1k zPeY^b79<%BV-sx?t(U~(;*;0|DlX_Qv0G!xlCkH;{p^uHVnJ&qo=eZpcxYeAx5IQ= zRT1tZ26s94xzmlRaIq)_dA3C@0RAcxkazlw7GFoIpX@t1tEaF(+5G-Ib?1c-K7(KV zGH|bI0^=B;U%~G8du+YRfq-u3M=EP+w^?YKeU30qCJ^|Q#&3(#<^-z+Q#O5^K9~vq zss_UQEw%6EzRXlpIUtOFFkBTSN&4!;#>u7C0s{Fh%mN1UP_5`{^&%sLbg0suFHzjS zpKr2t4kZo83&=mbr|Wq~@GXh!U_6oIJlorD&okiTBo3*r`E!Q z=&o{fHix}@kq`M2(64B(FG!YBBq5!4G2Gxo_hMan=`ProDdFt_y)M3oW`b^kBzC^i ztK;Sr!M?rIiGz=h^~rcu*C$^7eeYQ;fp|DIzrQLhO1N_&VQH*MtE{`EkA|!54jkVH z0~$^Z5M1RY0&WeJ`?ai}4@4^AC%hiwa~0o_ssSnaDql-oKjDb>iD00kMymuvH)%lr zCH#dJ>6^<(CgJK-nLo7P2g)AM%W6Sdj3TS zGcfZZT_lr~Q%d6F96(9VLX)N@xm)*E1rL#nm`=OK1656F(HL?oyRpr$J__B*P%4}MWnZUMJ zXkRu3bGSv#AdHpz2dZaB;XTz>t>Ry1kq}h}W|z0T@mPaTCg6bk)$RLbL=}grad-;6 zjfP)QxUwFBD{j&qsRr6^M@8ZwY4gIq%mR-(Z(ke*bX-wr05w1F#!Urjd6IPe~q)I zy;s#XoLYF*9OG8A>iID0o(GFokwdn9o?2^LEq1_!7qmo$ZJ$Vt_CsDDPvo$Y4X=r9 z@oXn2tQ;tHwTeUP6a|x6)v}$>dCV0$4=@6v;2@yOe0d+b4j@vAP5aM~7({Tl(?FDL zAp(pnbw?}JrSKu5WXaYJ8nAWVp;had(P-9~F^ggY6qA`k$$Wiu6&cl^ZS|l^VTT<4 zo*905;Kes3Okd**Ec+hl-JZ&pMR`Cvz}^dxR%#BPWcJQXV-pS2OWW|BoKgyu=7ux@Mo8GBC{Cwx#If6UEyaBk^r3x=0XXL-67FLI-x)aB!t!DfuztPAX zh}zTW*ovFrtOD!0a{NB3`eDD>-AJce&|lc~=;m(aAi*%-b4Y$v(IHOu?!Vs}iZ9iM zT2)#Zx>CZ7&R})DZmOa?4HRH{MpBnDJnl+Rpuq3C3yNQ)+3qD2;J130rB?_R2NQ4Q zfauMnZ^&+A>)4o}DqvM}(v22Sojyc^ujyKOf6!4-OI@q$(p_Nsr4HjDD&`*AyQBj_ zt|ff?u|-*<+Nr6vxL7q?@mq8x(a}xCD=XFRABzdGGOb|l1n5tOM7Ig`<6MU6 zB+4b=S^J!Mlf!5O?!jKkt~LBbczL(DT>#VB&4?0!rmr%GBO%m(EB57m$!T zV0B*9)bbI|VxC^#?8im|T&{3a)oNWX?~^gg?Bud|VVfeTeguyJEU7N3hWqeJzB8X#jkN_!-qf%WXVmVZDQ=Zt zt!U`Q#xKt<0)7VLM_O+EE`Cvw%$z`XvqEcMampOvVZ;6zfz1l{_kKKd^e}=5)OOC zvFma@$2^+mRJ#|GQ^DPA)yTRhB^rN*V?AXBAx(w*>Rvhas$MYbEx9>Rv48~zG>-dm zJ9l)%AWm+?0+s-c-qK=)j|R8X)!nvY4+^a?Yli z+;xw~fPB(xI0{5?C~qymAW==4z9W&TlL{T7_q#b_PCZVxl5r7(zuM5Mry^K62%-IN z@EA$P>=tSoNjkwoY6Y1Zlm#Nk1%cQ4y_b}`dd6`L}q@>7jQ|CxFf7mb6QN?n#&cydPv$J4 z`Cz)oxMm3U;aaeZNt(Jrxs3-t(O{3m@N2c|2P%Ppprw-IvH=y!@Hw8{W2X+LOKvml zw?tpf><(B^NxES|cW3Vp6YNMxui{`*q`lvnpXcm!^x;@WQSzft9rK_T2w(wh9SfP# z)wcfns-chsyW_&mpps~GI{RsHR)@nV*x@gXs9v74dNvxcQf!cD+q)1NEPGU?jZaV& zM&Y9C`6C24oG$}_96u-eBl@eFyqc4RNZU5=#XurD=+?_c91!baiZPNqiKZO7%{_Aq zKX?6S*Z*8q1M*eel?Em)OAkW4OJZi}^>&9!s!*X5utqhjFyGxku%J7wz3lrjJ7%JM z*O88tk=F=VVO?d7)YBZ52qA%8Ouig2Murie=E=*}B8?!RGU=gd`o-P7ud$<67&8O< z;DMU0Q;8UKaGFkE-Ws9j)+#28x@0sd?$?L`vXzFIweg{IU+2 zC_4z=;_?E*Sd2~+V3&SZas)?6KY2&2ovIrX&)<4n8p@GWswyE8vEwPBSjgR&yev(u zuIET3`-z?dXsQebkpW zYPGIyHmjjXhO>3m@?{_N#ud=!e+2)v$DZ7%IfdVz18BJ&W_%q^>PVLWNkF#0K2Z1|Sft}J$oNzF;4Y@U9n>sng|=o5iS_A2{J@1C ztqZk;ArsM+eW#ffAZ5a*QBIv)WxyPvkwLd7!GCI@H;YiQ= zLNBkiRDg$*8=HhDqrbrn>G?zI@ZHW}^&6K<$7C+5t6Ejhv+^kf@~Me3OLBx=$@BMP;Ecn4k$J*U3g9<+UX2=SZM~$Vn8W&8243K+Q;UF)XA5q6#x0EZu zy;mHLm$TH0T8{5^@Du$vLnijE80>N+Rfq&zxR2bRvR`n~tbZo-AXage9SOyl{tji$ zM-h-mg!1Xuf2@I8UFv#f)AbUP9>G1qdcnzav5C|kYDxU@l$gAV-%%-+Dg-IRdO93l zGWl9F=;0lw|IgD0>E-?qcPxInRLor2S0qsbgI?%bd*xSpGV?}w&&3NC3993d%I|8W z(O|v(_We7B^aDhbtUeyTJz_HPXueYL)++FN{^aD7fH20vQ*h$IZHV}{jq z=?JPyi<=T{W4UJfbL4dh=n8bq*Y3+$#G9Tgo%SXh6RTjwl@8)vGk)WoHVW1Ll}-eF zcB8XXFhegI9D6+pGqV$>chxzLOXeUjfP+xz#`p^41a1C4F$nIDVv^oON@8{J3N%xH z1Nz!Z6L@Vdo)dngH08Zit1MmRIFIK!;bK#XN$oxz;>so(`6NkbnDB>OD=)W}r;;{)hUQlt8u?FGXj{*m7Kaf!ii53LhuKKXs{e#vHwkqJ>fX+S4UcGG^kQU&n@PG zIDPU0fsF11$Vcj(bz#{uIz)5VaA*e8LgWw6B?NGCs;WbsrE@U`pi!0Za|TmXyj^vd zyKa(FU5XOS_X1U#4+3@gtL zKHOh->xs>*?ETL70x!Y3$Rx+JAGbue;u6{=YXET(PAGal{(RycHED_S6B~6W>@}sp z2YG-7L^xLeJUd*4an=t~MbXbSD_*v1d{E~6f5uN8b@v~9vBV*c#;F-@0JKV*3bA6f zY9P{Nl*X1J&6T2a-vk9T4G!F=egI}OMzL2}A>lJ5izeUB3M3_43^+lk##)RwdLrMC ztmtmM8i~vNOSCRVtMu+0`|zlt7D_w%X0iWQh^5>IKnlf;a6E0n$X>`eJ-|X7ZN|ew z>ZRVY$fX=q<(f@+*(@F4<9GmSM{MXiP(;oF{*Dcwm!SE!Ej>TdO58?r?q6PRAt;nM zCgEfW9fg>yP*dJKj zrztfo{l(jT_>ICvjJ#~WkXG->ao**jQWzrXI_Ocj5mb^E&TV5XQALu4B&xHvbxZQX z25N$_UsY8+VYel1-Eh4-#TeETSp5IHSJmVBHn|`TDRiU}&63h90Qjy|XtA7Jp5rH* zL$54~GUxh~A>JK;t#O?QYo9uc!0TNIX6S3@#^+D2xs!j8+XkR~M zkZhx4exI+Ro*>Q_n8#HBuPxHo**C(L)et1#v^_8o%r@c?ZjIP*tT=ro7&fZc@wmRq zHPz$oIJP7ZkW zx<)XriRD6k2=^l3Fv|_e*nI0_w3_=~GP*LdT+51YKm4yGTK->81L}i)CdOPL*t&NM zky(%|LYmrBGh!A^^IkLau$JAYPRof~MpwMLYJHc&P(^25MU0SavGr$Xv$=BUDhqZ7 z|7Vvm`*VDtQU3~LT?fAl#Jh|*`J)icGN&unxVtlDj|6(ab%iXVlmV(=>fS* z)&bEVTHI9|{VSttxu#wfEiRtcBC{vqqCaum9zh{PZFhZ~@;*?TU)u~;hq+?|QUN;t zIsx8LB5uJF8eo?tig*>9YPYzhMQL@dk;=wgzAo7c?skwKQ3^M^H5-Q)#2F%qo?eKi}jxKsXvt@BpewzC;HxgRkZ-@`z@!jYnJyB`SotMmch2c zct=hCGrNoI;ny15U!vA+U}fzMc487nFr967=TBpmyQ=4Uc+%`l!dF+*xA50QS`Ho&lq0iJHH zBVJxyB^eyf$qLUz@~phe`HZ%noN^>53jKb^l_Wg}m;6*dS)zsl#Re-qTTASjq!tm) z1e%od$)-#D`JHQCM6O=b{6Y?j!pGf-*CI30I<6oVQ9CODc#dkiLS5&-kJCDtgcF9v z*tmuN5OgOnck+p)6^$|vH2E4)S|@1z&Ur>jC@#%eSIKdb?(AuoVD=X{C@p0FBXt&7jq0PzO5sDqfF0C{L;o6f$;w&ZPcR@M|&Y5KoQi$0E#tIe5IvXs@5ZnrkK z70z@oniD!6~w-DvK2JE99ym;R3f0$c28{$SYuYfM=Rn@ zJ^f))FwRAM%coz(YFeHPR-s7`htps>%3^(qZ!O`{# z6qZ#S zr5V#`!s6)A;Y%xrSISi^X|g6bO>q&Pjv?zA(nu#*koGCQuOhb~>BYXFuWNA6HxMOMih}o~cZ}0gaAkce@kx>$~_kZRU>qw&NazJ3y$RGQn6?4^2w!VJ2Uh zX~DdY5s}=VNnVqT4x4t*f2G&}AyJ5biTHB=noiU>0i;kB2S^h39AsPj>H{77SPbcq zXXCqp&7g|zak4MrZkaMj!Lu^?GNhMSr+%Zdy~3$M2dDrO7kvy@$yC@@bKeMl*Epy> z8&XF*B*E#mmCd0v>U>~yRAE?ulP7{BfWojF{Yqd<(G4uJ*d;c6K=u(i>A*Dg95!lI z*{Avt$$;Al#6O}vwvQz6n*#d9l{^!NBMp5 zONHs3+vH2JZ$~oDDVQ0?@^N_Scmf{nbr&))1W_jBe|_S`ru1b)k8bBq^}kjs#-EQ$ zdHVzUi^)t{6**sbYs}FP$XqEaH=b35eqmGemR^L|`{OS*Bs=XNJXekC*IT4>NWWF~0H#vu4y{0&x(*5_cEJj@okVY#rPU(P_(aaxfT7`tm0O$xI(b zy0hhUOx}oi4i7RaIV&tJld1zlr`w4vam%O3F}`~+SkQ5~b{Ds~cRVfM*xF^qUhMJP0sFQK7p)KJtDX7mUOQVCh2Y_`f_*#0i#M;ZBEA;XN7YEzgO0V!L7h#7ie6pryR@S?_kqih1gLW~>FIbZkHRwpiuZ^Ux6wkj68!b{+zQr&>I2>r=m9To z6@24QCfyd(4j#*Q=v>;QMpE_aG4Xz|&h1a>zF2uIyO~xQTe8N5;;H-N063^ z5)8Zay&X|uW?YH?%-zzWsIR}x;uztVu?n|5{f=+w3S+7Z_ad9J@h6$!2XI!wYrf?-nALWD}YTj*OLo&)C zG5}h-rjo1}(PVA5y$&Rj1-u%Pkh;b$8_n}{wyI7@9!-dLn}rh)<*W`58Ger__V0J9 z;FqHddJx+J)e-Kd&uoMb>dm#s`&T&eN(;N>jsj-*Z64fJais8iyE%Pd*65869@I<+ z;RggMJ3JXcWnORYS5G3BjLv!y5JXS`RB~DlHQ#9?afFgzH8U1vYQ3@JKfXzNhgV52v*R6xd*iyEc)UsdcPyjb7fnpGepYfcJB=0;JO3)WbfH7^Kkw9} zFZAepHy8g!m9dzj>dT)NNRQ(qU8R9J=$fN7vuiY%q#fNzl2o&&UJDS2j#!SUFW~^2O0qJ`d?mcp3IKZ zgw3_cf|X-N1V(nL;4#v*5zx`mhDHPvOP0gHnGEj&ReD{$uVeNXiSn8~-NYPC?a z9hXE$etDt;1gXejf8%lAR{Z{&uv1KqkEAR$7c-8$1qqA}BAzTFOD9C;3|I|Cf$_5w zsGu6-gy^FI$G%9BdtO)2Tdij-zsG}|SB zpuXl3*}sVjHUp5{UJ+Q=uA0YZ)DDVN3i#a18JC`NdX7Ojq1GAh?7Im;!K=^A1uO( z+YS--3tHvRAE&YiEz$#dCBIm@Z`%QHyZzq~@t^@8UX3qx(jQ>+WG0KxPn2h#6PFlJBj9>2dq=Ai8n-Pmf5=>C?^Z^Ay|Y$Ngj`P zSkua!}KCd-uR`_9|dufKitX}`)ic0fVfBS|s+T{l|Eqt^iF!j;*u`{+Iu zEKwKMxASOnLP6uQ^`T#T#*|u<%1>wMmiJHaOda9A_wzU&!TD3NXOwrz4t^3^@x%FC zVrQIhtf%rKDdys?8-aRsi5r-;4Zg-CK9Xili}!aZp$tk1Ne-XaB2As#e;yp9nZBK)(j)N8AIsk3X^@t4a?b&hCNGPS&US_qu~QGpq?rufI46OpE1ki zwGx~>B6YNK_~Y$L3&7qNqSdyfWaD155(OB(^wpOpm1f*Ic3}dqdiRsWc9Q6~$9*0k zmUSqQp3b^;&S*<$+J;lM=Py0#F5<4&=3l&R!jV&vFZT8v~5Ax98|_vOx*JBH8zkG_fFnj)2br#XdJFO zO-9AXNwBUO8}>TonWD6i+xA5KDCSpr+HEUnurg-1o#YQQjln*26R{2uSmydufM~Az z7KB^nKw92(Rj3e6!6l#?MT?5tz9AN0aj+R3I}4>RQY2&n0qCW;eHr|v&`Y8ruoqzC zG8K6B^Cx}X+fB2T@UJceAt@X-2f*Yf&%U*NP@YMu6drP2vB4AE;p6{M9K?^i)5f3H zuq!da{$HC-G;!;R2xefpfZ z!04?Pm%fU+R_99JDOq=_p&dM8;iF`f#`W-Lcj<3k|A;oZOp-b~#vZo!X?5EcPVBT8 zSv|ACut|rtoE<$3U^h9OlLgPo4P>>&I!*h_aiDxRI8KL*BuLU7WN_Ukse=JZdNh!Z zHm8YLXDTAMI$fzN9S3kJ4qoP~#d5sv`>8{%1%TS2ju|G@<}#t93+h3U6D1)%^bVn? z1AyVp^Ix@d`Kr=T4tSv06Vp5tr0-TX!H7pqm3kbxK6Tw>PZqGI`rMk9sYwvPJF!t@ zPLhlP9fkNr_jd$l)YKcksEtOK)x+j}Vsz|9lc4MbUmTG1<&7#*Z9Z&*te}T?4Vjg2 z)z$KX+KKXD#^?lG5FK<>e7=7R7MqiY)8joFb9rdkgN;=Q(4c9d>wr=5R?D0~x+}E@ zqN?OR3x^;B25rri?MB+;5unVO=%auoAAb}q7H%hoyT>pP)jxPRPd$0Vth);lCG%a9 zky`jB$LL%QjD6fqJ^&B#KJf>yxR81*-tt`Bmpv>;H<1XRQ*uvB=+N7^i?Hcj?aEaD^qGi+$g4{8w>hbr>xD-5~^gH_crkQU;YgP~} zJkc;sLO6B+MT$%0xFY=0ONSq-M|~1vJKD3>OU@hl*=q+O548o1*rqn8Ts>#=;hyZ0 zuZ``y`iCK-reSphpnYo6{uJ* z2FH-nc}9b65a@cVj9cJ~&76a9uCEFFndSxmp$Y~)~oj@eTGna z(Ym3j1*&8{pFHn=Ce?ObUi}`xLpP0jlZK=Ay7{(77||GQc{O?1#oYxb43PKHck~X> z0HV+47ZZ?V3nyFgMw4`LL#_M)2w6+uWs*irnF(m**{*~MS(M)`Ygw-qUYVA}o`Jci z9fraoQ7Wmrj6HiVrEZ=0?kD~dhL2XiA^Jcs6G*x`H7zo-7soy8IT|UI6&Tu69{ECK z*n5T7-@^HjDoDF&>*f!{E823>4u}>t1T4z`XWg7`n|({{Uas{4GfPR$x)>dXSfc<{ zQNvRkj=aqC=wIb`T%mt6L;Zn0#Vn$^h?^FzGWOvlsMc0;_riS$T|5@D;kYVf(g!Hs z1OyjD=QSq6{OYs_jCrI@XfqbnwmEJ6QT@37ix%2s9=t4r56PC-Inwb-3SXkA+ko#2 zUfL?_$z9trwMTum2r{}D5N~D6z5CS#xk~5Y?N&)F5TevU#A6D;@|yh)4JUup~`{?*1c`rS5I79N)&BzF_}+tOTd2$;Sb zAJLTReYjwsiXBOQiuHmS3&b^zV#nWY#R~p|+c$eJpaTK(mPrpb@#*j&V0l#yT~@k0 z4X%BwkSM^+fzbYq>at~1!mnoLxa)?x8ktPVX94f2<#$u4Y73?`j+BUg*A+W-eKg#a zAa?y}%mg2moEoER!9QbSnt;Wmlz+6hG8OG(6Q$EuywJ&W(u}M-cf@^xoCu*4x_(hEQc>-xwz2Z74Hg0XQS zL>4Y#gGT{GXW04aV~x=|bH~BAyNLXtEzCeu-%)Rpg?k(`cKP@;*MQU4{B?I~Dnc4% zUH(v+#w#}y4z&(MSQ+!s{Fk|@sf>5UE11i>iT4{~L_d+b&esN77>rNX_oQQ`yMVTl zI!~Cco!y-|BnyCC34cB$x9_GA=KvX{gs`at1gF051cC;SQhVUH`G&Iuj%f zfDgn4VF_XhLwIiq?BEE(d%`^ZROx=*f6tsVn`&0K2O?g=XPR(>xylj_jeS zR$xDLg+lajS$z)X8I3YY${2{M$r6@p_-*TMld3drIpO3!HYzTbgFW)8H3=$tD{`E+ zD0ZwT^_g9~cIQe=tSAumeo9iYZx>!X-t4L8b@-iwbrR5Ietl^dVx1Q>P#8j<-~)qgsC0hbx+f>y$5DMJztJ68mcB zhO^0IO`G7aY(qbl921nCMGdQSO{EkvkEzXo`h?pTy}N`GU3n6(l1LBF=l%OuM`cSz z{R4fpHBGm!wT?Vm%{8X!Ec|<#gED+w)yP&k!YN(-wmr9gTk+%P4@Cdg4i(B5LQfUm z|E{1_zH1yZ3^dcE5-l4Ue-Qztof!28bzcztGEY4#zJ``7il91*w1S^L-1LqY$fVOJ zh#ijg#PmEtzGJ(i+j4%)Sz<78!Hg1$8G>*)VVQ8SqV)Re+OuY17e|_5R%1`(=@vs; z3$Khjh+K-=Eg&cN{_E2d`MkPo@X!Nf=(CA< zzO8R<3Z&?J^@#Nmd}fR*Tg`0Jk+Nq5OQ4(}UFm7DJrr=hw{j_Nx*6&r{&nb%yaXr; zdwDP(0<7`pBC7ILuio^he?rmy|Bm(l|5;*63Rp3TeJq!znDSkW$#{pnzZw;>=m67_s}=C11bAi78sJA%(Xowi_dd!}$B_Y~-6p~efYhBX|zZt)qA2?H{ih!GCHaMDR8wL#v8T7sTw>G zJcYGjRr7j&`!)J0we=J!Spf5PP+*k@3NFjhA(3&gW5ppjWi;;plvP%`UhF$VClMc^ zmK@wZHrRgEH#%1NB<(DgOf?vE-IiD>ss@5o#D|3*Lp5uL?lDZXjz`ufO&-vYD#H*JDEi>zbX0oES zS2e6L>G}Awhs~gfa@5`MXu&#zf?sZs;zv*ItTnOK;3A)h7?^4*jI)jfJt8Kv)r4rp7%w#&3 z3kzNyWSI2{$kJ-Ua)?*O#8FnD&Wj3JXrq3+W77}THFOI!XIbxSQ268lwa8+d`B#?E z!lEV*^)+M)=hA@j$4mM`8=v?i;9#_6q(8R~RQS8?x$hiJ!7~Fu$230(BUB^+nXC=g z4bEd+cP+`eQB2eb>hPoPyHT88+FWu0kwjSOYv{u;p_xSB3A=XW?h1yOHB z^`lyOZAQS-mPk7}%(Gj1MA_xh+MA(#c( zU=!zN#W=bz8y9)?oCY7T3K6PGz9=*jdY6#8Uqm2}WfyXs7?z(=J9-*8f?op`xNjY< ztXdnRN9*Bd)7!W3$`?Vi zPX4<+_kfWJSxEabG4UcH^~bjhK@zp*FrSRCMbFKte+*kCAztc&A;ek>#QcjfCX$$X zLn4bJBWSQX?IlRFad*N+8#;mc1x{^5k6uiAXkS2~ZoX^c;N8;2n;6CmBFT`w4q#-R z!^gE(3LJ#f+${>ToRQke&2mjMHA~yK=)ASt>n^V?-vy4MAkrF=*;`-CSdV;~r+ntU z0vu}$byTujIa|MZBtAN?hi{27j2f-D!c{S?JWZQzGTsQSSJ7I@BxckX9bR~}v-!DP zymE^oLUPw|Uu06=k@6wQ^NSyvovXo++}Q&9z<-6>z#8F?;G*A-T&1I_IOJElZ;jEg<()k2S-C7&=_5u72(7qDB zPE<*TiE28Y$H!*iqaD@p8~XYb;AOw{iRt$dj064UW$aB#V>Jam_ilGSc)A?tvmIBX z%)kJahnK#prikytU$RXry7>2z!2iopWwft=>8sSYx}0OYn3bRYGL)r&Rg zz7_w{jO`v)k8M;ir6E}=;I0{aO9aB`?aj4k^uUL@D~x5xd>`6RGoiKmH==Q2E9ajg z?&Z5MMrADYiA}dVj=~m)G3=Sg^dkeY>Ch~qqOn$usWJH`CcygA|22IwZ(2dlgzkeZ zjh>`pjqtpga_Pd9(|y2tYxWSddeo~-6}nn_n>pUrm`2q&cCQ@XV@jebZ72IYZW!$0wV$+SNa~f_38QGEG8ZwG zgxs70_Du7S+zY`aE+S!B;GG z6X{vY?b?W)PO?_b&*(#a6DMFy2G&=Um#dC3y!A9VJxFCM6%?EKl%HKtL#8H89hzPR zk`7~g5OG7kR4ck?#1zuKaAZGy5}1SV*Pekk-SERsX7u;h{-33ko%yZ6TB->^+Hm@* zY&ghZsvPGEo{FKx>e)B2nf5KB@1h>OD-&H%eVMjfY>uK1mQM5Hot}xaAWZDOq*+8z zNqez>Ne3^H5W~DLkNBczZNLy>FUG0Xm*iaJg(@)5Q{lk@M0#BLZz{SfF-i#Ew31XaL^Xypf0_PW+-Y@RHp=ZKnvY zqh?#?A$%V<+MBaU^*mH{HI$qR(UhH)ob!N~7Ow(O(RaOVQL!y+P7tvs5uTK|xav6i zuQ_kYto2q_U@AK%ZyC-zSW50+cMHq;{;m7ZxuW+OnXOv*dAjob>Z7~wV_DP~xkn@w z(mfWgX)2ClcKbNG<#tC*|82e!BRX+>#lbG#Cl$*hMErOdqkW&HBtJ5Qcx273?Sd!_ z9fomPbYKn@rS*j}U1Z~PF^GdV_*1k$^SyQ-UgUTfx+7C#h?{K}YY3JEpNx6oio2!l z!8vmcnupr=mtBgS`qIlW39aw?K?@T+2;*1o^;y9OiId~k@ZAppdj&2`8K`)?lNFiU zL;m#u`FhOm6@D6rYCieVv&8&<>Y*P{k!!mznMkvF!bJaepVZ;8ES0zNU|tmKXpcVB^ppD!alZtU;k)zBQ|HTcCqLqBwt*#?(ca%ctSVK8DI{MvygOr(rVlXhAMRFHPI`|F3@*n6 zX{c3KkoNnz5#bb4QjHNhrC-+HPJhzk9w6tpO;z+CzQdcnyg>h`r;V#5LA%M zK-Qe}E$}C^&J(9oCz~@rZfS$qZYK3G`lbLAh=*~mp?*ZCiLEJm?>d`|8e8f0OwKa)=m+Sr%B}8AUBwba;A` zi}J1gQ)n5*1iWE`0SR9BNdTmoKUc3Eo4gKspY`V5Q)?>&^ip>I#P?42f$A~W{>aY= zpzxCUVC~*$n~f?xei!`rd*{O|O}Z}vW;z!6S`IcGnO}9?&>QE|EAk_n?qgdg?HHMX7J|0X&7&2re4WDH8!@wX;cfMC!*qp90^AaGZKc07} zv6f4j>l<1RUR7IceT<6wM|bc!TjSI;k&PukK{eHrI$}@l)K8af z@oXRnGfBXE3dKjOk;W=UBqz$do&~jOY;V&wZ){kvo1}&9o_&lqC#d^-_2k%h-gR!f zJX8bA5xTbY?n8JtAsn;>&{(O@PY*c(fOj<8Q_uMY;yJvU)e_fPS4pAj!GTu*%-QI+ znFZU!ir+7Hw$?krIcKuBFy+jcxV?iPcuUhVP~w77-`A2L3%_u8YP{WwuMs66`vYRs zpflbL;rcuhqQp#dW6`Nb_^9?XI*yodcJ;^BxbwuC>Dn9OEC%Zrq~|C`M8$#VO{+78kU?de zCUNb_=HZ{-nM&!>XmoiJL%R7k^~1Y^=nj98pPPSvic^o$CK?w7f1B8+?B~8VT>Bb@ zjF2@3q&@rPKiKa4oi#G8Y9B1u-5(OT-ZkHuk z_IhE|PPj_j_yk@1;a{;I`+psm{_7G}{YiLm&Hp=&8KCE*qN>%uia7w!=&*h+<-x4? z0>C5iP|I8%yyTu3P1qVH(=zI(My1d`X1^tqk^K(Tq!w~bzH94)$W#zGIc5Vc9Z5g$ zLsXVqG7}yf%#HA*Xcz6BLj7@rB1QvnybY?-PH$^u?BULk2q@pc=bq=zd$$> zoR@qBc@;dS&pytVRLhrZm^><4#}w+(J*hNm%lN0`g=X?64^k>&eL+K}{tck_V+Z{G zZ8(=2q%5GLA&w82w3c@p!K%L|@fw~|V0Q7nTwRA!7qEhGN>d6^@IQyyKk1Pc%1wE# zsiS`_tR*MK8$}$pY*9BJL_B0EaO-;A!xk2Y3f}pmRc9!FUNy&lesmp_jbT*9?*}y| zwObpuVutFkb1;jQ5mQ*7(W6BiV9hJOf&hJFsFs(n@e943R#u;QC^tb*Z=L0q=siME z?PdXYTyDAn@SK84R9{`5(QpV7HS?<8dSWKVRwZXvJ?sI5yzBL$&yx*UN)m#{58FCi zxR{tYI^zQ^LB!tQl((y3Z&Srq6*&tskcmOmzb2Cf$SXIYC8lXlug3EG*&%oRk=U3dD?KRvU{ zzehiNq7p-GY=aT*_B>N;F`CZC(GD^v1|B`WSxRy{E-6y7h3w%s`qx>inZf4@cg>*v zwz8;a;(P*ti1v=Q2JxBoqGw}MRh$Kv+ZXduG1H?0zgoy>@k~8>XKG|f(ebGH z^oYX1m}<$eaSV(-hF1Zv^ek&+mS)M6&Y_MbL%mEeyhW1twI$zD1Z334yK8cBOGd?z zbs_@PU{%A%6X8+=Y#`3PZ9!dMRFi*wY);}7i!@M#vRiao8Q7@d?S zfQ{-#<=ImQsMG1Fdje0qgJXEPr27$F(!As93q4_Jga~QMkv@TlFq{R ziLv|B!~$>2FbSUi{al6^RlgQ|=I^NH4~EPyU;Xhb&O_<2juMKhjiRVVMwP9HUeC%b zGC97iOOT8D%=OI2F^i&^Pc-YPPF+D23HW|8*qk+aC4@Xy|82`QQ44)|&~_60fTKa! z^Z!(!RNk1N$Fk4;|6WY}-}4iOg9QxmehJBig`hU_<*ncDM(6}NFjuJu`r{Q{fa|ZG zuW{%iGBKuUdvu{f>?7YMRR*R~j^OcFU!FH;r%Bc=-pq<G4a?f@h0LjBSJCc*->s8gCNK!I5!{a;AXm;N&n*x$M^o*+i z+`s%Y^8p~CZm2e2VFMocmL(WPd_t3i%p7cA`zi#{ldYDbDtTAHne|k@(sIXwN1%sm zxcPd)=p|K}ZOnmK*;UcB5ko%0g2O>W$<;j6`RL_y9+3mY3D3n}02!Dr{z1prFSgVt zV3m#@?THpTVl)wiaslf14L$MG!YzeH>sl{M+A!%C1)v7IiP3S-ug1sJmpS(r;HUau zM@HWJVRJbP=TZI*JF;^XZ2{+*s2>V67~|KL=H9L}j|RXB-q57G053oC+S^hSEOqQU zRHtWB%kbG(5Ph4tG#~Zox^EO!KzwSh4MaY2AtZoj`0R3YckY|WOxV+qM}ymIz-7L< zUL*2!ACSDe^)AeTJ#@9%MZQO@ITToc=Wj%_mLzGsNr1Rip~@obl2{61U$ubCEw22| zQpT1UkjC_ZK3hkvSpuV~QNtbqwRn2jh`MAIgEFG2T>I1bD3KS4K!fq7&8qJ2g(XKA zbHXyQr`mSdq&H~vBnec9?PNk;SM{v$7io;?OLy{^VL@tv=^&%g|gsHXX9q z`DdxlObitTi}2Mh$E(Dd{0N}z1R{L_%ACk{rN(`KF0Fm{swMS_P^{8LD5{yX!aKIIh?L39rg_eclpB-LlCGQh+2lD4q)4%F=(Q zv>-1Vzn=p&+63kB5!-o7{dD;AorgUO(C;aGHIG=4B5AeR7=Q?^yv1TXl$=)*u>FD6 zhUMj$JW$@Y0kOX*KdV(PmA?%%lg?Y-g{E*C~3f`V|mL#3DEBYk8 z7g9}xX!AJieea{i#j{|REmg^vjM|RbY`bRD+L1BF6fmoJREBeo45N&|z#6(^>CNQy z1$Xvyg9bJgUNrzYzbl{GUBG(=pp{lVQ_p6WrK7K_2gG{(ESOX^YI~FfV4LSr84FY1 z-A*>H)GpwWWwa?vNT2fH+Cd~u<}vyB&a2DEu4Ur1lRfx|h3b2z$bvcWHb0llXSS%T z8o;0`eFMN}2MKVcuMl&WfSyjUyp_U`V-7=dJ+Orq?L}rTquV2R5cCnap&Mh*eF_?9 z%TUEKHjHweG9nD;xGt)+kCgqZF2jzW%By^Dy6VEFjCxLzECaAGNx?kMRpuIkSf`l* z!sJ0`K~Q(OAQP5oCMWm!4PNy+b!H^M4BRdb_dRycK0D1NOJZgFDkAzsTnMoThM$sM zNW`wavpWYxvdKaG;>Wf(?*%MTr0J&&{rE|$ja0c>d=5s-d7fv1uK~jwib?u_HTiz| zCQBX7jIS)`WNv3Mlh*Q(y10Tqgv`tbmW0LgWtUCG#~rD+H&2fR%r|Dcl^o~Hr(?he z@y18a7~lyze~Y-QA_6kVtPjgt%G{5I{}7 zgg#r2M?-sVqHWuxnf;iYuHNEgY1d!mURo2>J3YbEu{L8%z_>d4SoF(Ucp{AD$#R|uSdHwakPc^yRTGnSP2Ok)(-=G2?NTBj@ zxQHUV8-D?|Wk3xcsNMw9;$H_nY&ofFspyT=ow;R}_c}Gj~ zB&6mute6~YUMk@IjEtIU{&)Wk1(c5Kf35r2;0xUq2~+(J1IT57^pJqoIlj+EOA*x$ zwLIzX1Npmi2jGM0W?_wev2Z_Ak|4u-K7zDgbj0VBZ`RQI=j&f?L!0WP`h1lB{MlH1 zmEFzO%ckAJhMGd(iQT&F(Y0VL_f%_BVcrf(WOFqZSPu{L4q*AQVg9iAzdAO?x|QHc(_kAev4oRNDJ&$Z`;!3+ zk$PVbeI(htyA=Q+5tHosXfB{?I&PN`5o7(IPknd0nuFT0&pw@;toKjodlQu5UZ`ah zO|ELxJ)_*pf$1i5Pd>Fxt>=16zSs+l4xW2uhkV{z#fsSP6*|>O$np+|lfvG|B~6*=+}Z2o04;Tq zBJSUmNiFNe>;-Z7=PpghZoP$Ky9=MaB~qk!a})JEUXiq1mRhHN4kF-qWH7Y{6MvqJ z^XJE@7%`Cw&cXeJ7n*qEI<_5$EEquZZOc&|*R$ka(B^NhIPg--EC!?8W_sN`U-aYX zn(V$a&Ry)<3dif_w&r?dY*gHkxB_A{U;@h(TR%S}=-WTlj5+#l#Qo2v1a4VQ0(_;W z@U?>b*TkgX;i_3Y-aD|mT%8b^51=!Ek-Vl~rO#D!quqUaH=L@sbu}CN*SW~gXSP|E z!&}(G40s0F;bztiB)_Io*!}sC{*OHYD?{=$sXchvuel2+UUT*>iv>}M*cJ;s7;3%D=s;e73YYhU%8$uN$@pW8m?Ksj9D;aRSY8 zJZ4Sa@%ctiS+1Dl5zo-@0$HfY*?98zR;DlU`Mr_5Pt2_@CHu)EG9a4CbGtPmr8X>j zG6l-a62HD4i&M3s8OX;D#c-1K7U(1gfI9Sjdl1{ rPDj1nrj>Z)OO%l066TUK^! zwaFY#SC!dPEwN|*xFYP9T9z~tJr=)IY0+KPQt9FR*gpORE z&mSk<|2%KAp;TX;Q-QS5kKFs`Qs!3x|BsJ@ye_CV__BM`gfD_KkMm#=mNj+2CO>1) zlg*&jGOa!^ywr`28_1(KSl>&#=4)&byuo2mr(V-g(!ZWotnrES)rZ8UD}RfR*Vj}T zckzEO=6Na{7cdVd=p2QK+1?`jmCjsq<9qVs2Y_&5&F$&XD(8|@dr5Tv>@@Bf5mdEh zjW=Fz$2*(@Fc$WDREtQZG>HtVw*`3C&zG;!(`R8HPXxApF{c3%ruUy<90_?QJPAhu zM~E5vLjEcCfpkia2g8l;xqk9)plukvkdu5b6gnANm4FnJU;awuyP5Tf8~vXO4flUe z-eaky%Ka2~J8_^v06;XNSkU^PA|2RxXblhWKYuy%pKcu^L?EJpZ$pj>b@nijI47`| zawxCEgP(c8V}+4W9PV6}q?xk2ae3~>>@pegiPNLwbqErtJ3TfpRO+wtX7HMeJ(G+2 zHf(WU-*QquQ(O}@>X-Bv6FlHiIP^mvnPNCGmD7E7jLWH308)P%bq;p_OJiicxuL;} z`~96i_kF4RQQWZ##jn0$Y&)O-Gu{mT0Ex}qL10d;q6;+m{`^0{5=8$gSpI!V)Y2LH zUuBzx6||%?4?xmqMs5Cyqrwpb0}}o7j`??9;@Av~_K)hXbf@Xsri~1hGIyWP4iZ2B z9hU7ilRvXFsw97h1LG5rba#H0=Or<>70gsL?Xgz^41`Wt|K8g7v#loq>=^tN@29E1 z071p4Vtj9-rx=rPDcL!SOY)PmM*T73Cz)D@yK59iB=3**w-Cbry_kXZ8GlP78{n4! z0IK#V2Nl+cIf07T`lti%b z$SAz62|Q!h$slHb>a;@msutUM`1G&)>W7yx7?&Yi)5%`r5!fq|mC1a5QcG>Ls8$Yu z)5PmkEY*0?OEG9+x;qOwtpt}1+d5$)&Py? zS{p2Lzv2%-QX(KBY5x42uY2)cJD7_d3ifIQQkOrYiA>Kjtg-oUPPwq%-=%(haZ3Ja zdZ^F(3GW!XH%A$V+Am27@sN3=sc6KThUWVxfbdhJcMmH%r;oE_$TK`NhFwT{YK2yw zipUnB%P5` z8G6pxtoQ0Ep=LBq|3F9|;vYkMRRFm_t4g+Vp^!ucM#*fxs!=w8HE0s_W@|u_ynayK z*uh4!ZS+suIp=Q(C)TSMo=yUpd{;>joxV?Kd0C*!wj;A}_cV~s;y%3sf@rFgpOWb` z-+6B-%IV?C#8qDnq+(?GvtDd$w()hn>_xTTw!vKkkwn-nB79T> zrV5o$zy;O{kfqtoDj5Gc5HRuI=7dP?&k_@4jqjhT?5h>nYt@*TdHk!=ZvZQS44$A> z!iN^8EQ#<+az%sU(b0moEcYQ|h)7+~&H_D{D#I?+;P7UyO{V1y-o-4&5DK(psGT7H#mL zyu>2E&%7F)3J`hnOGGabS>!B5gLRat+DZ)_1Fxkz0?5W$ntBA+JjUSQ4Zm1k9%1m`cCgijd= zLCsLL;TTZ!o;>_zh&?oC`^*{vz7bEGGYLa)RUPwLhUQ)71Em3x)(N8TN{Vlpfx6|A zTS&RBhQzlyR(aRawvdS^*gTCRWYcW5_@n;X^(f;DWEPZZPJzqd-EoX(1ipmCaruD> zF$-35u~!?3**S(L+U8f1XJfiYZ+?$4gt6a#c>1-dhN^$8o1Y;MD*dQlEjSEFOvbO71{ zgm2kCKd3LHXKRm6P>Yaxe{>HWgwsRJzlOlSn{trAgnOqP=;Xxt75T)}OB#B6(@8=_ zU>mZ99$W9KGo<<^gJ0gF=4T7m9sd0PsYJSFfJGaTB2o$tOEhgpv0HAD}UL zYJH*2IV?6%81I>172q>~Sdo)$z3&X`#Sd*lhxIN-R2bMtllAxT^+D%q)lfftU3W&z zUztAo!WRL_?RvwDW;9gDSgc8G?#p1jiMgEIf5X?hJ<5pG^o*&0@BSEW$GCbqQgq~& zQAf}P_m}y@>3eMNZ|`(D?dy6yL#rhAvwzZ4tdo3DF`*`PbXQ8e=bLW^Cb1j_sR^+6 zFr3LlH&lIiSF=ne&v0B_T%M{ao9u%H{5Ra`EqM&RZv4NRRSp1tre7%##j8%?zOhrT zSFha73H_tv(39iOMnwr)7kmp0Maf1n({VQAAoBQl%j(8uq?rs$=BwFXk1jxe<8BJ_ z&i8=xyVYM42aK8kbwSw%d!_=c%=ppeNsM{;Km7AC;EA*f%>>N^NmG}^pHm2UXwS+S ztp{FJw$1*KLuq}7Rmf>OR=MWT)vSmcJ|6KFYI#Ij7@kO4CquH2Zjk9n#qj2gQ$(J= z89tx)?MeDz$2QRQrBjcn-+1|A#0i>xwZp)w`=S@&0exQPq#ynKmjKYV{uKXXV6!|S zC~c-5rMuklh$R~kmIWXT?1?n5?xs{DJ=JrJNS;i6X%umX`e?w!ZVPJ1WhCLr1SY-rO- zx226dGO~mkLDTcgq4~UuUiVD$0m9J={0Vk0ImPn5OPue?mjTyMYewuWMEF9KPIDSC zb3b04jZTk~#1_EyswEktV>FZR6xD*Q>x}_> zSJ%H-5elFA*SsB9H6(+Z@9>^b{7>~A-nTl)zy9a>4;j0{zw=BUV7fokIk>fLb8kJp zG=AjJyycYjeqI_!SZ{kKpwW!EKT7|Rab}DJ5TCMN1WLz&1cU?tZYlvUhPd$QK~!aJ z<$o?MdkQcnfWkfMG<#V1h_vqCl8^q>(f$rxon{gISeXhFRgUtT(HS&p-)ijPJ!(Nx z`ot?6etf(8=jqwl<*)0}>1HE7k+20pM8?N+^u0O?2{cE4Ehbez)>LESl%NL;S zR@#@642DmKLPYgyZ%wbS%fuhPyh~IQVNhzLu_G= za4OfNl=lmtOjtcrLq$CaA45}x7L5!9O@Fwyg{%?)So#6i#lOM(pCfW8)ZV~vbFktm ztg6vn{;KkA*73SJ4}#7wb$#wP6`qzk7h{gtPT&6iXW_4j@p?e{UeVuux4>L3^(h*V zKpr8pwBJJ1o4YPaegFWZicZhObmT7x<09*cuml};U{x{G7ws6Nf4#M_GXas+cGT=9g*p)^v;`~_~*ZYS?AcDyE~?f zZ=)G94#s7B$G0T*IbES75<3mB`7_yO-dm36)7vjYBuJlt@_$_LWxdu64=>5sYGTBJ z1wUT>d0Xk<{aZNelyyykw_e_UHh9Z6pDq@IG`Hu_@%!Y|R4F_wt%P-COgqa^$(W;G ztJ0ws8y5|26oW^%o_^Gbvuac~q_|aJfY8?twu5{GNf%#mAAs?U zx9#1o@!P04X`#xfDbrS`J89VIr21*Di|AgFG?lAtL1WsV==P=SP?rFu|D1-}M5o(I zWNScaVk4aUJ3uv48y?sAR$jN%N(C#1{q>TF=}({fbXKzSMVGKV2Nq2V=IDJ^uxkWR z=rQD=juv38wfbLogX4V{gb80MNAATZ97gfPvOHPd{^DV0oiJIXNX3-9c;x2irRY)> zu%aM~oP$qtZx~Pm-yT`FGB7I8I6a<_`m2+GF$;-qKjNpkZmV-%gYH)g zAl)GY{%gto?4=89kX5nT+t2GJ$}Mx<0FaU+v3mi<0hlIHSZoLx4Y3}9Ql4p0v-zo0 z4m$OU{?MJ?V1!>(+yy-@e?A%}kH+$EFXte<)zqvr#C8EB+1=;tfmrb9Y0{r!j8D~echkuaUn4v;QemXyP zzRPXZr$e6SnoMTYVgKPOTMk&iAM0+IItG9v3QSu45W4}Xj4gD^Bx6WcNeV!itrp}tcMbGKev1g!+%}SFE zc)hZW6h2R;^uozOHA1whS5nAhWAw=fz|}OFE14eVD{0Nx%*DO+4F0Bp%%PPYj;f#Y zu375+-9H~!nIR5YKZMUmyNc1bt3vSj+mf$6E%q2pncSsovuLVK3%hnzFw^Qj(|D zar`>n1QW`@C+Z|u5If2Dqz1vLmW+_W{^WHpuoq$ZvImmx7g{GWf9kUT*(4tHqn!x; z&*xh4{?n#^tL$e@$w5ejd;i7L>T9v49@D>l3g~#VmWN$=$t010FGtkB620$HT|p3s zYqq`v6hy+u)ib*qHJ8#e02G*7A^%MbEa z`1KfG!>#U!n%_pOj7>f?;`?JqAMAi=`y<|V7sE})Y<-!WeOj+;ztd~{3RNTyDju7Q z-v}TFnE}#!^ne(G^{G-LLXOdZnOQK}j+j_wnSR2yFHAP;Yl9J!N1FW^@Is_vbd6>- zup6BE@1_p%{}Z~0#jDo9WDwe*f)4@BpYzgxwR5oKZN_@+abCwQvD)hj%)jpVkWg#` zP`fp@%VvTp~+MjNO5 z&93FQ$hxVR?J~kGp=HOCV5flhPHuk3zXU~$^3bScTF=PK+5Ai^?x)E??tS%0`~LuxQW+C&(KdA;smn`!UaiR*Z0;>yGdcuXEu zkKFo*mELJhmQWR{YM9I40LUykXhMT!qdH16;C#Q01hnSw_(18d^xdJhPF_KS-(=VF z@L5{##MDLPpvdzSx}lf7K@bdb1hsKL;N&NLM`m{&klGiH8U%+pGe3YOlGmx8<6F+mA*JoVB8}b8$jlbI%?}a!3;yQr99D?KTux&t;PADi{iPacsh5H`VbxvL+ z7%js*>^}j7wf}_|AK3s9uV!+6rKB9rQ7B*fyVZ|I?Jtmh%|cCl*>9fF>?Z2#lY>;W z?EYi}R06>SY<)OgE!y(kLMB{ZxVbUKTN_}BuG?N$w$s6Su);i*givf>je3+!>eMoH zApj^Ja*970W+`g^=`w)4k4pYJluh@FvvD(-0&3A~FS;h*{p;KUY;Hh&Tyf(Uefeq@ zhli&`du5+0=E3giD+f{3b>;fkN2AF$#DosJ{it7xDc~8(CLK|JlYm`AJ9R(Fn8t)0 zJ=gopWhBkp>{@7G`K(~srX)%L!bqYRAML}thW+R_vMrKeluY-yg0SS?u(BtBvRO%) z7-TA^)fTwf*0FMR?VS0i4qURto|P2=;`^402|`p6=kUf(~%6 zjy+&JtYL`|^BVqLZ^DP+4SsT59`-+XnFs|Pbx>WvCVpeVQs)#shlRu*St^Pdht>?T z1Bgovp8Z$V3!4X-0Dk3$>XY-A6PUkh>c1OOI2}Q@3#aK8A)rrN-<>O@ zUT3N>df9HPGxBnP3;A>#C#LyU0G>}(e&Qe<+~fUKF|8lPWid=EyefBW-?K65++BA# z`}1g9Wp^zfpa$S3-sQzuM*h+YkR=i}*m6@Jn^~H{WK&UP~Qe zyvUXa%)&D3t@sM&VF3@rGH6B#Iw5GkD!^!>)6mK6-%2(VQ5P&U#_dJCPdzZ{ccXr0 zf%?=NJ!*o|_G%fBkzkkO-6nCKrB&^74|Db=4>6v?T3b$7X}DKwa|3yP-XI+bc0(m~K0jE;M_hS{I*nZ)K^jo^x~{!rjc>+}t9@J;H`}A3ds?j^#*&E3e}D)Y+S3FYhYtpK&uG;-p+c zqH0^77@i+*TwgQMGb+9{%I1U-d^kqUJ!9OJ6MeqoUoVs) zdJ*&Er{5ZY2Q`WW!Me>HEBVR;cb#hBA z_ACC`P(H6Ha<_|wSG`r5Uf*S1r`4#=n8x!pb6sz({Y|gYIG@|?PE2Rb^y37!AGqXn zi?kb8>A{psT8^I1&iJOV(cPsRG@1D9+;yOFLn}f4i_Un*<*f=XOFjS?&NA&Fhl6+t zEr|wcCeG(;?G?F3xtH5&(L4Cbb}$Xvm2Z<%c-Dy0 z6uqecKvLRbCF1ysj2{t$HiIq^q4ip1YRFfPoj8h(l73*VldQqH#*_%zQh!NJB#;?r z`TW9CDslZ304wYd?fOnPsY8ho@bjh9rMAwPvfw9Hmx*&JpI*aQnJGsiqb%*+;x^X{aR#?IyEY0ESWEq_xyD?{}2|Tm5b|sCnN@rC@6M#@gtJ ztwrP#WOdGkIPEYH+3A$-WN8TW^uB--8#A@#3*pNF$lM0N940<=WR!qcUwJXW;h8HP z7m>*sR|kK5{5+2caXagG`1cjen_BgpR=;o(l)eZvv~Dp}SmG#Hi^UIV^&9;`vbyh(Q28&WJJGVDCa>g5&6_N1_h^ z2}EIGg$ep`1ppC}J;pl_!SQcH_>Djwgo2~~5JE!XLm?kkXPD5A{CZSJ;F8gp7AL7Q zVW|g}U!0m4k%{)DJpjej!%r% zMaMVDi?6q36gTzLk`@`=x>uE$qtMe;$!Dwl?Vi$xdlA`=TJ4CBxJu!`#oTT8H$?yj z%d)*3N&u|OJm&B`p@Mk&n8ZwOuhdj6eyrIadi9=GAIX3qKV0Q^5sze~pOVdDE%}Yw{hGR$Fh3 z@$2CsqavDmgbn|8xXRe)Lts1l;YSM@yXpi5E+U=Uwm)}gbx6?F(&~q8EpNc6%$^IX{!}WGtSI$86w`J> z8$M}WfB|hSVkSSk?RTMIEDb=V^zuyZohOuEdXyv~L91~6UbL_PXe`C4a*z`i1t4=P zN8ez)>-I^$g!gKt0qf~)$q;gH8ZES~uaQ=On31oLH7t(cWc%4oOa5@qme`L z^fNy@HNxyet+^Uar=z{Yqr9m2tmKc84c3+qpEtE;CxE3F%MzN6J2jS)Y#K(Zs9BZvr$O*k z@Z!hI8`{Q#XApYV*j`>feQGq0R^dJ~_o6l}LysdVXz+R&qZ(UxIYkhcF?xXe+-|mS zF}t?CRxAa-iQW|l_~o;AH}|BBm)t4NG6P^nxitVe)1Ou+kqX2Q*w>+jW0p68oe)^a zQlG+xnZZOiFK@24cMxAj39x zG60E{>W4*H4@g~YT96VtBjc_i+d|z~Sjr<^`&5}~06!24Xe|0?YWzP8FQKS#cP2q9 z-W20Arq>p+zczx|mbqxHo9e?k7Sj{kzq5$L8CINi-`NwNQEp>kFI4npR#>kATol#r zL5f|lLUx5Wmb|})T8x(KuzHVXFL7leD`(+SD9(D_X|7XNe`7Zd0n}r8yOjw*u=kAL zS~Ld6m9>-RL7dYprsXJnG1@hKZ9d4H>*`#%*0J6f4;5kVVUFKQ2#L{Y*Be0f8u+LE6(9?~4TPmTtCy;@i%cO*GDYQ`~d_A#%$4Ejf>>yWAm%<6!!_A+kJt@10u!P!HG%aw+uK_fKi znNmQH@WfMjx~Q1l;fvWB8(#r$y^&d;%6%D-1(8pPFJ^FMKQ_XivY0+cW$0k|R8_hK zVzx+S3<_W6AfIkV&+7moIGZBYVWe2A^4;p|7)M@%rXclz2>BIR8=egLvdV_qqN3%@ zwDDa%53$QCaOEfvE!#7|>l!dR`%MI6J>MFr%)m`xsaGus^v{HQH}uaF9oW_Mh*2y@ z0-8R6PiiT@LVyoWKkL9DfWA0(nvL&}mH|jk0|P12XjmL@;%*`AoV{8&wFF>{1C97% zxTA_nC}@zuCg451YMMkvu51sP91Ud+XM-k-XXVG>2hd_qzY_$j!GX5s6g`R*xeEY7 zzB9QSj&)PgTE096Ig*21C0-fJy-=B8CcOlyA<{G9{G}E7enOnf=Q}k5yKcg>V^1&h zd8Nf>%7m{IHP~b(ZW{>OV5Im>9WE6`U;yVLC$hmdfW<7MW&F6Meg64byu%qZgrWj) z3#Z?>xOamE_vf)wm0pq>dthJ72z6}F84ygLAzj}J^+URo*vAi|n2BQCIIiDbb(rHO zGu9oK6b@^&T#!c2n`(k;8>Z(74qFc^@h4YaqdB_0+>b(&l7#!coq|8O8@P+i46u=@ z6f@(kW=C^-vfEawgt3ZRy4x?;4 zrl=0C#8y)!AjMjWtZ-v1r+_O1u&7Eav3K-Fs$&JJ06Pa;3q_*0zY6$2v*ve;hOLvl z%iD&Rt~&S;AXC_Ra-PqVgNnx!ob3xl{Io=(&nt#?%WbnyK~CPr4R#yQJ1cQj45w7g z+mHonSHsmsNs0kH2dKkY(EW$_Y2?>ve=dITA{H8&Bx$hPw;E; z$;^m95R1lua{%zx!QPavjkw+Rk~{1cBe=)lQ)ho2M_&MOJ9T-FmAO(*AU2dBrYLB) zch8d`UZr98AK+(D40unH#>hyUUr6Mb9XxF9Ip{l_etEd_N;KjZbgvzT3!Ov2-@xG? z7fGhoyIej1N@Rr!kpBp;o_Cck@CWVzsUxr?SllPm}hYgJZ529e>{CMbnfGokon--;Z zp7y1BH`2xttocBvmcpX576GCr(c^Ww5q|GIif-~n>SZ=BOxeCr;%6Vl-8h9-WdAL# zFhF91rNrKThp=g42lr@;AJH6EMqDp7Uw{2l%b~0VRzW}0aE)s9b^Cn$7=Hz(%!oGh z@O**nxeC#?1#9O;p2Q6I9KDhPp?}41^o^JJnLVSP@MMm)LL;VC&Tax(Pfk^f7_E~r zcZATx?`3x+OpHOQ66xekf8ms-b9+!1-3!Ec5w@)cQT}|-O$uK@rbc=?kuJm4OQzS- z8_%FK=YVD$XB8dJT2NWuUSG_vl%BRFRr^Xq2`=_0y2bXPuqkM8=`KyJvblB`JUGLL zxIAALO-Ey1mld3r^Q*n)-doLo9lQ+$92Dlm^h z5SjzDFDhKZ(QazQYs5&!RXm6(!Y}pJDI{71pa)^px`2gUktwGyc2B6rNHe=F?9RO} z>)sBNsv5D`y2;v(*+qG`%Ifw`*k0&sMzVcfO~mtQ#MZ{&^0*F9v17Dx^IEcrpVMn7 z3G`O!qVwifH^rAhK7#-lRQIG0R?_$1ys!1f8Bn+qe1NvPu+~Mn-&idi2gl}8Or?zv za>FkC>(iKhz64o8UC}c3!7IfB+9TvItA3twr1-MDRsit79d}mhK#M${E{E*e5=~8r zns;%L%1+z%pclLWRH6z$Doa!?^O-+?2yT; zKK5FKD|L%$rWZ@Ep1GU4m)J(gYdc*LqJA(!L{a+6-bO|ji~3FKIUI)=F3d$;$M;i7-8IPU0EPuqhjHOm*QeWS#lLsmM$orf&)xS*}0a~5^ zRA6RBp`kvcCBL;o(F3;StI)rAGz!KC>_OgZRWN1Afl2j$H9tB2nc~IZntQZl!U~3% z--1vCIMe6oieX0P^(xiGFXUMLbz)@Z*nQ@}GRd!o)gP5$_^0i>LAGy`i zPFi|yRI=c>Oao|0bO41iVsD{mSN&`^ktXCDSq&SdPfU^G8_QUKLaIoP8=j+1T9`2{ciRiGv&%8%lu!83dYe4{FfTCoI(6*nn z^MK!DgOIuJ#=SsLl@OT{$HutV)3R9_v35mUQ#XbT<`o`d?LYVa{4N=s;&4C`0znjx zY!yc@P=RafaA;7>J^?x^A`XLl=fyaKL0^E_W`$y#f1mc>hGRj596I{fsgg-pjDzVV zgul*(eg-`xk6#U;^=Q^?(t`>S>bpq|oMzTyZ5$NlOvBQUx(oRW&c7SgPp6W-8u)|0 zzeb`BkyTJ%LYAPzm*kNzx8wM8Fr)BOQ5cnaQ#r?XPD&-xdqc1kJc%8>OB!{rkg;@- z@ih}-{HksJEt)~QzP&^q?#(=M)nL(mS!RWP!dg8BWQ=P*lWXMb>Y}v@*r5oXniNVT z!V{z7mNllUIBo;x25z5-j# z>xW7eHmZlK%kA_oi}d+rTF45Ig-RwV?7DPsad8)oiPDN_%n)hbee5>#^X0*&%?mD{ z7e=z;6PmRjrv@%;v~Lu)IH4{$1e!asLYjj0P~8Bv{8JKJC6QZV8JC&?d{HANZQGT5|1>uZk>E%m$Xd5z@%2 zBZgi%W|nt~7|7jb-RCUf9vzD_`CMLCOs=PkGO?lHSmN}k(Fg;$m%!Jm&f-l>(iPka zKxtiYe`eTiquJKP#)tguzxXYN?F5>GQOt5U!-x@K@y#pEGq*Snd4U#KT>>o0u#s^i zP4h)mHU2dRs7`RnM*Rxj`pO$+xsmmA@uJP>$AU{JxbM5zA^=$6oEBQOKT}F@=Ayvj zx7mksxH@pn1oq+K4WjQkJY-PTG(sLacuKU4qw|alb_ai1#a=h82cPy2C&1X1z!z6= zJ;d)D!wQ(_zsjqA8wD@aMAHL zYU7%P5e=blZ zf52gtHv=7^)p66WP$gUT^uje3z_jk=zH39s5k4)dQ}VNF0|XAdOS63d9`*$X&pTOX zwTIVl{w=&^0mY9>Z%gLv)xt3cca6jpfIL5`wc1C;`(bOjmCI>U5f~)$_eFoym){Jg%yfYd&=-%Cgi|cNXaBw?lN&ca+D3 z0Uxy&so-&d^T=CeM{LQ(S~R|3F6`{L+ojG5umRH!k<2Fyosi`)nDxU9=F{5c`kck@ zXUr!H+*6@-cL3kVtpb!44P%cRcF!x%dS?cO3pXu~-=+7fdr^RhD%9#y)6dFpWBhJ+ z4Cyg^z)`C-1r2k<0UQ)(qKvpYn(S9CCknAz8kQtfFHMKM!waK%q(AwD(3k8A2l=|! zw`Wcsk1GTE;b!lZ#|_-$f>amvUY*5)jbIG69!OC=0Q=On>rFgePgumTyiSAUV=W19{vp8 z2}yrmPGCACkKQUZI)ktgU+YADjg`Hf(%abLZIvvMynJCRZ6$DaVwlE8h^gab2E|7z zxy&p!*9lD(nri#popRUlgx@QY9dFbqQA-njh9J6qlBIY$;}z*z{aBi=v|5$4rmn$a z+HnEH)6Jqc-T3Ij&yMF%R37*#foXhusW@Z1hpiR?9}>K(M~StEv+Tj&sv^`j{~gBu zRat*1jt!1675;C6{&^5O5ILc8E+{uON22Q-$_9SyX7EWhlPekG=sy4uNg9#6qs;zr zu+xO4ShFyhY<(Z}dYM`fQtrZ5$Lm?WDBwPP;GX&hUZnrpAni?nX|Tt~b68E}4N1%} z_VW{V+P{FOTwa*<)ChC+x~beGklyq^MPUH$(K@;+&i$g%#!fGL?&O5WWc#&B=4KF5E`A zuRi>B4E#UHse|1EI0teRiJb0mA~|4{@pnYzZ3o(Q=3JW!e0SgXvbzuZ^=@6_uXZ)F z!f5Q^dT#9b?P!(6^t2px#l>F4X{u;u6l1H&#w*)`SW4FC0#+x%gHoUX&Hmvc99pKk zf6((U%n1Kzs^HA*pLw`bGI}95!`i)qRT36%tnGG-Cp_*CH+kdB*>sf8eP8zK;|HF} z$(!+Jxe6Aw63p%v@lD$=w!O^}qke zf!>lOVCmZs(|Ynn6Z@f)b^2w*G;OCxsWdYoEnzL1&rw}vMd53{w$;Y%WN3zKzZI$t zoxK+3a8qg3g`$z8bKD%GdG1e*cHnX7=ks%ecM(xsNo2P7woyQI=V1|1`7aO}H1q-C z1B&UEsYy!x(^-ThNjP%$s$2gKy6Yfk(2EP4F7qk)0t95uv0u6x8E%%ztG8C=WOm$J z^EvZ{Z5&+x=|D>Z=SP^tCOxuiJnH~mlHl58$EFLwbN{DRf?&duw>Dh02t<%x#8g;g zKm4#hN@lFa)AEkRpuFYzTELJJ+?)-~Gy$SK3qc)fZ!3NlCJHHjf574XRRMBIP#}E6 zDriq5iD5V~{VsX%N>BYzkHu14@sY*ac4?zNVgZCUOsAdKs03MLsZ;cWCe&hyS~dBD zLRi!N_3$c@H5!Zh#D&ecq7ipTQ{IX1BKR@mMYy#wrtVCW)Z>=zqz@Ip#%M7YGW7-b z=zcZ<-04A35qohQG%oU+nrqMM`0ej@ku+da$ge6hi6=|9GOc5J6~B>)Sz zik|9Duyxne&)5?kPH-5%iaP1)JN9nw`>_>=zB`ZmVLg9WMKT))8XvO59*ez#*8^}X zh-LMt54Q{4WxPeSu_y%c1aG`V1~`91(s!*6C}|H@%9jX$si2bsv|=0Jsz18GlKhmS z^f{gy8_NDvR0v>-vfrKC4)M%LHbl%0x5t zR9u^Q@PF5of^KcSI<>S4`yTe6;y{0N`(UkM`=e}}|F60R6r3EDd^cH>~*MNXtMqM@b^)emn?Wb07oK6@W#h4RNK&ZwliXwN0Q{c z`r(;A7wXJ@evBc74(6IoP-s|LLkH(Lp=O_bI|t@pprZyWnWX8}yOv6d--t4Z?aq&(*{4g)^eETR5Wa(`Kg#{CbW#jg`5fu<1x3Ym290WtnTQvZJsneCCM+dp zw(Tnc4comee8bf-?yJ$TYH=LXpB9-#Jk0z+#s;kx{|r}9wIp(Fi@-Ki0HNA3iuxZN z1vX|IxQf~FBjZuIY*m1yKRiI{n2sad!U596;Z49NizH!bjj#xY8HJ<1X#uWpT*pGk zMD;Zwv1l)29o_chYB2-G57(i(bK6>kgw)4?kiJ&iGH}l$^^BsU6{^j_$=|WNI^*Bq zk(yRn+#91jHZc8f+d6-L9&e@`S)kZkV^80L{7W`R0thjN#{ZM~{oC11Ns? zFgW^KFh|LSael^Chk{`E;jJH}`c(wMEsM4^JO`a!NCs3TA-4vv9Av)dq7o=Vk ze_`g8{AO;w5CfnB@=_=pg9$#4FQ;cq0|bU9<$@EGKhR&k4wY|yk$<)Kz$5H2_CZs@OiAxfF`rjuH6jOhmyJJ-2_+uhRM+dd%eEk zDGw+b3Sv!X&&w0!CAfO)U3^7e0*fUldZ+ric1=`K2$Hg%a|0+Ie zWyK`imz;6+i><3A`Lu9a5YFoc3xmpk;v-lK{iBQcfdd-43^oV!fTn5Ck)adbgeRFn zMQP`g4dIvvN3%d$)WYyzZwEgN4oh{Ibm=C9BcIuMu{3bo;JpRk-eMF%yv{{ciGgW( zP~`&+(QmP%Y~n?A1}PQ0ZG%4lLvYdZli}Xt76%_;DNR1#B4?oTm*5@5mtH{Ze3faY z7O*$k^GK|v8&qvNPOv%mP9|~9b0^7;0G4M&5tzD{Ra*wU*X#PWnfz*eq6~O_Ryb$!;5Br z3A)s;g1c%AtC!Xz2cXxsrMLa0z~V6gjqg*!hTtG@(nGgRt&?;guq)^E`yO<2MQ*3q z2Jvq+x5q#{*m_Pw5(cBs^GbjCbMPYaZaYs7ACAl+hm|AkcBl)i!+3!XX4tC-kh2>6 z9D0}B`t14C?T;3$H8=}!_yk;meklLRVbJyepP}LVar%AL9L=w8d$6cVbKpiZ?SU@p zwX1G_>Q_NOJ^;)JkB#(yq!34q3C_kWN(YiXO|Pq$86h$1zf}O$KpwDF==(z(M@w-0 z$4kEui^77WmkGb^g5d8MK!-m*PG%}H?`#C z@8yLh=%ybXK56A4GV4SN3WgWa`4+q2S!(V=?<8z;vB6;h1(C5wLq&)l5)}P(x)}qX zc6iY0FM(FUFCOd@&&(pY&eUq!h;n~o#vxOR|DUOM900kuJU@r9n@jx=B6fXupb2u z>=;8Qs;BmZUHW8=Lg8gWX+u=K(tPngACG>>M1h3c1snp_X0NXm8}HO&ToI|)5L({g zY!#;mhetCr!aYyFW7f3_iB!(8sKvD#z$r#Z`~1XrWMs)tA)lMX6yyHkz3Q5fMbks= z?9>Tv%A$JqKIJKKcE%lq?7D4_Im+Men*yphisrtQL8PwRpozgMiF@@(4+dpr{1A{IjNb^!2dJpzDdTf6eNnVZvpFMfocrX+iPQ zuxO?%?aEH4Iw&8h{-w-fRX*%(Q+MjcJAPxAHihs_b06s-TjPbUO6%1VMkQNMbZl(f zaUATPVz<>dR7CetH9l3P6WOJ1^()Dae8Qx}!?4R1+5X<3dg+yW8Cu^lsFR-YmOXk1 z^BTlj?;8j~i3Pj-t%D54;crSKhxdr=>l{PW_bvIpksAkYy(5CH;y9%S=qi-NBtJ+ffc@~CMZaGRD%by*dWBt(#WbMT=wobr&V@ZzU*nWFh7 zU7o9&eVJ3^ZZT|tF=w`BUVJzlX%G+~B)~es^_hsMGcpQ-R)w^Qr|1ytLy8UVhhd710iT;f*m#am!T5yAJ5-`%ZFbrT7BnrF~O69pXtsuhK4kModDS{ z2@5MOB&o+uV3au!Ycs>_+P;FQE}FsI=Gy8k;>xI6>9+lR}YPEz?$ahYjrHH+n>I|Mh!^m_-=jSEh zi!Wvd-MSm$_6l(d2aj8kF<2x$JiKAUnfc2qw9)neQ9!Q0Rbo?V9^;91FAkRvXLP0r zJm9J+p#TB=r~Zt2gCk||KMN;K{Y^##&?5ROWb?_NF&HQ2(>k#$JrFSIWZu*sBW1GH zONQhTDpKfm--dJfsG*14-9<*G=XJk%4@_qSrzY?|bnrb0lMpz&f!Ap?3cV!6<@`FA z!yu|tLvMko#s?y_`#LI9->;VvTGiDf;XM}^EVtDV*RoB_yccB<;o4({7ho?`0;o7_ znmX6yhruAbcZUjjkkekqmmBltqrgYe1oC}!?xH4_9LFD`rr8-8+{mRI>z(@3{8lZ)BQw&ql#3PwtYg9kuevyo&iwd!pm5+~MfUGXjo58D zrz6v-H}H%la2e~sTRMRE;JQWOZQ!h410FQy1|b>g^A$jKB9wU00vSKDib$W8$9)id zctdUF0V2pl&5RjpP_XXLcc)oE93le8S~-c!?CvsFw#8oV^==BXcVXsiMF!4rQ4hR4 zukX1jeSUsE>HoMu{jmi-2C@ENBI%DR2QG~|yxE1k99n=DU=xalhRny5&FI<1sA=i# zWT?V{oDRiE;+>#fUl}Z(xn@zChQn*z2nuz}A0@IBZ$hO>LV1WRNV{5PEkwIS9q^+pL#cU|Xlw#0|TjPG^# z&@!Y~Cvz;j;Ir{g$Vjt_c*;kc$}AW4HyQpL4bs^|NyRyDi{dW$00$HTbg`VoYuPY%?H!Bxwmsj2{$)u=H;Xfb)Xa1;D?*Tewi|b?nGgj}05?mqX#I zZ`ay^G>BY;em~J?kU0L60y`s}jdrE1Zn(790W9{e zr87fvf^A|0CEbMzRW_)D?&-k%6;zWyum?Ue8XMwr1ZN6`1=E`k^|>wyay1Vk0R+M0 zvA!MZoeIT<^lpajX zTa5QO<~RE`oDdWI)Htwa%!TB{_W&^Uy7o+&B&vV!uJoI69LZTErNLDs<3i)RLod2f z=BK#gk?O{PLkh1~Ro8Y37nrz?@itPxY@!SQN>EX4y#AD(MW*%{Rnx(1p>w~C9Xp%C zyd}3OJuDRT`SFFgbgdhwx}*3Y^K~}2EW?O_PUSBE1RA`P)`6)Fx1I;BW>DEKCK1Xj4@txy4s{4 z7KU`{S1+lIU=%0XM*gjxLk{ABAE~D6{%2$#*0)!<+7sl1zXFWrL{S8QoNh`VWy~MC zEpZ;!D}MsMyML^G`E{M^Zo^@Q_uVDYe`f?TR&V?^{Uy~*@QvG5#g7H z!0~KRSFa{l%1yJJ;ZY&u$17)etByfSD3?^#50-r-J$dknM&pmX&2OH{440$02m)RR zFBRW;XReVIJ$vjOt1o6lNdEYK@`i6Ne92fM{auB0h!3_~S?V$H_mL72BJv_xNf>`r-fr-z$JCJ^C1~v>(4P0zRG; zcLia=5?=np%!i=T5?<_MzrBZBUaXem9AEJ!pLr?zuKFfy6rVJhz1uG27U&c4M&X7o zX79@P%R!jx@%(;8?!l4nZCE5H5Y7$0X(;*V^)-IX^p&bA-LR0U<(}J}i{IR$csN6% z^&W51!tqEHco}Ot?;hQqdo}@t5He1hith*w*3!xR2QABtLZPxAXaQwo-YnpzGmBIO zaDBH`B#Jf2%%@64ODK4jV^!^?y#qK!2?h_9Gm8F7)S`l(>~Ob1(f{yO2CM5-g+EC4 zzCEd!RMavx29ptDVYZfA#UQRewDl|Y4Ex6>qo64JLQuC-Xq`k8;);u z8$|okC$qZZE?l?e@vv9SX3MRu`!yx1MO% zdBdpwzBf=MYdv!sB=$E>&(te0H2fQn;68ElL(2FSfE$ZhuAM8!_nhCIL@;&5soKi7JNYNsxNHAN=#iax z?Pa^7B{GKHEtXPiB^9h70*rP8fMi_tiekMlpHAoZWCj#=xI>2)5JRd4FfL?(3e0Wd z%+^e*YfP)RKfvPtceoAqHeh^k10+r7-mzDaL&6&!NAjzxBT)uN4mWI=$?86&}^!2{h#31B$c)oMz zI~n!>|5F~>1B_@IIV|fOeNa^M)8`ioW%_K2MpU>_=HUB27wsORC9iqRyGPma+~ZX% z4~B-N)5sJR5iq4wF&5E9Fw0tN)7#h=Wm{ZF>vX$ImocG?{U$Oi>GSv84rI9(F%#oH zyZf9$;O_)ezGN79oW)kr1;Q$LUycL_{7socm$Pgm@0A$WLb!($5 z6gmpD>CYbL&K3Y>{UF`%#q;hrUwxGU=7+V%xgX~T$LkKNQa3{_VpqD#fREERfT;8q zj`{TGE|HwUR1^|UAmr;swE=1n)sJK0iFrxEn}aK%3W%`neoD@BZ_-g{v9#6+p19}} z4<79RH=_8lgJ$j8-Be`~kR1`t$H!s>@GN3O%EZ7cLXyz^piHwdyW(&gUl@Y$)Bg%mfC#XCZK_`jHRR9FN+k6J{ zzT7|v52tkkQ_1W2Qmb_ltzO#m&?z^CvW&j`JjxoPgZ{+-nf;7aH~Y$@|5H4`N*t`# z_Ww0%KtT{+97qE~^utFP*3b$jJ1@||mk6%}adw((-SsI#JC-WebW+pA`ta3vwFN~} zB^&1Fmc2eyXuAL%Qnt}M<=9dU18O*@W+O(+O?TO{p3b~=zgo9l!!@K<5!5K)v9R{WhyJ#~Zdb`O`8YErGZ|#-*9Q9;Oi7PtR?NsK@D^L8G-f znV0iqJe~S}ra3+YZw@*&mNm5H1{ja}z!6*UHZ^7U^6Tvs--`fh=)rrYJQP;4+b@Gb z`Hl@gw?&V$+cB$G4&axVvXmOpB*q+rcZ{ug1|UKi?<5mEX*yg-g6b~Um8dfnv$hk~ z#OiHrbDNgE)78mQq0sD*oujmEedJOG?3#H2!gzAO*M9X4thNDY*KjKZUmcKf#}n@t z>UK81wPdGPsaz_b2_4K;1~T1D6Tgm1#^7{Sg2dUFn;$5D7Q{xyeZe)F~!)aTG#Yp5`W zbCO^~YyN^c{njqazrm57PX617`9FI7Q~}2ap(Ofya7p;xzijtoZO$>p_^R3#fh{1f zOPLLvSReAs!?$rLj#-cP@TA61S)tKS%Oi8y)@ZJicZ-n@5zfe(A8&@!*sgWEm%i|= znmk7zxBK$Yx5@!nqGQ4v@Yx9?bfqFD&naY|g& z%;(NdRQCClsVH)njLp*=4eh2j{p~q<&Bs&(DO?q^;K2HpeBK4*DC`7MPb41Y7 zOOY={E2kL;z~aSxJyx$aI+uKod=;j=97wbtUP=S;r}Uz~{ZLCQX1Ba1{9^BUb_!bR z)v?(E#WdpRfMra%_IaA3LucJAVO~~3Q3290ozY`)xOB%wguIHGvH9e=w8t*BnoBq7l7ma@5;Pobt6w`NkCz6W|L@b8lJ!Xy|YJq#a zTiZ4;Dr7OH#s$a1uGc*Y@h&`ldr^neKp<2^*(*r4_S;#Tq5io%?UR+ z+Gq<*mz@O1-4m3ZkiACu*KMe;)#jvAr(51Q1|;19`uCAZt0M&NSKFxs9j}? zUsyuHVS_@w$IoKGH~wi;Dg6h-dS`vE;dy!lC-G)yt(QO{4jxQRm2cP{SvRB6jc+22 z%|x{QetHP^rufi$^r`L+aW@bF+s_(TSJ^|46};`{%nDLkQr550WagO9*zFu?cY^-3 z>F5~Rsgd>lMOB`}5|>QfhQZY8d$_hkg3AhaozJRUgJj5ep(-Qf6=1D}${l%w(4~s@AyVm4&}u^r^$h zY;uUwn*r#Sg13A1yb;WiT!!5?VXnWn*0G(=8T17hc$=S~`=c8}d{qdcJNz z=X32?oM)|vg!creUEnTvc-!^KL-Owl+v-SS0A()$p-!&mQ{nD%S|@qRE+T?vw@*WD zC$3U-PkJ-MTrYrW7XDhJ9aa|s!RcSl=R1GPg&{fV6DEj{3c%dxJUpaJovf~?T4BT$ z=iuXJ8pzv)@A^ zb?j&p5BVQ79kBk++5IEjg9XQbGU>4guW-Vn4CC4gv7_>gJ@F?x&H(Vkm3mD0nX*^j zxc7WCz>(IY{P)r5KyQ+gbqyC8GBRJl0f3T7Ky;W8Wm8m8nD@HKqVbOVQ+l3kVm~b) zMyuqzWjCHaX9`u;{xt#_m2fbv8XN$Li@Q)79%tBPm*5xjHcm&Lr{{pBeOcZCB9jeN zhqo2{nuH};_<)OtZ!cW~q{U{%-8U64^SNixhXFwUM(U3jbREz=viI+|Mc&PKt}W54 z&)7=lc%M?nnugacs&$cGWeQna?P=ZO4j}riVY(b}i&}W>?nR7~rY1qMM!sJSKy0l2 zMAYXE6UmKqEg@&g+8wZSFnkv3ywV&A-ugla2_tAZYXfG2j@JEyb@?RG27=@GfTU!K zwj;nj>D+49=j6VX9t*K51F_Y9hWPlo$7*n~>+uu9gKVloKiZ8 zUC7;e^R^B0P?orS@snKUM1b~PI$!q_Y!&m!quImlE1`o}us811c;YtkJjh)41$}Ux zIanBzzusIjJK)N%Ua~3gy_Q^I|}U8L%va^~bW_m`LmL4T=K!h}QF^F7Pe zA3;jA<|D{&W3kP+F#yuu09hmOL4N@(xbOfMCW5^=fa~&6Pxqd0oc0sjt9yKGr|=5) zzp<>UB2cyuqK;zGe+p6_G(>UqO#^TA+?piH!2VI-13VL?ond#^_*GHpZ$CN=PplRMucvUhZf?F!6%(s*z5~BNNh{}!*3r2r1-nxF1cZy=~ zX?sRG_Y%h8I^gGSCDAXR?yvcJ&6jNH^LMaZdK}p@=3|8(4sJ^s8)2OgDGoT1HrT~> z<^73l=+b?cIAJ(;plkXRj)7LZ0Tga%WvqAMJq))mM!G{K*eK_J*|PhY72okx>!=a19-2nspPlTbZf7(WN@8+ zSRv9h#P~1foro#Cl+5W%mCRnY{?AwIe~~x+fByWdDZuT5RG}RTutN>qlos_B2#^|R z`2So-v|)J#(3*rLWjnMlTI+GDyXCn$*jUZ@rDNF$O(E>t!=bmZ7|&l>Aw<|zAMc(V z;)G`NIK3CB6n*5XUT;r0Qq5ECGUR(=#!mefA8KS;Xy?dYEr+~>$dhx`H{1F@oW0kw z>q@pI_O7q!s8`$);my^HUAiI!_OH`P3Rwm~-+OHIq z(Mlr2(|lhGY`OG+Ncgxt>?Xd-3voFeB(4~T>Dw)bG~SVsFBf%X+y(OM@R=(QVs_H= z(41P9+|a^wVRv4|nqIqzYM?QMK%~9we90MmQBOZbxxAJ#)0K>$!&Is(n z9Zt!p7CbVw{!f}M&=M!xRJ3ivHoSV0#>PX{Hoa~SuDmlTfG;2QyClA;#32`EYX}fl z__a1{R+RBg9k+MwhautE*mxR}=4hMsB*y;bhVf_OYirhO4cf2xO@>1Pz?ZwkK?Ccd zIR`V)2mRda-C2BahO`=c1a;47gX_DP#&Sp}hv6<#>$eegrK;Ay+M6*<0#kK+0}V%2 zl%Fv0%g;A^QzW^+W?T*EN%BnB3y~EH=V}9&ub5MMUi5(b0^Jy-T_P zkHEtx5)AJTGkH31U?HvW+9p_A6D8%Z{V5;v152I9g4R+})|w=4w7XnUL;=%!MY>F+mvYXOzFi8@<_-g3>3>N_r7YVK6u1LD`GY5S;i_u!eGEMKPbY zpVMAM4g>H)upTx9o?A60lY4;^@!MYOcZ3;?&n%?>E4xw3GkL3(PafQ{bN9~JTNqp@}H zsmGy#(fXE`6h+y|YIzG#OhbTP$+Opm()L;v$^$8?kCoP!F+O^7;Wh279C-c#n?GX_gMRpmYK&w zt(9Z~3jS{<2k=lGt(EN@3egE)`9mV^qWy*z+pYHPZzlsTS3uO0OYF&LgDbR10mM){ z(|cwE5?Z0@rxbz!$AINlSL;)~^O?PqeZ9IT*HJ5ugfu~B zOrMt=_A5;1tygUL7(QPE2Z@IM^n(3 zSw&R?ba%BD#;_iF>7PEyax%Fu)N>D~g!4Zo%O4TRwIqvERgFJ&4UqOL?@qe5doKue zzLtNbP`~;=vuK!`fV3K;xxac+um?r#pPGZOE7ZsFeEAm@Yev75G=D*|KMhN-1jj!c zBEOxR-_!O|BwSk~zx>Fm|HWPGm+EN0azwwtr(IMaOv>9oW%&@{uXR06|MOY8_S&xE zm=W-!urmSSNZe10LX7d1oPXgl&thqyU$KiXicB~M&Fa51oew2^A;!_uol1+^{q2|# zcDFv`%KMd%x!?he{KAXcE5WDz)4uu3{z9LAxp2;(PEexh#lJ=I(c^&KysFZG@ww90 zgTD&E0zL+Y_KN)dQJ>(7GQR3&U#mX+t#8VDK~BunNQi2+ZFpMi|RD-q-5-Bt?m6WcN&hnr(oZN&l2%l_Z7@ftzG80O7jg!-U@9q_h)l(wu}a zqUqT4-A^N7EOENMR0X1Bub&+mg3Wk&Uxj!uIR0@gR+VyY;|tQScK6_MOndem#eWH+ zYa;!o?$t*z#u$)j;oCNOf(gsh0VC9g<+%`JG#y-@GyoF-DA?L4Ya4)uT#UV&`fzlq zfOqiE-FtFTZK=Ilx2JoL29$7oYcOXZOUAx;qH#SYcd@mr2=1>_*SK^?fIEJSaK|D7 zTT@PirFBl2NPt3cq|}dS9HWVCZ@@3t`74`b49uU?9dG`tW$zMZeb~H`NFr`^=pD(p51Iw9XV@;6{GgjWd+l`Yw9}m|;=^G<%f^ZIWvWz^%5~&@?OZ;b$N6 z{j^g)2wtaj&O2miV+)Me>S>ptO+oWG^rJu0cqrAGxaV7XmF1u4lz&fD%<|(M3GYn= zIRX1_Ez@Me@I#aE{ShTfghrAcf$?1|y{=JvBDS=tz7ZZa3;c{Zg~g3v^ocR?S4@qi zP*ueQ0eou^Z(N2B57E@`WBsn5EHDsm8Z_tOVxf1xgXa1N?-QVJ`azv$`@=FwF=N-YU$_gHV`v_id z9F-(ac8#<)ly?nCvux&?`}|NM-+kYOX(HN>uGT4y<6mG`e_0pGzB}+H{;9?3kM6?^ zs0IIe@}rXh(a3ySvjIFkRPCGCb(ac_cbBF8L3})JbAi|Ak0mC=?QOxb)5i~BX5$jT z>ppx!a}@t{5yzNv}-M zrotnJ>K8lff6qEX&DjgC*2UWvSW*sb$`z?QrP*+`vQ7WQeggw;{wZ9_@_q8FseSLx z=tPEQyjVU0hb6F&Nh@cpwpBokXL$ci2HrTf=a+yoRKOi)4w;`?Ek3c{RZIj@%<*Y< zP<>~Gvdp;fGwu5Hj=CX5zF~{MhWmsC6A4iNp!^&}K=DDMr3PPom|ugAByWQ|+>i$? z9EYS>skgWN>%Wh=e8v)hQO}?z0af0kD88Ccetwnz0Qe1{d%6GftkjMkrUBk z8r4!(wr7s8VvuJt zQPS^i(Ph~MUb$K>k-q|suQa$1zBA%)i}6u9zr1tjT7X?x=oh`_58&e9(`e%qfTX`1 z_g`>B@{fWo{=+ZtzXbMQ+UBokvcIW()FvuF>ils=a6ag5m$2y_q@>8kjIdgMtWry94u>MQYSd$8R-e;(}n zU9+N3YWDlnpsb#H;J=H3AncX*H@JV&6Q5VHBX-)MfQr;UoR7R7l3_~yq<)RNuVQL4 z*^;>Ja_>B({3eEP&U|^H`kciDrCNfu)}Xy-N(bxZ^a?%2ZrECLYxqQyHvR6R3m!Cr zxvO>3v2*03x)9A(VZ=_9u6O+G>gMF=*OIbPx{G&30GQe*(Z$N%Kb3hmQ9gMd0=Qq} zh3QT41Y-ZGa?6rZUw(39kU`xiDta;=7-qGt&h6yzv%Id}E-ph*G~jjFJqwr` z8q^W`-3!sp+|^s>_m9#L%4;o_f5k=csr(z>LY?8-r+S|LM+MG;&7K=~maq{3n7(jl zdx5~(d=JQwt;wr=#m6z3&wTg{LI5F-|CNxqGU+1KimeyD$M)TT4t?~lca0hX5F8_T z-)l;Kezzuv$>3@SrQaSMn3fS3&Sr|8D8d9OWfE5B6XaMt%SDF?w*5*7kUR*R$(7vM za=D>|Usrv2_gAeUyXs~HNXp#cflt_dE;~kJ?=(YNfM_~ax@5?MP^qi-)CX@Y;GnN{ zBfhR@$CvfL+GAdIHbm#&du3i4eR1{nF9vITsI~sJEWN-uxPhyn^@|zO3w2)*oxOJ7 zY43fli~vlr>{$I>B(rdUZK_&t^3aCKwKKUwO<1*WJ);q%WV`~{JoMTDxfc8%ziKB{ z^Iyh}{LemLTP2sh|3|S>>rWMoZ}KZ97(1RI_G}8zBKH+V#lB3>vo;K6cS((vSm2s} zKk<#aU#A8hs(+?e8qwpwy!2H(A2RaQxEO?2%LxkLlc4+xk?}$A*Si#QfX4%DHGE4u zlm#|NlQ%Gbw(XZ!Jmqig;7UZfblkrnv?#``ePvPp^D4hgwLp5uzF*~%^kW9%X^+-P z+^`Q|H14OvK9Rclp7u=3-^JfWn9ds-T7}_X$-okIWsdwxc3(aboR(9DhOw*9lwQB) z?s^-vq(m>i$1Jonb7uF-u>^t_6FyUS9le^`!5 zh#XH>dZ)4h+=6l?bYB+z`zM05mj4X+wZL-KTr=vdhzaQ>`)QVxf9Cos@@NqB8kujv znC07WbmVm@``Xty|H=*f|0Yz|=UYtoX5Q-msIT7XzhTpE8@rf{@n|L|{|Soz&l2pv zOI%vwFKGNl9$I?8_1C;Rw%fR~cD&~UkL1-O-5c%THtbnVYZsH%kHPZr>$Vy14f9wzj|nk`rbl_FEzi!LvHmE!1~=!o_C|HC7@AlY32Y+h`wW*X}5Yl&|R+BIq3Z> zrF`|$!(oy}()l4d^ecXiEgkc~NrPrZWF$Sx`y*;k4eTy zu?X33n1kl%j-NVy!sexD$`z6F4+7a<|Na&Og7fuNHt6Z^okCrl^6n}WFn+P%0RSnA z-rfEyffsx``uUz~Sr=%H`j6DT+aEtv{b}$6UvVPf?DMM9&sgRBH9MDUjxng3RPg%9^I?Y36L15(&p3^5sz-@i| z7Z0$~eK_}e=qw{;{6&h2+)ey+qQyW`AySTy}7pSNDig%lpN_+ zGBl0qPb~E_Azz!Q2+}*~*)reC`4cNvuN)Cq`LTGrZL_04W6y_Y!z5+LOHazZMo_r? z(DP>kAF+1DWCri zQw#!#6!~{1LG!=k8Jzu{&d}`d30lx8mZBt`=YRXp|Mq|V+jqgY|Ic*HlR3}8-~NWe z2=jLs`#Z|G2=ku=@t-*LzcUmb|Ls5i`)^ghUlabxNm&azLZiB-E~Irho24$CPlE8OrWr`zGXk4u(y zJ(vZ04qo7&DC^nlKo%SiUefRP;6+}a^}l|9XIU^0|8krNJ`4ZyS@3^93ODM>;-2(g zGl@A9iP;oEU%*-LXVV(opX0CBpU=Ga=MyJ*a&*S^#vh#dVfnb8pt}m)XmI6c3C<0U zyZR2Zip@TCn=Nj4mIHre?;`uWbHDfh`{LMAzjMz>=GjK$Z{XO62QI8|?56V^tBc(8 zdB2V;o_C(z*!%tIIf^TC+}p6DXIiE9^C)BK&GQC7rFY+(2<2jpe5^3L_O|2u3O$^inetrX0aNlZi|1(Bl#5CuN7a>P;1yKcJn?$JH#-Hq)1pS%GKAK@~BL8Z_TLDNJuhgp7+*S>OmZ;*-Rrf~=K0R6W z!>FC}BS2@WB9`eP?0P8ebgdrq!$piRK$tp4mq$&Q;fH|go?QQJd|vDwNaa7qro7%d z&z6b+zU84v2#YK}& z(kzo(G^34y-n4VBJUR)($Q-r(o1YcVUeW{dyf^sS!IWQO!E9rZd#vqCHmxXcQ;|E8 z)j`k61OBsFmv$tH`+q?cM&` zt9nwmO+W^mgktCAED`R$p)U89FdTC3Tp!W#!rzdewSbYu-Fjb;@W8BRe!H@XmC{5odYLja}-?SO4@O^pJB)>q_ zr;f##gJtiw@m8`*uOKmyXrO?)unmo%W*d5qXQJ`K=P9viP`u}|J6#giU}u6Y*(olG zPEBc7B1f>GFL&IWwW#a5>jUf+?#unfT@|0N4exYjzFSDita4&EAoB08v3F0nmwQs@L-=l8JCJijK58jvBoKWLLp5ak?}~fLlrqc zrMp2u_YiJnJ-6jErAt3v=sE9N%#w@;Y}}Vq`=U#JE3AUn$kBfJUkazAte!EUHr+5g zb17B`_PIEP%Ot_W?q31KQ^m)EWEaK9zWY>N2H6A*4urq`leFj?Vnvo94oy2W{#JY> zGlkl$O`it}2O*o8)(D09YOccRKq+T*uNo!uHb2OisSw_H1y5=xnLXTs=txx>B`r97 zw7e&~`R$1x_=;z>JuCv$Mlg-HRkUF8jE++whVMZ;CUlJ)zLZU_77I&c@G{5R#MB$X zD&X6pQPbFK%Lvz}P7Th6K{O=?*zr8h=vmK)X(Dku(a7Xfw;C$s-gi(W;B$<=c1*r< zIM#IXHO9O5dimspJ!IG{jx$_g(rXs~3mGGHv{qM91;ODJTp^wED9c4J3F3r|JPxI9 zmrt2xv3oh0LWfyKK95!s!g;ubUhzKBu{P{U>818=?+caDK8f=d-J3aJL5*cp)>y+d>6 z_eRAax`88&UH50S3ux+ZIQEXVcPe~ITf0xNQRkc@_I%ggS>e!|mu z!_3-yn|kI?5k&^`E>E==v1gM?Lw0_VozPlDACzYi(k^2K3t3OCHxN*ImHt3b!1Yzd zIBvR&oHy;8CkyX0t|g`q2pvpMg3a-PjZI9PH**?7+ZMF2KHD1+QOInc3pnke7u!o{r?yzmb9I$h^d*irY3=J8^1T<+)e_F$<=QmufKJoi!oZY|c;30l&?hhB zh3bi^_OC4Jd7!kC72P(L{B(0IzeHn<*qvVG*P=t%cy&ojow8;D$LA>ji=p3&D7A04 z(k5%(ZX1`k+H4rALMB1r^^eaBnermo{l{5!YG_i}R*d0fmd}759>k5>WT*pHgI=ou zs|87BDQW59hxe%(dNOsEG3_&0BDeM4RDTk3OY4^B=JN?lh4TP59+AFB&fCaQ0CQp8 zvG;6d{+0(LsXnzFNS$AH%^su9cckE2@tetQTtt&+4?a})(ve;`BcHv2C7hlwo&>F#^upV7-wJ!BoS;FYpof`lLM^~7Mfp{C z*8Eby;=aZJnRFzi50JJhnd2=N(^hhcm6rE=M7NN6Uq|$ z^Vy!=sTAFg)caNpRst~FQKZ()w?SY(b-I?XrnUS+PP1MI3YL}vz*nXfT98|H6&)UA z|84@$Zbb<8$5%*ZX#SdeA2G$n$IRQ4!0vQ$3r018+bYZt+@niqE}m$Y7@{i00{vAk z&MQM=O5hc(JL+pSSpze9E3IMDZ{Iq=kaSarCd3ZMcfG^Z>fCOeCE1Bd5E5^1Nvyui zx?ybi#xL*mz7igxYD^I^r2oo#csr11)%!Ququ`XgCUvk z-?}Y0Gkw6aiUR;Lez|0?lmV02w4?J*G|LIM^i(}dHkMU0@8&JpI^RL8_+ZYV-~8Ea z(TWm?lo{OPQy=Dj3)g^B`?`8+U!qviBN@&>C(S4z85go>66uj>9nwYH7^gG#=3}E@ zZ6yw4j-MPt+^n0KkcL+;3bODJ-9)CN16N->v)eI#nyPto^mfl!(;|?^3wzinOV^wn zuAZ-j#_)cKXib^Ha0{20G?8lL1WH$(M1^B_hIdggX3EQz-h|BJMqUT+0I2nOpL!|# zl6UC6L!#MDR3sc&sBr3Gegt$#41WtX#?c@x+()|@Z<~A6v3e&M5>|W~$#xvrsL}6} zHsRbxDCtZ)H{GTjK1IlT%+waT8lrLMBtB3`U!UYIIylNBSsfK$pIKDO_q}sJL#LdB z{}LU`ZJzntcsybt(>Zqw`o6DHG1sQ8AnnU$U#))6`StP18qwKPl|KrjHQ}*sXr{T| zOQwvBdOn}qp-q85F-ZH=Es~$90Xc`ycd&F`EIShMKS50EJ}xF>AnsDS(roRGPlvNw z4<`{l?g3|;o(=WgkigTq*#cxv%(%8C{bK&q=A9+|uQM!|CaAZd%0iDIQ5~7!s5_-+ z1TdiRdZH#ya)9bTuZ&`sJw9h1BWcRLE* zw1U>3CcUmP0M=h6WktxtAzFb#IF7xUpY^d=?C$L5_nI`oN-ZT+6Fn6gq?1H6t?I0; z%%_5d>Io0Cs>?T4b~=k5rXYQG-duA@+rV^d1Fj1jLe(%&?>qP(@7n~X3w#%eq`n-1 z6VlM^?C66fxCfgf2~r(gC5PDpu)wVk3@7wv*X`mdK;eaGD)I33j<#9Gx*_#v8DQi4 z%Iy>Ik-FX6oYl*;Xm3rmRJ7&n0PH8gK|gPgExkPf9vZ}(B~doZrJeaSJ7A!!d=;49 zoHbt^Bj|AYNa~*ZY>;b!b<^!M9=j#>{N4oHt~lI$ZdN4pH)vV)@la#c!paldgIjhS z*hB1y=rg>?m!aq-jgY9&=SS6`x%@GchZwcUx3|G$;Ey6fqrFCk8z=RqEU_jM!V}gs zfC9O&EWWZg9w9uH3dd2N^&{n%&sZtDPw2;Y$YQz?0ki3zXf>rOY=tPmax<6l-5NVe zb2uoUqlJf%@mDUH@3dg5%qj9n%IF9?2rLOUUUXp2-e$haGD7+E3QUCJp@`sjq|nH3 zEZjBUnW@Pft+w!Qf{4WD3s9X8p3*YQED3%EscDJ&al9fM$%^*R#DAfK^vq)-K1+c+ z834N8&={`hJRK1sv4FZp$0#G(D7}+SH#WZF38UM6+M5HEqiBh@;d~2^9=>LUNkgXjhC91rfH;+ zyjJsY!cagp+FqxqehZd-Rv#XoAnvD4G)%{mrvVpguZ&eJp~KFS2<#?mzAN@*vO))V z=S`bnbn{Osl>rCnL#^N2$?2W$4eLqYc@HZtVlQJNUf!J`W3S2;b4 z(TA{fuVY9a_aGLHjs-p}xqGDD5%re0zE?$ltB|8(C)6j8r#np-E;5RU1SNs<+?rt& zwC0FyVmf`bgl%w?_q0ClvO3P^g0os0s%xp}MvoY{|><(?^DiMN5)0 zNp-`mC`B;srdm(|+%+Yc0X_n&0!8m!k=ebe>EY|3_k%7|0fFXM7(R#2l%Gd00v`KV z^rCarctM{#ar#K4ie?&*?)1{|4-`EPUaL~L9kSUtT51yR=7%6^68R+W!>{L_w@uocH0N$`?ZFQdGJUpp!D2O)?wXye5RGy zG;<_;Xq0o7d=n=F5vi5TsnG1J8GZy|iIDTMldF1;Z98Q=LJ<`^qyj9O)xwTYLR~nt zc^5$Nyu-APp_c(eJHgnNQ9rP4`vEfZB}q!=u@7T2Nhf{xbB9v5&cb$dDc~__WJE0# z=vlGURiCMn_)@=isAsXN!(?lg0~XtcNJE&_`;LZm8ermSfEgp-M>N#N?i|tG-B60|tkOU#%3Dw7$~* zMenh;OHU!K(XCOz?R7T6Lcx{1mcZak5VMK0Gq?&lzip&f8B)>dJ5np;7mgS{rUqSe z>uSoA=d|y&yt&EzwRk^V(m!3x{(6ts9cK^L^(c;CggsDHg5`z%^xQCiKHbpJ zcJ$`QMuWg9Bkb_Vb&gR1$L`0Um77T#`8ck&9i|Y9UX`!!#QS!)8*s@cypCEZo!q|r z^SJ4H-?}$*k>CMMj2Y!kCV7Nvx)PQr_aP%_$O%0l$L$B`4Z{dJ44jfqHf*_&pF-2r z_|SaF+v4z+m(R~pfh*veT8DSQ<>!O?xXNaJ%sF%l4S+;pAM84Z2vQXBX{I6`%j$*L zPk0Z9;vM4UcV4DuDzOxoOr+4$`JEm+ly95ak?B8h z*i&iR&w#g;X`fNy>`Tl*Q2U1e1 zBOzOK=ku0f1EMG`DuEBZW_8L#?r}j-WesZ0Icm@*3r%EP8 ziWcq(Wx{dx#(&4)NI1vB^{x--fsXCzluRzw>h_L3Og!ZpZtnm$Cy_^tI(S$#SEyjJ zM**}ev9aJ?O>6Kg0@%0dJr;0mfb-$;JiGCyP>7&LDr&ur1wMmatoUf!8 zqX$S1<^ey85o9&bQO`R5vB|F)cm`aJ-s6+{;zqa%R~_6a=1q1nQ)YtZXl5i^)02GUoK3HLiYi)heM0b6gLDxJT0BkJgySj=k^NNU{9XA`@-kejnx40 zN6=5M3T$7bBG8#7M^d#gIjOYk&g#zwaoT!WO&>dNPXsM|Am0$33;quN;4ia_91I;* z!I8!8s(J*A-}aFhk(`<07el?>9h1G7LJk{0{P}`l4V@KH*yWm#tfu5D-e2!c$xLGELkHEm&J#x zd#Lk=X0EC#=6&MdH(!#BFHSWzOc;fULnCECz`MrAyA7Ol^=1A@9A@F;R>1`JgslRa zgOP`;Mt#s3YZV>T<~4?l%;aqY;Fo~q68Xzkzb8==@=FfM6xluCsUKJucLQE)-`582 zg!s}fda_R;6{s{~-r=_e{KC@?T;XmvKfIG}a^5+jw-%gG2rHivs<{(xk1`~#`$Cu1 z)7BQK=bsv!zmZ39_c4fSOx}E-B(g6(DeI3}lusq^p*9nuuxXn3tm`KKUVC210OixP z1yL*)stjZ^DJ-W!GgRG0ZIm?2cz-XR)|MHccKxPdFK0_6jGpLjEUN`^wz6px-(mTj ztE+GJoaPec7B<)Ak+B1K3JA1Hi>ZhQ5C7tjoX8r!0TNdL`5fZR-Y`=-xVmPI@G&55 znH~!VlMuF&fCSqd{c#&{>0Y0u4Io3O^Mt|BwBeIN(7(^vv_b}e82{C6u<9JvNye7U z0?_$zxUa8K02O)u^mrKy)}&1a%_%*FrobRw=74D*-Eh9=a?;~b#`+L0kV1W9`GknS zsuBe@`@B=~dhX#07`&la?M{C;U`+u~a2Py~k)0~=Xv9+S*bf8Rl0p z^A0j{%g<*x?;WIUJ+ix#do=enMs*SzPPuSsMB&{Xvc$B3!q2ipp*2_?#(q0=r5(+y zJvZ|PY~XDe&x)91&zpr#Q2|@NywCy7ryAyi)#~&Xsw!PEM~D~j`DScb3>O8f2~10I z1{k&0`fAUp@!9Z7zOnjhK^KKs9}sLUq0F-52X^qBj2lj7wN8G5^M40Fe+2|>Jp>QN zs1{}F4%|P{G@tENnu}@PUN_5`9k{CNIh!`tvatMP-)J^8oL3x&3Xd~s0hEwQn66Jh z^a~O#Nl*RRAA_1YO|7*FvK`Vaa{>)%b4->3nZww8{aVTAB(u~-x2gbxc~kb2(a2?= zpVP6+1k~Z#fR5Fc#GGW)J#qAVS*=p{kyQm6dfHk>J9!~2)8<+H?f?48IcyW~8>#jO+Q7SYRxe%QQb)A#C`f{E$iVeFUEQ5868#`{+? z^}7s(!J6V@!qY9rMqaJ%J3I_%wFeONWQ67}-1r&kQp$W-<_(cIOdmdNdkZnSJrX}f zU+f-7-{L$^1lXRc%J*d)up|*n)O0)6a$zz@a&YDKqcqhxu^e}j5hDZuCWPV(g$@i8 zERxO@dziL`R;^X!n<@258{Nt1O#IeQcO&3qZvP3^JOo!v{Uw-;LQLYJgc@g%=eSu#JZsLtgyGJv`{1S^kzzWGyI0 zI9!hryBv?dQT4n+6DfRD<_Fi(xXZ2jlVB|19H_OQcsC%t!Eq0uB<34ul5;LfnB42* zxap4G!32JjNuO+VGVLgJ^dOvdSDk0NK@on@_OwPaQ4w|AsNp2diAGCaboW0WuWAT{VxTgY}B0WnhZc3Qb#KCu3ZNzG80VN+FEo-6md=Edg&O1A93*_$PM|6=fq+ z7c7%QtdU8GgMoojQffJSl*ZoBIHTuluvibB9K0^?r+nVy&3aF<+-$Jdqj0v8w>y-K zn!h~(goWm8dkp9TaPuB{tT@TsVVV{)ymlMc=S+5~tjCK;I2*>=h_N2?4D!yM%;)0e zC6n%ozM|i{mo?41=_5px4?yvU4#*uF54y4V{LXgEes(pWOaA`q(&u+uljUfi);&js zTZ6pAH1?Vjs4Pjo5KI30gk}xV+x7{0^bV_Au`k_zOk*irQP+i`aOO|;nh~Yv&qmB| zuhpj<1qj5bq!C9@gymwxB+}EBit_QHu!LsUt20BvHuO=upWXIb@iQGO@N@vZfx4HtlSc!?k{m$WWe;ni&{~E>bfZDJ+$y!e`bH zfQ0Lzes8&0do86E%evk^scP}$DOF4@lw- zb>`hG-v5I7!H@>&j#CR&_mfyy}HNy&kIArjtebYXec4C&p++hbrT(?)FL(FW% z0&uAmpv;eN!v6N-mlvB);p}9{1-I)uS-emNGq1{_xoz2R0(xu1$c7Z^!z3MJY)>x+TOVv)n4H#VQQ}M94s2*17S`USBQk0rB>$z=+N!)bn5d}c9z*9thkk~T- zI_S~5UU%JykkReXm~<*9oxSgB!OCGRe}xb{f~k&nM^Pv#GcwJ^aj8b}{@o!}oo{ut zJ<>gZUriYi8gtb`lRY(hmAN;H*(MS7#wGw6JYGjVN&AFvX{?e3VWi^dDoVFOQY7L&Ze@B#Mq4vLr?O|)aTd|d)vAp9$Gh99fR6A7 zn#6SkdTN|l&#btz0Gvd8E2GN0d_`Q|A2R;a8Xeu>i~kaF)FU&KfhxM*l`6LniQD}_ z+9QW(WPl%E^|n4Z1_pB`+idXlYLyF?+7|xUjzdDL0B3Uiexc9Q;Jdfo!Nag9?^>_JEF217r3$AMP2-(bTTF;{QLt1uC%aHIx`2`R;o(@DV z@H2x1WgXnV+?3K}y*I-kiMzO#VL6o2Dp5Asfb5E-gnr_dnW9qv+Z9hWhOr>)#OQt4 z%I-Ay`*y%>2Zda?Hr3JwSZ(6>fl5C8K6`$|ikss!x#VHSB5A+iOFZGew^YhW(`s+d zw^pnLWwT}hvSCu5ET4*U?GlLpp#}z>M?VOSe#YTi-#;NK)tJHWF#OWHFt65*ZDPH7T{oSsP;r2T4|6ZaG_`Rs#7!w`K!ILkHC#2K_^@ zHz{86(wc)8fF^-!AJkf=>GfWk+5EbyilR4+s4E^)`K^i7JWMbQ>l~{ z>V-TM;Vn$~(p7Z#v=EzfGeuv_!O`eF<)co}C>y#(P4wY;@UXXvjBY~*kQtt8ZEFY@ z3a#yDIq>xB1`SE(tz-(7pFh)r`1zgwA>C4I)V}N3&UTAa(&@vu0dk z*<(3iNKp5;c9`*47s;3IScUG#RTIqfzzN}yQPJbGZ)QI&S+LOG{rmUCxE=n2V`QnT z(_kIT252kYSn9M|0ZMR;C6_r&@Vy4;1ahTEH%?BKw_R#=ZGH&xqM|-`60+!8(z7)8@$|F}8iko~2ku zykg^&#iW=FSd0^;M*ScKcz`N7)PPm2O3=_fWS{72QKUPY@ZFeY_!}^4OtrL^+TJ7ZnIHR1ir;#v%!{R{xgg`gq(}9MNe-LI|zPPCve4=q9Ggt#I z{6R<1+{#|DrqFPL&y@%y=%7nGxYnqD8sctZopgvgm)LJ|4>p=+3Dr}-yXVk_10PPh z$nu*{Ys|D@Z`3xbV(Ampyj$*Gyu^i+BD1PwHywZvZyv@{d_N6YzMeK-kn|go%J@m#h9f^EqL`MtM{t7~A=aY2bL{tF zWzL)fDW@8s;o^<#4h34u=k5)l0e4CIsFP?Z{tk!j*7F20q;2`B#*<3wGRSoF`0BWX z_JvA@@$Fgb((CQbVA747NKUn(Gt}N9zH~{Ff_->18s1cmf>qHccc;DnhRps>TUP&j zW_AwHHuReWQH!Oh&#fD?4OPd)GK)!umoNhj6E3A~zrPjTj4s9Rc+2SbHCyXx5`yGb z{l5Q@jns_Pq<+k(>TN46@JmK7G%Hu+8S8HC3L%_}FzB0myna5naTK(h1A)>KA)~$K zET@(0dZyL3_c@5%U%*)CKihF8nJ_*R5$v(}ZUCJ`Sp#TUOo$m%YR|2Wp4HR|iLTk} z<}x&!WE1k^?Uxm6BUgWX(?D^^+qmA%cE&k7u}!ZnwL6a4ImDL=9jlQn*HTu z6?>onhllVRcf<=X;0d3d)Gd|kMKKV;CQ$XGircC)KbT=j`_hA*Vpz6RrEXqqLPL+E z#@qbPGVzep08gG)u%$*;E*wzBi1m&k0ush^h%GI$uF<^*Ngjf7Bh#eQE_F&UdBeRmQm zb#G|0na^5*A~^a1FZST3idf7(D{ke^E7r;kGC&lz82@&NHB}|8EXZ1} z(453OJnq~P+F`2rg$~y_YC@4~L=$8So98WoW7P2cmL2H~$}VbZnDkknC#gvhYq~&Z zAFA;q7;RqcS-f0=ySat=$y(jNh8HX`6w9r7kaxk~D^w-GdGBig$a?o)pcFUzu!#i>dTGrH^*Snq4cKYuq61m$( z1l+*`tFjU{w+(p*92w8XGj3LkUqW$(gUlv4@-oTr%%F=x?{|*0ktx*cD-#CTnu05( zeigRCd`Y3FKWF_WY-3b^ha1Ou2mw0J?%6DUGN|u=*#7mcdqYj(qMLqSKJ;gMQ0{81 zU|M>WTe3$%Wb(o$k6H=Jik7!G=K(_}y~KcW}LGs zbMiuI6hXW=qJMl=0Jk=hHF>r@iSj);q&4bTKzZM;l^$UNNb6y3JsFq|+}vf3>;F1s z`XQKGkS({F0uF@wt27>7Fee`fRJ?pRx*Eh_VHo_5c+P<0a8k zrWZA=9?pWh3cN|ZFi0Y>n?;9CXT|vGo@l^^SGXd27>6!y@mIlTWVRB)nteVKgQSE4 z5KTDPgi>$hF23UT#jp>$BrQU65a_2wzT>2yt>dlFHZWxHS5p(J22#Wh5K$GKG&dt= z3SkK>5*k(rt(*ET+9us3%NJd-hW}cTHDca_K9ELzq?pl5JVCKFpKcYDLfE8 z);g=o;!3vX4WlGA5F6W^0GusDGc+3ppG#IDnWKkPBv~m{bP1CcAFPdDEa>$JI*F1i z;@l@D2JzW4YCUdjcx?ZGB)xHS-Hp6F$GFCt285j{UT~x{TbQ}oE}ALWa52MsGX1ms z{(YZFVWadYw;q!$j4r3{|6)95w@T;SK{PfbtCi_|jTESaBg>q8OEw(=j`<30v05LU&s55Y>68s*4-D0r3NUL$7{J3`s6^PT~l~ zB2je1%d1Fi5#$(!mBqLeWN- z%ThuEmFN|E{OK!#T!HH^$$$4DIP1+%ZsLfd4t2S9IW4R2G9keEpb7Z$>ANM zKfd=wT)s|~czik@Dt$7)Bm7(-Wbhh6(;f^sP>%mgJwR^z88`30!awlbYy|lAT8=Z& zLnCH|M@LUL7a5~3v?Kd{&TYF!Ht5O8 zOP)-jv50~#l0tp&il4;X5Ld#`RwM=03k|Dg;5&L-KIldM6omQslW*-IPDvK!n(2%8 zF|1lKtCXNmQHKVzTuKq7EI0$Hf}iDvOg?hExZwS#p$Mrs91A2cu1np``B){qNXZ=} zWx@4(x{z6i)#6Q|v0Vt$ekVxj8&W`hiaV?lrce1*XU+1YU}W*yE&~BwJ0)__#QYZk zTx1;)W&Az7#zcSX(ZLKYNTeX%64bokw8QOGCZK>_1J)T1WoVo5iJFGvZjg?pB`^1I3FG1f z%Ts-B^N@}Zkp#?STR6Up#=7atx<3S^Uj8SU$}S!Ty_ru<0^-#7cEHkU8Y5p2_L?vt z5||0od)}j_eGj|JZp;Ugo($MM`iNO$;#O$=Usr$>S@ip;-2+B%=k#eN77@A{#4V27 zQgRQ$RBQ(l7s+lVAeSGf0Z2wKic;mFB*p{{kM<6M*_JmgTBLp3s4SIC_S@seQP0ED z+xlJ)U@VP75ehVHq40ftx2A#DX}!d+v#D)<;-n~onGsCb1yUe_UFxK1U&CmH%uGDmZf zV1T_{{qVPFEi!R*@^!r(Zhq*!$*E}{cdT!K1TR~$1X&WA-BQpEWXJq% z@CY!^hZNA)z$B+kH~v-?MotMk*Y7g`!9oA7Y=z%%`hGb+omCm25ypKZ6cRzS46XNJ z=z_UH3=j^pDPay?zIh1(lzYj9*+vQ}i7uSO{H_N)4k1ReJN!s0td0Gx1!+R0oA4Tj zu&C9?r%_HDH_gBG9BAPs?D+0m>bx-uiemSwUPIF|SzZEI?8v-tV<%S*4x5Rl`xz~#QCJ0CU2z!vu6Go?3CV()WpBOBQ=9g3gyK_86;WdqW^_x z=_zVbmWn9_$7hz<@)i=JiCzSEn*##75ID`oiqNtsYH%s%z&edNvx;rP0VKo(lJfK4 zZoDvoc1Jo-w|f@}E1~X57L+Ejdna3^nGMZaKFhCb+I`A|ilB-ED!S4q%J(%$eZ>k) zd|^JCSspw1`(%xXRV&Ofv_b>s>$|K8P|jN_JIEmNAW%t`nv#4(;SAO}_NrOlWD5!s z4Q<$Ri-G7+dlAHoJ58*pC4^j_40#kUVi0LAzl@qG&b9gHJWauoF;03uV()x!N4>m? zyzsO5s*kh|ji8$O^i&zj24PrZW$@-N+G&LOYg*1BZ7IVgPrNe&j!KzC<1b2p#og6R zIr76BRf3obKX48IbdB>f%gsg>IW7t=itl~tzUU6htfclA?|*x3tcXU(VC0W2C?b}A z8`tlcJ8LaFF9ANMy({ADjZJBWM$+c1^0g&2*i;OeeVl(w3f*Sjhe&Rw`uaeAqTGnSrBG{JJIUpg2h;ex(To|39K3G#-9I0l3V6R7}Yy zwU|!RMo9`K4ikMemD!H{88-BSK8fFAGym@C`v%3^lh$!z7P?$$>qU-lJJt~rjh}(u zQO}*qB4}BX>%}Vd2QL?%uKlQuuLq%gs?;W?@2H$?_5PL7JuSUX;es_*B?9LPRMCXj z9uR9Jy<`vv)_s9f|Dh*UeSTz^=y?XJ64@*| z(<6~bSIaA=NLsQsRwd=Q`+~B>Z8p^EN7wy4$r}H;CEG9yy0gYTga5WvI1Szla18wa zS%)UEIE-8&3P!c2&m}8&&_%Dh<1Uti%3w#~mB;#!W!&}Tvxt0=*MliEC{_@mh}{?MYv#l`$d@yS7$79z=G z{swsSh~{BLjE{D1n+u^Ae)jekmrM8wS9_c8*7`O^Iex^)@5=_rN<3Xuf3Ar4V^AJZ zUsCdCy!NX29M*5w2bUU@yqrP2a=&=mjLwoQ&i2auznFpoDE$J}Qw#6D>?jW-HW_y_ za2iF&@5oaMB9=Gx~w zd(XUxg?)7H>p`DzwCcqN;JLR@Ru{x`mJ66)YuzW7)Jv~rT3nAnU#WbB!jCbhGYguF zbZ10e6I9AgFn`LRKdALRtex^}@i$?mKo1$UKS~~2pA8%|qkc%0wFfX;`|Tl$0$gj> z&h^e7E}U=10<4)%8tqY>pBzI)bA&lYIRsid+ksQ*Xp6Rg!rh*Xuz5j zSWrqE>BxNE_O=)i@rfEvvh4CEmlF?{fc)Ufar4iccqpKuR6U{1v`y+M$xl0sCA_c{ zW1t0p?_xp3U!Xo&+p^^ZQpj(6>o9HykqPRq&yzLVN$W#*?AFrqpTyDiyK3-7Hv4mT zMB1mqIc%luyX!HHo-C?VLt@$?-kBrTFvGd4Ej@`UI!J>29d7_Hs2>lFzZ$_L)1Mlf z>ns4`Oh8@w9dIicQ4yy>1|p5W7iHf)8AC4hhYIQiq#=8gbE^KR~W*>-+b zs>L8?1l)z%w>o&o-4rTb)PhL(XeOy*%Qit(JwqM~l~^uF?X+Q-L+dS0it&Cqe9m(De?VB{QBdE@0Hz>*ku~LR==IuS1 zMB0#%zPCr>n4j=lDQf?A>ob}K@yV7?`Ebw97TtFYaT7@85F7?8%u`Jggvb}_Fa1X4 z3Xx6Q=IYg>X4;+&_oC62Drdcg-^X;8PJQO}Gx>9tUYKbd`8(lmez!3r!;NXB?pIUA zdHiVxUC(zU*q0PG)r$56P2X7#~F@@5rr}UQ);Wd zQ{?rUVmWL@w!b{|NZknQcD0+YL7k@g-mDnP#<)<)8vdJt1>&b961-&}>h+7Tp$o+Px)G4K&gW)61SL`!)5VPvbTd{)CFzHWbdqiv}i{ zWoQ`EG#*`8&Yt2wy+u)MR_WbtnpGv$wA#*n(6v)|J3d4Xws$i;cq61$(F&bxROY^E&i}# zZQ(IPYZj7hZ4^2`!O`y5hP}QGx;(i;(s}`84`)i3nOTid1D`DK%bL~q)iUgntjVx1 z{V`g&*oi3lhWf-z?^lmWPsO_UQmoN{P~>+dh@5zJKbwyGa0j zB7t-`l%1Ih8G!T#a;5)TVLq17rBY%*X0m$;v@?N&&*9*Z7D5blav`NT3ibC0`qZ`z zbI>OR2!h0&{vOVBo7q}GUmD+5MTvGJ?R+KVti|+)=@QEZuWvnjSDhMNLo-kiT2zo| zy_2LeuG2YXDT~368uVnof8+?wr?kfCN0^ZS7O+~A%1x6Af=UY*csP9WpEP|n{+BoQ zj1``+@`NMN2HCye{jM6{e44cb@1;p;FQk{!?3BFu}PR|d!v^eM`8>J$4#Ft zw~K_U8i{%$&n*n20zuNeJDa0*K54>Vc)Z- zW>k(8&N0b#hue&LQMs91+VP!&PD{UM3gvlIIO8&i|6d!&&BAkfdh3@VQ5>E>$&h+s zVq;`O>JbJEa_0TeCJ%CxJWA|7kFEky2x9*Vca$zv%^U4=yuT{bp~{$o>3Mn}(Wy|K z88XZoVlZc2HcXSe|MnGeLctClLbRXZx1;N&NLl*jJy@6;s4JqdCG86TN(c6LA7U)) zo?$4BrM^5@mCOsJ*hNHwF=HFD;i7}3|5gb*nh?$KUbn;H?_}R(e{Vk1+5tM2AfDDa zAe1bYBoefvx62(WccFg`=-wRZphoDay>|8yTH*?MQIX>euqw@(G_1{yQUQ;d{9sAN zk#1>(u?SN>?_qkv57orBD=VghD}Vvqu8R?5bIHNT^toA|)1iL}YW1<=W&iK<-4p2E z!7uDy+~58{4-6*L&IW6vKK4AROPl>|b`PcUk!+bY+eeb;F?f`*tWafmxWJL{|!i6^hLnOpVL|>f+r=whW!tfmAjh1y1N?Yku6{ zood2N=*MHZI>5PaRuL!oQwj{i+oiNTV1Bm@Vr^g&_7Qx5?<&dU4J9h$9x6NgWLw%7 z0si8!1AScq8@sneIso(sbgrk?t~Rvop-{dqy4Z|k64G5+2Tq}0u(KEAc(=rejX=rl zLCf&24KS~L*nDszqxF#WKO$-#Z~d_bAYG|6SJ=E0!hw?WeSx$Jec} zXFqp&;Meh6)KnAg_h^uWHQ`HCJinoi%sms>ge+Y$=-p_dG9P7E!p*AGl`HW`4Xl*yp|c?u&!pFlp)x{R3EV~Z|q(0Bh?l- zxW;dlnNn#$!H78p&>}k4G5wXggK2?E@7O5K9PJbk30V%h;^R+z>I^}!$({Z%PdUoA znVPleirQWbsU!M!*@iwCgXR6csz?f(ex*c9m+iVq&kVkoBmE&+sRPd<8VnA0We7j36g?etv=rN(EPEK4gqZ> z_l!Jw-Ji7FKHf*xG*&)KPOv6C8BEDzD$u8kw4yZ+P9Dm1U!IrWM=)V zSRDsWP(`Cjw^omJUKu=v9qOSn^g9_;?Wo?+cqYHtku7?i1=-=h|DG%_UiNjHdxFw^ z;F#{+kzc7Ty*wcwuT|~O+AcspTut6U^L7=F!y065*z*qQr2Hq~+yE36g?vlr@qEDgw z0{QZ^0osf;42Y+#wnntbXfX;RztnbA#|#dw$#2|AZvQ@5EDml~yduyLV=h#7^1aEV zfHxECaNBJMXl(hd;-!hv)mi(tStXND5B+UNf6OH1cB~ubXAh`HAH>C;F)=I-?u@xV zB=MQ|6byU`zh2k)6%Dx&?fU?!9rpc_VMnj(t4;mwt2dLWCMmj8Q%uB`jEbgP7qZve zKg|JIy3R`y(nV+gNBsC3isf|74`(qO?Rn2DD!)uTx>o* zk-#Oo<`?;X7ehByAn&9o=Rzr&gb8I6M@tC-#2^O(9pvFxtx-3S0@1XTCSKG1OwD31 zbZO|<{b~Ft_J7ZVNF^}op)s5!6q1DAdb zdb{-oz}}GT`wzX*&b+c_s8}!o{2#h+RAs^nar1f=U)SFXT_ov(PI3R@C^Kr^=L6}7 z<}-@!g%jY_;L(=*WY5N&_`5FZ+dAO~U)tPfw2)zEF@Me%#0%(kRV5rQR94*Gu>QuI zpfZ6;2;q`OlxEw2sH269@prT8(RB?Je}t!4Je|jqGaWPbC7iC^)JJ?a%H`z?%|2wh zHxkgdiMh^K);*0_P7RPaC$Y!asZBxv315EtLBxlGzUm*4U$Yn}pFGs_T@&QOMmIX= zR&T?ym?4t%z9%O@VNkXQ2QdQ|esbj}nt-&py$l!Hzdt`xV9kR-k=C6FxDZ6(1va4h z;pqx@I=-~_)o#&Zk;7kKFR5)K(*%)YAivT{{geAZV6d}O6ljp-{qbI+>5cblK1vn= zd}U1Xx#-Wpi)+ZgnDKR0Ri3d>0wqtwhp5lGv{m6h&Lgn_Bp3>Z6FF?y5Hj!J(t+o| z+7b@d#T#8rYaJFoHH!$ATESkfqkidWSy)hL>pdE?2s3xJRRJ5>hhWj#aE6ALw7+-3 z;_N{&C_{H3^r7|Y5Ne?CYq&1a3?7go*)nM)JU6QJ;}CEe1oqr2f`DqmVkn#rFGnl0 zmxGa2puiUluEf2o(V~AR*6n-eJ`S}Vh69DwV_og;V-_`Jkk&OdnPoC@QI186qmM<@ zeOy({EJHQ*W*G=)sH$i5^NbS3fkk}Q>y;@90Hs(?XgwZax6d~l0!-ElDRMzLjVC_i zb{W;1RIcZtb1STXl{$y%^GHN#YgrItV?W%!c`Ot(DGgF1!zX|3`nvXW!awykBdq0b z;Q%FRl3CkF&%a-9^x`q*&9#9}a@_SunITW#IMHM9kk2>_djAob;^ ze(tm2<#~j+kJF%OZOLu5^kpUY zm#S67i*3nzUC3XA53~xLW(Z7I#&dWHaaUlhT-&muMkRh(mrBd1^O*h79hlMmmc};8pOc&24O3xTVk`GX!HL{=`n(uvg+O z94`(qp$_`XZ=0z-RkU77OX*3gCVoym*NcOQfXp;Ws#(h5jab`I^C+6;5dJ;bo*Z?6 z9BKFE$cO-AKCy*0c?2xljhFUxkMp~Y&KTc_wd>^QalqkY3j3xF-FB!Qcj~awi2BgN zB&_c5V-d|e`x)#F5v&b6$G8)SS$L3lxI+aD2?h=O7A|)*C#uXuy~&5{V}+yxer&&G z!(-}Xw#3c#oSi=+sOO2urY&Qp2pMF{#);=Hm7yr(pW8Jd&BtioRk}L?Nj^_g`6q(+ zi&0Z`EL2nrM>1r)rMFAxv^-G=e_@p9-Mm~dL#rk0Wsc>ytSS81* ztlls_=3-pa;?7&B2FW7XYgo+$`d*>XQW<@vNerO?VrMVGL{ddQyq5+b3hJbrq2X&U znVeOE^?I-;NF}0LHTdo6arVlAEBVBeCTzyv1>R}Kpz*StxJxO|h2X}@_BSmz>Ea(EMp z7Dzak*69b`{UnJ?VYvC)bA8V(SrbUtv6B}0%2v3A3H~7^D}{V{l%-iEf6lX!Yi&0Q zohNjX+gkxONEFRF;IyBrd4WQh<>a;dYxzXQoPmM%2`zGiVr)%nqXV=Ec5S@&s=NTQ0bkYw=X6m zcAN3HslZTDY`tm;8vjtVNt?s#CrDg)2r8aTNKC#sKJlSP6SFpGru~#>V$g0ODL9ME zdhnXjI2Af06;T+5zi+Zy{0a}5=YLB>?+Ba|^zRUrNCTL6Pu+z*6mYD_$j#O%Eg6;p zR+Er^gKHPk&b)MSIY7o)?ol$LIct)*xj`1W`8D)h(+0RgKxII!Ghsq#?~i@`lC<== z*cp*wp;#P86glUqGS)PcCBW;(%fG#z%31dJBDj;41aI8zEtb+BQ}%(nsD8ff!4JWi zt!buDQ7V9kwIjFEd`JiE3O9w(PK=Q{E{7gPG41B{1V)+!K*>Is`6@;V9_YVi;}wES zu@u%!cj@XHIe!0KF`ZgT8yJ=T_OX4!m`tZ_?e-MTS{^WG7HNOm=JEaQ)Zp#SP$-+$ z#_e@YTD7XE*!drIK4f?ae8XH{I0>k@XL#yNh8}bd|lTm zJ-qim{`|w0oB^Xo5+`Z@5P6&WfHu)C_ZPA9n;EBb^XNuv!HAa6GPl7e(xGGX1zK*l zpH9xcj7&rqrpcOJS%4&0I)t;CkRo6Xy;RMKFmia7+fY}RmfsVzza6QRCwK~eF%5;p z(uIc7u==a1{mMj>VW_*4`^{dy6R$>kNy(DDY*-a8bTWEBp1nanPPdX0`|=4997bSX zzc%7_j`m#?_O;O7EaPFx=H%ce@a-uX6iL`8<6D7$iDdp8ICND?x>_=68f)=$>pT5#5ocL+DYTI?Aj&geTi8uk%)R*u zq5{ngBYwYU6OC7N{(gOO@t|QOdQE*tKHN&q6xtCYb?tym)TzC*p}}X~4(#nRo#Yn- z`zdOFzzuZX6|+bF$dkDBwqL38scNkB{J!Yy5gb(Fk%?Dozr8%Pe6nl%w9skV)5*6a zXQsnOtAFa%BM&l*;=tj*r_KL1qR{k_V<`eEkix&Ya78as>)$KR$xrygTlEB71m^|lF_ zdl9VVOst9UqC;~zUwn0f1RnED1PGHYm6$DB`0ex!EZcN7(1#bI=Hebo;F!#|6+)av zU&*?MX@L2|EI-A1@5}L?N#)FP8zEtFa~@_VzPH{#FZf5ji!I)Fs=%gTY`3@EN|rPQ zhB%0Wq`XcVcS{ze|ZmK775@|KM<@@nY?rX zg+!Z2t~BG@Sx5UaaZ~6k1Zei=s_o>cXt^=yJ+`WDOfN-#PzuF9S*Q zhFwOx=zTy|iY~PKGIguYk&-7D-goYHF{B=uGI%G80+wlhb6qxd(6f4(CG#>9#Bh+= z2QJm%PYVcoqtW|=1LR0IvQ8`EZ@bIVLCABqSFGew>f)h$v45`d>_nCU( z4MGON2nG)4B1)drd|_7>QyC*Mdl!L2aN}2&XU&2yTTFg(e#O0ZkfwgHF=Bw+7b$Vj zafFC(ZuhxGfWLK-VmmFDhIOmh&Fbu~5?Z0(pbMEu99v(z38O4U1%hK>#tDIelfvJ& zYi^XrT{ag(@!NW_YvZU?rUF5+_-^5vvCqw%@KS48$cFNaFf^ujpRXQ?0swZ3vISD) zDu$5$9<0R99C-f`WIKOkxq2`YwQ(;VfyaF79I5!LVgQIEd7ZRt>7Gpg$CXrlX&LE|F$@7-?K zzh!h=!>H8!sm)*ATaxrCO8ic5*g@H)UA_M221YQ3)N;l10zBvTU~>z40VOiyA}F| z>g8-ax6cMc0<1Pp65&hACZ>fCius}0dAhdjdv^gLQ=rSGJ1MjbN&W4^QUgYhNyAh# zGuHghc1a~xxS>SYdB>3IOZY!wSbPZGT&0u5#Mj=zit+p>9R^MY&>P6j+&r1vb@mq= zgxRjf*Ei-DG+0X*2RXS%oWr~5Qbp(w^5%R`sQzhxRqPDk_!RI9UG6Nle`qd^Wc*Xx zbRM61@^DG5T5%{Nk6GPMMqU@?$E!nz*^3vT&`4eTn)v?z{)cF3qU!RaW#}BeGSPa^38FzCE-)>CXS#3tM8&Pr#nZJ z*Y#lrv#?GhYYNI^hDV>c3zIAYrD`pG)azZ{0wsJrWvq0j4B^+y$WcMu&FhL5HFCFS zs6kZTm$iHP%SuHzw4ShT%If`+TAyMUteZay^|wx$@<}|WQZfSK9$#=BA|ekmV={)P zPvb9@4ZMacnh@}(@?vW+7&~Z{bY5?*q)^3ffttvty-8C6<-ny`HTl-W#bNT(DgZk` z#J@J(x0{4AEsnT-)2%hQ66yJK8x!~U954gGhMtX!vr;bF?ZIL#5-rg8aPI;AyovC494*jSfeEyJTRNumAycZakr#-Lw1 zLZE4hmRv;+ocqq$4Nnfc4jLthnd_$?^Mp6klr3Sq6izH(w@Bp-OcX3gcoQ2c$V(mT z$crt}MX~Ype;l1hlA}NnMGwRR@0K8h_a1h5VTADR^iw^Xj_8gVi8A@~eJC>bFlqXP zlEn<&CNh=MYWI=T1_9>V@5xoBVzWWBU z27ZaL{3?V(fa)W(D1;2J@$cE*bD=a?4wcRP9;q%tS2b;RFw10a&w#Q+gT58Xt^rwk zL*!>TLBh}xf$4064hr3kplXuUNK$t{g-~&hF;|tHZ$K8um$yG40^K?I{#<>#${(Y< z^F$K29hB6Wx7gD(WK8cG{!GZ_v)C@$^>k*%K0dzuZphp61dMnhZ@#+uTT(xN)N!!* z-R(!_Yfr@#m6crK=n6hx|4+3_gBM)}NPo|0+aS^ryU5DIGZOY2CbG=5_O%#0{ED&m z{c_|+mfWT~i3K;r(QEq2b|CY48WfGlV4!b%#C_AMG!me({_f;v~q6NZszc{dEY)@Z|F)f%S^x0(6nOSoi#c(WE4xa&`Qu*R^e~qu}_5>K1rh zB6~hQWe~O$kDiuwvVQzc;+CJFr?35VBx;1zF;=tMXoPv2lNoE96F0vVg0~9z>=0<2 z!YV&OSeesXNMt9WSqf@O6PZ=DSsGdfP(ha!YcLKZE7DUq3p1)DokBxdL#t1s{_(4v zLx8m3eTjgrfDkx|insjNa{jNexyjmVqWt7^O>%AP zY+q&$h6x8Mtngl(k=Cmc>+d*WkWdY{u8yp;AhhHFwlstwYB|)zG$zBko-*j5%eVN*cj3MzD5$eNQNaglPVDxTKydYXk&%E|Cu3Uu!a?$c(S9=ZL>jnQ!|NfV;xF zPiH~E|GYeYk7pADSfowop9%6ph@2cIoQ?GPC9~JvQ*@xw^?}rbXO99Xby{&)38z(c z%3J(_cIZ7K*Q3~@50Ht!D}rKYXvIDsbo4O`?zuWv>2hXJ#>ccf>Z{S1R0a_w1He>I zK8RVnr2HMC5$KV<%=LGa55an0q7G)YOSjCd%N*K#5{a(9DT-RBgLOCq3w3Wh26nm3 zm^ZFr)6VK$G4D=D3v%6)%kv^Pl8(qsqkN#E8pNKF`H4beCm}vvdKQqW9s+#&K^-fj zvRYKh*Qd&P!+PYFv&uzhHpyfYY>L7KGDB5A3jIyL>gTNrx?zMO5ga@K4vrSDtiH=k z5(s0Pl-t0&f%v%Y$@f=*Wd^Xs51>G@79&S0vSgvTMx|_jgv}U;T|=m_JWlZ@4UmAj z3H>EQRfz&4d=S-MEziY3viVw1Hhz%F{(DcwE{tR`();66IRuzBqGZDAGvb98QILwo zn{^}_CjUxm;^Fa`!~&+=d<|N+YGU=aCyGI-S`cN~TE%12rY>(vjlQ2v`L@!@x9$au z>mk;!1a7N+ntz)B^^k18YJEb~AiK#Cr*Jkz?EZ@~JfukBlt<<&NRGQvz{oUT1dh6C zc5q(vrv5EA@3J-CA)5ov5a*qBd!`?mx!Fw~!IGxyMtq-QzP44}i z!q5TU=n>S?FuKrLSiL6N(VE#L!>gFFLEwC&$hG zb#iKauzx+8o+~Q(so%#9f)q7wNHb?Yb3EqCUA(&vmsvyFUhvix*NDWZTmTn4Lnb=~ z4V*sJ2b$3Y(N?+5Jk))}tGpb={1Nuw$*)(Xn98~ZpTz63m)o#_u@czY&qF!;ggJ)p zv76d>B$(9y3@*gAO}77OXxyzI3-@>WjSiE7<_-Dgrn6wf)o|-D}kTdic+XOLop= z5O&I9kzm1Cz?=aw^1PzjlQc`O%=M*M2PnQk`&#*X$eK6kpZS$w~DgcZPmydz*iWFvc0qBp;V z1~kdG1Z>xmMb5D(E}rVTw3p!8%L4k$lTM5+(go=1KuK#(3PoJkX?w?DMDwnEXiGm- znHKt$(TA-h=8bo!CtEk`pcL9-jjVB<-kmnfVC$G-HK>H8?-!Vv_PNa6@*=vULV8bW zJyM5|-y9YHd-IwSw%E)TARhW`_M3kMV`jXWcePgsdTWtSe3ELSNsLL!G`Jus`$fZ2 z+6eGKSxTp;k6%%7Pf|%S(orl2QDt53i5y3*m4I;4%Y{Y?eX6D6K~7r{AUih!fepHi z=tAcL`8Y$WMI>aSm9$>JIo>io++c-yf+{n9m5VIihL(lmj`)n0lwM#)gX;|ovb~JY z-b?e9mbf$a6^34jR6^9#%oxmD26pv3MD~`l6C5GiU8V#jhJj(P^XhgoONJb?rh z?{IBBEs&Vtr8IZ$k{Z`oXho0`&YhBv7D;9PiHglK%-#dpJ(g?Y#r6&yrKc%DT_o4<_;p5hO- zW&SKRx94?Am#$5dEOdV?z_+7nAp9cBDC_Frj#iwEYRr*jhk}1d!gLYh{o3PNH{Y#}kc? z7nO?Yi@x`Hs0w+`;Apm#&yD*wsVqo_Xx$;l0W+YlXY9D78L~4?Wc9C&I34JfF=iIN zL}8A-jqa}+K1#9HGR@^KJr<(hwmIoqUjJ1HiXE!VKwcYJZN;JQ^NJ0ZkQ20C7dQC| z+k=VO-7oSl8RY`C-7XCajcW-`D$tHjTc3;ydDZC5?fdSX5flPp2;^vK*xn*V(CIRk zshW-7q!#Ve-GTMDJ$k0KYo3x3dJ6yw7Q#KIoy>mYj{j&;=HK%m z?tzIT2kJ2>YST8MK_7DbW{eS3>gtA}CFarF-XiGH;?$$^9Zz6e0(`@-OzOQk<6$`= z+%ZT(7c-~n3)43Q>p21D_+_?5Ysc8Jf`IEpff?=oD!2KNE+RV~kx6Y~erFls@|C5p zHb>ouwI3T98-X&QD3RVXREAa+?O!H1(3-53O(z6IRj_UJg+yV{*7>W)qAa8fsl%0L zJ}|llU6ty);6Di4#S`9+1YxFF-v>sLC9vMJFTla1!nlx@Zt-ZaSF_?1tQ|2?Uy-$0 zKi_!PE z*)cQg1rND@9K>5q@s7S{HI(Vk?9|yrl%K%B5@#3aDctY-&Gq3%#dG;g2*B?z+4TX) zCul+EMf`^#Z=mE-fR<(LKr7YaC2k`Qx8ub4Cyj2 zSqv|AKw)kZK}k9rZvG6P2)KwfDu*|Gz8HyhN3qJNnDG>X4WK7m3QfT*k)Xf_ZufWC z|K6>=<2Onb6Y`_$j*@2)%@xc8)PyDhI?7|gk8-tDI7k9uyK7s@(aF=Hlit7eovr;X z+RQ${!^tDnX##5AqwOcJaBub4Es7QYyI+QIFa-I7E{$e{9_U%XwnJC>1%sNOVuwhE zFtZ>Uu3P_5^h&u3l76XfLRf7+CZ^T<<+Mq3c$S2o$gR3D+nQLj4OZ^u*fF};+3ERY z02~X{uc;cJ{Aq1Ls=wQqtOi%RC{Z?`F1`J%KCH)fnC~8-kB4sqgXyEXpdVsr10>T`R=6%0g2g3oOWeDOydY-`Fq<9Omn5=L^-^Km=B{EL{)3nyE z&b~xv`#1)4_!NqIm5R0l({Foc$Cz3Fgf5(pp`k}&pUc z7$@gUI6t#BZK`RFuey{BSqAdyyXdC(^!ZD_wgeUiq3%7GGW?y-i*Z79{3S+{L^kCI z>@j0Ii+8A$0{JxUsDxyA7i)3l5o1p*#0nwS@R-07d9KV0vq;< zpKZ23sH(FxgxA?gD#~+FAq7P#FbTG)%3XT&@j7XuNgWKQFf}UO>(Yv(DxN9dmE8^) zUdi!5UBoBB+ z;x~wvQGI4N>k{vQ%@5e9DG*pVK=JP^+VyB~3#M$sV)(b=ms z>;0VBi2kXKiP()gx54+MnGU!lbM%))#&UE7#NYYEmH5UZ2Ylge%XJ)MG&v}*u`if( z!G-f#Ubr}s65I=%JkcR!kyp?5bBo*9p%;`tAHpWWQLg4o(^)t(lP?-^iSN@S3b0Cn zkB4Xf>*}A6FF)PFv+8XS1Pie#vbtI)#+bekW;j_lYQIy~`gViT$KA-xM0bhVB7IW# zLb>Y!!aw;TD>T{LcbmSm5#WUi6gwk>P>83Kvtufj#|>4@Woi(})f4=VxiT@~qZ$mU zQb4cfOcyl9=_z6P?&5%43cb|w$Xm5j5AhEgjD*(zMxQ8PNhw(l!+W~y!B9U`KaWSQ zGhS6uZy$;-QZ7JzSZEOKSN|g|M35mj5U(6JKE~&?mXH_IL%nuruds~#ou9mzXR5>8 z4_=KY9uPfuQKY3&uD$BMyUDu3BVy!n0%k+9WJ@#LUe_SAT9Q1DHFg#hfz4 zx(%;-QI=@rHW`T94Ke8y3>eu&TevYpK2@X_!WN~r$5wxPHxDdo zH(E6yOg8n(Z+4!ev6lw33`l{$FT9g$9B{UuuzbHo3=287kD66;NBUxp5>nqlC){>L z2J6l_;1j`++48-fJuu6EJ%`hGiETMOjz~CZ)4kU{1$+C7U{tWaU3fM|Gs<^F@Yb0T z8V)9*AxXOG?(x6ZkqL@Hv~{{rgW98Kx(}A5rVP(9GQ=#4?~cwal>zHJ?R~o47I)}p zZl7)KlWrG~tKGjF{hN=Qp{ttVWE%l4$Zt%4Ke235Xg|n3TyF_??Fki7bnni%Y=%85 zR9H=)4-I!g_K*X!j-TUM+VBN{o$8C+BXu_-k=|(MV&BmQzft6&|#yL13d-*jM9$IrKA?@4MKm`ieWxM!8EohvTYC;Zwd>2CEQ zb?Mh^lDX)AtVS|wIG*ce9_dcB)!k!QddxFh`^A~x8+NsS>W}63Avq@Tx<-H1d`7cG zc`N@K?sQ%8hj@g|+bgum7E%=EfSyFfXsEw?a^9W|j<1DDn%idSY6}!0F3nn7vEyI< z8vM@o*^DwQS{X$r#`ejNP)ph%r@Ah<4;L&NXQh>cW}LjsQ+LvSvDtcO6aPM++xk4{ z6S3Z;#ruz>BI&~yOlKc?=D3pntE}v@*Y(%FR!fMTWe$KLGEWLnO}mg?S2~>0VT+2U zPWY`pAF9wgvVR)2G$K!-8({6OSiqrSOdK9jIT~K*knYq(yTyT%G!i0^(0e=q41qDf zwT2gl+cPOvcI0&EB3)7TFflvvu12C7A_dvskW;)z$)p2rxw-_1r!o|*?f zW)UUuTZhUE^*yF6DgshfS4h5a!OUz6+4S-2G{ak?8%x01tm(G)mHLnZ`;s-3b?Lqg zL#B(mgFg|6ta>U4<`DW;q8!wM<0DZ<(TDhM4 zQ47KTD@<}to54`drZ?u;6ePuFif$1f+@gkUblAOO!+=aL+|)u17?6p&k`trP{DQ^n zx$SJ7VPsZJvXAX)1U~UDHS-R!RKYs18*Rg5BGf+q>Y85hE41?Up}b-)qkby3MIKRH zBH{9`b!uF|K+ z8_!T%Ck276tMa?Z?XPO{f3?A$IbF&kfj>6EW@6y3ujNf!Tk4~zizU^PYQ|?6(@E7h zpfXs7!D}FJMgxJL&={>R=O*865Bms>9$0wmk&(k+RrWV%Db(me`=iA5%10%9ZC`JJnZX)ghC@+EC9QMY07NdXkm45|t1a&a>M0)1^*%C9q1M zRIN@696k^3G)u2{4V%$ec1Ic47hBiY@Unb^m7l6*wPu5VI!>$_>=(@HmxxyZ?jkHf z^E)8MkyL@@(FVKBY?u=)+4`DfQ-ZbYJE+dLuq6>z1!4J42)ZEQ*G_})Nvc%RZkjl; z)t@#FY&(wC$Ryc=NmYOmsGZKwd-l2X*nWM7Y!S(lmV}iIhRSWqjImMsuXHlK+$w)G ze`Ip8wr|$nK1EF54%f*yO+T7dl3kfyT6*Q^%p_UXG23JaHL2W97mMzced^t!W@ZTl z=#$mJ^Qn+a9?L!eGSc#*d-?A-y?z9d*u&3mpFY|E(8g{D>JWWV3<1EoN^C3tzGmT6(b za!fhQDb~Se%s5#GZhg-d`Mzk$Dla0JS)R?<7Xo1os*6-Hk`5WJ2H0LdFumD!@JzK> z@YioRLBes?!ewtE-}uMSbMx%Gk>1P6&SfG*6BGb4TpajwR&wFDj4Z=BT7Bdc$LPOS zrfn=PB1E}`X2{dXvoljeiq%I!d8-MrdWIKSMsio9KXy~RVom&xei4xJvkB63vV*4k z>zjiw786WA^O

CuW}gk!+_IHkz%_Dg<`E6yA%=e(QvZ$QtIPPtPoVmoDFsm6jZ{ zU$xNxUiO4^hKapqhG7c=`Yq^jVtw4tolGLPQ?UHl~m@KOwQ-RM5ULvbJig* z&6^?q>WQ%fV`a9Q8~R;m(O@UZ6>llsDwK`$H7yE1=$eupVE#|9F)ltTqCy2(13fZGH>RAo4k|qu;Vb zULLyn;$&#U+DUefvJ_OnAVn)5S$Lw^MzfJ9LuR?m*wo(3*^gzm(+6T4{=&Ou%NBhD z@Eb?27{os9jY9_z)dt{ofBT3<#Wf;%KJF&4&268d==qlEpCxK*1l$&X!O`)4M11&r zTjXOcl4;Ibky8{sI&Rdjy=PPfA^Shgqml4}pf;Jh1*3|Nq57?KM*StapUUWLn@HH1 zQh~SkH>;4jC%#c{3O))P&c$lR6NX#jw@U}^YilDk6i59$3O>+PNJzjqda15I(>8;J z_&aCyjU7FU3{z!c{xCE6!hF&Z|%M;Yt2|;615O^0?3_5|qUH z_mql+91GJJ2(7(^cBiMfddT4lg0KKrRfE*T8$L6D)ZFb|BuSshvM-jKuM?pHVJ#~V zpTa{6JBqmZ%=!g3vkZr7LQXu4Z~N-6L^eoHTWFluBbr^&4Ie}$VVjw$4Be2>{8+^Z zZ1UU>hg7<63Rd{LvJ5!JmKJb834Lf|HGZ2l%0RxhIpdJZ(!hrY!Zg|idQ}Urq=zmC zGU?N74g5)2eLS4`v`=SGG|Bq$o9d*iM7aA$K?jANgnE<9V=uQY-tROLd>|bS5uQ1AqYUQ(Mm!H&1OI|{s2X{eoz*5vITjMLXRov1|h3MXh>^6&;0V}@OTG2XPB=#sNd>ul6 z7fiwZm2dlwj1Ii8`9m^&4I6VQAs)BEUe}2Ml64ybbfcGtnxXgKn+YUIF(@&lm8wI? z1}o`(r-bCkRN2fu6MJFS&}J6dr2e#`t{Sn;r61n+V`GZ^eBW}mq~UK^0%R|R{da+! z*O4nn08jBfyiH}qZ9z9#P3$dLcCA}|3c@P3@N39|ScaLtyKjWnr zQ{iCm)r|{ocLe4~%hunkv)JQpZGxb8{oW6lv*eGhuiKJLaHKQ+5``m_4jW^oHHNg3z@!t zYt(0FOSSEVVI?{8toNwP7#}DU*PWc8&2+jUM*=B%wgDjM3(nos}3lTPnQqD8ae37e=0r(EV|`LE*&T z)B6MKo`bW4l7W{Psp9Kotx0>Q>PC&HRiQ=M7Ph~jS>Ev#b@Zi51gKBGhKD_w)V630 zn9z^{&hh=eJ^dLxq3a1cc@UBiE3NbB``p6sM|Ffgy zOc0AwxO=wD9VfOE!iO46qo}>syZ7 z4udjyrEg)E5yPgt6?X}5mMLmvp@mPo#RW*>Gw^sV+Av#?B%@GKIFwms9QK=Y567Y1 zq|BcMQ`!{TcrgQpJz1}+uJQTGGPlec2*cybsrd1Tf8t08$0dAWw-=Jv`op)$;W7&if#DAaT`r6G;b9{GjHZN}PoII{sO-1-R< z0pmeBz5Qxo8};he?ETV5v94_TEp9U~aN$A|6?OXwyh~#*@nn0u05XnKf%8P3f`>0P7`#z`5=Y;ZGF*}@chP4t!2$%&d@W5vX<9;}sv2SwMQuobrl zF(FyM*1e>(KPQmyr#g-$)bM57YpSZ<7y>(^u-#j}_#0a^z@VWIT*$2MLmF|91` zgCVQ7~0J;1o7`!qktqiyPAW7`b7mnQpg=K?}e;? z*<$=Socrqx4+{ySiLZNooTkm-UEK20S5?iKXKPXcHKvn&USo!d&y2CMtpw$6eE{Co zC%G>k#ssHc=tp0?T^XYAScL^(^P%~8Z){e{r3s-_r_8k51IKMoF_~ZNm9~W(em=7_ zusIu3GJDl-%Djr-19Hcfn*~xEKMU8{AolRZfxQic@bqW!v;kQ`NQtp|_u2hRS;UrsQWd zy|J$E@`0{OfQ7ozdGG;OGZzQ>o8q_*7p9|WMvtRk#pfj*;F&YiK(b$Mhs)V9_{3aG z2Bg$)WH^dj)mDEnPfEt0ztYhb4Vl)FU2p6w2#4 zSFyDjw}GZSfh@e~Tpq~EjP3oLJc zpm4V=+sM0Y`MAUF)VYtC5{3Sic8ctHG?Sj0Xwwr%4K_o~Y2Jx!Rb|bSh%YLTYwGREYX^~dMr3C!^EJk{#nGbqIw)7rgshuw& zfzoyC!xKTF2N-EbIIAsre+Hf)=O=fKhmRI-YH13$Y@U=~{osGztif22S~{!OGsdIg z4(x*krB=qQl;n6@Bi&mhpiUlLKrZ`{Z!3gfM{0A@@??26lPZ+d23MqV5q_NKTWYbn zcZfkSW?_C&K8W_n!-hPE8+$&+z4lwj>`3<%g3fsx0wO!8noyq{?}_ena^cNcl}n@Z z^Lm8`9vKM-Cd1O8>8w(OaV1uPhrAP-UfyL@mUy6e*D5HDqt`_Z`a^ zPahN&^7-nw?o(Imm(K8HysrjgBN}qth9o47Ea^T6%3)VK3UXe1qUysE+jG6APQlRD zcNAf`n(BbjdF*#Yka?R3feRsm0s6?%c*`qM4XO;oA>_02mXqpx_$#tn3GMOMZ1;JZ zU?1@GtgJ2jwv?XX(O9PhJk*>D!!bVNc_cnrY~9BD?=1~mma7BVw-(i?r~^Lzm8fSs zkde7|VC)(mJ=mSEV2 zUW4KxP>C3T+Y~>Ml7F$|uovn*c){Okijr8+QlS+%Gb{*~Il2Ro8~zw+($!M?1fAsA zPDt@J#hddwF-XtUCA*_VKXm4AK>y0_Xp7385J>+Ae}5!d7pc+hK#o`aS$DxOd0-~a z)6uA{`@3HeKlxG~IfZPOW*;0(n$-@bDv0nb!pdSnWZSHQPdI$~vAri-C|Ec^*<)Dc zmuvu;%xBTdCK|WLF`z9C-Q)P^yLR{XxbqUX`>b%Z6Ce?_R0{%yQz>XGR8v&VLNAmf z=%8AxgCwiXR6eaDNRZ#1hYxbN`s^mQPLCah(jt+qdQ{)Y>(lr^`^6{j;ZJss$H?t& zC-WF1nAJ^*`628P@Rc@MQqO|wv1gl+(3#qV!$bi!un_qEAAqRX0@Su(g%}a1cZRLK zpjAGqu`XKydL%mHm0h3SBc8o~F~HQeN2Pm_l#wqkfBoH=$N?r+xt|X=E6|4u?4CBq zC|ws-d&aZ~oS0RScFcgy{YC6R{ec9t-6de>b7E*_wgaGW`jj^_78t_OXSa z-+}Iw=&uGL&xOaodb0?Iy{_D|CQ|l0;Hn^b5j#!lBo5#J zdw55tmwEfL1wqc$g<+rBYax$^8d975cvGcxW-^`=ZT|192Kt-*t4bPq>6R;=LROU( zL4>83fPlolIIY`BFLwmcHblXDG0-P;ds>ZcKF9qq3f3QV$V5120d@)b4uog=>$|KT z8~5qCLE-FLW*GFwG0DP4)ge`?yuQb(MONsU9T9B}{;JK@|MG9B9BzX_<>GXHMc0?G zx2$?|u9uYk?4qAQrWX*)BBryYM=@Ha6HUFb)B`B-drp`hz+Lma2<4=l@82^X-_gft z2&Bo7>JSm6Z~DX~&Tkl!=1^*vC(UK>uJBb0cBqhUU&h;6M$4T&bLd(~OiUiA3j6{|j6`H!DU?)V` z@s;v}yMrstwmbKKDd3m}LMuDCmXCI^;)fCGY@ECE6+Qd0`RWGD`WGKQ#jV7HtX|*} zhU$r{{(93b*y8fvq3iIPUdmK(3|lW0N)56>W9dnZT7+uPUP-R|VCb1()VNmTbj2RF zFY&wZ-MsPvhZohBVRMv9<;+?n39x!XM-r2+mACBLtXMYd0DvI%3oVY}Rj)n3f>PX* zNLwuTIE`?7Flw^m*uupm5Su8`^Cn+wpUtq3tOKt1=x%J}HDmUbz{&nO&Ptx$?%pzG z+$K^i{<>2La9=;mx*12v`CY?|3`OjCH++)MXNY8?9-}eE-7ZZ=$tX|~QYS6j5_aU# zSe$`~jimlD8AYuT8fezM4~OCu7)dWN-A^yF#;eYZsuJKqr5A=Ia9} zTe|QHsj15|go({A`c`al4-)FT!hTL7K~?z8c!YGQDzMJCHr_P?wwS>vm2I6DtUV6w z^|ZeCmTjo6W|oGS2FhputS-@u(~Q#(s{DHj7Ye0bxF1OZDu&EOTz}oaL#)#^>_Hw? zj+fZW>Bd=BFw5URGm6(9Y?wLOZOq4VzQu9$NPaHW{%mKzYdH{;naF&HI0F^JJ9_DTl12+*l@dVD$Cqr?8KBN zzSNuoF8^I7Kr4L6E0If>ba{`+ao_w#P$3o+v1rDYwm74v({daAvYIry#l+x|La#Tw zNuN5~IqOG2C#TGQ4^t=X)lN^3Sq9Wj3QBh69sb*1G>EzQ`$>4a%fyu z*}2R8kK}br$CD4TOu1A>AFL;@(*kj=^fUbfw4(4bg_6Q$Ahf|wg!M)1g&f5P%N%!L z-U0^LYIYuz{qnBC2!MBs0@YPcR+uvldZzTh6R`v!3W3+0N!Il5!u7~66uS}p1{(`6 zoH47v%7en*+G;4SL(D1kGINk9zM+{EnUQ3lbHlbyL2&lc!S)iISJ?;5g2ums$rbYL zHSdvuhGe5NIKeW)VV%kn;=;Q&UUDo+EXJyh{MTJUPQ{kr_q-jqEk63|?Bu~iaaQ10 z`tya-);uVtn<6q(1%rFIl$PxrU-;ppJ^2q+c&nbm*-9>_LwQx^^CmW~pr3spdg!>6i_OV62|)bnlqG_0 zy}}mJ9_po=t9ps6Rhx_D4m+}g5PNyQ!EcZ|#A4fF=OCnV3=(BU)vuoDv9756k<}3W zhK9|1^HzzwyJax&pRP^u{=*n?W!OtS>%FsHK1M;)f*IzAobWh+{A(|Q_jmlEsNvP# zZ+b<;`SKA?4vJDETWHh!AKA6{k1b06HfnE^PBbD9wKEK@DcdO~3jtF~g^%^i;1W`mVND2O=Rw-O z3ywA5`>&Mvoyk+N@Lq!#vcf%gb_#@vpn30o_`VqBqwAr+3Vzc$zAL#M9)~4!1C}kN zemw@Yc|lf|)idr(^;dVcx(Jak@!>mXxdcB6d0M+s>*J6Ad%10fSH^xNu|}NsK-F;r z!2e4!+=&nS0)TB9hU@(6%a9DqC4WL3&gu@Ad`nq=4h&hO(^{zWRDdvFi@ zZ$su)^2|sxKe7Jj?xD;THa|XRi9xv-Ug+SOAg|EErvmdSck2CJROWQrWN+P{MqpVk5kNN zp&FsRPd-R^l`HvV!pE5pzGUvLvo@`QgMl=aUeHpHh|s0&4UJKn@x)4L;W2hF z&t2Rf6-YrF#=askounEujR z*jJg8-2c5sfj-exX}a#}nU~_+Y2wQR$ZOD-%U2MmG1Br&)d6dv#l}x4y{yb|)m91P zVV5^T2ly3dW@tMx&-IQNb0M36FK)i!GLcNG!w`(3X!%WD^FBkC6CSU?Q~LF!WZQp{ zvbTo_e5Nx?D#_q{_Apj}dRVo0Ov1_j%3(rykvP5uSDUx}cOEzHw%oFujVo`NkGb0z zVH-YQ`%w>catNSFo@h&o%6`@{TbwV7YjUwNz; z_mupTe7nEq#9&m0u%UqU7i|A-FTS`|TThq{2P{;#C85xSa`~3={;R!Y!zcw+-Dvu6 z^r%o9#irB!80+bMoNy@PW2v_Un872OXz}y*JXLyC2fjltt1;=8SCJfa6&|Z?T{aEV z%K&xhC|M+WYkNHho@EEawf?Nsp@rRmFnSd)FLxcBiQTQ^ph9-(dt}aoaDJfrcz-`8 z0Z5@=u4t~^zx!$xTIc>84G#W(aie_ywOR{C?9u9dzkc(uVD6nBL>p+EXuYd!`fcTA z!Uw}4_D^?Mi!rj+Eu4E%w^+jaPr-Bw1mN|3d=_Wvr+4VwhIiS2&j7e`hHRyNL%(Q! zTx{*soggd&LX91MmvD8byDF6(u09~;3RE|n_iPWeJQ49_m-6fS_-7vm(P1J`9OsYy zTSB-M^T#jL z#!NCJw@j>7iSZJjDvYsqj^gazAeT;Ox$S&-U586%mUWU4v+r(KA|_4({4RWOXqXzab}Rva#x=lj1-(~H`m^j;HAqZjtdK$$&~TP(VcWKuboi-Vi@ z?|>&k0=i=QFOLH*u6R~u)W{ruW}{*U5%lR4;Z?rJ#SY6k0)U%Qm;^Fvnw!omtc0FC z{&933TaGG05d9z)c((*ecqcsUu)=%)^;12|ch2h(qOu}xh$7QL+qOG$sDeug(czaV zNn5aW5I@h|>(@Q_P8!c~XEQ}DVS>*Ou`r+VEnP10Ys@h;HYE*I`L#aR@D_HyLD@Y6 zln5d8B-P=`^Zba{uRE5Pv3JC9^+laEbvQWi`L_}!Ex9gzD?#LT8LG@c`U4*zT5SJF+(}wL{bvgFp;qJ!Si? z(#v!5??=;f)l}Lp$+{6Q1AWAi8vXnI7i6{z5)Rrp>Q~^NhcRu7E?8K1-a4jtOzK`3 zok47wNaw{kg_^#7MRVxk8_?IjrgE7M++CE#A(xFs$czfFEF??lja60Xq$oUmxcdv0 zU1+78vkvqWnrwIoL?qZI6XV@}&HQm96D00NC=p}0PyFMXfpxAWp2y&-LeyUt z6zs=eSs&HoUI*uEZ}Qz6VZRWL7dg$#?{0_O1HV6hiCY#F!muzqYQ#l_M0(g!fMQ_` zSoCU%zs9lzgD#g0?V;z>(JC@DpK+x59%l1ehO!a&1v20|oOP(bdRq=&c>(E+k69$`4@`>)kQo`h*fmUVd_mw!o@M*NsKdUzyp zq50}mq@A+aH?Sk8z|Dx7oulnm{k;ha=m#7!)Za)ql?;M}o85u8EDEIo9!t|w+rD&@ z0FDWEzKj@&wc;}irZ4{WzYZ6RXFPAtIzC+?>M2k63XJJq?!0pYxO5{ZX1`5=;GwaP zaWDQ2=w4&mCzsi5sip`O-P26Gl$>KU;dLZrQj)}Cf1{J3B9O^HayDm@>F5O5fdGtf z?DaHQ76#5g)&0F0O`nV z*K1YDpAS%&H?mOLv2jSBW2nkDH-DBHv|Gs$9aYZcNA+eT!yH-!b}nP4FCo~C-(5z3 zhf#>(1J{#x24bgiM2;r=T@P_cZ)^j^+_1(7)rku=$fF^M^hNJ!co_Mp#W$M^5YLn@ zCFSt@Dz{)=$egelYC9vC;bQ0jK7KSbvEn|&$Dw})9tkk(y@gpR(!?A}O&MrMu=Gac z57CeeZaWAi2dz{u?vrA=yRy_b*h$R!*ES-gpA7+|_l09f%a9wcf(D++lYnt>34)3- zQ;sWOO}Vw0sBIBC)mXKvw@f=wQi@eF6+OfY`l6&O%-(4}$`x!;_ylKM?9?&izt3o- z1F1>%Jeiy5Z34RHkFu=jYW!ODZ**$Cz8Lfu(w%6j!=8YcmiW^PdE$#gn=NY|z4`gR z#$UnVH3CWdJ>-NZ+XRQu;q|n5>*b+QJV{%2s zluQyF)hia}ET``|ePR27uk|X7KRNR zv+L+&Q}G}QB)>$zhP!q#%PEuyR}2c=xpa1?(2I%XtE{TWNj{^^O8lz%y_Yafl-QC~ zks~F-U#W2N7flSk--ZgQKSiSIstiLA6iVi$K&1oL`y`(@X+UB_H3wgO%rk=jdq*re zb+6$DNFJKz2i6i2a-+$4r5VhGdcv_>H$(*2=6wjZ{F`-O+8IZX&-J0oyll%_LQYeV z5vDtL&1ETlN$J@sZtAd0mHc+S8_jDmc<;qG$)S<0)mk23p{I;U>lLNDW$k41N2AbU z)+Gs_yjrPfV;vJ2`>`+Dp<=E;dpoR7kheT@HF&V+fK5Zod_KNbt|YQu2;@>aG1NpT8OK(o8bSg!qG<7wI_zJy=C ztFF!TB;}z1fBy^tJ6m}Kz^4XYA39_3MAma%YQ)WT#yV2umZWL`r+119m=eDaRv6qg=9lV zu}#QN%S7%Y5>N_T1k#q(#FiBvXhi^Juz|=Fv7F206FVxM@JwM|hN1G$C#XEZi0mo4 zeooI7E&~c)_9DH|)2~elq|KW0DSaDo=J2KVxJKp$V_|k%k;9KS9!ijPgpZL~1-5O9 zAXlM$4U|vg#KROJUI#3^_A$33gd%0dL7pEBIO{xm&0Qr?P*tmg9)OiFD%LGZ7AR3K`1+R=pdLG#-&B2R>dw zd?{To#@B~9B;+#!heE7!<96oe8m;~Oy z7o!tqnRgE&@{3p-U#Jw>WZYLeuUh{Ov;yjQOvTjJGK_gQcO)Yp-2UsRV{YGH_X1Y7 z?~E0yTqS~h3Ll{|C69b^reL`eg$|GV1l}m@jtT{xvzEAcqxCn}fnucF#(`-h z{O`x(pO!eSB|Pe7S{bW5TWMV+Ku6Ds6WhxihHX`U{0?##2#io5HKraU0i!%P{vH@} zEBoOSzS+e8n|;(XCS!YLWH@Vn5Eb&!MqWESq{qZEL`xA*x+K~SLs6y6x<54btHF9A zGTm79xW%iED$X4KUN)7csLl;_Fv7%2F7Z>DZ1P_FhzGEzy+ZzcslY;LJq1OVIJ!^0 zbE>V%d;EdaMZFG7Nv9`+0xZ-2dKM$1KNm~P9K-9k0Lq19z(P|<5EfZFE2h66ZYgyG zM`<7PpOjzHZsi)F7EP$zf^AgLb%luWIByK?R~fal?_+sctH;jk~~twvNe(NrY*2(1bUpybu2hnJ^CFy3P*}2xO^~e}#D&S4l>>&&g7JQ2S%Y0PqE&Uad z=sVZNJwcWv{st*o2K%)J_KO?&cMd0qe05+AF4c^2d(X^ybmEZYKwWArt>J1lSN)Q+R|lVybb*SUPpr6YP6 z+~cBIOt8u~=Ek%bxbk0@)RNx?r-3pAMljB601VJz!&wQfuZ+|?DnE_&_vpD86SMww znf}IRblR{p-mn)S=zGa`AxI?BrZb-$4i7|&3!Ss1)mZQ)>0euOBt$n;(4<#i;C%Tv zk{8-?MVao~Nr%goa{xj>y}whJU#iX~tQcr+lg?R|Ni>T9STdTu_Uo^}8CmoaO7LAL zGec>m48k76&e`PLCj<-=-IjSroGe8?7D>Xibg^ii=3#D+)me{pW%_iaJ4ZGb%;S-*buKD{e6=JE58}>fi9!wmBA;9P{x35dcC{-HTgOv_RtBtyTMb zsWRk%#J=`poxuN$-=%sH3;75hF4ESVDS+eyO=)C>BX4!W`MQJi`#p%ZXW-fW-Ks$r z%X1bz=SGwD8F7pujBBT@>DP+>YDex=RFaGaBhvXhc^130mgRx-&8@yfmK@72*Sl_vRf zQ7tmfk6{qk&*TS7VGGr2Ki}44a=qB(rqqK__Wnw*Hb3h0=03x~Bu~2S4z4V?gA1RB z`{XXp&TA;tNJ7fbFeva8R59h7uzzwR0YCL-2Ix?kSl9wHU4Dh^)5NULQz5JqgPF)SmNk1yjBv6cy*y0frN~Y@8PnSZMlXz-P zNDI}X53)6~5dYi;qM;+$aeT65SIsoQq7PWew9fPOD|0Ll4@bCqnXMY_b0!!+-P6Vv z6-o7MEH)iMDjWqj!S=aFkdfN$`uRm^53SUH3`l}9|D%b_<~(bk6`1`4-i@N;gIQuz zDM%hH%kEEoS9N$qDA$GErio??QLS14t&>8oy~pw2_vu(E1$B9+kxUzQm(pD{L&EMB z2I!5)uo5Wn`6DbPZp(FqTx-p$YWw(a` znmknA4OQxUC_9SYFT7s^;VOQ&{mUAeFm;rez%Co_kRH6+ku#2CG?aeh3kJLScV8g< zdqA|znIe##2gV{Cn?BA@;z<(V!+nU($o#z%1E5Nw0?RiSCk=JDg?-6*yM&)AiWfnt zG$-r*b+GIkNb)<9O>@gPIA)?YcCx+raR9?@R+H~V8&Vd5?`4?dDyIfSly1YL(#-9( zK*chlC(_-e(ec|lPwJCWR-$uN1Rr*ua;pnXhyMW>OAE3qADPj)ui0BnJt)eREp-+2 z)2#k-Jf;ftN@=1~s8TJqjGl+8(ZgQQow@9wfygO3wCNvqYvdr}oCR{vzBhXeO2EB+ zQItHNkkET_<|LKSb=^)#Ibp+~uq|Sg?e8d?mFMq-xHE}0w`A)k*OEKrQh+SydzTgy zmeu(5)w+y>LnAjP_IP!mBHsxh-|PyqDs}y#n?pdQcyYRMqp%Z4jVt z%5g;N)u<$>aU{nTPPs%!hlV2v4GyKGP~re}5SEZ_;*?LfO#|zsv-mq5-W6%JQ%Ik~ z#KkvGfZO=39i6tDtQa{^JN4eItf7AZPAc?AVpXI?0M0v&h*LRyzPemzd{Nt*o6)+? z*#vvoGdWd88w6 z{OU|B5hcv26sbbdHAvlBT-935QL)j^+eKugDcnTqIMKwDG0tVb=R+o;RDn?xCY)T3j#WgaaU+Snv;IC^Jib zEcUPSzd2B-i%{P6@OxkAFoB}aVGfUDGps$^BZ@ndW1iJI#HC)6M~E#i)R6=FBbx&$ z?v-$9inQ{gW2$E!ylp@$T?HA1Yu8XhTg&R!NvOpdaP-p37IKN{Ofs^eq!eN9cJ?qi z|HZtU0TmMpDMCa3x*Hu$ZEs6!b-Glug?m)2lzT|_aQ6w41j>|cK(+Scum+4})L?Jq z?LHO9QY15rEo*9DU9+)Tx51m&e@sU>3$yqE>2gu`+sv}mXKE2VfUu2)_%?4-5w4b$ zreU>1%-77$l;}H{APt3H3X%*PvIf84;@;cutq`0frK9cby(J`M zwfm=+@_*A;t_ELLdU5$g+nVw-f+ani8D^Vr^4qnRYa>Mlw7!KQTmFckTY(WRc_EZT z#W?|hC5N5JvXlx+TpXU;H zH_)aiZQ;Jy3LVld&TO}R_v@RyU#H>?L$&5An^w54YvPKkCdn*BPx6EB^7iZjGX{o3 z7d;6gGI}jW+dYZCfY|!O`HKdZtx_zzB;xCpzurHBcrLa?i6ua4B6SQ0y6^EB1FUTc zC~P5+-tm2O6aTHei?Cw0-pvQoMObri8)k+G-zSQ3oDFnupfV;*Gl_yc$=$A9RG;1Z zgVmuVCbUkJE#Ao8@%Uj3<9_+n&W?d6FPdDY^0T31e#NVn$fEn|LWySqYG%t6)`Mq= z-^RsX21Sqyr4WGDr*Kk{Lx(LCc^s#-C9YbH{dEXT~9!ygo!C{M)O3B2wTQGQx6 zntM<1cnj6Qr~LWXX@mG16GgWD-hJA3N$X3J6$&2uBeY&ewNt3%BITX0v7~(l<+;Ds zNugpyZq^$?)oCw|lk9W`xk|LClue~EbVH84A#w0&&p`Idg;=v`60_^E`ft_M179-| z0$CM{--L}c_qWGW72HrvA|SRj@yq;e5#1@Nm;0uB17s#mpK+ECNC}gK;=)l~!Ts=M zFmJY%GPFfEH)ayvs59c6Gx;qFWnd?J*?$tJrop}>z*g3X zlSz`0@VZWa;po|F*2Rnw({fCFAq&(kgkQWpMu9%G=0-L|ZFHiFMt_EpI^NHRdk=_E zK5h95Pke%Fm#8RvG8bIKjKjzjvb)55vAK-?71+z%N`!>OI2VD|e~N7>ZL-{SAh1pF zl}sAQw!)SH&`Kj^$Li8RZvACim3)T8$&qdZxR>s9upu}#K=eRMZuPwGb5G-ya(vsZ zc$eIQP6W3k|MWD2`#b*4OXCpnBa|W8+fkrZx5%u&YffYbTUaqv}8D;=)QT$fa*Xw(NW$!G> zwNT0@FD9_Cd@GJpbRjj@@zdVxxfQKWL?TK0a&HM)SOljfRs-82Xzq17LD#p;QYd3psoz88oIXhYevHB@8B|nQC%jG zldwAze#B$V^QB{upKEYi3gkt2v3)K-C9>WgGFUn09Mtd=R|jLa9FEBk z^krWvb1?2>+x9KGGc1;3`T%>pn@F2uZheU#QZ%VY$oL$!GO>fp{t zKEm~(=xkwKrgARjKKl!6ysy$9-Z zTWFw-S16ZHI^?R5jLa9!R}$=s)MCyDM79t@rq@M-OPw_8YyKiOIfH0+RJPEl;bH@G zskgma-SD#v-989$`skuUd1#!)cs|!{q+_;!_mM}%nM0}rSlNZ~Z^8TZd+}-X>nyrJ z=!Dos&3RBxOhcH(Ihxh3$Lns5}_sd3e+6 zcGp<60(bRhF>4h*;S`iFYnMDc0+ zt>N?dBVGk81Zg0UBKP;>UDzPZJ`A7CpE0Okenu$ym(fsf4j{0R2(V2jj`r5wa1A=j zK_9Ulx~7dD+$c%_m>8av&w{5XneLinNRan##p8YD@vfm3@;8>gaocvpO^Q(pkwDtO zXP{u>A})8aGeHlrAI7LO{cNse-qe}s3cZBSzTe-r_(y9E$Wf#hJFtawR-OY}{$ zL1f};7zTv~<`rji7NKex<^z<&$O_5;G9PfaF}c*lA#EE9Lm3#cxZLmL&gz<@5yC~Z zM>^89|WSf>6@P4e|^JmX2ONiX>kF6)mry zavCi(L|KKlV!m9ZuK(HCQD+?3`v0@Qwp#)Xl3luVR7hb)-5c>>468|MgyH%ADJ@^jz3^Cpm7yiEH@fZg`c|c6Htj$}Bf#x}{5*J+6fK zjPcVN!8>16s^}Y<8pvoVHEB-ilPf(2)#i+NSy3LlW>}CLT+FuZkjg-lKi++KP6#fMq0qd)O|1U4fZUV6rucL)^zTN3jP$dOb|z+Tm6Z4; z-w3}B6TR6%hB*2-+5jJy_IXp<@mOC%4ZruyM_1K@Zy*`D5aSkzEicjg95{%S#m7rp z=hhfJWT=~sY6pt_3%P#KIBB1KqSx)WL@&X#3_fAsEBuD*Ro<^ ^=oJG3fleQP_E zPE@p9CW4R=(U5CmpTj2fpEkyc#H@o;l zDcNNh{C%b-Pa*b1>VY7_RGnie#{-ox?Epz?&Y zUw{p>u?|3 zzjPmoIpb@^!|%sn?vpjc18L5l+U~S@v2fV1fSSX#+p+ue1OGWpVVP`GMPl!+sKk== zPxhMVp3vworvJ^bLC#)olw=^m8gTt&wTAiZwXZq@$GX@P=0G^Acb5&n7+%65?>=P7 zGLvGt=mYrlit5;d!tt+#X5;l)AIwP$cxjEI2=$B)Cs-7^%0Db_EU_rmOUrKiT2%MO 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

fuf%o+ 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Ȥ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

#Mmvnt4xhKRhi2#S0;dF9%jB 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

Z|d93L&MVqmjSbLH{#gZz8sCx_r1DEXOF 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
d>{yjs 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

9PAG!m-p^em3b#Kk#glV$cE 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νcKP~NgqTCa|NMC6fLPR+c>=kI*J4(aXr(8l3&B+i(khV zkMKwa9wHEpRsSxZtrtE>x(-^+1a>~+4@?aqSJ`G5N2t)&KHeutc{5Cc-3#5f)`9Z2XM%J33! zXgUG6A;!lW#uVAaF`_$V((Dd9FoXEmGaF~M!E2B_ata#um;FxN^e&@z^9J5BZt~O3cHoO2Fp>uyK5mQ zNV9z)DSnW65-Op8WG4hGc+@`d4DRFBD&uu?YvTorhsd_}Va#2c=HU>Nb5_B@7}7}! z!7*ks8z^5i>Y;L;x#L0hcF#exvc7l^;3QUnqzoEw|6B(WwdDs!ZNowj6a{G-#i0#B zo!Ki7a&jHsNW*s;GoUM^miecL4#Nd*{Js+@zoF%%Aw^3*(%OEA5ciD!Cz1t*7AL+} z>TIw*g~K1*X57zqNw{Kv)5690b2KCL?{4yEcWiaHk#@>rHb#jrE!6HDbGjB_3=r12i=W#k0D2Z#haU_bJmYqAT_FaSd^Ugbd=TEpZR|cj3M~?3O|3YNw9o9w(0g-AMDGT ziL+_oFMk~q_fLe{+a&H{7EYO`SMDGp0E$ma7 z=j~S5bbs1>#|*c2G;`bsaVvhZS{ifQlPsiNkEFVXZ{sz05wKn|mN8K`-6ute*&m)% zK2t?12Z`YT`@pQeO~2phY&`iA80Nd`1^it;SQM@`D!4d8*d2EyLzSm9vRIa$IybcH zNfQ|PIX20+R=jT4DBLtk>Vr{Wt+9=!bI@AV)j3pG^f0j9?J46}Z6mXt1b^Q5LkZl& za_#3MqEv;(`0Xd?t0rb*a35eRw-m9e?$Mp?)JrxBudAknqXsD-;Jks}j$t4R?(}?M zp+wIbG6UE{C~kTxN9CHtF`EMPW*VM}re^t=pZ)-V)i`|~DRasq^M}+x@+3(deLY$} z*B3fdu7+*qx2i7_FbYG4G^fWH-GmrrL7C`6XPjY95qgKOeD$nnLspW%} zAxXpn>mD^^V$wfLA=YBf!)yMu4OJA;3XJ`4DSU@wN^%~~HUP#&X}2F5kBgnJ5wJj2 zhuaX#5#8vf&KajBBVXEMyga&bq1!LQw@IyoQ?ldQyi(8xT;4L+mPUnm*8b9*+r-og z{QTeQi}ahb^TV%^+-th##7D#XCPv#-F4mNCrn#fiK<(s;OvcH+v0#Kg(^`^Ra=$Eg zx8k>BP__;OprE?U9Vei7~727HJN^cnh1 zX(RL176}oTXA$7*%8_!0B<&`ZclbsTD$7jzk6>q6)0XGL4KEk!;S=8?X&xPgpw!)l z0vxLXe z{${BHJJX#aB)Mj9EN<^Y^!yC{_I#?noSF7}&>(JL+0XD5 zpl|XE)Hfr;2^|CszMlZ1Fprs}LDc4NM4si8YGw*$8T|r}1GpGEKaO&g-(vu1xr5;| z(m;GQF*mEj&3r&6yBKm5muj0NYpEly=*y={Xe7F$uX{(IbEyLDwCI*iAj>?l&JGb7 zaJYnJJk0gt!-=`F={P`o|9i6R+)_4cLf?wQQ_-)Q*uF%O6f!IdC%@;uQLeN})Y3C&%Oq;R%%mKFsnfT;LGIfZ05qe{#HEU0+kFFYM55=G3 zc}8gt2Qg>x9bjU4my3{m@|=)_=pL8CE9iABlnG6?EgbC1r^~|I`S~Z4vp4|{)L@C^2MjSr3H53=}PLi77h>`s$Y-gEjw;tohMwf`#fCP^yPaN331kwvh zL^_wO$tI|Y&Bu6>zDMy941x&j_39cz$x{#e9gk`1M;3+5`E467r(|@t|4kRx zG$iP=0=?Mji4yH5fpfKQ8~id;I}MQ%OUc?s1l>QU7`>>zY(jX3VU`^*qJ0)v4*jGi z5Qdv&E$ zLEX5k`kYcvDqajIzVOtKR9=P>Rho{N_alF4 zw|72FoWBJ*b=H5PhU<=}86w*^+`@YcHQS)i7ouv3%%Bkl_}2z= z@B$C7m2Nt1f3al#$*!8eu$77Pi^QiRC-0YZ96+N3=-5jkl8Wj1486Mb%= zE|%~7`pS8P(FPM)!u>z=0d7TW1OzKG#5vh&zq6zZ@iaS875fG#br~7Hu z*%SK1;2+_lM;i=Ap?g#JRb&;pCK}s*G8dp$!7tlroD9Uux)_!tU2;IjZj_SMzcVM!z zyHksP)F6UOh)et8Ti9lMN46&xN$d2;3;J*Xx;H#O85#ZMr#>!<-aDF%I#0S6FgiBQ zmr({v$&&xOj$GD-In2*|XnHdVe*SvnAX7y~^*~B|57-9#xH*}GlK6UDDOvAP8rk$A zY1X$fgl9VGqO?eHDL|ZTK>(GCo}=RwYQzOR>59*a|pZ3@EIGq?_Tzhpv558n{n zep@(4U@zfH;ORw#L4*dT_)tLdYORSQ@BsxsC!K-#0SS|6b?|$tUE!oZMIm#8A>(?$ z8h0VG8XL=MPabC$3E)QQ?8ra5eee6XtZ1=}CI>cRm(F{KmA3EseZbNUJ^;Wo0@OjX zolp~_s9}p*FW|u*e^P>H@|Jmb&Tu}-)Q?KSB4(|DBe{eV#c|(|pOOjir!xbCh^m&7 zOng{wpd>-N$0jymekV^;L=*ul)O<^Vy2LTIef|5yAt<552;*E82~P9d3H}zMSCD>C z;o%Sm<`5H>)H3KhtO`M_d7+O0W;33SW5ExXu_fogYm@~Wu(T|3LwARTbuQcmlCk5& zQh8cMKG_utQa#jWn4IFJ%7_5S%S4u6nAvozQa~9W1}xC1gzu}|OA{n5Fn9)_fh7PZ z>_@Rz(mS9Gq@d^kk+P(_wn42EXUEMj^tf3JqEhR25S6b{=|X=6kSrc!qrL?pe;uzQ z%XHfF7Gc@v_r?8^>DXq@m+yb^d%|JW4QXxgH$=$YwpXV6uDe=}KOc-%EZ56dH$@5% z$Tv%J1yOjL9%JZslkIG48wl=XM=!Gyxa*K_>T3qd*f7c~LkjizaIS1nYRpG-I1kr$ zy5F?l1fwOBAEUNhuaQ%uWIx}KDyQG@?~G!fX-HV;k+^d-yyrC}5?d6>b2h4f?eg|N z1Tc=a!YrDm$Y*<9Q?XYT-U^Fe#V=oLF+wF(i8)||T~!-X%J>rc_4sZg0lGmbT;oXH z;y2c@CQz%@gr3+d`i14|bF8Yw32kLlwI~Ho=5MqX%3(~mFHba6m(LX`-RtuPo8RCf zEHQaHtu7FO*r$Dcg(m>i&kbWFgZ9E1>MXHTIX}qD&!G=|V%W^{?=*8RTF=seKi@#G zDGhdeqg`(D8_!pO=+T6Vh23-Z-@DMp*2})fUdsGq!l84WDj(LA+nnbmWAVaWG6lbe z=X``o8&PHh&6%Qe6=vq#ux58d;}3z%kHpG2Q`f!#KHGg+v-R0D|XH{(d{fue^b75>LP4Efd_e zgyIeWY6!m2K`f`uwA9d=89V@cpFS97`2t3>>Mvkvml_or{ z2=3&npy#W~fitgK8Wv7;6oosRG-uTcWCi7!VHxASrS)?DorE18(>)wCmkS4z zq|(?mCAakum+q4f#EfG;Ne<9Eoswsv1s9&M5ixRXysB9Vn5Q#%VH=DYiLw>O?}#RV z&ORLnLYx1Fnd<%|=-qIU#7x^uKf_>sLzluR1Ar5UEA;&FernqlE7#;HX9trX^_N3$ zPu063dRWg>c8f8l1E~4OwQe#mMwp`;*ZTd zY44fY=fiYo_!f_Fxu3YxT?2HWh4f7#ZYA>Vp-oAWUe7NaVL(+3dmKE-bH(oxRKb45 zvFT+``Q=!_R`%vMmqlltCHFe2t6V|3C!oda3v+%sw&D6st3ibanTet0OLjT#ru#dp z(Koe-hdfr8;FYZ+!Y#h3C_tl3gp%&M&>CX8O_T$M(?+Nnp1t(rK5uzrway21Ypl=3 z08a2F;-54oZZ1Bo;`e7Hrn(0wtg*y=V9hK2c=8cl(j%d|5-APh+Kv=R@#l$rB?Ee7 zFYh_q2o52)MS3&7HyVKe8m!!LlV+=a{aaR%I|xfHQqm&lFc{A2wkC3)Xt?Vad2$Bw zorP{3!BGaHai@b3Zb&|Qa4f-Gzbr`51wMcd#gU!2q~| zkggRG*3j!M23>1;5t#YKB{?w2p^>LeZsRiB8u0gHJARCy{iykIjo{*u;cVZcuNai6 zRvff}PQ4DoEK=%&M@38zP=JV;tZwPv9_PHSX@Z?XHco!RqYI-M^B(;|RO_j`1vayKe=kRl1|%A>Lw{tZiHe9-PL?rR6X{o#b1o; zl=EBqoYZz?1WxYQ*e?#+TKo!~R<;m{lh~GkbHueM-2w3ABulPbzXxJDE^u*JLimFz z?;`(fWiV}7!!%oUu_!~J$ zMt7$k6C+<}l>FRKaXr%N^v7)YA3Ss_mNy#kUe5K$kE2<&;_qTqM|P2=IRCF2K=D#V zC~Ds$n&AS{{H~l@x+;9U*faax1bpJl<-Z?*g5zz+8tsirn+jy)kw&31EbY%sfUQ)= zeq2IC2xz#-Xl)pi)4R8MMn{Nq4E`&kea~~XheCPXFuKRXF6?p5n$#NS&g3MT@q-E+ zi9|c5&^5*ud+Rd+5OpXI{eV6>&i}F zOqLBDKmFRUZ;LO|+k_Yn8sksSYui6qfdwV$bzjnc?lV4mEbq8S#8e)y;imYW8>nG= zL3pIj^M@eNd`BOgGGQywRQ8i-HC={fNqFrgUYlim!QkpJxpKU56N8#KvE`6>O3CkUCN3eZF4f9?}nN{CX22(7P^-Vu{ z40bK4^|mKuP2t8Ax2@rwH|BZgEd=Npw0+Je#MbP9t6ol2i;D1D9MA|*p?l(fc|&;6 z_j_0Wr%Y#mJ+)mT~|DMy330Pl+aU0&U!69(5= z6#VRhHx|`Y7maa*M(=+;-21@>#mKGNzq^k=u=0XdUdxH$;pvcrTy#u>e|{0?)37?` z3k_36Vh2ohXwW3_s$o)oHjt)!2WVuPz%dj5%or6@HrI|sR%+kSFziLbkc1v*CjOW< zH=!i0?qSc1-Y^s&35&l05W|P-8zz*WP5CA-ojKjI7e3|ACwx)3RZSv>rRmLX6FB{w$P>|aZx9$=K= zCLw40F2IvP^6t@khEF&eEs>B3$A=6XUfd&$#s+3RS)448Zp%Fl0hA^xV|fYT`QO-d zKz|wV+RAKNy-}$WM%OdlrhA65#jzELnpu8KWGi)bR{hUy6&gK07fOOnV|=cBqeX-+ z?aMQ}lSC{>tWSurR9+fQjxS|fQLR|uD?c2P8E&`%v&5BDp*>#6{wb z+4g%p#OpVd$KPOhC3Se=K_5lO_u9urHyf<&GbUNE!`iVW=#Ny784DH;^^**6KIDDW zXL(cwYIZiF@?C90@pe#@!~hI6*AsOY@MPI2MxR$^M`8n5UlI)dw~0?;loI$m4zCY! z(3uHzDK0m@zrslV7;_BKey-5S_dX9!o~?u?UnLb%$h((2VoVu><9VVwe*h%YJ-5@2 zvdw8QFUL6v10F8wm5cb08gePWj7O2wJi=?Osyd&W-X~Pt6pKz1&0ah4Qo;E(bu1~L z$+?3tEqZIy!>Kgwud%)HZE^N4j9+#CaZhvgC|bG=nmC~M@DuGUubWl1^A53Y+sVJ+ zswA)}yTVz{Nu#m6DVjD)r?Pv$lszL-8l(siEIyv~VAj!Q!16+#wD)T^k=O?XkBcl0 z^dO7R4Hk#IOnb3Sa+7;csZW;X;@=XChT?Yu6q?|p2(7d^rDSn~Ip&|Wrq~=`Zk3@; z8r7ywpRB&(`@z?vrvhzr%l>A%!hPV@#F$1&-k2^DI>OTc0FSb@Lwc3`U^aalzosYQ zC-Tu}kRCo&F7s|+4{2jDbHsh60f7mXvtf(*dhu+tFFK5c5>U7-?rI*$>`J44NUIPH zlA=K@xG^XNml}HY9u_q*^Qye%UjY5Q)G^QYCb-DcBQ*SfN)zN^wSaWbPh8-!f7jRc zmD8$lcQIq^cE?SLut}EbgvFXWr3-h_ZTTyQ`I#bZOMo4>6HoZLY+@ph~PW^*4y;{yeiM?{HG3xM$30>70^ah6hGam#_5%&qm8g46Y`R(uuQ4S(&P)NK}#&PKnwsg5&;HL(Dg^)FnLKtCVCyI zYv`mk3R5B}#G2#X;E%DHrJNRKHDkg;P*G;d_0~JKbiAOLbo6xuo6icRAl4HhQ8?Ah zBO9N}&ZcF1_LTxET_!L$;O0I6Bi?+Fm_|AHCJ zjG}c%cX%>Dk|s&1SIVM9{1%N6BQi@69BDeWjuf>iL9X*Rh`trZSst z5P;mP-{z@0Xv7T@U~7boPVyRuLh6+8ddgs}(}+nJCp@m6u89gjA}?uput1Uy|5?#k zDF~YYKQ>|beqZ&8+B|BuE=DwMoq$yKzOXT7T7Ist{72&M8>zBD*OQ!Qm=}J>zS0M1 zy4{X)AQj+S-n4c}rN`;_GH8h7hLS(5RQ3J4P;JZgeLTXM$lXoFRj?{5%K$P2GsDf>~B4=cT~(S>~)`hp%d1X@j1 zG)6-TRmX?#Ft-V7u+`YdrA{Q;iD4wl?}Wava;VXo|9|n*Z6yAo)H@BSz`ACkZ;m<-bd;_bZVO8ezqOY-_-39Q9FR({$?l+uQkKK;l@NqVr1HT{3QKo)yC z?at0lWs1WG02CH+puhpn_!r%gXzdnQvOzr@Pi-0BL^z2dZHS+4ZY^w;=gqLygPPw- zyCckdu^l1Y{VkXHYXzZg%0H1_L6Xrydiz8c;L$KWi4_Q>he5*B1p)-KgJg+-=rW4W) zXEct0!1C{}sXy7diZg-Yv05Yj2Om|OCwtzHbf;6U2>ewohYV~cs^;ksJDT)hwz5^Okekd!5rCd zg9r!@Yc=ykD4ivbJ5}8^v$3#LuxwLm5bWy75K^wMkGL$>yJkn&m&^D#q3U-X`^5w< zfUyi)ES1=HkW8JSnp3S6;=i=E+FsJpGd*}ppT@CANes0@Z{JJq)=%yw3!GuwxNVeQ zK>uJVtj#XihAqkfn-P2-nA(;L6Y3=`UmejC0e;xiqh0HCnr8mBtyNYdF^{$MMiOEA zqF=5v-Z`(N_nBJ}s6MO;@lK^1y-ldSd|}jjO$^s;1bMwXK@V(74TDM3?TtM{u~l*rG+m%bdsn(F=MXrG}z`v+I>lr~6p;@o;~T zjn>-eBS_uXthn~H+`)>KlvqKxc;Bn-+@$uP28-x;7)&nRG<%lbG$EM-n_0=58-`CV%#u5KTP=+mq$?LgW(aD@2clDWb+N$>XU zfxTc44acZ6j=As~{s77MlUfF}2(eH|DB3{=r&QW14Nlv5LoUf*4NiHG2wZ#;KjYg# zMH@*z6o3R)htUmVjOoWmTu ziq=3c9zU>N7WEbyCZzo##vLGY_c#0+2Fn~ytoybajAb0VVOr>qA#`;eZi}Z3%$*h2 zm1ZNSDkLQGBOjIHN9zlbpCxpQ2Qn!#F3=ZiV0;EDZ})_xCl00X3V~M10i`hnOdfh$9u~;!Z&Hxa1K^*R zz4+`)-}cjj6#cf0u3zjg2l@>JAImCthJTlbBq+jC-4`REn+(zFVDaG@UCAc%lO00n zGEI18B1Joy$Zy3rI6|$na-@(J!INeLwNU2BtQ#*lB=ECaW6=pXnT2Bt=*puNb~SvQ z#}mE*Y~&axyN%IoSrp~>4V9enqxz;bOZ}27c8h78YYx1G(cok3WS5l@Kfre%r2TuP ztg*4UDQE^6pJ(gt7YA{N2(`kO_$d@q`~pAVe`kvSRNZ83@SEz6Vz^ePjdLUY^sy@a zeL7G$2fHh+z4lb7UL7YrsFw}(`o*iiGIU+*dL4GNmmfe<0wPg%Y+1@-bgknfz^>i< zGj8POe!lG5RAIHkZGUGcbY&#mUO+D)$Xw6;Q363s%VtM?vI0hvr}}V9iQ>I17G>%_ z-CPm>Fg}0lgNlpxNdSMG%X2nV41(Xbot~Dk5Rc0Oc|`q{RyW3@h#&2$WDe3d#~8iJAIs8FHt0Y7bu zq!j>;WfsKN`6N;p2?fu=v5Q8e6EKcV5L*<`JtRH>xnu}W)@qSZf!XJ#s@IZoHx@y$ z?Bwd+d2R*SU$DZ@5y3|I7~u=+B~B)rBmK@xs>u#|P_2r`7wp|Wry|cG6&Jx^q{#AA z%AN_rMH2*5**k!amcb+TacfM$ri7o9*UPU30*+-3QO#T+wp44BA2_00&I>~k+Z-bW z*`GD0_neHR<_Z`bLcUsyRV#|=Vy!UW(fsYF?1^d z-xXETCD;Py<@rfftEg95RLIR>sB0OY7}&HWXCM2PDdUH)nkwn@@#IJD5OkalDXzR% znzbjV=p$V!!hU<^fStvFoPbY36UYJ^`fKX+P0tQ`R2TorOU#i+gx7dPsUJ$AF8&9$ zr{J;=he$9W@c!#3{~Ia;~t#{%B~h*|((sI1z=)DeanfdV9ql5qdZ4E#DFh#x!f{n7y?TS20#CH|{8WSrd- z0u&8fX8jO4VfQ0V(Ctqsv{0g}rL!ijnA$M@+C7ZC#W9Nrp<9Imu)|7{KE}g*!SYA` z^O5k!H}o7D;O_Zwtqf18Sw=hac1tpAj}~Nj8QCZaukbvD;$ z(Laq2`-ZNc=y}YOo<{RGWU|lB&Ps2ZMx*6tr#2ortjuqwc0TN`i79Up;40` z%WA6SrK^qGNbSN1a>#T%!ZSv;(y&|FL9nz+rV3DxL`3InwP$|-iw+Fh zNht?7vfLLE1bAY}AzRgm_8I0HQneiuFgS7RxTm)1%agZoGIY8@mg zYuFyLIt|P!2mT^v8pnLt0}DJ$!0h(Mm#>&GNnSCq_Bb)8PoCr-HBUcFT{e}t7IkyA zjmt0PtF~v3egn$7eZEV-#6#-Mme7CI?VW})`=6nj43CIs z1>Q&9)~OGRkh#QqrUXZ4n35tFf8GP0J)EURB2ODCx|L=EEeG2fjJz?gN{{X140#t= z{b}CGoLKG+Ig~l& z3^`v!rZ$_Jggo})YIY?Teg-e=_QL#{rdv0UJD1D~s*J%mE+#!}q)5c|L$tzd>r9LH z^%6RM%)2ML?!a~R)BVXmP<*_^O+#rsli|DUyxuFtA7DBe)@?OFD0V3M*v&1^t3j08g=c-qvFk6UVtCC?_?2qW z0bAevB^}V&a5w0N`9fJ!V$uxz%X{Zz_;M~5g=rF8>@ZM+>=gM4eG=S}$8(~1oz@1LCu{aU^&0(?fiTOehmU&x*ml9 zzOEu=CfZM#b+%x|2vPLr`@Q{!7|pg7X{Pt_Xy1WHpnz07qb)rbbDU2$&^uy}?g_e& z`5E`^Sbw34F@DNaiJcGn_JI%O?IvHJg;<>_JNO!(K;h@PLMVpBpwpALBS{K-^q^7N6O6)MR!$BH2jd`YI7(W_b9`FFwy$aWn;M z#&4<)WpYEo*IsA1=v3d4B!QU1fj8yL^^Gb@**9$}n^R!_A(>3n*A7j_y-`8PRvdn= z<#^uu|AvrY~SQr;Dd7Bh|Yv_Gj?5O z3{;_(AO=(F0|*B(G2P&kApHd2HpENxiuM*0BPG2EpwYt_$qFsS!U(gmLjHKyI)-n} zZVgX9bru7Gv@ge5NqEH4;O{h^0~`S3d_sqyvPHTM)1?8BmZ$Up>gZL)GySQziBY3m zj8R@Y6w#!*-Jnl6CCu9JM0IYSk0bI%m6&90@Xtc}m|2)u&9|RE7~_YhRZ}cRvPZvU zss$`m?vz|>QM1ExLu$NQ5T9Wj@BwY$se6Qn&{=4naZZ)i7yCkTC2D|gb2pzHST>mt z4~ZqEr4NAMkV8`3q{9BJFG{E~>GXQ5sQXzDVR`tJ9l{OX?EA(exZIJ?-^dN49NL=o@~CXr*|4&Dcq7 zlffs7F(0-YX)uizw)Z!aJg#=42YW9tps=?F3ZafowbUDo># zyz83JtIpNr>s**04b{h3IQbBQ9G|>0HT@VOqHi_+*yGT8F#Zdm=1Y1zY#zlITrde% zN3isLeyc2Ex4;tJvzS8;#b#&LP8CJ>@F(SkA-)<%*jGJ>pSrqVT6%nIC;z8%W|@(U>oG6Cfqe$uxVb2_O?S4v${@3DALG%aumfsVHjO>S zEwrmfv&p}9|en-+HCFSp~dv$TkMQE z`}jJ9qn?N&k@aH#B{`k}N;#*b3F9j7ZM$Ji#nN_u?3;G3D`C;^cx#j9tCr0;>sf=E z#%Dk(WG(cSW?L4dL9nA?a`!1qeA$#b{To`?(6YAX+j9Al5N%mc{Z2+U>wSWv;Q09g zck{b2!2}BDiF-I19`>aj(T6ArnTy1s7ueNRrohP(g`KDmeRd4*UlS>mT8YAk|H6qX zph^FY#A#e}bqdGQ=cQ2MA^-^J?~w$=ht;#%f?Q1(S`RDFL3w`m9f=NlzjCFtKN7U` zscj-{-^#WJ{T()@S@}O-X^C~KPO>gkqAuYeMN|V#caS0m2jXaSG zZW3?q{`)2ik!Mrpe!JA0%fzkEhyA1IED{_9f+%_*7Pu|Z;0^(HxJz(2{V?0nKv&g& zFT-@No=;&a#%3F{686oI>sF5c54du=v_gIuDHy&YT->lBt*HSe(1*evCF2Z0GS1rf z{XPE&otrxlGILfZHfQ^Sy;m)N)k;4AZ)?wcMy$8iUa3%BS~1hIl}t!$;L7+Q(Xxa1bT= z^aGaDr$}DladC}Vys|6P5SAF3;B1f@;dDJht3rz*zjTI6fxMWiD$cwiIcx0s{Jhww zOiHKYg9HWAyJSjR)eoRJn=VPf47Zaf4#I$e&)Fl>`XO6RZyL8F`JMW!fN>fHcfctmR;JR6-x zEp$_UG3;kJU%U?|O%zS6Vtj|+e&iVZ76C>*nEjc#ET;db6kEKw>~3=b9H6FoA{AI> zlDwm&si2BlNoDc{b_cQ;Tsb>NDPYg!nb_W1FWju5E?WuCwGR^d`8Sq}&33tR+MQ36 zJ7pc9X$jlWyJ!4>jWbB z%|+m!6h4Ou%9^d_97p_Q<7fB^Hh9YBMm2Ujf;BX+DzSvBxt*^Gvk{R41Wia)_JaL?XTA_9n&L6R(%v0ot+diz7HUQT|X(1&Il;e~}mb)BbnMvEd<) zz@sXBn>Q}IedjX|f1uNohC~zNmzugp^lSHifBT+2^=Y06{y>!bjo_IOHD*c|=7DD7ytRl;r z>{$`}?Q5muy{+M9>27y&J!HN^&#*W$3OaXpty#R!4E$Jg!27uuDt?6uGCxECYvTtkNuXIaM6@GS6J!NIM&q#_-A$YhYzKCudfnH zKOMUPHY}<9b?fK0{J!j^_S%YT-eB#}g0}`8BAtDfGFftF&U^J&Qz68B0Zr9JUyOgV z-rW4^CTVp>MQ#mERFKl3GEZwY>0%yc7U4Kg)aXnu6@-zjSy=BB%lL9JA`n!*sv zC4pT;ilx6kxYusuFUK*~9#5Z~tc*VDR9*G!=FfKg{$M30VWALk$Qs2x0|-RX;Q70< z4@3#G_=8wr8wv%?c^^)(`HHzg(e%w_O*}$D8~ezSDSc$2nJ#S_Sj=JMs^BiCk5J`g ze?M~Z_{dQO@(WpI4UXK-#1hKTGMQy|F2WBelO3X38L2n{A=ly>@Pzm~@d#=g0K@^l zYE(*r#}DTwp$A?>P~nR|^B+mrPl6!s;#6u1s^1hXHlQ?^o!Vdm_&*Qoo1kdB0>(PN z=21Qr!G0?XC;5wTr{Di0y`2P-Q|ILQK_W1i@3dd~aLLF~+s{E$nkTdgA~QlMe=L zXtHP_MaL!cPa;B?VOzXjBEx?_mu&b`v)8*|gWR8>t1Vb6O+Tz%231$KdbUdUL{km2 z?vD?3%&M=F@({kh*}8k->E{wTX(#5q6i3uCbB}<2FD}o9LdVt8`R|KO%>tYdaoW&p z-rCEZ2Y)F=My`EK>zT(~*D;l#bkhCZuiraAg+tu*;l7B2QZvh9s3hQ4r8bILKZob9{~oQm6-dOh@^DV3o1wI~JXtkVvZAK8o=B%U zWxmi6y6t=fF+PB@VHm<`T0d#r8F=7jZ)I+Wp5o;M2(r)n z-^0O06=nSd;24mS@w}=ElCl5cA+*Adn4A-(o z)B1z|@R>VgKaX0IQIW@6Xt3BGYk>1!MPqOKW#w;fx@VM$^N>uu~G+sDQJ2z zP=o7%CBPxlm+S|9oqllbWh$J2QJN1#sUVfmu#>eXfkqSGR1Kt!gI~uY1~a6dfT#_j zT6kNnLn^>ibxL@K!l6vxnmw;|WJm$|QV+(EB>1~h3koksG)-M$A=p#JZ#OUBstbQk zL)ESPYa z(hRI)?Dt8?YpJ!PFRk5U*vzf^-o%Qezq?N;ekfn_Ev*JBMdpQ%UmR|9DbRlSsQNmc zIe4p88HL4WLETA5|ZR$ZJ7fe9EgG-g`5&+-1rwE zjr{{?bwxbjMCIOi4|c6~u=%c)SI4I^L2bAS-Mjd6fky6`y}L(a_2UeEP0TY|S4 zAKFWD`@P=wh`EogvK0$G;_+Q$6_UuOt47$Fk6ys~SS%nyxD~&N34#K+SqN+wa z?>#QpU$zM?q5p82j1xSt?p@YP(vF4s(DxrwWJ|Ai;P6w`T76QPBURn!@jk~#%~{Ug zF4nPakA1YA|G13z&I+4Pw!LnW`5gvkJeJz}usiy~@nEL}@QS|6SC7ZS8j=N?-G^({ z+Tq#Um`BNijv#o!<7g6p8+(Ukfo<>TFg@k_>8`!ZMaDfgYV&R#8@L3}gG-VK>e|2_ zJo~YL>{CO^tj`Sis9biZCWwMuB||o!o^q+Evs2j|r~0|A0vMUb0QRcF5mAYw~BI`+tu$hm@YJcf^9Z2WJ67bP5PCPP_{FJDP?!)~@dSnb6QmYqofv zZx5zGxDyr4SvpEo>v<$&k}9{?^r1!I{&7|RaS|YQM))Jk?Z_1HZH1q%0`o&t z7wx7;pM*m$vLyoz0%9wRu*NYeKlVLu(;MmG94PrS%VHyxM2cg1g4#;!f^{n2P47rR zaVa=d#U($hsNroT_65Re{1U-QY!W)TMD}}~WBJR@t;-`)AKDAqAQSLm}A{1xT$CL)1Aiwj?aOO{8yd48kgzmd91V^Ro#Ub z&Tr=)k%YG6P8Ml~f&M5~Q@*)|FDqineZ?at=Tg&2nGh12vW`hf&RSsoGf4D$`4KiF z<@*LZ+HL-gvQcHz1TB<`R>!}ATT;YFC9+KN3o%zRf*QCt5jPe)4)f&IQHfODIgd?t z&fJ)7RT+puVOpL^g2hp>66>Py3glAnN72J>@KECAIcUqBFKc1Ltrdm4@P$EH%nvQ& zD3lr|8b1;F(nS3EtjQ35#NF?nzMqw`?ia^-DPF&_i1^e8L23L=;z~9eLAmfZ<@Kl&(u@C)%ya<2Vm!#DvDmWRI8T zDQu;E`><)s6aM5e!|l~D_R$LnPl0H{-B=hcF}ff>8KGM?Xk6=w00iCyMXho*7VCJg zBUEyX9vJ!kNm039U;02%@on4=Jz z3Q0MknA>jISecabQpL5h@9@te2|gpVrSp_+Fb>`eIpk0C(QEWG7~b@9t+Rf)`%;_6 z4ilisa~Ru6+^izWA4~JjOv%(uW?COQuuY^s-Q~Pu2++a0tYN-UR{IZAF_qY)4w)u7 zk}MoUYU4&jEdYs>(2JF=b8a(yYTd@WyL~u#eLhHBI(yNTD_30-9!D;8GqtsP+-Cfg zQu>tZqV{#ei#+dOtGrtU{+*jNP%yxj%KyjWh@f}U9&SXfTNCtlA;DWY{mmFR%N3`6 zm_r>-E~+M8n?)`XIc5zNcKF1{%bg)FueS||y6X(E5?NIwF%q1Jkbv-{M$5$Pl-*1R zy#$o?FJ64rfL5J3Yc!3D`*d>s3ca3$>!`PWmEELXyzK>tcl?SDdbHs8jT_s!#F;Oz z4;lt?QD*oj``K(v$$?9<75u&N1HxxT1jS~s?B*HaTHhdv2mN-K6aDHaMH(CW>34UA zih_9NokxId7C2+RUVy;yB-yEMeHQl~&{2mlU1H&t4z81=PpQoFZHpsdX*s}P5Nj93 zKpM+dmcSJS5*Sb(Fx`HysAV*oL~nb0=UW{Sucb4a_Pk4+IGPDpxweyY;5N4$rzBc%fptZ(8{I;;*hKWXt}N zs7D&BWW^zWSiS1sU^_DTI@V!%pj)**w-trG#gV;%1NQ>R**)0`VQ1AW9s`_ZDeh_B zS)p&XHe_o@cI6BMgb{6hq zNwhT%J^~~T*gPgiz?P}6seOL3kL`SGJ#r#_ChMn3A+9l?Ver_X1tEpUT@`8sMt3%# zrhNbAJw)wiq;wk{AIgfWOEy36x=m`cvS@uyUGkFw=~eLa=V01h6>(9f1VRBBsOXnj zh89FIEZJYQr9c^=nQyn`MP4@QcH?XM;OOf7=u7O~ygo&zj4N!N>AZ4&=KGR**n@8n zBXhnrAzGSDnbe!fO!>+<6K%e=8(}ZF|Kzn&>h~eqdkpl~#L;@|LDu0W-C9+| z?9gPu%k6pR#g|*^7y5p9r!ps$6GQM-NqBy_e|>n>Wn1d`B+9JQ`>w{C#!lin*d9d( zRr06?Ob^0aYJSbRB-N`W(|rwu8>b}og-ms5Ja-Zbyo}(`m*4}1j%))EOjB&z9YprD zE3iFt89U^Z#rI*zriq?ITn(uG0Oz8KY1|Gk%OqJ{lQ^00PH@5a* z_{8KflHrI}_eSxEK?)EFYDfCXihWfHoB5n3vrdxZW<%<`0RTt1X9nRfiRTHBo&urh z+Ymk$BLCnV!C^3Ty(y^SKDB%J!{k-vM64wg1G&vV_`G~b7&UH3n^OySe_rA2snq(- z4j4~Or6{R(U;RbCo*;DM)W7jf2vs%_6qCndUOqiVu1r1|r0kSf{kzm$>GS0#Pm{J7 zH4r58@lL-y!k*TGVb7km>(Y>~U4?j`aHs1I$K$JT`w0MH1|{~jI0)_}DTnfPa&fbu zxy^2BPPhmUz2$R5o?-WK{Jp%{E!l5&Q~l&4_KYtymxAYzr1!Jc9)PPsh9DS?_AAG| zu=so%3wvJn1 zKJmEkaNOukw_;+QcYHEmgHp^yl2w8$%T8?^v-EX;Pu^V0gL>3g^fXuF?4H&ccOrih zC5FF`waj6dqH+z*Dk2Y0Iz56&qur%UCd(9nZHES4(j8}CJ9-yd^C${ zhN2~F9rqHFxSiJhGMIGwfM&E>dOi#N(<9A&Z|WKn<7F1ZEPxP zXTq;1@NEk}Th}e&(F#JMSqPz_@KD(-g6gp&%V|Qw7PBjPS}&_7dn-)n?wWD>Qm%kI zULo3U04|T4^($#Q4hK~_jKN^~)9hynes?K}#Kt>h30}e%PZWj!_EvM15OA6Mh&DJf zL>-)Fg`ogvvgatdqX_zHA_NvCxc@)(ff7Z|ERjHg-bK6900LA1H9*S0TUx@xSm*(R z%~sxUET)K8V~J>(Gi0W8KJVOD!B%hY=5A#)@&`z`Zh4ai2mC;=Y-}L%fs=f@+h5ff zah^1qV$ffTK3HVbV^wVjNyOPDeEDb|C8u{tz0qx)`r7 zcPqbE%pj@5KyX?&UVV5I%gcQAd0`2QsJ7ufi~PWhZuEO7!^!yJ+H&@^p$o$8sSTJ# z*tYLc8^VKr(ssJaT(SLjT5-RY?sz)@xUgs89@Sg`&()`Je~zOs5V$Z8tDfqNQxb$l z2q9Pxn6F)Xj$ijT8Siu5zs3C`s5-s&W*4FWv9{0PyO+t#I$U=X4+~N;#`_J>lbR0X z-}bn&sR_LCF=-s{^Y1v)B{{@AQ)@E;R!oY@>9Z_$T}iV$8DVDuIttOw)f_YR!m0X7 z@v_#S1}Hwi^Q%Dln`Js)sYf$MM*F`{2X4Vg$FX3@aZ8hdBa{!E^b&Nu9~b#+X^f;H zV8K&PB`occrQHl+DhaF8IxI<}`?)PiyS}L7PCcAtoX{YW-;p+`7{XgaDTEa-F|x#j zZ!=^W2{UFA(L0)qgm1&TB43kuh8ea(>_=;QOnF*5LamE`2vgGzQ5t%8Su!c_H+q9o zbq71yV50nizl1R&kYGfc8ZaX2+tLJc&nVa0qyLot2E>pX7oNi@;q>!>f{4@>9NPLc zw4fT)$LTCZD=7H)8OBrj4a(r@sK+mKrh{Svm6NwX!HvR&@v+`n-tfE}DF8FYCMGk$NPMc?~Q-@RO4*dhVq# zriRh^@xhZY5EIi?0`z0Tl)^3V+O}}V0it*oF!)J%8m7lr=!+d?e>A|BpuWAq92TdQ zb=!q%%*g`J_|m4n$|@6USY;97ZH65>N`1x=)x}#&QEf9AdyaW*rYIkTP4SdizQ&hS zGZbGOn=?EML#${do!D)bu4c}-F(FeR>zh=%r2WTL#j+AIF=Cd)mokwIrYoH7n$KRz z!sT3@lq<2LIt-?^Zd6~(XPsH=ZEdcSXwFvEH}(~f@ka9(4G^r1o`47!_^=Da>v>Qw?3$en zy1*!)VoybsaKVbcBlN3L?jpORKPP|=`#Z7l)wsNldK>sCM|ZMr2TB%&e-|yZz2B$MC{NGigpfIuc zvcElA-ys{3CWyCZ9<|_NfRV88)j_urF*V%aYWxGJm_ueba>&m_e11!-oixiu(b z`k3TvbA#9U*{tatp9C4S&6fi4eb~DuQ8|!EZ*wz`yqQuO@AqZRMYRZ=IM!$U?S*67 zeH&fbKVF-(&0}Jo4!;l%;`od3Z*csOZeg{b&w>!3KZgEcbO-66qZ;i-T@PL)!C^0@PLdoJ( zX|ua(Jn~zQ;uidra^_YtVuq4F^kb7Vd^#8IPNUs%0`WxoiuvD^NB`k@S5;+@&RD^D z*PLT9e!k<F>*WC}y0*&!>F6_4yp_)_-i zQu76G$DfW=ciP96PQxFLwB>D*zvmIB;ET?ecop`tD zFUUTSI-nHu%ed$#JFGNLnG|PLaPs!@fqNQD8O z1K!5apSq7S$Ueka8qmoUzBnN5KMD0(7Y_TJHH*6U{WNS)j65b|49Rls7OYk?v`WKt z1{Q~rlQ>Y-i;tZIh)=|_iU@0c879N`v-Erf*}2f+_B@(cq}*_ue^U`9rBGKjg7p>?PNf>cd+3YPN3TOZ z87hd(f5lXzYGP|=fK6;twMB@rFB6^|Mvk-lw#%-oP)?NL3+Q_P6gqUdY>n7SF< z;wvKzVcfV4mcR&9YC6o{v-QxiawANx;FJ7PhjTJqZs|iN{ z4&s<^%B!I!=DnMkd*^@InW<~q?$H?KzWEC0u-j~>UQsaVsJd?ZE(v-KzX041xm~A& z=cF-W57QGUZ+s2$pb=G9D={7qs>50de4ov!uHo8InSKyA$PBJbhP=FkIyxa-&X8>7 zllTKxJ&0@H?=;yy)dW_>%~8R%5q!?7n%qCPP6*Ivjlk7waEn0XD{%TpaImF}YAa9L zkt*&-**ABp=5`D@k&D74HY#eChQ^aOhGDv|&{tZgH)CR@N^+Egkon z?7%!L#ES;J8(fx8@nt+@qeDi{c8R8moIDOtn?SgZQMj+)QG7U#8B;B!Ejt*^yfJP( zxEGa54pXtf0Nl}?AuW}Pd?qaqrJY7&$Hw--r-lB_3|9JfqCS(2@=}sGNlrCWHbGFU zQz`?(wYAg!qB`=4bG>X9ZuvGhJu+T{rl(TqD!cvl^o|S+^O3C15*RwQzHaV9+;_b+ zH8mz_zCI_(n5_FvJ_U>z`cGn)dfO4?MBA3Tz(898_|c;9f_~#SuW=TOh|unn()kAa zykzB@#w%XNc7wcwc_E-0I64jE0?5U$txXfggk>ZO`k!yv9ropp@%uRtqh`E*Vl38_ zU6uyxQGWdzy;9A%AP^%7ntD9652#Pyaqz*VHU(9d&qlX`8Gu;J=}eISe}mTd$ez-M zGiCgg&7V%*AJ`0F+}UEoQbanQH_Z{S`8UsEi2Dlnzj>Qnn#36&q9#qVJ)mL=_~?$` z<*P2MH)zZ!wPd_-@hySwc`h9eINFSu?oitdJN>8?`l~aOR z^9q2?T(KN6KJlBK-kt%l3LSvdgQCDEwLvhUS%Rd(^Wd=zxJo**plQDa%;0R#RHlzP z=CyOgbOhmMoxIc`?J*r(v(|7pYVq$tSEMWk?BgR~Qazl&qsq`pV~1O7k%YO@#8Jst z6UlRCCYJ{tms&kKTRX!^1O|*Rhzjn>tTO%Z?S%>L=0R!Pcv%huL?{2Q! zytHVRD2S77IOv=vA;r0?=<@^;(xetrR>1Z0lIABHq?Wc!$yLaGwU%+a%6 zu=v<9&KB(pl7omw{bT+5bBW~|y;gnxF1>oR!&}IMr&a2MNrr9ADTBp~&T61DcOH&# z+CHfsMg=x+4VGG-K%cUtB8qGM_z{f#Rx`j?LKGE-8nX(C_G;bA2CEZv(yc2H?Q!q72v=jgEI^gGXaHL5uP@9rp!A zgEUZ#-VETyqM==#nBg^Mo6Qjh05ZP1;W%(qGc%IYTjb^;cLgnM&yA?qdWkRCrXHqAD^r~!XNp6c~bR%Z=DpPA0_5>(vJ>~wcBv3-v`qNny zb~dgK&ib{p4FWn&Vl3`#zCW1So<#+wTLobo&l1~T) z@<}KkKh*04D86`7w#M_$cRec@b4QK6B05f*AQl}p(Io*{(A<<0kE0D8`%BF?u&`8{ zFeA)H(Of6r0(xC)XoOK36zaj^`J|d*%6wNo5bNi{U);vDyqGsuoq(tifFLYl^ozBl zr*_NvW!CcJNmp+13eJ44gZvqA;lG1N-*`Toa;z)T4@WC0X(wI0SzTY_YU(%;sZ#3(pCC`jB{gX6(sG(Zy*{!OhCdRYC9WaALXXS zbqQD1C_<0e5JPlu6OW}RPgx) zpR)tZgm-5b6B{enK8*jlhiwf&EPqjew{nbuXg_22?WGMrzFrGL6#Xi^>V@39e3?uR z<%z)D_^0Sc5F%A&{ay!bF}X>D%>G6hWUCOVG_}0d_CQIy0+6Y>Q1tM8+)iCEm|S5D zeDt&vK60GZg0-a9Hr8`n%B4h|IU1y-1wK8H<%f~lfYw7M%xE37K&W_+}goHO@xPixM z{vhO>g%fOg{fWZ)vDw;SzcIr>%@!~L;~p3L=^O062Dug}Ang&96epGt^0aG?=mTD* zb-C03*`bCrli9(zw)T#woe2ZOs6w*2zkHDhrLSeYI-E6#Gyw`b>isrz&(ZE6JLdCa z&CqO6y8u4^cfT5KPc^LLQyY_O*~B2UYVb|O@>~0N*>D)Q2Z&3Fx%`5L1V!dbi0^4M>CcDn8U6j$b`E6})G z-`Nb?&}HxPdV_!^Y4iP)3a>_gAsE=Z?SfPhCf+F%Sy@(e1sYh&*B7lhC>%E`aM$8J zulU`SImj?UV-p-d_CKgQ6)dd`kmWuXOG&l2L|hFdr&wGn1fIkPD&K2uuqT^{X_Xv; z7lhj(^{6sdGB||3qhdMT`omSE4(t9OT_%RZ1&}_s197*-Bfo|!k>?qrS(A}#eg@e9 z5t|~}$c7Y{i6a_{Y4=O~wja`3BNW4~0{K+|a22P_JKUOZlDLV!;N(nD6)FiQAND(B zIr0iGHN_VE8bHmdRa~nQE_RRXR_ZjKnNVzp&Xx|7PxVd#zgerJ^w;L^@{9SBQSj5{ z>!rZru>zo)7lyNsL-Mj}f_ZgCWg7D6<39=oTpqPs zqglr$Dv0rps4fE>FTqZ1{{J_`mai)o+%+8o0D?XMIs$;TP_WXcT@sD)XcO`uogXhmss zFHJu4N#E1q9)^QQ;Q>^EYBViH_)R2NQe z?q^+C;c%J0HhnZ?QIpIM{U9P0Owc4_83VC_cX1h|G>av>*F@lkni9L4MVNH9y@Ko?mpgjTC1bkZ$Vp#nE`qDM?gf)-w+0HF6^N}K zc?awJ^{@9Y`5MD;&o$u8f#<7DlVB==Jfqma|`4OJ64~oY`_il z6Kw1OKnbRcg^gUhJHOv7WB8S zK={|r9}F{Tjz@As&0SS#KG<|1mF~Xr$|%WcFYOQ4)bg881kPv+$0^mmC-)e9cgQ zMP(IuaoYK3Rm?GZ^;3Oa2xX1=Eq2xQ`e8KYEC{f9uepr32V|Rv4JP0nw)ZsiW`+<| zZIdfC+wmnLDi=Qk5fd4eZ&42=Jqze10l< z7_T#bt&LN_04iD#<3N1%sZ9ePAIVVIL}BEu&oU&jjAB1qi)0G>;5WGo7Npn~XmKnK z(`df5m;ab8eUy6ZG(6KQKL*nzOU8e7BrUr6L`O%4h>tEU^Ewsq`DNd{SzP_S2Ve8j za;Ca)&bipDynDkRjxG<6uNQEdw$p$UoN0z-7wpVS zL~#+=jFCDmecGg12!68}iT6pFI-Nrz;%J%_rSBIwhS?7~0;LpoLyrb8tuAoVNiZ>K z^AmAbr4A9m-5*7G&NxF-&q&>`Gz_3DghAsX{=Ls*j6gAue$NKm6x^}iPvBRXrM>`h zK*JnN#Zc(d)j`G`VCa<95$FklUoZ#vi7=Ry7JN3_TrcBh<(#W6TZWY;e(jqV>14*# z4eirgpcsk9dLI%Oes9+R9F|770Xy}j4Qk~N zR0wJ$HcOD=lZ86IE)u8HqHdznPI{@;dP1e0^Dt0GOSFve zothl;kO*B0D@L*9;u6_D<}?)+z)pf73?}Ax+t&$gFGDfAW#gz?=c`c(+qRg0l1_${ zD7FJr-PKd{$Q6C`%8f_U_Qcv39bi0+Omg`1DKvpGXJ{ZL$&OFJCcfKLCtNh6 z&AnHg*HHJM8e8lVTDeuISIF@3#z0wXg9Yr7*{}XAPKad?K z3Q4%uhB#Y!C0Ew0&$%@=H z0!>iWShRb!nZ#jxFn=<4&;t`g-$eH~>tNa)iboKHK z+9$dQQvZnQW3h>~!t?K21yrk85u=$?!k(Ntoz3UOc%WQ)WAQ6fM?vt%s2>kJ`&X@o z4_gX}qdW#wOJk+mDGR>&!)d458s%3bY4H1lsHZS}56kZMVFsUaWGMWJ#&((m!t9kf z&z$13;@b(U1PRH$Svn0yBE_cpV)gyKA6OG4W$HpA=% z6V>rmUek{9G&OTBVF(%GpDg)i42MP>KyFsWsVU(a;!m|4`!lK;A{NZ-;%z3`s(+pD z+_%Hsphm-qFlN6w)1bnOZ7_D7KH`-%yT6M}>x;$(rw7qds!Qs<}8p*p*hqGa}F~ zYfWFps(L(|?-pPnzvtM$si{$45VW%-f#`do|R|KU%pT!aG@oyQNHK zJvSvF%zd?v(x|O`PsLDTk*kjCO@d>{rh0%lN(d};5U<~bzmu6%WutK09TOYE6E=@S(=&{vU1c)#Ez1 zWC^~{uV~b(>bB@5-a!LH@4e?6jYRKB(KP12&sOI7Zq=@zVItpIU{#gxl z=spkreUCq3X2U=ZPq*6?k-f3!6ZMvt&r`ooDEj{Nj48g)dW1;`gKn=7zHT7yvv85; zqzm|voGQhX)X1R}HcN3^Mhr)zi#M5V6o#=XJ(XRPs?2o^w-3!o*$5gEt8UnCh-ONt z0xi9oJ`|QP4KGFLI}aPC4O>ssjm2vtMQ}P#Om3M!qjYp+H@LogFP3{-QpO|LF4|>k z4A;!|@zDs!vU9})NjNvf?8t(nRAajFv_3GpUIEOX%G5N^3$dTxjjs$mcyF=tbrsxJzL zJCQCF30d}t;ogm+nvn?5gY#Gi{7b1#OT zdNNi`vI(Sy#h5yk(r3|?La%$sz*xRr%0+~r?{zHRpv|r-?z6{p1xrLD4VEKCtCk+K zn?2F|##A5SUinZ`-vj(fywQ{Awok2KJCH~$&di^(xkazd$PnXxer|nV?Ao>w&mDeu z4z|5RawIeHg~WM>@C?Ohsq#`|bXgJ*fCywNl8N}+$SjSPENM`1BR_32tDWK26;ndE z;ROrE*>jmSemeQQ*Q4h?Xkt7QloCPo}OGE!x!Tu z`88t{mAVuo-;fuq>+J`$RK-}X@R*<8?h4MR=X-A@8mcOCpKiAnqao;Jt0;sKEvmOM;x4Y z5!X$Tj3G@HPbYK74v1t8iznZ1ZV%5iglg#~FUkgp7TN0+kHn8CYuY(a=J@`J zIraNkE9v5cA^?w_~8&{sfW_xAd1caSG}M#1V5 zwDW5+EPA;YRKa=)e(UGgx-%25{XNC&z!No z4IC09!KkIBvu)jDv&3l6#~_b5|3Q=AkM$AgGbYG4Jq-BxabfS%$J6wE)F)_O#t52< z(#GHR6a-E9l7}7T9tRI9sZgl9rJjgZ-H*5{9baYMg=o?eq{fuLt42!4s zd!-tzv(UOvrYx;<#~K>e@N}f`Kh;ncQZwrjc2#|sS4D9$>LDb{oi?N-Blb;zP)DkH zI-Z37f-eZ?~orsqwzGNJP(#krKDY7ifsl|MF_WNZ%=5d@Fx4-JkO|HjHd5r01rXGWL0(7FH zBkg|K+t+R;_Y`eWdrT9|c_Ro<+UY{nd3>bBJLQdPj90bS_9YVS1MX7PpJ}`%W9&2b zD*RgGm69<1@opG->&|x}l$kif%QeYQeG(hJd6ek)&twGAhWWy>;LCKyM*21(&EPbR zcxNbP;3Zxpb}JE z8LB@A&}f1Dk2JRD)2^Vrf4N>yhgCv>VwZvs#bQE#;&7_>ECz=YCbn| zyJqNxA?W=v)Wv%mj5iej?$Ie za|krl5c`rK^%atYVu8FrSJg}H+I;i~C~|zw=J}&^RR`YuGdEK>GS$FDqS@qcE@ng+ zb6pMrKauNN(v%@TRN9d~C#R>Lwwa_7lvWiK8h(9AzptJD+3q7JUF zR{nU3*Q$l6y4uj8GjvC?canhZ?F|(wtFWzVg@S zycLC$Fw%{=Oh5dby-ImO5kI>Jfk5~t?{OsGlKWMu*~6HJTSDPamqt&7Aihrsf9pl` zGQyCA)5m51Yqxa?F61b!oESd>X_&$QH=LK(0FN#0v=b0~g$XoZ! zCkZmFK}2r!@V+TI`()IlmoT>H1eSc7v5?!OieDLEVT7DsbE`c}e|^KR+zyhH_Qf0Q zf866)7HJASMFrkcCEiM8C7xE0yhEH()6Gb{@AHdEiiN4|*%_;5;m;=Pp5FGf(D4W? zL@-Q!CTs1ZMuiX^vZ@n&OBn&4%`)Y79wiLz>VGDTU#vLUun=x+6#}x&#B!#VL4V1Q z0}v}kD7=WdP}C$Vr&kdXVqUPR6|K?jW!e)O%V_7h!6c3-wL3= zT5Ko{j_rEQn?1it$yYODHhS_UCjDK2Oaq6dkSX|zg`+#oV>`vf`5TFhuyu$R5`s#H zqBH-Qwk{;oWknJM#;cWlmBg9`)Wd#0(|VhklH}0oR!ST<^qTfuBbUAFnmm> z=#6Q+Il>yq{R!R3YJo|_Qu*AL2poI+ssN%&HpB;RS7~rPg|?V(JQUI4|dIq$!tja7&3NFM*d3tX$UXoEIf#TvgpI2s+b}5WyPPzrg8jfRluN zcMG=XI$go~8%mK732(xMIp%GoKBnEaTc^VlAUf<);6XG=;v?$`}!`$T)(cs7$4 zEe17$(NG!D5`8Cx5kluI{a{pLX-AgOig9vpjUm-whCqUi`E2fPE}^;$+XU3yvggtY zpdqkN(&jpP|0vEdl-AwzEfI;|ssW@31=%V*lPK2N*x6iNc+;Jgfa=*l%OQFy*Zwcy4^GvKnBdpxt@2zj z!E=T7-52tj+VLvb`IytzfZgU9>`u{qT~fU6%cr;FuNiBB5) z>XPh&szkJ*j2%8InxpxNquPmovM&2*HMN7{b>&@FNn$?6qCJ?71nZ7eB!WN2NC|zv z*D9&_K7lTWM-&nyW!- z&Fu!8!G^OR+7)e@TR=K%71qUFx`QD)%VApeJEmXUX}AR@v0&`PPB8B>Tx|jCpxTKkmg{J!A7Wl{$A5u>r(6D(rRh{Yd!61zW02`U@(U37lrf}1Gzy4t`RC7I4tDC>ZPn`kduJ(-|tX~rTk88E`o%82?2G;dZ z!{x4@aS-1xZVdM?+(^LB4gR$a#Nun)1C9fS{?1bG5YZp{(Ikdt_6JDoB*HrzZ(a5 zYG7b5;DDcNzz&cnmxlm5Ah13-4>;@fgVQheFpvw#pY=dqWdMuO^*cA)3^xLC@SyM9 zpB%n4>=zS1xCA(q@Zb1@nEl`r>{nADXQJQOx!?G32*3&c2m2?8E5OAUgC5w=I`r>4 z)gPZ<9vIjDj1~CzjKBHz^&yCt8~w?P?|1|7q`|*)DX=fVI>6NLzTg}nkACL5fNy|p zASQs%I2fD(;QS|UUz~n900Z;kUk>XxTo3t)+fNQkxceq#a7$9mY8 z@1$Rj-9elHUc7I9LjRV()-MlLUk=W`y!Y4m7l$aoGy-h=xdv} zfBnS*s73A%2Yzejm(#A_T#3*>*FasdfWM@FTr>VU|JQHl7qi^)%m2W>e`nJ-ul{h$ zA70Y%Z(Rbp`oqb;PIe+~6>str2^Lwsu41Td+0M570AKr4lyjASKyyboU1sOl*{>dZw>pOsb z1YrHIalmO9z;T29*7`43765Ag;i#Ag=#z{Gaf589#Xja>wPqeh0De{>1$2vkdu- z!M84gnpp#%{c!6yw*aOAFa7xBpK=52{U>(-xBY68pIYJpO``o{{LA6rS_N?M$1YH3 zSq;*wU!4MS^NUyP&lv%}K-~wm=nv<9>&+LV&`+M!-1hYu;N{;O`}zKBqxGk@NMF5# z{9qesDgB$LKl^_5(GTu%?W-pMw!vJ0`yZY1#R(Xr5dEhnWq=cbMyY>s@vXIA3>oYX zF3`W~6sY%rCn@~juKjS%@B4qQ_1`=@zw`AU{{PVgu)+L8GXR~1!QUGHGndW&@Fw@w z82F$2zBLZq^TQLzPo97}{5$p^w96L@bpw1HLI2P_-+KI0H-7y2jmJ+-0q6Q^Wa!Vj z|6^bO=F=DRfCHj0M=ZbG4CVr#{_YEOBr6?&m(V|2_REccx&NxW-&kb7XTGHEH#R>! z^sSG;4uHe@2m4@M;EQkV`{9!R;ER9K-a!9+c@*gFUq1RPFMy{1!N)gmzO{+^xAFR0 z4=kW9rC$!KKyCiks~?Vyz8c^M|Hhws1MCNR{?~e6Ee|;2iq5~Z1;F3+tAnC1Zh$@k z`sFWe0dfcA+m8nLANr0TK%ZovQGj1@N1mw*RkUo;Ao;e&%Is7pNz8xn6e`T0Zb(88s z_xtbK*8e|i?-XS-lr>Y|zc}nj6jg$o501V+ElT|U45QG1o#5KPe-#?A+v4dWlfL&U z$M+g&t9Od|{g;u+r6tM6Q)_}2j9<#w_okU-a zpr>4X>}Pa_a1xDk3~QXQBzY2vjEu!cn9kG3(z38`EeFtjl3)U8hjBp%RMM&gNNw<6cHoP`pH{ew)H}lMzXi{Q$ z)6b=Ux!q$78ucMGTW*H4u}x`!XyqDY?>aU+f~;qGv_)oNP1&1-qfLN%1Kr7;GTnF8 zj+81iB^5hEVtaM*B-Mp5R#j>0GW)1m{!V#6jtC^x6cqcgEc1?`+@*%Cv>wj0;U4j2 z*p*EP9tUN!Su?vsW~@xFovW6K0Y8Hydn!%mD;M0)S2I+y=$+WQ)qN}h)7S1}J}I@^ zi{3B&c;2xX5bpfap3(L=+2fgCRsDH}HO#-4GfQdR5LSs4iyiu~4LYx{5B3%{_rvhj zQ<&&2P%ZVCF6DsER`v>AC=U*pt+ce}8f>D6VHARrETVNk$!n4~TS`z0wyTWwZEc7m)!@6u4P84}hsYSCB5aZ*c$VDiR8oWl=Ud!31n`m^5qQ5j zq89AhLe~McOXI+$80$Y2cVAnbeGM!LX*0#sd|@$1@730}YB=k%iNn;KI!D_YQ?|dn z`4=dSI_vXXFjr79Ek(j5+u&G=_2wXzzOFrGC`out-cyzI{FQp(M6 z+;Q>U_cFhm^ZjgEl3fDZn!GR$-Xgu{W_&%<(J|0F#pCI8;im_OV}Wlej-hr7tq<-z zo!4hDpihy9i=YWw>5^=FGmDcNN&1zl=IkS#UBz{kI?!h;Z!*{^Pd>5Z^T#`snGpKe z<6T@|=BatUFnS+FU$(F;HxH_hy6b7DR8Z-7)@DPD>$bNe+7slt#iFdVD|(6pQdoMw-lN-%h;cM=ajuSdm%3PX$i)xa=2W5My47KmYBd^na6B?GxbYK6 ze#Lm&XPE}3JDLb;H2S6?Pu~#9!D&|hF2g}b8?e~gFiYGHA!Mu8a1Ha>+=z8+a#vK; zq(v0x`8c1-5#w|I8jjOOKDa%e$NYT)=_}N?cM^0URcg1xdb8#TQ}m@=6<>fl7!7+g zdG)Z*n{eOl`HX|!8qitn406ZLE8SFjHeSu3`ezk z4f0vt-(#<|r+l^Qx}9SOx45ZyZ}~lpc(5!Xk2DwGu2+*vSIL{fp%E>XxIhixpX052 zy0s|-0{>+1a*TNgwy&3JXj<}t9&i3~yd1sT^@h&dy#`&$g_F6;AI<9Jf+5Qk7L12$ zj}k^0t*10*)Y-hx5Q{l9g|~LC0@27bLdA!agdBfZ8#_w1j-|5FM9VxPyP`|1qI(lzp9 zTsv|(^I317TDHD~Ek&&oyE`gxH@)jKiLMPON#(Xe0n&GP$DJL;KcX}7DYvbi^foiE ze6~Kae)?>qK;LNXKp_cTwj957xEkIh}g3=Z>8%4 zmxmas?#mtX(xn+DF+M*}YIx4Y^%017ts}04G;_`*ZWujK9yjAvd$(;C+J~X)c`zHz z&98G=E_p3@n+6-K$gA6(Y|-TRDzc?0cIUgv30`5Gr-k`hcyqI^o1mA1JF_hJ+Z$Kf zD#qL`8k)qs+rj ze}J+Sv{j%4--TG~{Y`bqSXdpSK5k+#DmWrLhj^C?O@A`hqEywOVwOfI%l$?5JG0%43hG;-HKXw=$!|Pe)A+Enu_QC}2A}Y(G#-G8%18 z7YNUlO{n$ao?swRrVwW5X~+j%hpS4U9qfE7rJ|bJ15HUNPUi%jAyUB-SnGNWTL4`^ zqQBvMcm}CwAdNnHuN{sji~fFjD>DtQ%HQmvELJIpb&hMK&WCA8X|l448oS}PA2q2L49;q?cb z(pJeUd&~yAFjE*3FWeA|UXW%S(lj|u25_SF-=_el-K#usbA^MjjixNC4Efa7%?$|% z$gIsZ<@TXNGbAj1ZrrVpcX__5Dz@Ve^u4+hKou^Gz30rEO@Ru&+y|wMy3S;sq56Y7 zd2dUOUz5ls3r@!IF&6yUAiI|wXV|BQl?kw{O+ccI@jV=GvZ5AUJ@(AK#zSX$Bv0-A zq!RM`+uL5}j7#O$0x0zjj5*ue(q%L5+G$5+lu)wf^py=nP0e{g8j^dc$dP?knk=<* zsG!c_gvxbmyVs>1eGd@V&@A*GOIw9H+xH4WRwSm-HJNN;q1==t{0uj_+k8k%lO`EL zM7!~0s((aKPMc@y^!vsQeZHR$%heh!d?~LwWteJG8h+-lXav+(JU2b>-j?ACm<#gU z#?1C}Z@YGm@Y`}%FSyqGOsR(ZaIS2~L$p1L?C0Uj{2CNZxlIi8#A9Pll{eO%OzF+T z8Gk(P{D+`TWLiv71mauDoRS~;oT-dTBq$|VyVi0acUxxr|2=xy2> z)T#FD_-K^Z_SAIfE|`{JsFjv5({*DKh!NV`iR%e}grN9;?6hh~7R8hZyP)}_D5H}L zpK9`u{*6TN!-j4G%b~Pwmu^WEbN3~WPhZ~?8i3{!CGFcq3gMSIIgYXRwrGVQclF&JbLpjVK4W{9vy zOfk*$)KQ_shh1p2#19qf5%S+1-q zk%s%>GZ~=zPHI6g3M9g8$whbism>jbQya!GD1WV)~YjPsBv6N z$=T14yb%rN2XkU!p{{{)OzU@vN)eVvC}^9CPlzb83b2RT zL#I)Z>8O>Nq5>mW}NJw3W)?=B*vwU9fcu97T2+YpNT0mm^g%TAgheR_#M$iBpF((D{?xSIOpUSBtHYoAM&!;b3pXh*aT#BdXSu4_!Q2DeK}G zc?gD8WmPiq4$lTLzEpF8IZ3?L4V0b7)B|%Q)b}2inR^lD!h{bNwQlXPdMiD4EG`bY zD1lyOp&OE#lEGKyh`aDO(Og}_(L`HnV~r@@=y;2*9{hf*0c)$WaXemSG)>&d4N<%F zer1lXrL(t*N~l{4*@IE#7M*hro5(;ZD(bMXw4Kw$*At8OiPs)Gq(l;;S@ zvEUea{rj13n;(PY1l_N1sZUVhC&-W&n$FAT^X(`?_EMr-pq~wd`xcPu$3*~%4uc*~ zn&+|b3k3@8*qS!mPeHh};tff#kV?1$I`uQVja~MLU(tmQll#y{)PLI{!rN(ohjdEI z6aKWs_ueieVlSmDK%nR{Rxh*>dCIeEV$3Y=-P32VsdC#`b;2Hoqw8V2Y~RPn5(Hjq zZ|*}mQvu1z+FRz=d3(Gb+Vra45+J<{B0432uJpOpUDS_WA|ZDxfbMeaeQ4j1RW8xD zQu_#c>hf*?P|qR!;_bs?*Pv0z8AvOnLv8}6GLLFP=vB++Bu@Lm#?*A7;`~9sOGi2~ zb6_|J(MFjOouJyb{j>vcbyDQCngm78L4k;aGEa4`p|ECZ&O1y-$alf>*rup8WIwROB$h#fR}XewIh=AwDxgKW+~8THRp?cUfYF^w3D zC1(moxgTIWbsJnQ&<(oUcr>zExH|k*2}}~GQA$#Ya1N=&*t|5Id_%RvTZDzkFzoRG zq(>Omj9OHS7qT)U7w6vc5^Nv(1f8O5u)Vl#CpFqt$2^IB(&XF1Hh3ia2wNggVN^h` z23i^@gI$Oe&cHTwbTA|mw~ju}kvP=VB)Y`b$~c%l)4{^u(V1?Cn0s2#V`I23kZ<<~ zge1b@;Wx$tvdak{yd4jRe3ymBl;$NeWVj)fBJ1eMHI`P;BJVz2cp|!Wb?zHFSeWDY z@0aeBierS`yyl*Snwg`>W0umytLcwcU6dCfEFL8cTmLXiwb(G*D~Os^*bcuS)=Ak~ z%=wXX?lL7)PW}DG4iB0&2x^CH%CBZN$+nrJu+~LGP7nqU&hd18+764lO_s2j5`^c; zpgEpHBF(RNbnVq+$ypf!{hLJy2IQ;tXTC>W(^hC(`@PQ!QT4AZS%Vu9J)t~Y69&8Zk~umM3ag5 zM$j3jQ}l`5zHA1IWj9ahzO7Rv9%GNZTN{hi!BWR#DdA8oOx(&~*(VrJDy1Gur-D|p zegvH4iiOcB6X^7pSd3`LN^M2jX|QeMFI3Wr>{wME1n^LGB11S>zzo19Xa)5VlsS?L zDR`wod-M*{*;YhDQmsv}_C$bKTiF{`bvRz8M{3%kooei1DNfK-*b0|4`u8FKl_XfC zo_TT9ZDw%}yAUu;#Bt*?eryHj*9v#&tn@lLt44>@%e4v|_!1|>Ju@xR$u=>0d_hk< zYx)}!s@EXGxB>=!F&Re5soOxNA{5SRkKx)3P}pmwY~OWs<9k2FPOnkN0CYph zN^h|_K_m_onUZ(yfCK$`yNoh#f^2^Ck4~{6!=x`Ksomw6nA9MTXBI`!UftNoIoiv_ zp)wf+gw=}^(0!&TTLlumyg^r-L2)|x0Xjp0K=`4Fk;dcNR8R~;^1482*&WLsZcKE7 z@JvDRIJ7(8SF7W9Ks>n*vu<_dq&Hrx?DWe?HNU>-NqDes==HHIyn|YL^ai$ zb-CP-P%bY8)d@N0$n`a$_Rrv3+;nXmIOfj2$WMgFAv~PYs`=IO%`U$8at*aEyXCOx zRGZHSNc*?g%JN*cU=-%MCIO4H1%3*vh&!(H=!w0TR0jxUtfC=@s^>5q& zEt%Z5DMBslG;8MRypVl3iS|~rxBHrhVvE#6ga;YJk$rH)QSO>S=wcuFq1RmzN*1m- z?19>CM8s~R*D9frOVuuR|NU)|1H?2nawTnu`-1E#v9K-i!XL^biThVj%MH#xe9nY! zr%~t2xONJM2gYNq#9%XALQtIe#INKjK67xiW6Wt*<)_NkKhQ&VlS)Un&rNE? zm2gUrjAvVkl}obBV39F$MQ7QHU{!@8?!XqH1{vXkb`SJ1nWs6lBS;|s6?t%Z+u9k8 zJDUKtK)%T1Dd9z41k4#U6>_9;3UW9Eif(P!VsS7W5^9)81-dZ8ul9*~lU=0@$#92GSd7~g7;u3$w$7ToU9Dmm{1PKA@AHrq zu*s41gTlITo7aOb2cnw>u+_^9oRHe%dWTWWuSdGX{KU8-hSpxwtzF_FGZnZ=Wch^1 z@=oP8*;_PJuO%cb2%q^q(O>aFJYv_l&sUe;w9{FSr{StVPAAFO&wT&3(E$^swAa^a zHDcnBc|Cawo<9N=Lo9A}gL#sEgg6ZkZ|_opnMXE2*Wi??b_*Ew8f)C-4Zj}7A%b1m z9`F}Wl@MUj*+LDt^o!m-*?3=9I6yjL|ip!FX!Ka!%YzijjgPlX9evlU`CJB zF1L1-)}Hn9Ju)ZNQa5CxB>L<@A!&qYcy+B&o9_7Zb5TH`I;6=|3&gwi!gGJ^gn<Z!6@Kq!a*3pSutuJVi$S?XBNML97j(7 za3eNOBfbi`h%c#PGD^dIvq%UNQO|5BcI)8vaO+nmzRX834J3~}x+8=@&K6l4uLm}K zjyEZW$I}!I6Sq+d=a7&3#-6<|5k2+T`=_56xL9JCMUYmM1)7`>Gbo_&5Ev^YU8fs5 zbxsPd%qyTr)`sWjykqE^VG9OgR5_b-w;#PKz*y$$HQ_W+nHkTyFzb7Hzf)XByhbla zjou$H!`Gd>uEkSM5>7K?*Hvq#P_r=dJ)>$Z=sxSPVAn(TOm5ZI^caQjC+}O1f6CS2 zyzi7uBm;)d)+~T56YVhYlyEj`*!RES2doC#9qsYVU*_&b_uAEaVYlB<1c4HyI6i~MBN)v_8v|%-LK73MN=;FdjkAs8 zzAqqqb|I|cq3+2)^kK@|)UuxPV#xjCiXXIw@`hqFapIx>Y~=(H&Yq@ro65jw)uJ($ z%a>1(ZaGTwaBVs?c^EK@|j$bEdZx=4Lup3{~kS*bjpMx8q2KHt@r`NwRIg_tW z+Njt15jjIz5Liho@+HCNR;t09@~2Y@sUEdVH}-Y#yxq~I~&eiH2NF~@!|&Lc@7sIB1olO z(Uw!riEc&aKxAjpg^N{Q4E!$lj86I}>^GuWUCPC6Qw_cX-#6FswM zR|!zubm#D6#tPM+)AV6{B@iZ3TgXMce^_USOClq4%d6dlUL||oZcq|MH3_KQ587|M zom|Yv8IY``b`FRK=xcP^YK_&PJzd_J=+%HXTCX<}o^7WY_uz*6*l>XDwnoBqB5W$~ zPdb(rn}*R4Rn)0#`hdA+I6fNDc7DAGPA^N-d- zj@rIQC40EK+y>^(s=I;@yJ_Fh-;=>!_5y{(t0kzsd3ExgRuUX@119hN=k3~97b_<1BSJCHgoLqOqk*ugzyr@qHeph4f}>d?5E zogeYh6J+V|8^l(qevP>149CZOcY&ld?Q9yVdOcaH)F0ZujGHp6y2M7mZqmI8g?jV* zOc3;+kkF3E>KEt9Aj-&>AqXAI)8{zJQ)`Ll8LIb02aOH2Ok=csdDx zH(V>}?&g397+oO4DM;BySw{VpO^PgLGjF<))`n`-flanmY>TyPi|+-0PqNX>q!Pp9 zql-b3TA%vK;@LrLLO&49ZxG)1{OG(+oQ0?4I1lCKTewTLm|Q$u7Tth|2e-w8Zms~t z<;Tfld-kf)2LRREPL>xOKYAT7_g8-5>5a!1x+N&RwfWHg?ztuiG?KH*AbRu-Jx$!> z&ZPh9O6>{s+kD^9J`K(whA$gm+Gw(KIA3rbokKVxNyP}ROMI=_Z=tHQhk=?^t7sai zheM=Eh0}Io0LafvCl0Yf+GS1FSCTi^&xUvXxV=EG#OX)U;EzXBj%7{5wyf50e>HU}R_~xJ=J}FN zwTiM#OGg+p>-@IR41+F8NQ3<+4Fk}L#uOq;CGmCz22d)9+CXZ6JVJ((BVzYjlQY4?0$PDJyc zaYc%gcAKR29VJ4VY2&})nUf9V(J73p{L@&Rs%7L>t>GFQ`=Z~#y@}XpTyuGy7k>KLHCc`aO{TE3CdX()n#P!d?zv|2*S@4HsotB4qZ7y+5mb~mL zCl)v>xC?`BJ_~D&o*g5-R8UuIMe#a%1U{LH-K?77@M^xdN25oz;D+bb;`hF|VJ8{h8?G3& zKfw5AJ>uCD*W!Juu!ZUFTHJ#(UpP(zJaoXq8Qz@4LW-&0I~Ea_j+h~NKU`D{KK9tR?4Y^Z z5IaJs>f*N-)s2sHAw-j7Ro0=B(`8U*Yxa=|tF1{<=d@NmQ>>?HUQj`*QKnp1zQ)@G zWjhyA+=IWYoq1HbiN6h~%M~+aAqc-*KGpo=eoqG@G%)^g@bkhd&irwGmQ?ntS{}Qx z`}qa0Ijhjw#{8iN%$o#D1(kW#4{3vub&yJ7Vy?Zui!fjKPl8=_HNU|bbwr- zK^qc^R~`-QBaS%jrPrcLD7$i9q6hQwTp&@Z1EJ*>4$>BgdYCi!wX-Yny;RBdjsAFv z-kLj23aphR!|Ni!RtV&dF!^hCef#=A9!U@pUNO*WQTNfh{;w0tgM+;#b1gB*Xs!wX z5903dh%^5^DooYd*NEmzE~c&Ro0kOeDn7Os-~*Y`?o}HU!VJGPqQwOenC}B=!GfBB zZLct%X)c_sl9<+g5_)a6|G1gJE*m59LWVKOFkbc>GA@A=-^#DO;KImrFM8kp8++CG zpoMaE%`dc0A?1>(&qyTLohL>i(QCT(KG5JS2Io;x)J^PF*o8N+-?u&rYYPE?t+tY< zyimSxe>kucJL)Y2-$VGHdcFUgFd`bc(vG5;EmF&KFnf z;%$@xI|-%n*UV}gpp}aTD2=kCW(l29Ze5S%5o^wS4;$^92(`X5%jQXPo1hAS*HWrHX22 z$|@|y4cDlY!k^93diFg!sKil6iNcr9uWM3Jo`(vSW;Nax7};WNj7IUu(v$oU|3Y|H zlT?)J>lmEzN~ZoTnX_;vj%Wq2drj{pnl@Q+K8#(1O^Gt<08<$nAz6y?vbcpYRYL@| zrgpqZH*|Dk1Ap~|(1oKkN(mwnY)xWZ|DBC3|Jktd?31Y6tMK=x`)nGe ze@sms&w2S8CuvsY{p-ht67y!hHUw#_*ZPFgmwDA7YvKmfAe~dr1?->%G53gSdHtg` zcRqe7!|8-`S_P)jmcRMCkPGY*`OLvhG6Po!0vL@9`=(xZcp%6 z55HXn@o=NTFX0V)I(LKNK&{iM;RQCXWnsJ|`}KDk^t$s44#rQz3f8>{f}{Q0q^a3= zw&pn%-_(44C$tv?tdsN_XceJd%76~gKgtzKKDPGZiZqRhnwCXro{`PD_2WqK>u-%- zUfF{NYg#(+V@3^QzoltN1BfhwR>GQy5zmx{|9U2j)Vc>*S{F(IbdwH{ndXG5Mokf# z?13!oh0@=1QXDIZH=Vh-C`0yvb{y)1{(N%y0-a{Rp4T6ocI*3B5YQIQ@a%1lOQMrx zOTs%`#~s-8!OT$pd{1sY>}=dv^oI0TGMILM8Ie9etA?L=08z zVQ28izguO7iQqY8rfZY4LaCi&>#9jz-&~EzsmFtLop1IR@V_2xjH@?GaW|O~0aU4S| z9ItO(smz8b*M@hSlDvm4)L>s&io&s}mF??OPlv(wm4Em@p^>Z3F)pIyh(2ylxtP(1I{ z*oYHHjnNqJPo~m&Iyf!#?By<)=-Lr@Bl`ZV(4K6#}S&}&K_>y?h1-~j(@TL$PGLP?q=In?MuHQSR zf1_w9vKgYHTK)PRrsNRslrj#{ZQ(~Jx!YceAAOnX*7{p>gboIoO>X@?eXyFRkxppW z^{lnyeajLJI#GP~RAf+Nilr)6%!u4s`aIG0!5N73rIsS2aH1NC%oto7ZP2sOa-%TX zUAgMa=z!cK^8yoXjY#V^%AP&TRFRT0^tsZYj+ycc3MLYHF%!?lp~S0W8s*cw`U`ND z`WoXQ4choY!G0p{t6O79*?!~IU$4 zFE{)KJ_fjY%JWLI+mTkp<|{wCAYW~>3j;W)wk`%-0Y^0pB^_Py@* zIT%W(Y;8V&)k+NQBcfu=q(-ts+xKsgzKUTupH#@|!Ur9sT$3?PD>?MHbH~c|O2zxm zFE2~q2|}^>`AX3>$@Ij^>M^NX_R-w=BJ0`E#vFz_P-x;*39@ ziYK$D#889mIluEY)TB6z(hd=Q8|6R>Pxv`@9j(i#hg6%X@h!;7)QJ~sgk#@VFV*Uj;avt=x66+* z$v(QWqkvkFR8;8!EN_@KpK+g9B|_n*LVxfsT<|rRyhPVcLo^@i;FIb(P)7|U<0Fbq zd$HTI5c^xi8eM}}M;v1XT6EPY^Y<0MQF8j$F)Pn$jHt7vE_0&J zlz;1z9HIg;0qfsUESTPTyJq)2pX=N{-?aNlZ-aOq-{31bZ!d>}D$WNQbf&G6f#k!i zAAjrQ^0HF6WgJgWQ&HtoxEZswIbO7hiI2jH-<+pXoXl|)X|9?4?ZDP7>IE^+<79JK z^*7)}Ya<3a?>0|qsZ|dsVb%P+T-M+&*qMYsl7ZnNpR&Ihu}a{DB%(O0st>_Ezed=; z3}|$4_-L?qHz4NLoOih$(NJSTHHcZizY(7J%*ons*@Hb!3vV*Eu0yCpx7h(VDCcr9 zg)YtdH{tjCOF0H{!sl)dnjd%v&oBwflv43;Vi>4>DH|XBw@mJkkLTlfLxKv;t!*ko z#5(yYmQmr5JtKZi30gz^$*2-I{Z)6x!d^V0GRbcwG}1@q_5{hJcLP*2w|g3yBleVQ zfg3%QVhzwsq?eL5D_skHxN_eOl@#1|j-&#P<2^($zD`#>7LcfaD_iO5&7>y6oCXaP-Rtv|Fq%Zr+9FBhkXjH&_=l~Hj*$f!&bF@eP!5q{zLW@dgBY&Z1VgDq zVuiPI;)2rY{Q=J8zSa|+h$nB88+Rl_+O3cEG20~mQauPIUnL5^ac^*fA?PWzH0yLa ziueJ^MDbqSt^*S-$_s;f9e{P?hMkoS-L{tAql-j+F|_R$21Qs897g`nJnO z9Dh*km5TIgcRFvBtOuxBDRsf(=;-e|FGc|UqQ@l@ox)#hCER$-Oh@YpFj>QP=?E>w zlF4OXmGOGgGlkLy2IT?A>9-SBL(@7d)sX|F^$7O#acp`s)arwQQhnYzdQK3X(d9o) zvx?z$GM#>UiTtbKONN9zU!9TQ^^YHdEU<@WIu&gA-4;TiK#9~_dYE)Zl}`?`&!_0M zPMBbj?h^J4N+OZF2_}|JZ#)>*Z4V@#1`!+!^9vqD1b%rNPCriU%YRjed~1@4+wdW3 z!%HD%HE4vFjALMnD~Fp-cytVZDfnUp&ff2?LJjC>|BB!-1o3Gih{P7?-m}=vk;pW) zm0<3uXy)g*oASJV*rem_n7^~WE~xZs;D~GH<*HUHd`$ulOM|B3`|vj+163%~$O<<73K{>EycT9A^hCKbqSsN(G$_ z#S-d!PBa0U*2(e3p1VGUh)NO}LqFU|d5+Jg%wBoub?5HlwZ7c?NyKi|_Uiu*rY(kiHX-CDt*1jFn8+!5MNsPhJQW#nGFHadn7~o}lc$UuIBU zXrH?Uf|?eo<=?Dn8dwz#>oU{9oJG{LCBe&VLtKbyk?8&PTr`y<2fDJJXj%_tqegx9 zXX|@B?FJovh-39{-`n5tl}YOSO@C7Gp4ZN(0|N-9y3$?tYhMjYbWrv3)>%Y(N&28l(Zzcr#)7C3=1 zJ24>KW@Fusu$Sd`h0^DTAQ^GSf;X%TiH|&qy_Q31;W{_OX*cHX@ zzH=84c6CSSbUSYw!O7U}_M(y6emp1Zr9kWMSI762_M7KP8U@ovEsRWclLGslpR{h< zhE3$JqpXl|2ut5zpS)X6U$lNj8~gA3E<5pzu8mQhti&#U{w0DXe-~@J>>)VBB(u}b zo)G`2cQFvs6?%{_bJ{9%oG=? zc$&j19&)vYt7kzn9#)I8QEy!!Y9)n5R{|UGW5xQiXO#`hitO!{A`LoNiGe?YOsRNk zt3?P1tIp`1s<_dsK#!H^F}2F9!pdA}w5`($VCQoGCYjdgX{ymyp<1ict=g@?p(W75 zZ|NA*SCiYj*nX5A*HdDohV3Vl&$DHpFQhjrYCg9&8K|r^oDU(fO&?=Ix0*I42{V?# z?HRH~7LKO`Jq^=zw=Q{)ulUT>&|>jW{#Cd|Rdtt%b~`8c7fEl5&fd2UaO`7N>3&ij zEawwAuZK027Txh>mcK08b4QbH8gB=w?3%J=w4E)n?GH-G(w9B?c#$)ld^~i0;jGZHH zO6rNd;NGAq8VEdmD1jilS%3HS2sVJatmcS{7qkI@75h?Di?zLRn}G`hUZ^o+8{c*S zN2Kn8KKbO|^7D)XCbvd9fu3l-0|F<9EP4dgfDBUMN)S&@JoRRHr!Qb$4Q533Kuua^3aa z61e=pOVjjmwoZu;YyJTf&Uw`lx&H24ZGEI#gjZ@|Q}}&5=I5Lw@rtx}3{nk)jT6)t zZx>TSziTH!O%|)Ohi?+pZ-EG_mKc-i-Z(8`%G&(!F)7U#(MDKcHVGe7Bthr zQ#3sG6i=M(nJVJgNc(fZ6Q!?~se3b;n(901%8qRHy1b0mU9PTxhW%TA(#SjQw)6~O z(SP4|zySVy5M!rq4C9K0#nMJxnrll{qy^m=X_}uH5A-x!@EiS~6J>ZFRdo_B&f(Lv z!qzDQxFpV^$&W{Q-N0ZkfKbQ& zFsZ?V%8Za}{A?>64JIl#B4I!**MRi5OpoTP!e%sy1IpO3U3p6#%@>>oRYkS0RP5Qs zgV8q`xr07lYZsh=TCve2@%*h1vaYDd8$J#pk*rSWMHjk}cKq?r4@86VQJPyBkt@md zZc#(c?_EXlQXG!5-8d3ny#n-9F2kuDIxxM-t07Jap_@GEkCe75pKPB3L)cbAXUKwdm8?h_1s|&{7TR+Lf8T$Y#2&>XzxVp7TEY|s!S4`*xGOF zZ$>2nW{~#OI#8nHQy6U$X+9m{NZyDaeUbeBJ~LJA&&bgC6n0;W6Jk#+BykzTiunj_ zi1&S{;S=OZ_KILx+G^J;?>{sWeH^k-P{}$Gd)iyty0(0ergew~gI=pK;{ht>-1re4 zaK^!4BtfOaG=QGu~I9cUQKjeET0Uj`oY0ByC3hxmuEVt6He(u_2s5 z-`e7cUJEWRD@b@wH0+Pf31r13zaZg0Hn4XDqiwx#ee+e_ zv02<2LQ!#2w?6+y@G$ReBiLy|XS2(01xvoLbK~UherZ|N6pbdLi)2OOBBO1zKA$8p zPE65M+BMjfQc$%(6 z2;kz`ypk8!x=x7ea0I!gX*=n9AI+Kv-|yfex@fK-cH+)0qjRFqghClvX<~ZG_@4e@ zrn(hkrA}cj)mEYQg@8rvlAmk+?;4-(j6&IgQk~q0(^5JWXZr7QxFzVg3!Jwz3OWL0 z_w}5CryoxcHS0awrL;xXmRb}%mX?<5)J9gOBkbL(>g|W_COs5^4BmrNPqIfQ0etoM zu!h00sLvgBAIWv2zMcZ9eJ=jK^IU)fd_#Adzy)r- z^R)R!k|^OE+ek6}`Y0;9oV)IAV?>QW%${!}Xl`Q5TV)zwTdr(w)Zkc21O-WD5{&c$ z?TrChdr^HIJOy;(jBS_&Z%VL5C|8N)($2WqIi=bI@2kQ+yy-(vD^f63zN$5qz z;lHJO^NPBSV8V=`9FC!c-|S~CJmn>06xut68cy&eYnG7m-lJbhzM|bcf@X}!B!q3- zLik{dg|8#%sqEOv&OiFv{7oy7XN;XAs$l6IVU1J(>94Qfx_8o@4+e?Oj!OqD*cy%q z=)1+obNj_-2FLhh+VF0_Zf8Fn4EtTAk)d%^xCYtbujV1KP3a42DDEsV68niWe-8;` z{XF3Sh0u2oGKLB5DYvac;}GTv5T6gIPN7!@ z7)dz`e;4{-7Lk>)wtjxeCYomt);{wl$YG-H%Uo%Rw^5_akws*u_Kt^8V$K76Y_L8( zxY)cgw*5$#;KvYr3Zp!MQ$F8J9os8x09!cJVs*V3u!+Gnl&8c?Pi0a^{=lb$0NAZ` zC$#+jvvj5r>FW|F3-#<6rlP-~bn=OC}jrv-98cDxsy3vng0X5C*OlTh%(PYf;F$~i?M&OX*C#!!OFl6BTC=FK@+B#D`8Y8 za5^x-m975BVIcqDPGWB-CIeQ0wQAZwk_NP?{mx25Xaak1Ap*|cL46vp!aK>^#atJ%6s)*NOaG!^2Qj!J`$E#;3*aBI#A53F3(X; zt*4C#Cgq)LMD+l6VaTovoeda6#4kD#K3=hAcPP}o&xxeB)XxY^t{Z<_GC+rZ#O$E# zPD&Z|P3Zc#pDn6h*`dN9Rwd&-iGn$!fCp@$ar}8LH{Ia3G?4%5vjakD=tL znrXylM~9!#^cLapbR-C{5llLTS*39`#&MxPG<_miW5W8ZC(aLqW5q zJV;#z%h}yF=1o3UBdk_+wCh$SMw2Jv%IMeuDkFx_r~<_5A#8P1r-v>!gWdy?75tXq z9*cv_t3?e4tX+J^lKl6Yt?orF5_w^5Xj<4oQTLMkIK@TUi3Tf54PS7;h}W9XL9a~0 z{a@#FS+gv;WJZhXEAXy#RdO-y=`)wNpSw04YrD46^fDa>Vb`^vQ0&bT-FVcV>h~+O zT_o1{s}&n#2JoV)R^lj;cLMGz$-?YN5 zOwh|^f!s$eyX=Rah0j50XJ=z(nT-;Kh)P8fP#Onm@SuacKZ8rPSH1xfl+gOx(X{7VNo|jS zp?3=(y_1MJVQYd4xIWX_UAr|n+F|kz9H~^kYl3N?sD=-_zC%nO;e4xPpUv)AA@|85`8#7qy$pyk=VWFAYPC789Q%IPBI z(_qkhmQ?_4wAZZ$tu*}Q7s)h^3Pd}x7Wn2?I<;fEIXC$1is?k(+m|)|z78+$CYk-n z0ip1lW>q=3pF)klrD2-r@DEZ(3pEcsTnA&{-e+aKD>H9(E(Bn? zy>|GScWil7oxhuUZv-8=(N@pogwiPr(usK3v&F@>%jZbSug4C!IO0$d*QY0T6Ig2M zDO}aX*bcL9j7G*t;Md!lHWil-4pukXCHl)s7FvRn)@_>u48p=(~Qp$Y5k8|`9_%&3$V@|}}vjago=Pa><9NQt!Am|?oT|McqSe}WT( z6WRiuM{YXOTJLZ1b<4M*0PDG4Hv$9W-x0e$Vy~)|;OX)V%i!ZgKuD{vfO?DoKS030 z=~*z=vH7caWo7UFUTA2%g;a_D1dH?6-Se0gX9iC!b%iA~qBcH1MXVCT>k?sB|4)So zzZ%KdDanpg^BqoARA@PA1&y7BwNU$6hhCwtknyAraTdd&7VS-tt(i$B*N!Tx8-er* zDl{ODgs);tA*x(ob5ULj12j};0h!&#@!oP&xik8YA`&K15=WpnrGlNxoe2EtIeP(x;s;O=4bYl7-tjr-QIE)KMlt2ZJME5(cx zPZBq0vjXex8W_%tk6lXZrgFBf(L!wXD)r60K_pduOHOTDvlJIF93KI1LQ37DxBlU{ zxHSq+?Fiu|K;L0Aw;!N!-JQvh(F6j{5;0i2!N153-b8bl3Kd_*)it$PFMLP(1Kr45 z9CmBWy-Wb1j5RxUyl)q1h{VC`WH=ev=M0oS17jjXEiBl%&=?N*$*=TWijG&MtqLQK zPNGJ8@wGZz6G+=N%9i?H^luzy<(1a6`C7CYhx=O=rwAQ@(P#hADL81bY5U*b?$(!U zLWs1#WinHPJybPc>(7!6{U`9q(v8>K0b^1}R$^qVFpj0+nSvwOtirx|IM>vUtu*4q zK{)WcD%wR8NyX*3hG|t~L=!`VhSpcmG6-e(wBzvEq-q>dsj>(<|J|_Rh_h0m&sX)8 z9K+%dhC_=bh1uS_n|FAt1t6BP-6Tv!IFK z=G&>)dyybl$*8!rfRcQ_o>R^mT`6+XS-b`E`%56pGO*WB_4$k-(%oU#hM$3#|Hi(^ zTsn4JYmTw!=svD2JoO-SAdal{Lm<~)9HMwtE)ri=>fUwnG>xEU-oJ7CBQV*E7&$xtp((&IZC3$G*82{NV`Gn zu_Eh?9H>c)qiF^$(RvnJFKghuVTLRBmbZOAZe3|`Q^XPRWpKlFVLtX(#OiWMz3ySw z%&7s<=J5$sLuMw<1Mi~~NDZI8=86j6&ox}%a;p)*JoI#A04A90N7!8noHBz=?YuN? zmq`T}F)gljtks$LRQRj9Ua($BkTjf(a@l33u|Ip#IgbrsJ8e#l(43< zFGng+Nei_YFD4j-$;7{#G4iUXS-CW!I1!*!inE04A}PqWHIj zURWKx<*93Mx&G`&Eh`=KArp4Ky;G87g`Fj_*zewIVI$==%dc0z$y>}gtgi8T8djzP z<@LY>GouUG8>&*l_?s251*K2x<98Y7(ucQ^>6;lG*xf3E z`~SiXLG3VO-KSWPkUX-mHJ|du@t`IN#YnIbu2HKR!nppi=<_wuZ@-CthcyF9tp^)$ zR;n736!Du`3!Lm0)!)pNk=R+*oa+jdS z5ld^1<&GbmhTLhlKYvmHIEQ?x`Zc;ls48i6zpVb(OnG_J@<(5u#w0hux#-G@? zG4b~p3hZwU>ff55=q;TV^kv_ZS-$sekknHNpds>)cbmfX(@Iml*T7}@uV2u4DtYi^7Q|hBNTb$sT zqEPl;R8nx_X0EJ69$%1>rRrK`H>NHKnA@v5rF+#;PGTiuqib?NdF4{-l}TIaHA?@R z5WF(F52{#*gk0AhnpUm62hwRO4`8@&(h|3KaHBry8mzp=tU2TKSJ z@M3KB6=qw{pe-cpoU9Q-c&Q}nCQMCv1E+df)#WH=fob-kCM z83=AJ3ucWXkL%+`&vsV2sG9D2dodz=1P$RtguEBVQ^6_RD!@?yf#)k&;H~qV@8HT- zGu9hwMV-0)OwOA>NL8buawOsGDCT~Nffrcr4f59Ac+#hTHQSi@OEzcLVy#aV)Fad< z)ZcL-jc}T?s|Y&pNfiHr={`}m%nYpqN+Wiiyde#a6Mcriv0tH3OxAarX~*(%)|QL$ zB5cUeji-bzc_G|g~|&b&Rwynj;q91z^XMSX_H<1y;~FfN!iR-+7a{e8j(_D9{7d9e9XiWbj}4PAO} zl4BBT{2L1Ya*FNEf!6=M1HtQ4X=~L{Z}M(jvEPK(?_-%87#Sz5edVrgQ)2zxW;Bd` zEA0})Ly0xRkAu!rfW4F$Pa@IU%KIZecl13_y7$U!-KWtA6sFG?E7YxpbvvclM4cFw zim=+Ncs1o5;nB7^off-aLlH#yTI0x`Xy}fKeO96^Uz=A8B&hr5L8|it*j@fMQ&r5W zP@rVZttiV^> z@2)6hsG>>x4mifx0`0c?s4z)iLakNWLk)B<%IVjmp=jSl_z-}&l-Cc*er8Ef<);A; zy{@`RzC-xYJM?ajjd!#l)L_8n^}i+yAndIbLCZdR_6cP>A-B`)9B;tko9JR$f&NVC z_g;BS9(46C+j#u=lLtfZKVJzZ*wp1^jkFlozR_``fq&zH+4;PDoa5L$EqOx54HVlC zLta5_@^-v>AachL7IzQ`+W`LUO?-!7Jja)M!-Mo{5^&O5^X!6o_}^^_TLTD#D~6%X z2m%Pn8pFQgJ@9p*TSYI_=OG5QP9gOSTYCc5h#jG)R1P^0u0(l*`^=c1X_3)w*7jcN zlg*Sqsw|XXTe6?vaJ!WxPb^PX9vQVC0^bkRrWioFL?WMB_ z=sxcAaWzyHSIzSw>vn$Dk1c%`Gn}7Ciw^&!LNjwqZQPo)RNnkrp`v;!m%RDL>1mJm zxjxx-91(tFp&+>Uy+bjITrEb^(H;+=eYwg|Tl*PGRKoie7uC;*=%tASp8rMez+3D(hXRm~L2fdYU4N!b(;cGdaJj%R1Pzl^2 zLo6@u?~H}$CwEOp)!Fw=!9_)MpKmqsr{l_-T+mlroZ2vmP&{FhV6m$KD!mvl%Nbk1 zM4eAKH=`IsT9<1mac^5Gi{Pt6O$#6ZH$X6~%fGF)ZOF+p#@(p-0NhRTB3w=2g> zp@y9eNM^Xih_-i)sql#jClF!=HdY0vx#`Q{ikQ@zJDFkX^|?At`Rj*gAI@1$Yw+!~ z{b5}MdkCo4Q!KKoD%;hX^4|t+;qf8k>C8sQA44P*u$Wh}G*5sjt`YM*zPkR#$W?W< z{1XI$b9~S8N{1(y$t1x1QA7({`##V0*SB2-h#OMM;qnp9?@wTSeE{f7vfJ*U`2L)q z87byov6`#;%sS3SanKr!uQV6fcerwuu{=0}J%gbtB`A1)U&V6ZD&>?HXtRC$KJ)dq z)%fAHpDpBU^}}C*WKycT1iGp-}gTUD4=mm-)rR-`%CnRIIg*b`M`^Y=)h0J{S;(Iin#_&9~g;J?r1} z9s1_HMyEV(up961&Jy7aDvjr`T9Pb`T{<;!L~3KP%Yk93YjcAhvt;A*C_ZX6)eT39 zhe%*?L2go5cpnuEv^PRcr84UF%-k>S;#k z^vrIS=2g7VxKC&n)jNWR9L`*Nth#2lW8B84xqrRaMR})!wLozS(73L=4cuJc3U$L! z;_Fgb4aXipb3WYp=GCtobb5a`%LqSS0a&-a{!ZUWVLRW6LPg2!tFmkG;^&+V`ngMJ zxhhX_^{Dna4hGw+r$RpUhl*vr*YKxi<4BIAK&ZUo>Nam$fm0Y_YvY>Y>LMMmdM9*E z#lR<_jlnecSd{^$3>?Ge_py6_oIY}Uou_<$$bCb<3__yex1J^3mu2ZBV1UR&BE5=@ zTtY>O(^@ba!Bd9}%C}!(KSjvyyM}&5@#EqXCgcPCINJ%u5RBm1i!3nk{)PQyM>>67 zHHc4d4zX<2`+e`eu4}gJ_!vH}sROUW@vMz&2%&6&ippb*n|up>HhWTc&ITx@BA0kD z1qY`PzHlDr%^hgR01L~Dmg>e}nP%0Rx^J-vGcYqar!u|F&(U!fCsjwbh>2?zM*sS| zuvfe%R}E@aUjU)-P44=}H=|r%aPxN_YGEiHrIpi;!Mw}8Swz+?!{y`af-+UXs*sC4^5d{m7$bg=wM zaaX*XMe};zp~?Ug;@kO~`RstJq0P`o5tM9Pu0T|9zuG7CV|eTCh)?$Y+l`!fpbz_C zJocFs#y##1q!v$bGxh}tQTUQx0n!Q!1TxqoSjgb|^S=d`kJP0PiK);s7%Z4pQWATKbb-%$B zW{F&iMgDz2hHpP}DCFSz6S`na3QTt^rnfS2yYEWQf;Lxb)_=9`HE4acoZxmSI3HAP zjrVvMg-e&n%Bm8_%u4b6iuyI{1=YT~+?8O*K{5MldkB(L z|K*~Yo`+qSy|t9FJcS@3!mmtMMa@TMEh`v#&!HaTfL7>#K_sv1W$ z8(BXHQykH51_cVVeC{@<7WX&Pz5RdR)?t?yImr?ffygyaH}pgBtWX?ayl#{T!ZNI< zKwTDHTll~R;skz>zp0{?Ps?rJtT}ZiSsyRIn1@%eQ);JctudVfu+Db_v~z`w1FSWB z`$eh65!__*f1VTSS!hT5sV`xoo3F#Z0l8(Zz>jSPj&PNS!-#@3 z<4uOUgPywOc5Qbi{$}vr6l+M?b=Bvxj`FSo`(ed*338Ty__k^XE(P6GFIU%mSNW^% z>Zmy6#Ak=o?IQ=*gua)F46M(+$AC^D~T3cL8ypB`;rFw?S*mfhYDm2;<8|AN7fRm~}Np2?-+G%aA;PLdJ zh$$}i{q2ENea06ozj93GgQzw?vVAG03rNepG&l>}`%dkW^iilS(V|SDKDVFS?b9d> zfIzDI5GG&DKbcm!kI|Up&yah|q$eDFf_DPdz-{8u64)>0`tlaC$E!E~J~ZNQoS!Yt zhXd~FjQd3(6VLol0)Hy+cyCytGKWjMRFXf;D;+nO@=Pb4Gre3*^_jd#qaml!JPNa- z%mwGm9=kDKy7+w@gE~5qh5_4y9VHBKZhn^zG__Lf`3ah*MO#19rAE*$6e+MN8AJA- z&2N1iEfLhjjL!^mA~vYhXT|I?lyzw&MAX8_;X2)3$1MCXZU5UF>f@cH4ZO8VaBUas zTNOUpU)`)cGK`yLb)gC&X4BXu=F7+~lgwK*%TsgxRH#Kg1J+00v|>L=3|$o!ZAo-f zEJslVC`K!ZIcEAO=|H2UxxNi&A7UP4Col*R;m?!G+8Jrd*9Hg7e(?NI3wKo|Pw!4w zJ_L21hg-!3#noYfbzIs2I?PW#K+SB<8?xdWPLI7QjA_={+|`9@P*u%T`X z<1os2q!u2TZtKh>Kz1 zK~Cv@Kqf8W=ZYw{tG#YfZat9G&6#`^RMZ3nMd1xnoA(<3XM=^@aCBw_B8^DeoiQLr z#K2Mk==2tn?8}GI9XIrS_wP#b8YlB)hq1nYGRCS>v3@wm`V3CPU0{cAQ!O1y*gp!;|aj#?dEWt9sZ#Y z2CjUGAa~myjRqy&U!UAgHy`zIij=6oBVdgVFKSyC>4M|9I5_ye>7pKd8Rs^A^ZsVb z{@uk2`Wz@L%)}+s=3FbUEt>v!|Bt){aN-zc9{vGtXoo_%#jzXxHf}KM;%m<}tV$yN zt#HZaL9MF5DF99&;KV;MOXKI*_Fl=@3Y8%WCR$Ev?h3h<*}D;k2CTu?MX zd-!d}w|Lq0$fLek>g4SXw7`x3{O8vt1gL0FHMB2hO3s$x+Lf=*PrDZR@8coo-Zj`~itbY;Fe)1uPQ~;)G9t$4-WLxELz6m|+qq@aR>LfBWZs|w7KYe~1rm$c1 z?Id7Ku%(T|MzVWP4;elNw2;~arBM~9U-df1zJ++rYhvp!Q(k&G7i!~HAe`&bNwC?j zdu=k=8eZ(g9gotvQqT{JzEjsm2)sWonrg?+7czEqv?hTgk;pJ2etiIf67#Q4A~b z+;aiwEI!5=3|vjGdXpllIDR4S@UPdR{j+#EXm0MN3wdso72wROt5U-q{61PbfG7Gi z$nu!_zW+fVaMHuqIE;^jV&7He3n542_lgUNHivWjzSyYPDqw>aws66iVSCXYY9YfD zqrLhp|0NP+810GeKX4JmZG3ea&GKtldvf$VzHKAN$&FV1rk`!n3|VFeu&PrH|NEqT zG1JIR?6L10c~9d0hZtF9T*KIN&b{3S{3$sw&N?px)ZTHG(ImI)>Em_{Ll*(WYrI_; zj*l-Yfe$tdjxRlVVc+r4OR)^My4=<`)k_Y1!nAeL)y+ow>u(*K1tH`Yt<(kZyJ)+X zxek1}g62Jr5k@it3#X9SPwGdi#N4p%Tp%ac`vd}iH<$oRL-mv0T5k!`V5X@XO-np$ z`3){dEf&8`x>f9gE8Dc(UC?aYyfv{G!%NKQ6&wow1o9;F{(RB)5XokGh_#&WHM>{5 zY1fS*w9DY0y`+C@zrReBHR*L%AmBCa7`fqLA3J^m?Pphk==wGRAeI0{1E{b)!}}*F zAi1r|6v4=-seXk0;U!u&^B>h*MHA47qm+*fxVb}T;AT z9+k_oc<7&ZADeaoFNZK70W+m*iv-mi#hj`vOE3ae|bO2Y~F#)`}m?o)QD zgpIgb6elA4tiV(dt>#8BhugT{qNVUDg~I@OtB#FdMY;_b!#;?-F=$AYx6T_>cI5G1 zA##+^H7zRtBD!%F@b~(^=_lT1+vmS`1_(k);~~(US=o{$xkQg#ze^*+opiso7~bK! zUPyLlh|4fn(COZ1^`-m1TP-a=B=-S=j_|sV{R`{|vTmM{9B*$i9$XFKSa?S*x_Cwz z)p-Wcum{7dJ9Hlce-IXoi0yl`-?+V9==>J`(Q2XLOt+E1YTp%?H)Go8Xbs;TH|978 zm#K{^Av8=c8rc?138u8)%ynw?iO24woP~b*ftBD&PHf}U#)**!>1Axbzxs`(qLVv} z(vuVZ7H?h`y6l4l9;jip=R+gL%LU8ulx(PAP&pLa$kbfVO~^@}MIE>t+w>d+Y)sF) z-AUHks2;uT)!Ux9mr9kxJ6dYPp4Aj@JGU(l??NrB7iC8f4LXX{Xw|Wh-rt!=+}^tJ z>GymP#@?!+SWoe=XD((yCz5loB6wup`#-(eLw+AP4?9=ZpbJ0_`30K__z1R>@Y%gS zXdeHHUrd<8;z{$pVPMveRrVSUJuU`2X2@l@g*yBqp#2nuYno&p-?5Ta-71`aT1bn& z6?p{S+J*BXPS%Yo80)`V30E$jFDEt14$ZQ1eh&9R4>bgh7YlLX(B2qbB*+KOTDfyA z?`ul;d?uLl!#V*M366r}j#;@8CFNZJRf>b1KzSqXdj$?)m~xZD zZIV>yLhrRN2Tpv*q4eQ}4PQ(&Lnz}}=)bK9wMS{+_As`uSHpz?U{C- z+?$ngDRsc@uDd`~2rQ2O+l8UHFJ0Mn6g#OIPN;3FsrzV>HLjrqj^6s_y<(CdtBNI6dM6Cc)>p9ihD;czBfV2wV+w6W zRU*jd8d^$RX^W$o%W7r3COcZV3RZUcLw6UOx1u$-t@#EPi|10Ij#ElWg<*-Qz?xrw z?0Z(W8EUhqx8o+pyk6PEM`l#GA7+Sc*X1HTHnR$BudD3s1~m9Hymr&qW9F9=IqC>( z`xATw?F+u&xtrj(_NP2}X1kxRH@M{+lZn0e3@8%vUM_a1H~NC1Avd9KWS5BpnI{`& zw|?=O>`WQm*@0#V88Y^FHlu1`E~sRkD-1*7%?y+>9ogV|gpeU-ywBN!SsH$9o=&eo z`@7dS%GRb`k!_!l#9pshWZM2K&L7nu?*nBlRA8ykb-3G|n?bEr%eitX2ujA|bR;cH zx@u!RNQh#K{0*I`oDQVcxX+Ct)>A60USP44#WDiBOLeVJw8C{YQs3I03M1;kWNd{@ zoV4UupPWv~U==pvcNuT99R^^+22&*-_wP*{`g*1Ot>(}0{E&>fY==}Icq`S+1x^O; zZ71lIF$5j&U7r3{QgQWKQP6`1P)_;A)>I=tz8?C5QY7B%y~ch)5fQAP=0Q{Y&PH-* zvUfqimi00G%^t1VCE63gk?Hl|HFgTz+M0C@DySFKdTxv|NF|)et35`y5?fx!cswjDD=?(JP1TlexRJ>&B*bFa$Fv6K_gFP1H* z6KHk+V$bTs&Y4Qyu&RnGJPS^5+uUDc)YtjK_pm;sUpeYzhdM^NUBS4QigEJ8W{LKi z(rbFD-;UUX`z5#6Hf}AJtf`W%_q!>w`1eQ5(r}8b#(TZe8Yw(LX)uy2R=*0vI% zLa_rSl+6sPwXkoy!gq#wd4r_u1d~JW3IxSO>UZ~yYlahX^WF@XzM)7D3qP8# zVLkr2n^fpi$>gl;oi2Q}E!X<=J~OfVUTw*@+&_3rSV(ex}d{YI!56s9mYbfv&A)7c^BvzW;U?d%8hhfcNb%M z*#3a|#JD9M@XmXbXPpOBv;)foC`S0PJqiD|@zzE#Dc|o%&V_?9ag}t!zLduI`Od>` zbao%#Y>lo^-xSwn1r%C+Q5%_(73pd{E z>K4!N4W~jVk$`C6`b%G>-gg+po#*vDG0&K*UJ6Ai%2&IL`;h(Mye%_yxI^sDz55k~!`ry5qYoV`6FzLjc z6f;zqGPlw^@$8^ZvFC$)QQK*5Q`MWp$JhAIBvl)o-}uo(zC>=C^qtPqg>LNi%BZNj zD_geEvr3>3rHNxt7c!&q)xNi*& z9EWadCijVGo^+19eg#7WoUC}oS*H>bu&wE5yx8(03DYj5}zZ>&2Nu89iVDIsrhxO=dIrf`uj-oE1pAwt_vCAO$=ZeTy^yZ1(lcN z8L-~fBuhsFR+u53LOsdeqQ|twT2KRrf-zr~%FwSKR554w{Fdk}hgrQAN0j~I6}55l z&!vP;l3vg3w%YHgn%lUY`hUw^6j)mY5`SCtIcjTPdX!_8mLsZ+)ID16fF^Z`8DmNp z{5bxbQM3ssT<4U~Xq_<2gLC#uo%uSEGc2oRF$+oe5r%EH8Jk=%f7|L8X-i#?kJQMF z=FMQRGI=315!IZxFCP=8(M|;;UICx9z((#;Z4JOm*!%AkD%S6fp7T5^Zl`7i7~C-j zySzFbt3Pko{@GE(MK1f&W5fV9QWZTf1NhqQVWSP2pJ?P!QD#tf_4k`!O2h3i{YbAN z;U;_Ep6_S>`>-G=^soY;p3s|$Be*)eH z?eEIo%SL)`^DbKWgu-uBMFt6pvO-{06nwWAa{x}aAz?lhu{1v+D__*v1OCUwBiZ9Y z*vDnp#l3m5vNFgx_XaFfz|n!!8#LC3aIb8ywX1CP1sAQ8R znRPEOn$=TC91FEUdDz+BUCry+y1eQ3;kqUd8Md{9S}d44K~dxM+F5L@SxRskPTZ|zKJcUV zqe^U?z$b&LULbVM%B^+QTU#mzYN9kM&(ofVYz#t8`@}JUO>NZ)%9CofTQD=eQZ$zV zYhg63JrBCPQ(f5m`uvdFB1|$f|FxoZ6;R|*rz{3_m-piztEz5Z(b913y1;kp(FkCf z%g5FL|b3cxr;n-XpFx7T--2dyZkb7wUWo&&LOMOh?VHvF#Z1zcnS zw6%MinLE40Q9Sv>%()a16%oI6TfU2XhjK>aB{+%>H0_`-yl&WX4Dh8l3= z!6=eN^hvkMO1gVcp5an+0+TZ&tlM_+7p?>HEFrMuhvyZ>+9`XDn7t~ISBgVWnxcY?dfLXhL z6Uw*4C@rVaxHrE!{LJ&MCC%HkoO0gt{6tNYQdvv`GF-*zJShZ&)+)8q^%8cR-D1>D z-;{IVvP^+&GLrOeDqd2Vk4|jJj-aK&(&wx_*C9K2wV1>`COK9>vR6X5TH1o zi!edH>9OfXD<4Y`z{m{0#$*e?)UW%nmZK75;ikm$@+X?Hy^gKJ=ge{OPyCkPT@Ase zeomamKu+A?>+j&(=yk0h&vKA8$wt?)fJGP-+uIuYFC?e*A_B9T?toHFX zVko70dc};EU>!m%5kgjp&IeNIdUYSlleRWp)iu@*=|Z7v>FzXG@No?S%Ycp$JEFwR zDy4C)_W7*}0%NE!k97d+u&8ETNK&o%y_e%f1pa;{ltJBTz`{}b;#jo7 z?h=}sfvgBYH28JcZ3+|VU2OQ(;LZhg{VRauM!J{Qz1c}^|0c1X)^mA54{dNnWSO%l z>NTM2NWcXC$WL!*a8P;)Dw@xoioa00h{C|+=e648V=|)eg!4Viqt$kq=45@)vc_-U z^MZI^yWnW0>5211d^usYk16(Hcc)Ek*wqzHt`}kP_~U3A!SLJzqqDmp3I>2TXEkH>v_v?3~pT{lsp*=Ot zjIDpKTYa8YRYBi8gY;Qj;(eaa*9d<+sx3Xgi&`(ETT}A^%$l8{eHZW&R9!HoS^@hK zw)1>-#fHpa;zN9l`E{zg4s_fx=;8lKo$7N2x(PpR96>m4?d1Z{-w_Jr9T{nRzstFL zEbK{s*O&tBWXiI=Vu}2e$anfGD#+n4iF_WTlr#8e?0lXJO`y+jP}`?8MM4|gLc+g^ z&H#KnBsY_wnvdDuL`%SH*tzg{&9)OZ@0yaM`0Blhiex^-pC5%DVt)d-4Sm2kF>|7& znJgGOFmu&YQ;44x1mde`+2?2G)&%ii8`?(poJ0QB!ZVF}@(E&~Bb8<`TDIxYLIjsi zJFW+MJ*%~ly{P=MqQ`{4?RjKs?Oj099ug`dn;?nT{>#Wf+zxFn-HuKe?tKlC_=eup zzOauHN%qT$iyRA#KKh*^(Yert5ZxHo^3yO2r$yJ=;(&VLaA;1o>4fgJ0@>4?MAu}G z<}sCs$MW?t0#MQ;Xi5?5e0tcu7$$NfbF)u}Ai5M2TD*TJ&}Kb9?sMojUMMPsY-=_3 z_oFjEx&gVt#Y149wWtFGC14cai)DL0p5;#B?)K#mA45R_h$fLeDlf#BF?Q!y0Hd(& z;R(}{Q5-+%75)%cFrJWQ;n&EW<|(h&-M*bzUFzD`_bI>5A>cX}9gWkp{eh7}VQM4d z=KKsho|R0zs3$z+fG#T7n&q_maQ>an1d`1vs?iw8OAr`le;Um8%wqgRy)No~lL>iY z%CM>a)wp=*dH3OKZmM0wCg;B6oS|mNDf}7+R$SU(L``8W-lBmh7pxE0YjAn>?(=u< zkdif9kp!YNU$I#DYeqg1xr!?a6MB=6!w&&E7d1i&19-2fD@-R6?9~nmHUk1D`R+}z z==Eix_v3o){G$bJ z0KhoTUuss5YblY(etE(vkoD5c?q!-DUiN}F82nu^P@nG{zcBHmRz#p=F!_rBRgWco z{>ImojVf4+0Rv0}h;>`l25Nad5JTHXc>T@fF9WPen^@vQeSMOdBA@+$i=mG| z8@A==F+=-Wv_v}!W?GUh#;&%+#a2AG6%Toga}iOg-H!o2P+-5!EzoG3C*`8c=x(GX z;a(w$5omY0Z~kY7yzCIM`yS8eoVPn+S=zS`pd#C@SR}t za%nwcJ?dB^#vdTg@#ilD0C4b0GQ6G<*ugzJClZd+j1d} z3*8HLmVYVEvGPIBJsqzVd)%51=So+AsqSS%!K<*+=--8N66vG!$72g!-7j@Me>#+G zX|uk{%FDP&b@XhUY$GIV{Kf6KxYdS(rZ={xw?|i944Mny`j< z*=0ubBVzNfLqPH}?0}kkXUx5OR+YAxv+nrYlrKsioVsfRU8I-thflk^kl5!h>Kt$7 zv2rrg)XJINmu!V~0ZIYlE^8NsKMV5ZVP!Uci~`+XLLDCjIw7bd$N4ayzW*)DMEJpK zpa64SfCQsuM|%F~<1!=;FES3;_IcqWzg+sZy?&Fq;#qzOF%l4T5r;8gynVSTB;s&h zTVa$SgGh!c1Oi`T*?f+n0i}?x_C?Go?UFT2{VYsap zt29_d|Lu6ZtZG`%BUG1gXzNH@r#DN+fLB5>{zmDWShw-TJfa%BtBmPu9=X!T6O@@}Jj-)Lr_JZ9KaT6|h za>TT7^qpYw6=LU@MP4LXIXVFTB!IL1dyk`_Q+ z-n(?=_Xjx?au|XDa`_`kNDc2(3U3h~2J>cAt@byGhuNr$@FJL3$?tE)*?s@x>^-(+ zWwJEEc|S$1n0iJ#!cp0iLTjy=Aswx82YU6pxA~1fqaw3vQsrx%dk)tD1NPpZ0L%>D z(2d1HjY#Rd!9)Xz5hj-6<-)`Oh0-pFL6fJo?OXUA*gy!x#4!&85flA4 zS9Z|}5gS*&6rk}|+{(bMMkHU1U`x2pWYczvupk}x;UvgB;{C0J>%NSA zebPd@K5jG;e>w7i3aLShkSe36Lh7VHGP_T3yOAo2IwWy1T_t4<`$xqRD;0OK%)KOF zw`L2cRs5{ORZ{~1ve|=%98Wh;DQKx4MHdb^Dz_^(nQC(7bNgIBWPx9vGjwU9dsKgZ z0IWZHxzD(M_H~_*hwvNlw7j3GTcmQAD%5;79itb5c!bOuY@W)wTkNv7Km6XT4cZz( zWJJuvT3LssgOWK-d}TT<*T#_~&*qi@-l{>am$pGm{7LZjL7y@9%KOu+ve+#?d>LI8 z{5lS!A~VK5742%Lx{yy;@EaZ1@9srb<+BPPM`6WOVH0^+uX|$yPN9v^MrtFJ7(+JX z&(~CsPJlOM6Sl;*nZ%}3dD^$mBGl2)&bNK+`h3Rgf$a4ve`u?Zwm=Op)G(=`^k_`D zQekf}PoU}|_@dC~g*@FY3)&>%^+x$l*A`m(#&_*#>Ne5GS9E$#5)fzOCtHZM zgbtZy`wfT+Y<~FbuJ$eOEmd2=q*_G}o9n$!Nh+k9;{8zst z-)v{>X1Ip}eiDmnG+W1acl464Oo(;lm$;qI&rkg6e37F?$`2+eE>!v$pQv7Pp;)w( zDs_d(a2}dqX$lqZa|tEg4NFW*imW!Os{f8Y(aL^aXmh8ByBJ7|m?GOsk9&+`vuc9M zb70x=j1^YTFJkxasi2o(i+lXv4s#eB;VZFHIfG zk8LZtXGulos(IMr@01sz4BCFwcU!UN|0&w@=4&w zclirsxlS~0n!InyUzTsrKen9nM6H8e9yC*(5=R{RtoUI4Ob)60d>zZ$ROctk;U$pr zmnGC^i8mkceP%S@pAZRWwarW`!pZe2tEeZ;r>ea5jp?^IlJ85}=w{zGqPZ4H7k^K) zdMF6muvzFr&FzR!zv4Gg#kUw$_^Ey+L zz>BpmP~JkGpud4}es6)i?xPNUO#iEvq}X^HZzMPcBuW0f$NP|}wmi-60IKR+p8Kzh zjP`pz-GS|y?=dKKsIqh+4pDquq5SxK_y}=RXKfZ|IOi2Y9OuHy`snXMjFe8oJVqg8 zG%}UzO7Neq}$bGxs-B`$)TAZ6tG! z@O5USHA&oMX@cuPX{n82M54%$lOJ&gMs9WjMJUknq|{n}jZSi6-L^maQ4iAZ3r9B$kX!d$ zs}p;|J~o7zXf#70IWqgrw4{pSn?5Vx7PV2yv_kxryaP}s*LNDmo z%M)z-+No=H@e?>dPeS$q(Tj{8#ZC?K4}5Q>>CfP@5>{26ogmTRoPgqbt=&^~@U^xd zrsN}JgjSLCq`BgHVJgyup9?YL!GQwouytX%7=E|D`zaW{6)%nv4Bghq7jFat6ad*{ z(v62`T&H$kI0TlxZYw2YV(G=h_MUb7LKD010Yn=7Z>SR`rL!2D8>Hz~zZsgxoQ3r~ zYf6&Y^LrQ&_bfV3?5^wgkbvbw_WfpQKuljl1A>}~-yx{1Ej{%&la-*%hc77dC1jBx z<`O>%=fnnt)J1^Az4FTqYQFac5N`#d`U8szzgZFG>*%_#rey*G*4uw0K>s#wO}GFR zm9U3S{9Z{(0*4joo()+$0R<6n$5(QMH%}L)Cd_pc@QJ3%bXA#}f_Y`{>J@h$yq_hK zGLmJBnI(li^dVS)I`X@=8b~whj+?sz=pk03XA;h-ckE88PrLH^#_>xo;_e$yF`1`Pqx6@m!11Yj`!gCI*`1QeQN18^3Jg%qm)7C) zApN@T8D;3o=gSC>k#)1&HRhuOdr*5f#w*op#vv#F-h!c8%i+`7)@93@I93zN4+*^c82M>vx;mXNJQi?z9*G~-UnW5kkMb6>kyY46s`oFhET-`K4=QiQ=Ze_bFTdn1#Q zU9BTAdz8xfTxX(kVI((!-I-r6sMcjD4PM2doLJSPnL`^4y&c=+$ZyYPG~kB-Be*sc z$bqGBv}n*`mU52Y$YLfv-%n_;_vIYt)Y06i<{GUR#KCTYjBBtkO?9#+kp%lq{6w*c zbG#*4SmV7v?0HL?O}LofdonI^>T||r!i_LR5Eny=m>y&ZJmTn4?8baPgow8F?-==t zm!LZf*h;1m8u-gNAJq7?T{`vzqd(ha$>FoGsMj{ynyDi^L-PYE` zd5r!IY>$|Z0JUImKut#S^((LW-Ro=6zZuT06L3L9_PPec@O|gq%8tf>Fd34E;KZ?;FVr=>%zwvnEWRjTS!D$&;O-LbM z&YSFzX~B{B*|+Pcjl{zW@~m@i2iloLFGlO7m6rbr3syrFOrl38<*)dk<6*J{2o^ z!ejj&(85n;rv*`q`YYjIuh*ItoZgvBut8l%+)q(}v-|VtE1-e&N7USbn;m1SmacU< z!SGQ!;zcNrw8z(HKiuO3xY9gNdjxf^{83?wTQ1wEWcirXnM|ZaUYZ{9q*c*F0+C>& z;1k2nnt0Kq#FPPXjYlZm*kv$9&p2I`mJ=)SC|W%v4ZSZ-uToQAK2V_>am52t7l7oY zWS!kR%0pZkzRC+Wj>~;0fWsuDd8vx}y!)E>`=A_y7xkM(_!ScsQbbJa^TAMsRS%m# zR*W$P(z-0HBt@fgLgA9>HL%;&!=#f>6C6wK)S3xl6vSt~jLkRgEpFHLrZhT^Iksm9 z7KkYqdW*lYM-wBW_3&)@D*;R%#evdww_#RPx96DQzvqPG$VuN;ZUwRf;IyU5a%l%@ ze5`}_8%J%>dfi1>jEnRTqfJ|A^?T!DC1|w;IkV37Yp!F{6a!Mq0(wwvAml=IY`Z3Nm(3tMtG7{GeUqE?H1S}l_3hnpCxbSC&&wxkjPW=s5-=8-zCAyC> zdY}wPjO~!nZSxyR%@ocUC1JY=e{0-!ytUC))zpw|*7uv)k2D>q$u-uTO#E&N-Pus1 zZmmiy3GH%9#%K)P$@WBV8^6(;OmB2LIPWluYxvQKoINL5j6gYul%a4t@9Ua%^iJAt zO~!qHq0&Ge0c<~8pvrQ;-yg#ja_e3tp*dY{x$0%Uf-=EA;!|is(|BB3Z>>8n*)C8r z4^Q;FEW*au3#+mUe%}e+6G{KP_IL8yb%_J=>Y^TPf2=7%zHa@PPjXNVGg^(i7YNV! z=7Y1ihQ(d(*t;v~c~bXyd_=1wqZ`HXL*$6P&UP+*0m%vN@C-515T`E@)7b82!i)MEaAp!6 z_XBCFrzSiuCXH9s`xlGE>C!vB7yCk)^x0u zBH=f;6+3_euH(k3G0Rwy?xq*8b4LS}p->u?%qKJ^LpbPu?UbLbVD(N)-aZnM=S=9@ z3wd0F`hHxSysp-$&Tn9*Swg`NC;QF$tnAaU-ay_cmztmcEVpZA+81DoDaP33+Psi= zd81W+y(pDk7X4c5F1OS2dVPIBzH_k@CtN1K#S8k%S**qSEGRgm&id{Co_cqt5QIT& z06B#S)5>lE;+S8kFt%9-+t7Gi_mCnZivQsV1>qN`iX-8+0l|F3=l3_2XD2}W_M5bV z&eSeRq4}t@Lv0^yZanwE2@P92z`yy~fN2shOcZx^`%N$uAixAsHz6uR;)-Xd zo?%g#LZSki+x%df1mI7TSrk8S7o>ZyFTi4{qI)$>NZU0qXuRy}a_HN?!g$l5SXhpr zs(`LAqn!3E*0*T2v>kZS78gD1F=`6gI6RQVOw+

uyDD!se?Yh_|b^`t*ht-n=2<0ZhpL%;aw%uJyf}j)wDr@;ZkF z#%sQ)onUi(${Wqx`{ap>hj;hNHNU*5Y$68L3ROFKMh3Q5KyD+9x*_=}HiXUbPYvcm zSMRL31XVh2hp{aMi}rZ8Vu+!oOdzVEACa&HnWG+{c@A}b3}2pQo%ca)$QKLLAuu+o z6rWo*>w;LCrT*E{c`|1zQy!u*WQqkd*ClF3hX~7-Bp683{s#OBQv9PP2ViTddv0uK zpOunG9TP1;4353d`X|>J`GW&qyJHcxml??GoXZ*W=D5{YP}pw2;Yom7GTHP6FLBa&9Fa~VoZc7Loj{z`exsE&sBn)K@1<$JPB;A? z`ttH`%3Ge6?VA4Qf(1Du{ZS-mvQ5`m9c&*@B8RJ_Y4CnSU(|HP56jGn4sQz1h3dGs zE_}S~)LxY-on@~tdX$VY0v<0H2>l-UBuC!8KI;~^$&HT{kVhK+e4UWk9rLpAKyzkZ z-G@|wL^HZr=d4`^bJZ_eJ?->0U z^>68xw~h&JAJ1D_Q*Q}K6c%&oLd2d3!1=6Wl8AqtYe!?Dn!m&AP>yqdAGXkm_i-XY z#%#+;dPVyiYZ#=}&UMZj!+KJ-MjT}}(d*1~}?MvlJ`G3M!>l6Vy` z=uZ!E@ysp6*g&cRv+-b4o!|inFqLSVC5DYgM;eyxd6VupU1YxaAUwNs}$rWGes#a{uNzv;^KBqh1D^Q4fS}#KG`a#Te zRE=HGG-|Jt}Qak?%V@+N7hBcJQR2o8L9ih?}fw zF-ODwW8!;*)6+Nl%5qWkDLHz-7jr%1V|0~-fUjWv#ywLZR;PMwzKdNR9tcIBqVu$~ zP8qIRbhDxfPm*dPzTfG{j#7wmQjg#4i|qm)`lc>ptd@LV$$BZG>#`C%%EAr#r$6|G zgwP;NtPcns&~R+U%tm*E`jf&QP!qe4Z~%pjek)So?u1R+g0n7If+u`6Qwia*nh*QD z86sp58R@J>+cF?8n48XU=ceuZMm-Jwn~0U#A%{D^M{r+JMkXp?2pRrNX2Xl@6SM%X+ ziz-p;2zQn05xG`S`?ap-{f&*~ut`Kjj@6kVrO+xJ zny!r*dZqXXBECr12TM-az((PChSjwkkpXRpW_lS!$){_j&aCnxYS-R-_sM&paI+tx z`A+1kJ-$BuYE^BTXQ-Y68M=6b3upy`PI&pfP6{nCd-P|Yxj1fOoFwXnG;XE`B>z$@ zVz!I2Hv&orNC}dW&AZkzlj$2MO|tD9_!ZDSoC=8U?`#;sj>MZ`?PWA{G|w(|#?hE; zvQ=MMip@>pI>WQ@$Uxg5DG{-0|1)!#RJ*$1i%QS z&!zNaQer)TL#|_DtT#ar-rec4p7foU0r|azz6xiwnb-Qu_=-oJz6Tbt53Qz3+n>a~ zR_Qb#6|C?FNTJ|&1E1;Pq6b2fjS|PPzuWXn5>;k?$YlRg>$}xX%p{3JE8q(nKF1VI*cK`dLRyVF9(h%7_A z<6fS^Kt7sHc4CAXkj-#K2m1PL)HYq9!2Lps)3FMdgxan8gre$f$9)QSF*&`>#6EC+ zzCU4tK09Q53U~g{oJ%>^1h;hP&z;F&5a;Xoe19Wp!rM_d?PksKaSVvwHV6kB&$F61 zrOCzBF4VM@$(Va*uh%Hgud#YRTC`OJ%lVv9TJ#!k0S{z)yYumlyw+#FQ!+!;f$VXV z{_1{nJuVk&*-eh~)W9Bwb%j1T55O+Om!JlX;ws8edSd4dO8yPMVj$n}T*ca^$-!y) z`MW&Dw`d4(0DMg*pIMT(TG(Yw4(#~PNtnBqzatqy+x2^#Z)_pZP#RcE3Zd=ICBZLW z&I?$QG%1=_wUeAY?Z8P8kIA>E)DZxk74OOkeeU;c1wXD;1-uB-qWGnSoomwocum&( z{vT2Ib?sls>Yw5F>F_UH0rGU({&c<-!Ad;%pNabiHn9!O|M2OzCP(y=I$JiSza;LS zf$Yg!!?nb|@=8xy;z74rJ1gY9b+IcMIt0->sxet6^G{IGQ9U}o_TEEc0*!GI!ErVFsu0fHW(iT^2k(^`x>sO<;|MpSIGc+&Dky} zM$}#QG<(R;!@-lbJ9kjx;&1*rF<$8oXL{MkI41zUHPe2hd;RXzmv1qouP$7G9{C0M z(19|ie|^g(c}bi#b$|U*e_M*(h3zE(pb%lP-z~}WfPu^G4e)d6MH+z&2zr4`fDm~r zOb{!l0^*!onN0Ba4$tz8PO@NJJNnJq9v3a%U@52p zXn#8->oI~6$e%;)6~O)`sU|pNdQMbA$>YV(9qP{Yl<5%8gf{E1N}Am9T+cFhhBW!b zxYZsyZ;PW|A=kty*8*V)kx7h4TlH@E#kCH}alnBM+HWpa!~SLZbiE9%8##qA-x)b z7CQB>NP(EN_Vh1N`bUt3{}Q`D3qt=3qW`}L?Vp7UY-Gjr(C|5N%~ z4kDXg6<{caOh;f?8uP^s%#%w1(06%n_qX`FEqaXri1S7Zet#ohF#>4rg92`bKfuj} zby4Rtef@C{{SBnRtb4%CbxJS8L!^Du0gyoW&*u=o;)KFQwuROKB<=h90h~o`|ETyk zdkp{AJ<+LVK&bzOg<*cNgyese@V{!k=1=hdN6r3kE|iO>AheqN#%Cie-}+=DxYc`ZJugWZ?;_~&rTm%eaZ7{#cQ0i!ILa}Ms-TZ# zWQFeoI7{=(Fr+X6E}WkZ2*M2NHKvh(J-rxyuwk0SnFi2X`aK8U4hyKueKT#q?fJ3H z@4g4+2{6@GKrz;i3ILM*52TFkpC-SWf7X;Q!`&Y-^^eOf{^xQ7u={@;wBLgCd?tVU zzy905eEz?G`%jC1OSi4u|NeWlffW?~cQhxlzrUyy`aAWi(7%^d24O{spm~P>?ce{~ z|NEb|1zZ33d@a+xEWmevLm-&?JB0onp*)!ScMSXY7x8}+5SIMy-~Q`X&7bxEH(LIm z_OM@b`P)a4?&-1i|MLo;v_Jm2#=kd1`YxaCT)zLGEZs`{^>0tAeZFYgcDqDz-ZVqo zG*|+^Y3_-9K@4cf;B3g`?3c~cFJGwjP+(q}=yaLXKySIB8<~Z9wUw9OkHejcdFO=d zom=4VS`eO88X9wzlv0hAk3A!>M@2GVpJ2Z)gR5zqtL>0KV_&MbM0B=NbAS7k-_KCb{kTLw9FJ1-e4VQNq{j0dAWW#tWGYgKE z1uZ?IY=_WNMhY|^3$(yO=aZPj-F0|jj&1>Ao5D3Dbs2n{(sqK_vw|dw-gVvOx``3% zjVpJQ%F1hxqaVBv>kBDaYkqCR`<~JMzz=i}6CN{(ZdA~o!Ny<_1rzs<(e-Ai$^B^t zlo4a$K%6IiCBP2XZlWkk*A1?pNE5Q`c{ABttM6R8qYwRr@FRl+g1d&o#13*;Ka@~) zdFObpYlz&Cj_P(5CSE9@of#Drr07KLmK2+Za(nCWWN?2?{90($$Ub1zgC@kg%nR0n z%xK({*kckdH%>vlgXqc=Lf!DrDFNnq?mq7O{2pM}5cn}WPf_$Cx6jSfvEd+`x2EW0 z^1DKwqW7YA|;&Agm!K-!mDH>)(z&cT1GNX5N;OZ;T zm?-j_5!m}9yz~3^y+zgiQx4r*i2X07VRr8|07cx}GFoyqMI?H^{~^{GRst zR{wN3Z%`SLkFLtYjSWRw_b~;;{H6%b(9`WZy>TMJ+t_*Uzh$h`I+1O zNi6is@%S}3;Bc|uv{HNBkjQhEeQA2;F@Ak|Me%X}I&!nS=LV!FIM#s`K#u!Ih7o#O zylKC??I9`j!^&HJN{sSrO7wkPiC_jmJCq-m&FwfWBo*>>0?}ePhAyx3j&Mb)IO0L( zfJ;Av^ym~&AmC_4b9H8Tq0D~3c01zfq^FZL%^1GnUy9Z=i^&~hGHzkP&p9c}U+Rw>q(qP#F_^dQKbV7v4@E z{nf}`t$_HN-aM$;6u8XQG;QNuCo>oA91%rFqHi;7U6U+K9w20|$uGo2i39nMSR@y? zW<$c`?n<7`TTk2c+LKCF8JQ;i3GX_*jAE~<#L+Do$13ke(O>6mNh{?(d1fFyHyK7&EliaJl|d{LSAOC@*s6Jk#> zCtLp2O=E&6eAn&;?ELN)^*6gaZbWf?nZuT=KGvcy(B_5SNTaHl=20wR^``MXT^~ND z#|ZGG`;X77xqBJ6&oe%_pytQC<24u;J-dTMDjCV-ZsbHpO1_p|#~e)zEiyr@W@7c! zI^?4FOY9LV>W~_km9oa}6Z&vVo(5~j zSR4U)-tW?V3Uhhj>Xd6=D(C|IW)N#K`u}J-m1)+LWfeBpsAYwfhYEN0);p8z(!X$( z(x9ib;0IH=#y@G*yT(Vj9vv|^H&RCf7}5e;`#z*>vcKUKQfy&k6y z#Xsm*%JkOE>}8Lqps4?}o|>vDifgpM6AjXF5xm*K&GP{Kf>9mR|}35dzS}+yH}+@Vy1<(;)cew zfB+)^^ix${R(o^i04}^&C|xCF_~gC(90$+QcFMXwMt+O(yxhCYlU|qfu#^e|W|+%Q zq)O$Ea%=wiA|5nONvb!1ExMy1r`s-5Dy1x3P>gf@%|~>k1qw;kZ)Ce4X@6tNaNu8e zM&1bf2s(PC4~Na`KI68|RJJ^FzyofrmfIk^#jRuJj6d9d0OxlJPiw%V5YA>hcfMV; z3Pb?d{If!>oH+h$ThKtSOY?$0$RMwHB!R6-LV4hs+zr2A*!! zqOzQjLm%&#rRR6buR@^P9VX^AWxymHX?Ko5yNGI2iq*-uJfc|2sd~N72H=h&a9DJ{ z&9a^zEhTonfQ{cwC1yYq;)J`&FW$+5>OqWCl|DP{F%gpO$sJ*S#%}hYV|abc{3;nD z2nIQlTm8nREqUn>FHGTz?n@@c*dxR&pUUB0ya4!_Zc9QhTIxP2I-58Gl!*UG>TTVh zyLle~+?78s`ncF_O&BD8pv`o=51}9#j~#?XQXe@$Co{Q6eXN6J`*vFA0RFBLM8@+? z=1t3v>@0Nr4$`0PmlCz3VIV*k-N3y8sG{9L2?)x*MlU5J0}Im|CD4Lu^}pJd^Yy(w zecXyR$6lz(fz%?#=GOX6q_k&)GFUQZt8f^*!X;k{W$g*FxUk}j5*Lj)vO4YOA`6!o zSz??DVUWZFg#dEWLCJlgwic;!uv1Te)`XjDsXl5}+k(EkqvOscuJoBNimBLpByd@fz=R#al=m z99b;#01J43N7s*|r3o3G0#3Ln^@IrfPq+c2raGsv!TXO&6)o)Re6%Belrsw&)@cQ( z0>>oL9GPVtl9A;hNRVW$AKh&uSp8UtPogiB2>6nHzxJN-z`@_8>J(FYnY zS_^fKO#2cNeJE%WBhdMQ3?xpzP|LN%dN>|$>eZ=#HS4&D4<+WGTgBuJFk#eS=obL8!EPl zXq6g<4(dwwzYU+u;XFD#F++p<$U4ke*S}hJK+8>`V<1(&j!EB_N5ZW;?nPDG31nC* zXt{+`G9Q-CZ?FrI~{dn-x&8}`Drdg_lB7-0B;5Ia+Jo0J~|elD@c7Yh-R#^V|{dULVm6x z`WsvI=J0iEvYxa61;E-CV%dqP7#9ok@_o#?RQJ=PrVxVtrM50JlECr1>PKz@)Le9z zO_9M7@2`lOc`Z+vmugS7i5`?A35t%3x6-@BL)Vgk9S0)(Wghmi1j5=%D=eZKl-cpo zX!6;=o&~jfWt6k)o`$Z{$suE6PjfZn^c&-SdHP~8 zMf~7gn=HgT_0c2+T#dYFgB-Xgo)Fl#76L+)pyMrtUR%*H#PV zHBmM1n2Rc}yEm>mxEEh<+JO+*5v{nR+RGM;YKnvV2YR7aPK89|k}E=nCoc54yaK9S zl-r?;b$!zOWj6rP%(`t^(-d`i*wg$f5Z0tWBQ+{3dFap_s&CI=ZBgM@^!WbXAMNj5 zX?J&5w0LA^mK43#2U_s7DUPHzw}c|~Gf=mDt6~k0Dpu{)@RY|n5xz_3LsO*YX_CHgZyxeNbR<~b$NEsq4IsbTY>xpxTs zw5?L5kqm-;K6Ro(p{ zU56x;U`ZK~Q9v?l2f7Cp6IIQYr`z16{#uUxBSKb~k3fNbn^pFMOUtM^R8A?$Fu4&B zet}a{ryDxW!{;Z!{bRr%ndoTYmgD^F!4t;4Wmfd3I=JW6$jpff;LEuY&o|tua2wIN zr7-sr3UE-rJ>ct6KJsR6s1WCyY^c-}q8<_ywWPaH$VX)TM{UEO6d`Arkb>wtb_^~P!iF5FP5}WD&u`**>8Ms8^cMze#@dVIpqy8F+vUBvPsuo(=!`Z^W7b_I&YDP z4K25%%?f!vSn_tqjyhk{1L>u^eQP|)QwA{1Dm<83T5vA4P5FL$#fd=Yz|is6FubXj zB#vP;G%II%v*v+~}NrARI zf*W@C*WXnkcOhJ(4I{@(nx0HzhMLl04kA?79_j62qfc4{1}?!itO_z}@e@JJd12e0 zR>PW~0D9bj7Am80))+FQ^>W?QmS*7m1b^pDrbrD=YwklmO~-_cQVn-kdK*2rKuzT>PQRZQMdtY>HUnO3-IA2A}N&J0Q5W4WPXA zN}^T(9k0nbQpz(#pJ_Pfuq zgkC@Cf%h5{VMiE6(cIpVk%c>D);=w&<4_BD;iq^yD!PL18NP?$`FUjD^1UP;<>792k0Qj}AMS>b)_&h+LCK#WC(M3-XnIp-!KKy! z>L;p!f3q?wn0+;!D!|z)xqb~uPoX^jdMvQCWW5KNQ^J1(5%g~D8gPO>AcTP0Qh^uc zYabMmrJ8voN%VMO1{EJc`TVj+SeN<9(}!^zS#{wZz!s1xUL;pK9iu(G4TBH05ZXbH zdd7}INY_2_*fmHoMhfY5~toaIAeDY!$ zy*yxrYRAxvj_aErQ(ks?q31&8?>kEO6=f@4TEm(walJrvyr zSI11IljWmBo%xTON5(pK?v1N}MafHvCncTcFnJ~tmeu5`D24$K<5#%t@uH-$23X@v zuBiE{nY;0SgM34JWovDuDA57AO7I)Y@C6GLgbp)x3o=;p!)(C(PziLZP-2v zZcD7-CW&uH&z#10*{Erdd1FN26ffaTLHxr&HimCzWW_C^gz-{k#<=5H&SqRO(Y zZ~$X~$|T{X)h^r!{EgW*q?orAm)O81tDXwuNK_S2XH5rb`1>Y@Ruu)tNtvmT=Ev{_ zZ2EbB*;~zNL-!Xa5S^k$w=lv!T+z2j(mQGvP}-@l#*Js`b3UCnZJz;7#VImF{8mX= z72=jHXc|e@b7-RUaUuDIJoc5UA zxA3vIte28ULuuVq+C5|@MtN`F$Vv&zKg(20FljBvt`=3*GH>y}-eh8=iMrJqi*tRq%N*Ys|}mt-wD z#mVf+Yuy!1QP_)W+-W%$#yfuYG4zNUN~*8aWY}UX(Wg#B#cECVd1xliu*AGeN7=Mw zw^H12h0~V%Aps-L=r+?Cmx9}MPUoy8XJ)v7W?0=|mM;6SU){+(a7menoRrsBLA=LU zuhR$^Zicq4-oaE9$#c~L-}CFw$im!h$x~c&R!DM z{r*z#Wk}P-H}`W_Hg|?h%LeCjHraN}>RkIdq0@dh`<*6yuvg}b^c7-|<-WgX$04!& zZ353)+Nh?PT%KaT?K@uVvMED>Ww@g467$egFe{*Ju(ql*K+7ZT{(2;1Kr7q$dF@aA zl_ogFG74lxMVeleOiFJ?4*k`f(5fn43lc;9g6vuf&5RS_xh~U>!d*|h7U^CI-nAza zF%31Sp|vuGX-L7S|36vhvE(R_Zs7-FA&hWKy!W0RPI&Km`b+hlSxrTBcXSCs@@M8b z-$A74dHF@{T)LWM2{4xrbT81#%U4?LZ_R*T?h3&K9W2nBr6; zXV>z5w>D;rLZ> zh=31XplWFhguT5FR=Bze4<%@dm(H29mYs~x6o6QwRQ`Is*{WzW|L(gP1%5Wwj5&V7 z*#ZomZ_R%XZyn&n`We25eblS@TfJjmaH&|EY`9ncf&9C0mD>`XAK#Q!A*#zoQOu(~ zEOp0W)kHUvn{AS<(9E>imLtv&WGA7}lrFgUFolk0w~w5E^To{B-(&m@DV@v#n%#dF zok6&RV7tRUPJ9@z1maHE?pztuvY?M&++0LUz^=>^F%7Wl#gD$&s%a6Uz9c>4vR)*5 zZD!E!J+aEyM(sf{IQ&-NqDP(%{in~&nQY2@8KVd@Tw1P*1qh+&GP5V?O0njVXepL~ zbUcGRLpR>9VmP(NoKH^Eh%x<51)@EXVDQhc|H9Qf$Zk@wm| zIT}(^DdxdFKy&m`Yh$Qj9q0*z(g3!Mt~~h)7zy@8(5**U6%vH%$<7Me+!a1esyO>5TsIbmgx(Huuf7K1 z=EeXXF3d>0u<+&}@foymEu;=3bscU&Ezbi+++ZZ+?`XE8GK+LMv|{W#ckLe<~fw*n^CBvn-p zF~x^eg73VRtAkR;=9?LvUsTSZy}E(ALbYKgbB2uMF67y_d10JO;u|W4yYQlY#LOx0 zZ45Eu4#Ound&((i%~SeTT;T9b3Q>-a+KdMtY1Y(y-1m2vO&aD7FT~pZIdt-fMY^oB zlkTE@Blg8$5x~-$<4auf>$>t786ij{AEYhPyYEfiw(RK@=4P#L4)a!r z79{Q5In?uI*Qyl6nzK;tf>klv;9;*(iw&<;s90^E?O&`m_*=Q6L|_p5cl= zS)lynxFKo3?}lMVCK<3!G(mjw3DAaqVw;L0U!!Crv~pJj4J3MNdD8Tf_zz8dC~5q{ zJfp;y?4PkhjHeRkOPRs`xa``}X-p=nvw>3_Gpx=dyvf{3P^gF}KN(!?rxF;r6JC}j zlH|5N^IMI_?DhZ!9$9!*U2>_GL=wy}M}5C&`%D^9?ej4FG7MzBh0koKt6b=H%c3x} z$%hN^N#XkVo=6hy--&F%K2S8K@77@Yu7kMX77B>`z#}H-C6SY?LgKBp0%E3UEAaNGoGJVT4E)) z#87ms_Ewi6aga^wI&p#oiWOG5M*Vz?qO#zQqjO-zW6n=`{oOMV>b(Sxn`SwxI15cu-cpmzP@`_kBVY0ru-OHax)4V))a;gAK*qNR zg}LFLLyaMzq4Hswb+J{9CS(ohutz{anV)k;m-~0IyHNOcqiK#f^Iz~K0|Z>Hbkn<( zoIq39fMd%g{E%ho`*%4(wAvFZ^oHy#%zc1H;E%*Eud_k~w)7Q0HptyT1>E*9-N)OW=P#5Ts@y#fQ(lTs*ihaX68oov@O}zeJqd#f7so@LZ59mb zH*sFcCyhS5A8m(OVK6kiZ^`~vH#2V&+}(H<;^#TR3rMfU)+j>JiRxZdM}MJ>-;&E~ z@W4Z4ns%nk9APaTi~##Z6S!yN4X-GR9&}yr9Uay4|YL-guZ3;wu|1gqb0!KZO}>R3+D5&>$ZrOVi_l z!%N4DCpCL!i!)bKWw6gppXPHfOP}Q!KW4PZSfFsFBzQxBc_5TZxR}T^AIwk`?oY-J zr2W&ggZ7R^l*jLglkn-HBkC`XYLqsvi%Y63J7-O>eD1(I2l{=01{ukJ_4ZjZC^M&% zK!Uik-w00F&&ir^hLDgU-I%Y#+{IAS5<|BI68=VA%8(_`-{XwRN>z`~{-Ag!NVlJ5 zNzKx@4aXfqV~{HxIFC0J%f-#xTaYT9hVQvSfG|PqyU+eAkFlZYvNUmTu0&d2Q*YmA zT@Hh43##HpriNG#ad_)6MPOFSRkWlBXdG(uJv@@l66FY*F+veF14Ay^;Lk}lz4FPr zaG^HKm-Q~9SUN(!vuv7@pmmT5c{Vq3q%ry zYZ6S>96yyXT`+#Aew~fboA?>U{eiE(gslB?=o>0K-VwFw{vsg=co;ie9@Y3wNqsIXvesuD)*theM9@Fat3|#BwV(-w? zC?Y&=d%Hd+KVx#mWV-DfvP+t=y=wadl_e*jDFN6$McDKgT8U|765+&hC$Y@Nj^Ul{ zw5Igt4>p8JZZYZ9IihUuxaPk%h-#6)rlNA0Q~XKLV6#pHn)*v9L9#e>y7bFEP!!F5 zvzYJ0R}7E@hN&&@VO3d~pgQD8x#HFo>?mp$YdB9pObfRY3o5fMg(C8I&)HoI1O3<3 zocG-SKJJ#Btj`BKfdpO+QYubz5fmI&zf%`m16PACJU6<2&D~iE-e7U--9sJ zN1WDYEXKS;kQ$%wOlWW1nDwq6m7Y0JtR`JAFfK$$(yPssNP;6|JQia$F; z)(pMU+j98VnpCGh^_gV@3eA6O!J*_^?}>5uJ2o%db+ccAX?Ij@%SrUJ5pUw0FjrUEV7qeFxr_?SwqU8<6UdqVtvRlN z)v6{7?N>(V;%ZIENQSihB#Z0ntq@{ukDHTh6>INwRUb*mDBrcFhs+8ZJXcOA3rE=b!asJ(;{k1pESy9!Fj5@n z(Ivhw3%aB)GQ30y8aQLtd;P@+@88{1E#>gbWIR*vu!2=b6uQt0j-(cz24d27wwc`3t z{&xqwcDo$9*SdpzvFVi)nVV9VLUf+ot&^BleAagIJ685;Pe0dpUkco?CFj8DiOtE19Mqz|Iicrhf!`hx!K z9O}P83?dBb`1iBi89A&~qCFqNq6CVgsFS0q|M}q<>U96JmO%Qq+Tgb8S{s+B@w{24 z=th0$l6Ig7hW}orIml=32U7-tw3)_~ii*N3ilrNvSEuH0lc4CR>gHgLX6_r*V3>d@ zEyvqwbVW` zmMM<%M$jDo!Y-*>W5^;C`I%?F_cTj6(?O=#8R;t6>lvzPz>IB?TNT35rLf61N|5on zyVY{0=mRl!BGe5+BN1{>AvWPGn!zaF#L`!cBU?sgG>(xjla}zt&)6IqOd>LHIb3N> z&s~%pZriENHJ=jd6B&7{GkzMY_HKqetpYu{Jvh2@6_>1dyyi0z=?`y+Ih~;q{7r}h zFaU?8pJxe@KrK&NZ;qi6qI!QEw`qFo;7pKvfe)4?LHBA~+}*)SPO{VaE1ObI2loAY zmUU0YkAsc!{Dtb7x}olxhS6!KF2N~Q=+sHqXix%%4EUh`48=Bj}`poKb};x8k7 zMpG_NWqF~en7PY3WpI;dq%d1<+%1gbbON-oxvamwD%3+6-5ZH|^n zb=A2a9p{++w(VXImKVbhcyH=*mZ$ejfIc=YiX4X#+s3iw<#9i|$tE)U?XMWOybf%+K~BhPFuVV|QR> zmd21YI;|LMSC-{0^4kOhgUMq?+yO9kZZh=|L5yOtd1oYPq?1??f!hTbhjX_$(b0y#{`z9yBUojQ=mww z(Q#q%K<0TdTM3fLUAktCvBZO^w|R-L3ToX|ZWZpch1LP~6nn!C@KHnH{p??T7XG$F zJg^r<3S<1>$sejEGa`l8gf%-Dyef=;wO8YW9MUD0<-5e@kasx{Wu zeU9FpS-5NWvE%!(H6tY=F>m5wlc~uDQ^{@RN;-{tOM;Ajtr5ENa)>~#c~M!CZ>rAt zKtJv8lCS9pud37Zq)SDpkP6QosbX9!BY}%(UnNDSZM20E1-h2A$s_GPFQ~oBGDC7^ z2c0CH7JRK-iHt8YrV!(g6mvZYI2g3AtGS)rhdpZ2oXo15_M;9{oING-;Jq%z8LM7J z*fVULzfW^ELH9n+f1lMDBt;8FKYNSyZf6TFxMuM48=hnavRT&_@ZPjJe&;qtsotDZ zuR#d$8$jo9ei(}vUT;-qzf+PcIbOR_E$Ka+>{y#36^(pR(w-bVieX0OGVDlE$wpDq zJBdB?D(;|p3tXwrLtjfe@&h}}bS*5_yM=3%T-%3Het$>Nf9v`_#U&NXd9^-6QQCkdh?aer&mWC zwR!0rU-sIUt->zS@(nMLr%!#%$5?<~#J z>w$$#bx5p1{4;{q+;s2jGiu#mTg-i>oqbt1$DiD&TT4w50+&{!$ilVI96u?4kCAXJ zddIsqcHaN^@ZWr^{`!@UnxG>$7EXyht%j&ivSvxN{%D^^ozUPvj}W;kTH=Pna7>Y515_OVw}Amts-LN3ss|Jr?-1Z@Vu^q$r4`STg8-Hodj z#YPU+HamK)(^Av{V$aw%*f9nkcQ(F!R^(Lsc*bh@zrfZ-nL+a#p?0;c!hFL%+TCw= z9PdA*Qfd?ra41Wy3ij#N8ky~9xEnZX#xL8^IGBB9c3=%FL?sgTz&7#6o-^G2x#5MY z(bBJ%Ino?y%1}ay{)J|H;99mUdySA1uw6f)x;FTJ+N0`%?spb3kT}BmenS#nEIRAq z)Qn~;Y`XnC$u0}e*ZQ~0A%2Grs8fi#ruT}AvlUP^f}R_#-;sfXNFC8jrRU&ElC-nl zxtk+~T{Pxn)B@E11h$VDD29$h(O+3n`)z6I7o$P3is;JTe2fjxobcOz@Fx`TVCg<4 zz{kM1J5+{EF?GyJsxm<%Da_7_Fl0M5`XM|J1*X{?jG*1z;hUu)x?a}JK%(W{RE#6> zp6p9ze5}B2ZW}t{DiV&FC`u-W2F37-I$4z^X$-DdTS$tM#7=?I&n9mAruy7vg=1{b z%F^Z>uca6FYJW8nqUgfLNV#>I(^rG*_OW079Zv9y@!r}+zx6jTBTMY(#=G5)gZ^;D ztOJd)Wku89Bt}$QTC1K=Y{rt8tnnIP1bvBmdd<#%de&G!<})Ae*r)p8pkLbs6|3Du z-|VJ9-?bG=bO9HOgsXEg4AM^B=an4v*ynd^PKx!B7}}npjeaq0Bg;J=Ys#FLm5r|Fe4-gWLuLe;Td)!(9r|5*K-IJf zT(jeD7M!bpc;otMpsh4`5`glaC2OA>zPDrMl$Bp?<tmgmN9)S1sL8A#u0V@iIqV%cB8+vGd;RLXr8O4{X)v`^ya zVxf5f6|48wN=>>)1gSQ=UgQ86tlBCxfoOKdy3nFI@p1Ja6gJy8HrU{6YzJ1MCE(nt z#<#VY`D>qk#Jap|X(Rkw+0}+mn-bsFt|rh`ZXl929R8FOo>u5mKd2S@OdA0WB3-MO zfgP;9)o0_~A1`>Ig4SZgDhIV05g*nZ$roN;!xasFMEO#iEZ358Kk603z(V&TR@%&` z7w->r(AAKU`y|co7axbraBEzIhFi_GlJ>QS7V84kRGz1>ArIaSg=)!4mZl_}c!X??qk4mo+=;H6jo3UqJyWM}8^g_Xr&QLSl z^Mir1+}J{S6bf*)QKLgnpAaOdW(hq;vNNxPb|RUADzlEsZrYr3Bt$G6XidH=J6I3K)dsUfxI8VL?>qCj?Elu+ zhF&^C!u8Yr#OnBM$Cr4yQ8EsojAi|`=&meE=XrE@2YvBL+%M24SvyD>T^McE%M`%? z+3e6as`$BS5+nB;E}XqGKOc_bBI`w8hAOESSiSe(lBZ6Ir!DU9@(ssXo+G)O#oaRf zcb|6@a-PI!5AV{9*UNVqbFI!X)#~ZZOKP3&>rXqYw9WY*`1>RjRsMay1#O<}Mlo-2 z+ISAx6N-D8yj9^c{CVf_vtG8AAKbX(@N(|P^0zu5(*3t7fQl*uI@MX?Vf^6uh!@W6 zD(F__Va@Cr4ZUJD-e^4D+5}v$TsNgxy*avV=x7WaAv2;1Dm)2MAs4h(u;$>R6!BC z>p{u|HTm-%4sP@UoXuAV(zonmTL=g1LHck1`@^LtPO{9R;<u8;9vrxLYQxR{-QvFH^Y1lx_+$U+?r$vtqi7ApAB`UUL}!3>;eNbuQ_^RU@GcuiE_GdPoE! zS21e`HHRSjTykTM8a%m|C~N|h*`g1;N%tjCv08u;;zUP0XbzigMykLc1O0v{1mGdQ zxgk!o-{h75CG$zqeQ^-h6bx^&2Vf$hm{81GJ_v*fv3UlkHwgFkiP1LGpVhy&jcvl6 zUafYH9~^%=@0!0}-+a8`9SmrlyJDaROq^LGL_=kCB1(PV;2IxSkX;&KTce!t$dmz8ipvS0Q@w5(X|#Zk&pdkq>tg&Ea{LxVfYow?)_uTNhV z+1bDIds_@JtD{38_Ogl5Qf_`6l!|@{X}>x!Gs@5G(5^rY`eOY!cg<*dffb=7T#k+z z%yZ7Da7A!5@m33#?A2=QPr3jbXW|TH!+mGMnyATZ3feCSUX4upbpJb5>Zw+U7C(oq z4PRsT9oTH?V+HRll&_TOAXl;}5G@NrQQo)t=fbcbD5^Q?uCASW39!F*F~Z$}q7hY) zP24APtmpfzqN4j#|H3i;ICPrr>5vVlSm?Bt0jmvuO1#6j(HUeZJk->=8a6{+UC4h( zsnFUFrt{&T`6y2_K`Z=JYVL zijH?4llCZN$39gudFjdtw4!DnM)X!;=BT{UfYPwSiEW6q8&0G`ZM53QPZIym<|X7e ztW+`CfZBd#bJ+&qz8|yvyp2Pt0@O35{gI({p(7Kwu-MfwV~!1ZpPyCM#Bz%htx*vU zf4m7Z^ z8l|7PjD*2@x*>Q^A6Jv+kx;s42`A=hYA3Cpy$=2^bwhuI&4)nt3+G`|C{I|gbklrh zDjo$sZ)W|uAw0LKrTlmnF`9nvwgx#zs3hF0GJj!qrZgHCs2MolBwFJ16nk9{zExFz z@4FR%-$i&07S(rkK+ry-f&IR>12@w3+%RNRz_%>K&fJ(*ao>V#1uqh(3v>6Ut3GqJ zo~q&57pMCCEd06-y8pi4+mJ) zl%9hz0ZBDN?wD&62LYf!-;zkbIz@}tV{@2z@amY47d^0M?J6hY1+Q^bKh9uVsH!sa zj12i(eF(}AKgACmYVCrO`R1F@;S>2NVRaKkwdZcPYjt@8DM=uW~)yb@r{PKF7aQMwa_^$;aG?WtZQY!GhM=uo0=H#0K{2;;;r zYzDRacx&{F-Q?|SA)A-jF3+xW{oPAj38&(Q|*j0Xw$QyfRtkUoW+SZLns( zBi^QdaBJ0E3V@U*u_?_3ONZ;G$BqkF+I|`tqw|$Oz1Ftkg$hs>z}_vR-5Cj>5pOFzdtar;6viHc~W^a7Z9SiSDG6a zOOq1#H%@_OA%-|);z|;1+OkYjU=}RsM^}E}+Pb6#Y4NRbDTNY333zqlC1(9LWWVpW zFuC&V@$cfhn2K}Sik_}^exe5fr|1RXd)7KK$fV>0E9jS*4r;MwfN=Z>^kXDc?Z0t5 z40g2p(7j36nRk)D%gBDk3trP=9g}vMZzTbD@lP74Fz1lw^@W8W=?I_SXq~m7)w}6Q z9#*tZ7tI!S^7iJN!HbMiYID>=Lvs-n=>hg(`Rxt|5$)~cxwUvM{(3Y4F#=T|o{x61 z7)CCiB1onF>w7Oz{oS+Dob#tw6%Ps42b7iyKP@*bjkjtl?cjMu*RUX0xxT4~g$CT` zq%G;)E1rO3tpq@HW_2BeibU>Thh@ep3~Tot&b0QDZhyekrS^e>nM;<8*&K}I4=>46tS4g0Gf)CtTe;syq>?>rEZc6N}Xi6ZOMvPiomZ~txxpH z=R6)6?yXM>`&=^Ov_GP#y*8QJLG(+=0i|x$ZrtH$8SOfKsxGgl-w8B-AFk|Pv)U@a zb&MSVk2vj%%u12apOH`F)?|1eh$^||L&@{KKYrpGaWD$UB;Y;6q*y`W7WqyzVuJ3c z4}_BvVj5ipP5R7yDORC<%&}c>G3Xwu@=medtNBK#2pyn3t87lGKiEuo`1x-TCR2rQ zkX}f}*x@Q2hlx2xkc>C8bPEY#49%W$kg=j83t^&n0(xejx8Z!(157d#&=L{c-=PyX zD6F@$u&iG5vL0Rtl`ftL@bM(X2rV`mtdD|;NIT!b*eZLo)$K?WQ>4%P&+Jh2 z$Rt&utjE&`FzAg zyL-)LV&Gp24dElO%cN~4L3u9w9qPEI=fyWa z4uzF}-amMjkcxQD=;5(x1CL-^{?6mp;NkYLGzuzl8w`If&PZ6!#&a;|cr%7*ar6yA!KmBYO;i?dd&!Fu`JpZo2_T`lKb zyt`i#Q)rsHRhQN&pjWlUT5A zihz+97l`LZBx<+F_o=W{@IFbV0%}dovGswtD?`M)W&J?0Ukwso-1!`kuD>E%ezt91 zGEQ3`?RSe&20&&~L}dnH5Knt;b{XjNHY1xL8WYVKe|qT`s$k#Qz^4#YIO#i#IWWl>*dcKC$>0kk`k^^W>G1x&o+}|e;c>!*V*tM=HsvXi`Mr5)u!8hc0 zP08;kkpq2~Yv^sNRvAQ`3rWfO5)?YZVGk|N{lusMiKl>^TR)(EUC-ivyHaO=-#Jj`<&bNBQqz!rE3Gyl9k6iJ8+pmc5e+8#Iy0<@&4QL?ciBv$*ICHVTP^JkMMt98PzM{0 zA}9{d!bZ%hN*Kb3-WCkIQ2N>U-4E>t!vYeA;izO!-g1jiq#0?v>tB9$QI0rD=!?U1o*;6fp+;K15#HX z0DN~UFU3}TrC)E}(IrbkQ#+_zbIvIbd`$tQsPvq5ET2-mtR>ro zbQD*y`>?x+V!un_e%W)BgZN9|ndQWPU2HONFskzF`dXvpZzzvmWsY5>h9Q^~xl}ZB zcD2J~+e%FPx`Y*eClKcrLnOj$MJYdZf9_`l2u;?|tswa2=P;1;64;x&jTV-m63hqh zUa_N#Ia7N9iL843tQ;Zi-khNH5^(x;6DhsC&M&VhT~x~6!g<-mQLtKEE{N~-(iTwA-ErksYBM$4Ydh5ZTOfpa23RgU(%<-3vv54 z7xd~%O+a_;EjP3PayyA3Bv8)8$0d`y!n`;u9nFl$P3i1+KA+?rw_W}(eyah2Qr8>5 z#2@{{XD96u`7LF=o5-L*(_QaNUu^VoVR1GN`(ua^90%UEuuK*L z{6d8NdTp?Ofl6C8c7F&v^pYkYtJO%p!#G&9FHg8(+7$oN1GoSu@#DfK2?j!#$9F%ViJYw^g37!s2IU`ClM1Tc> z1K@Up3!Wm{An#e*)qK~`4l`-1j6C*Dol-Yg)rb>Y2}(^diV^mV+kp0a@DOLwzklrt z0+BE9e}0yx*GrUJ2py!Uy$+DJk14wXv)Z8i@cK5vVa}G5t_qw34Wp|we%?j{W0Ix9 zE+L$ZPyR>(;5xFv*m%hhX7-w;$Se)27$#5Mt4B)Z5tdDzzx+M5wZcmLjs9EqcY}!T z;8M_kr1venUyyDk@(O{i!Kibh%?b6JgKBMz5@`_3Y7ia^dgWG)hs?M$0fZ30{rzA~ z>Ze^ng-!5dW<8{Dw+V-Lc<VyLU6-om&D%d?M`Qp(*Akuj?{M zn125rMyj!{bP|n_O}Te#W>mifP6rb142p#5tnVj`Z!~;@^D8YnneYYf&Wooe_5-Jk zh0n><5-i!q)K{boNzg9hbt((nd4R?{?j1dY#|NjeREtY{DaI$Ig)+)DJ-6rFsUQeq zs5`G2$&>fqyLY&6>}bH3h&pA8aBHTO2AOow$4XwmMVcsX)U2v4ui;$9Np`S3?Js9sr||t^Y?5b59T;HgJi-6e9AOEXOk#96r2X}CwhuLIX9l_MX3M6uiFRq*ULxG#ABEpKYjv8D zJ(s{IDSW7!x>w-t8Hj{<6D717DD4X(fOC;5IkKy(Pi)xgM7PD(^Ubbz^6NJidKfnu z81$Jnyn^+?7j~3-C4cxPxAqFL$B>$GHYeQ$mJd#^?0E`CU@rA-y39EE&i%E;HPC<_ zAslAiqT9S#GVU|;(%^Oac?*;I0Glcs9NFmg$=->yP1zJxeudC~g7aPQO9j`k%bdGv zLDGihb9pWEP2+=i7Lt!TFYg{eEmb01i90LMIM#6N%!)E{c=@^4jJ*d23?v3NTlQ+h zZ(=TM?eu|*GfpJqgoN4cHx2H+gqBt`Qc>A>QMPatj!?OFuR6mJlHhR{(TvvBgzHzL z>Je|{wQH1}Lsc;yjSzR4uUFEjP9R7ZvPGN+iZOtl&MX0MCAGx7&55L;@G<7b(U>r| z&*_KXKKP8x_~yJwtG5HcnD)8blLcMpj-78#BrXRdyQ{x$>yA&^^)i080H^Op*_Pc1uq zirTYM`VH?@4Jl2+ZPM=S4H0&u)r}1&8h_sOUG`~=2?O^z**+k6g2V6U_^L?tmpcul zoIrV3KBGj?@6R`*#X@G}R@OFS)aFb=5mIt@&N-sU7D=h|A;(v@Yw?zEXC2xrIhXeI z+_JH@`wcxM*VcOfFePU=4kxc<7F%Uc7|kg!!D%+ySI9arB${qpC4U;E0l%Aa z7Q-6ahsg#1&BgtUn-ea?J>N#%;v;W&ze$&ZCqIWVOj|Ywci?8hg$RP}(s1Z~eX1YN z9gAfkzx4-HQOey;BA|fR+UwCroFT_unLy%g^f$Qf7WNkaewx_qIsW#Ca4N`?e?>C% zBbE1S%4JYmN?%Ye*`AWQpM)hc9Pc-nh%T1)`uUT#oG+&O*k2Byrts15`uVN4vND|q zc9Hc-^lH3YZA?RmGn5L$6#h3E3E}`DRvJV?NIXi`*~c6gL865(qA`p~$H8Y)*~v94 z`1_kkSNZjbAvb}Alt+0KLH?l&!c26eBxsnRzYIgY#mi`1kiTIaHrWw+-YJ9qCP<9e zJ=?>j+NC-Iv`5h49?CMgFG5`bO{j`8&?tl$My8|rT4JuWphD+2ejrCj?l5wRQg@cj zV1WUG{*i-(u7u`GrLX#bs$CzzekQ5-T1vq_?{lz|mK^?ie^wl09u{SAh-@JR@^xet zG-jA-&#!=1q4njf2Ci?4(C%=y^?;V}ze3~MrelyD4U+niqPZnq5J3q66j>n?LXE!(?ej=^!ANiRk>ZxU~i z+N%u2ay!a+#f2i#W47mP#wBED#=Pi9WlO~$@w8}%y|cn$=MfTwQwOLnO&YEhGI~Sv zMNA9yazDNBF-!C9*p=Y%2X6AON#zGsQ04nGP|woW;<-iHdq>WRkJ7{}}=sr0@F zbJ^-vh4J$f_%T6)VKJS0#P9F_m^zOgNtq}MKM)JNTjIS*62HTn#Cv%9q5htc26bnv zN~&szxc8jzV1j_*QZA>*P4pw*4`&DMo_Q-e^0hJH&I?Kk)G%9Ky;NQTyGn`LHiD2( zKzv(fS+Bd8X$SK(fA-`v-AhJ#vGN$_TSJyN%T7|m+l`DRB^RL>rvG;ukbP4$M&3QH zchvqIZ;K*s+!|Y6+bS4G#b5VFA>)Mu3?c#DgL&%vS*u3Xo8j0&1{uXqWt?hxHaXC* zF|knjH~H(Ujho@>rCd71WCY;P9ux$!^SFu|WSoB_k@Mim^5u7HeuAr9{rq=Mgl;W2 z{Pdzp;+6TChbYIBy~=wF^W;yxqK9MPsO@@Yjx?VM)?f&o2QK8smyT;(Gy08C8dP!K zee1K6kUexw$#Yn9V%RyBALYZ^0qAuI^Gpd+V5Nq|CMlg>2`;;>MG^t)KvR7iRew>D zKG?{D%H#bC<4@t;UIPrEhf^Y6-=VySp5G!DvN-4EkLe2WJCSbp$!iW&%=R#ID}f(C z$qpV}qE7mH5bXDtQJT)_99{P4N+?G>DQ6V5*l>w9@iRN)MQx-*w9p_S$b8k~Qz2fK z+EZFO?cGO%60%XdK~zA3aM$S3N}gJJ!Gv{!5N^IRv(^L z-E*E-Mr(d$_66Tq!awsql_%-a^?hQhbKy}WBTA=b-z7>`9>iSKm_vIG-vGndJHPvJ z%KR*lV#d7jh16%>^bt_L^I(DpjsIjLw`gNos*e}4|VzQ&D;eizU8E|qV;B< zw}F2YFVd+HXq9!*{=nJI1av`2)Ci8d*2Y(zlCfRbZYv+fRYb)KW@^QEfJPu;Zwyl&3IN3D?^mdbcNXOG4S5ulKB`_ zr;RNS)LrixNG>|gazGPv(L({?G_cf+9pK#M?@SX~l|ms8Los$GdEfV@|6-b0<(K9@ zpOO)(d%B{%MSHooo1g`-_#_SVUY8P&-DI2D8aP3?7M|4$#<=V6{)C4DzXMdBjIwrg z9p7nJ#EMxTHS;^yzBEA$aUCJ_Nn6T{FIZ85pTyh=ZB0%K&sa2@1PSmE9Pq9`MH7#rHA&fWj9O1RgxBGNI!~{=nAo0bbZrx8P&XnWN|c zL$R-}yuqNMrx z!rj9&>OXBJ9Er{MndA9(iwSCi{oMTuc$szDi>Z@LsSQV zYutt$Y(u?|g0zXB4_3P7J%~77>hQ_~u;DT7YClY*5XtyLf$M9AFcK&;{&Mkyoo_xTie+u+BJ5oY?w5< z)z+9@zNb$NI?YOfw-DeqGfW}X08~kKsZ+b7MmlJU*ti z#^9~+Aa4hWzRhpZYi6TeEEJyzHN|&vk!2s}(Q(x;`4ww8qIh>qiH9Tv*=(!Zn5CW{ z#C;}7$%}`2$sIj>nutzdXq?-(G}Ta+_Hp-8#liOEP_l00{V}eTzHE%d#zr`WT@sKl z`$AA&!vHnDksM88EAL`G-H){djc@!swJi~b&SQQXw%wPgenD|sc!f@BS`c`iZ|U*) z^_L%$9-Df67dv3Aau-bGmim@_f+t%S&BQL}B?+xdvNa3yk_n#}cIfI_3Ywn3e>v+K zI7>mK%Zuq)EF<7)qTUZXpM^=fmm!-h>>kRr`|(j+De6|!?M|Ml1l3lBsJl0DWdio| zGCy#cL)<3J!|n@jWS@SMWk)}EcTZls^OhWN$T}C7g#Tu33EkBn$B!y7x`(zD z6K66Ak`lMeZjLtLLt>PvQL$USwRSqhaka}*tnDmmy%%dp9azTdGKRP8pl^3qyOHC( z5@dv|@nS$s1|2w_EO?0SiGB-qTrI!TQldlhF5$3&*;juwuP3=T*^!Igq*y9>Z5(L* zQjqW1%OkP2;@1=k&YZK-!IGx}*_1pei0cxj z#prw`Mgf-x(}Bl>&5zKcgB3qKunOe;+NMB`_GOT#+<^D_|Aaz5L@=>gcWg4lHb2MT z>Rt>m-gN23&tVoHbnHC$nMkSu9XXZ%N4hv!{E&g*wW!8LxqBwUmN-;Me%o6tsj4%08w;FMbwMd zbo2vPMT($?D{Q4cFR}F|hK#R4ZlOYb65HU|ytec{X(d20{O`K?m4=^czF2uz25DE} z_8Y^e3}iQPk!W%ux^*Gnvb>hksadavd-b=sQ~AvU6_si1H9O%QIDP^33>Zr^s1yaU zky4UuJQQwlB^W?H8zK(avOyWKxCD$m`=>4vI?Q?a128Jv>CKf(nfWpXH5JFC4*YVZ zpNU1xRBlS*1lZ=llD{Pqr}_QoDe14xEkLC7_b5Eq@^7vIE`I@_B2OAq3M!;`jAt$0 ze!RvsiOLRwHdBc6k<)I2_i)iWT$f!!c(C|2R+?xE3a`#VT!X%^(3I#Z)3v$gK0GV{ z_2>jn0~-gsg$^2zeY9E>1yX%PxY@^B?Tuj)lI0=e)`|Lj>fTS0uNO+9NVl&t`$8BL{JsP6E6ilQ{&!r~g7g;v`C#2-PNTG7FKc)C0FZ4Xlf@JN0 z=%VdOQBGih8)lfwkqB#9ade~C;(tc2&FwR8M_nhpbbV$X;NtUcYnHkn`&eZKW9CnO z@u!QGrLgW}`Qm7e9E$>+9eDn&3N(mVmIwg-vroS#NEjsFup;g{l!BPI+jEWTYXa2G zwz*?Do(h=n@`Aw!$Ve_y>E12{nf0`7z~THp?OpRucGPvr^icONbq=JIO}|)!2SptI z9f(&D`(PQCplG=)JqgRCj2FK7q{0b|=p@C?3Q?zChRAN}9M0+DAMNrkVgbWml7xGH z5OB^5>HfRR70hhb9)vMn)l11)z-9`)RC!GO%MbjU8mpQ-3x#r6iw$xq_XRZ=!D8A%Y+~eOoX3ga4q*a`5eS9gt z%%u2(15yRLUHj>kvuoR*0zlwq>qXZ(<%@bEA|D=f^`D@9MEpkzpHLLn*|}jSjSy3w ztiL?yJy+{b$P&)+*QgCM95?(8c*sF9Y`gjCeIQn)R@-NHJ$TV?zMvnhKBnW=-roRG z!!#!JW7kK;Jcz6ASuMV^`gmp${&a{43~Rs#rqH5*51}z81Nx2|DtPDU$pib2z5W9kmwuH2SN3vw zuIbNt&GIwIyBgv^LR#EB=Sp3phCdS|(Qj`5F}M>L2ljw`EFeXzV@R$P(HAL;BK1N) zM`yWhk7t=P;dUG9I|J?DbZK^LJBA>EIU#d!bkQj>Txa8`18jt?2SapnY zWsAqJrUdePzxBkTZ#}sv#bOPY38jw*CQ{cGvG7M(-^{&i_hVh4;dJx^%Xq7OzT9h| zvObc+!m3DoLh0=Qy#BK9W&^*xpz`?XqKA}5)OmCeuLpKRC?E&(god!g|ox zw`%Y%sz;jX_;TIw8Hl7+TJ!aXm(YFr)~=*7IW&y6D^ zVDyRV7lmqD(j8g?{cJLiPra47#0Y=Iq^+Hx;*Hh12B2g;f*s%ESJ8m?dtF&5g1EOtq{ebcMVt*1O(zJHK+@p~GSD

&+w6k)H<@QHEoaUq{=GsAq9fREXjrNK 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

Lj>&? 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 z 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 z%0A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�~{xKz9baqQzJ`0nO$#tB>bBNR&_O@46 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 Pqai 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 p z1+wDkss`xe`bu26T_@ugtB`)5gHpK=e$oetj{o+CIUV-*>&AXN5azi zrQ-69WiHV?-KAIf2IwNFY}{ufnZeRjb%9kkTWU{zaGc%S$2g#1o?&~8y%9#c{1=J< zGcjE~Q3Na#Q^&;sid;gcZ8c?IMFoH#^y~SNJdB|+iCKTBK&4!qX?K*zQ6rI zcA~U29$}uwK_P}K2$T+?wFSxza}-PMeyWOohQ@~|KvW#=sGU-&9Mi(#koHQW$S`Ro zMfG|hP74^-*$z~(T z)=c(CO`y&k1Mz!Hj@OR>-_xVMfT^`>O(JAo-$edkAd90q2II^s@pg{jE|bOhm&5RN zH?wg4CLyToj!=<*51N`8mG|#h6!P)n1PIhuKL#8!#nE&@^*$(`*bD;GL$TMCbdrY2 zwl9EHZ{mo(j3Kxx)43LAqX3;4W*T=v=)?10W!N3PiKZXtD7)C`+|lf32_-e?(d8%d zsiCanp5fiSn(b&JV4={N>Q6RK594{O_|Z^Dx{Xa4f@HGMqRKq^QuV4$Q;S^<)_pl9 zVQG??o<#wE-AmEMw&W;=c~?pf)V#Wn%wixutr~16IisE0f!i7aQ}f#(e8E_=28c$D z(~?@TVZAsW^B>=w60Az(%DJS6jxGn|gkS~QKf zRQYJ#<*JXl2f7Lh|BsGm0#Cek~}k_|gdZ zUU6Z%d(LK2rz7``OW&89RW5XA64`@*}j9?BGdN!SU*oSYbSRz#e<{t(={-gVV zU+3q5ci!iXQ;e~Tc4^j+<^H*c?XQ>C^ZnRgUr~z0yZwIdpS~CBj;&pbn#~W(cej`p(P7S{w4B=j220j{%<6tE zT70%7@%?!3`Ox%DB{UTk)Y~Z?whXJ)|0eEL{NH<7Z{(GLUi`+kak){My}B_0Ecmvp zP18XR4-mvx2=03B8?ye3(i%iRdBZD76W(El{5>q1w#cXUf%u1h96S(}3Ils+c|n|2 zC7Uk9>0c(~{X;7lh-3-B+V$5Bg;S+*Q;ijTn4;B{5w-fehq))4oWAhDuk{I z{eZUj3%{%&HlP)0oZnG>@vsh!m+Ci_R=h!O&MkOCna3J|)eWtXOYXX3934vwG#*Ze z>XYt-b(O)B?N5i>W^;S!JDV3g=65~%yZ7t6&tt{|Rp#?heN=^cPB)3Y?Hl>gRHr4% z==HaiwnB~f^8OU-G$hHr33kH~=^UPWuBKz^pt5%K9}ff|YYKqu~;5Qbgs%426dT^e}AN z7QECC;I;E$pwVYSDCd8@TcQ;D<} zdC>2D{8P_aA}3B$(npqmFcfSfS$}n0Z(qJUFZC5&7uaqUeP)Vq$^hAZDxpktv-uF7 zDklMqS1ex~Ofnp%^a>jqf`bo*&{;Rx<333;z-<}kFbyJ|Owv8V;pCo8oxS}y;3 zA?BN}CT$LmefjLUm1rHGj#1G{&%8Y*9r0>#I%uM9aVy+;3nJ;#r(=5r^LcaJ++1-K z_N}JycMK<2FKDJ2I=i z64<(urD_`%ren)oNTOJt#@Dvp$~mgLp!v#IslJ za|wHk6i8-S8I;+YdMpM&r<(dsr2XwBt8T$om

$&$S-U%45ZeZK+;0t!E>@qxSI! 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 literal 0 HcmV?d00001 diff --git a/x-pack/test/functional/es_archives/cases/migrations/8.8.0/mappings.json b/x-pack/test/functional/es_archives/cases/migrations/8.8.0/mappings.json new file mode 100644 index 0000000000000..cf252ec426fb5 --- /dev/null +++ b/x-pack/test/functional/es_archives/cases/migrations/8.8.0/mappings.json @@ -0,0 +1,3049 @@ +{ + "type": "index", + "value": { + "aliases": { + ".kibana": { + "is_hidden": true + }, + ".kibana_8.8.0": { + "is_hidden": true + } + }, + "index": ".kibana_1", + "mappings": { + "_meta": { + "migrationMappingPropertyHashes": { + "action": "0be88ebcc8560a075b6898236a202eb1", + "action_task_params": "3d1b76c39bfb2cc8296b024d73854724", + "alert": "96a5a144778243a9f4fece0e71c2197f", + "api_key_pending_invalidation": "16f515278a295f6245149ad7c5ddedb7", + "apm-indices": "3d1b76c39bfb2cc8296b024d73854724", + "apm-server-schema": "b1d71908f324c17bf744ac72af5038fb", + "apm-service-group": "2af509c6506f29a858e5a0950577d9fa", + "apm-telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "app_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "application_usage_daily": "43b8830d5d0df85a6823d290885fc9fd", + "application_usage_totals": "3d1b76c39bfb2cc8296b024d73854724", + "canvas-element": "7390014e1091044523666d97247392fc", + "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", + "canvas-workpad-template": "ae2673f678281e2c055d764b153e9715", + "cases": "ee88bba6d1a7ae7157452b72e598b85b", + "cases-comments": "93535d41ca0279a4a2e5d08acd3f28e3", + "cases-configure": "c124bd0be4c139d0f0f91fb9eeca8e37", + "cases-connector-mappings": "a98c33813f364f0b068e8c592ac6ef6d", + "cases-telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "cases-user-actions": "07a6651cf37853dd5d64bfb2c796e102", + "config": "c63748b75f39d0c54de12d12c1ccbc20", + "config-global": "c63748b75f39d0c54de12d12c1ccbc20", + "connector_token": "740b3fd18387d4097dca8d177e6a35c6", + "core-usage-stats": "3d1b76c39bfb2cc8296b024d73854724", + "coreMigrationVersion": "2f4316de49999235636386fe51dc06c1", + "created_at": "00da57df13e94e9d98437d13ace4bfe0", + "csp-rule-template": "8b03466c92eddc1458758ca6827eb540", + "dashboard": "bc881edf161013a17165a244e0812e9f", + "endpoint:user-artifact": "4a11183eee21e6fbad864f7a30b39ad0", + "endpoint:user-artifact-manifest": "a0d7b04ad405eed54d76e279c3727862", + "enterprise_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "epm-packages": "c6404322e681df9db37c9359c9d5a6cd", + "epm-packages-assets": "44621b2f6052ef966da47b7c3a00f33b", + "event_loop_delays_daily": "5df7e292ddd5028e07c1482e130e6654", + "exception-list": "8a1defe5981db16792cb9a772e84bb9a", + "exception-list-agnostic": "8a1defe5981db16792cb9a772e84bb9a", + "file": "c38faa34f21af9cef4301a687de5dce0", + "file-upload-usage-collection-telemetry": "a34fbb8e3263d105044869264860c697", + "fileShare": "aa8f7ac2ddf8ab1a91bd34e347046caa", + "fleet-fleet-server-host": "434168167d1a1fd82fe92584ab9845c2", + "fleet-message-signing-keys": "3d1b76c39bfb2cc8296b024d73854724", + "fleet-preconfiguration-deletion-record": "4c36f199189a367e43541f236141204c", + "fleet-proxy": "05b7a22977de25ce67a77e44dd8e6c33", + "graph-workspace": "27a94b2edcb0610c6aea54a7c56d7752", + "guided-onboarding-guide-state": "a3db59c45a3fd2730816d4f53c35c7d9", + "guided-onboarding-plugin-state": "3d1b76c39bfb2cc8296b024d73854724", + "index-pattern": "83c02d842fe2a94d14dfa13f7dcd6e87", + "infrastructure-monitoring-log-view": "c50526fc6040c5355ed027d34d05b35c", + "infrastructure-ui-source": "3d1b76c39bfb2cc8296b024d73854724", + "ingest-agent-policies": "ea5e6042413da5798f7f2bae9ae0aa7e", + "ingest-download-sources": "5d602f6594f3b589dc9a8e3a44031522", + "ingest-outputs": "e2347e19e5c2a7448529d77abbf27f30", + "ingest-package-policies": "20e952d74d979ef91a4918dde7617eb3", + "ingest_manager_settings": "a9f356eec1c40d030f433bbf47c2b479", + "inventory-view": "3d1b76c39bfb2cc8296b024d73854724", + "kql-telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "legacy-url-alias": "0750774cf16475f88f2361e99cc5c8f0", + "lens": "b0da10d5ab9ebd81d61700737ddc76c9", + "lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327", + "maintenance-window": "a58ac2ef53ff5103710093e669dcc1d8", + "map": "9134b47593116d7953f6adba096fc463", + "metrics-explorer-view": "3d1b76c39bfb2cc8296b024d73854724", + "ml-job": "3bb64c31915acf93fc724af137a0891b", + "ml-module": "46ef4f0d6682636f0fff9799d6a2d7ac", + "ml-trained-model": "d2f03c1a5dd038fa58af14a56944312b", + "monitoring-telemetry": "2669d5ec15e82391cf58df4294ee9c68", + "namespace": "2f4316de49999235636386fe51dc06c1", + "namespaces": "2f4316de49999235636386fe51dc06c1", + "originId": "2f4316de49999235636386fe51dc06c1", + "osquery-manager-usage-metric": "4dc4f647d27247c002f56f22742175fe", + "osquery-pack": "ac387f64e316364439c98079ae519a7a", + "osquery-pack-asset": "5e1c0d70ab14c3c801b6e9a2d390ad7c", + "osquery-saved-query": "e1f9820518bd51cacb73124ad6552adc", + "query": "df07b1a361c32daf4e6842c1d5521dbe", + "references": "7997cf5a56cc02bdc9c93361bde732b0", + "rules-settings": "001f60645e96c71520214b57f3ea7590", + "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", + "search": "df07b1a361c32daf4e6842c1d5521dbe", + "search-session": "fea3612a90b81672991617646f229a61", + "search-telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "security-rule": "9d9d11b97e3aaa87fbaefbace2b5c25f", + "security-solution-signals-migration": "72761fd374ca11122ac8025a92b84fca", + "siem-detection-engine-rule-actions": "f5c218f837bac10ab2c3980555176cf9", + "siem-ui-timeline": "0f4cc81427182c41cebd7d9c640ec253", + "siem-ui-timeline-note": "28393dfdeb4e4413393eb5f7ec8c5436", + "siem-ui-timeline-pinned-event": "293fce142548281599060e07ad2c9ddb", + "slo": "71d78aec1e4a92e097d427141199506a", + "space": "aed174cfc1cecf5740ff4a872a5c9a7b", + "spaces-usage-stats": "3d1b76c39bfb2cc8296b024d73854724", + "synthetics-monitor": "b57abe4845799dc48f948c2b3ff19818", + "synthetics-param": "3d1b76c39bfb2cc8296b024d73854724", + "synthetics-privates-locations": "3d1b76c39bfb2cc8296b024d73854724", + "tag": "83d55da58f6530f7055415717ec06474", + "telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "type": "2f4316de49999235636386fe51dc06c1", + "typeMigrationVersion": "539e3ecebb3abc1133618094cc3b7ae7", + "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", + "updated_at": "00da57df13e94e9d98437d13ace4bfe0", + "upgrade-assistant-ml-upgrade-operation": "3caf305ad2da94d80d49453b0970156d", + "upgrade-assistant-reindex-operation": "6d1e2aca91767634e1829c30f20f6b16", + "uptime-dynamic-settings": "3d1b76c39bfb2cc8296b024d73854724", + "uptime-synthetics-api-key": "c3178f0fde61e18d3530ba9a70bc278a", + "url": "a37dbae7645ad5811045f4dd3dc1c0a8", + "usage-counters": "8cc260bdceffec4ffc3ad165c97dc1b4", + "visualization": "4891c012863513388881fc109fec4809", + "workplace_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724" + } + }, + "dynamic": "strict", + "properties": { + "action": { + "dynamic": "false", + "properties": { + "actionTypeId": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "action_task_params": { + "dynamic": "false", + "type": "object" + }, + "alert": { + "dynamic": "false", + "properties": { + "actions": { + "dynamic": "false", + "properties": { + "actionRef": { + "type": "keyword" + }, + "actionTypeId": { + "type": "keyword" + }, + "group": { + "type": "keyword" + } + }, + "type": "nested" + }, + "alertTypeId": { + "type": "keyword" + }, + "consumer": { + "type": "keyword" + }, + "createdAt": { + "type": "date" + }, + "createdBy": { + "type": "keyword" + }, + "enabled": { + "type": "boolean" + }, + "executionStatus": { + "properties": { + "error": { + "properties": { + "message": { + "type": "keyword" + }, + "reason": { + "type": "keyword" + } + } + }, + "lastDuration": { + "type": "long" + }, + "lastExecutionDate": { + "type": "date" + }, + "numberOfTriggeredActions": { + "type": "long" + }, + "status": { + "type": "keyword" + }, + "warning": { + "properties": { + "message": { + "type": "keyword" + }, + "reason": { + "type": "keyword" + } + } + } + } + }, + "lastRun": { + "properties": { + "alertsCount": { + "properties": { + "active": { + "type": "float" + }, + "ignored": { + "type": "float" + }, + "new": { + "type": "float" + }, + "recovered": { + "type": "float" + } + } + }, + "outcome": { + "type": "keyword" + }, + "outcomeOrder": { + "type": "float" + } + } + }, + "legacyId": { + "type": "keyword" + }, + "mapped_params": { + "properties": { + "risk_score": { + "type": "float" + }, + "severity": { + "type": "keyword" + } + } + }, + "monitoring": { + "properties": { + "run": { + "properties": { + "calculated_metrics": { + "properties": { + "p50": { + "type": "long" + }, + "p95": { + "type": "long" + }, + "p99": { + "type": "long" + }, + "success_ratio": { + "type": "float" + } + } + }, + "last_run": { + "properties": { + "metrics": { + "properties": { + "duration": { + "type": "long" + }, + "gap_duration_s": { + "type": "float" + }, + "total_alerts_created": { + "type": "float" + }, + "total_alerts_detected": { + "type": "float" + }, + "total_indexing_duration_ms": { + "type": "long" + }, + "total_search_duration_ms": { + "type": "long" + } + } + }, + "timestamp": { + "type": "date" + } + } + } + } + } + } + }, + "muteAll": { + "type": "boolean" + }, + "mutedInstanceIds": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "normalizer": "lowercase", + "type": "keyword" + } + }, + "type": "text" + }, + "notifyWhen": { + "type": "keyword" + }, + "params": { + "ignore_above": 4096, + "type": "flattened" + }, + "revision": { + "type": "long" + }, + "running": { + "type": "boolean" + }, + "schedule": { + "properties": { + "interval": { + "type": "keyword" + } + } + }, + "scheduledTaskId": { + "type": "keyword" + }, + "snoozeSchedule": { + "properties": { + "duration": { + "type": "long" + }, + "id": { + "type": "keyword" + }, + "skipRecurrences": { + "format": "strict_date_time", + "type": "date" + } + }, + "type": "nested" + }, + "tags": { + "type": "keyword" + }, + "throttle": { + "type": "keyword" + }, + "updatedAt": { + "type": "date" + }, + "updatedBy": { + "type": "keyword" + } + } + }, + "api_key_pending_invalidation": { + "properties": { + "apiKeyId": { + "type": "keyword" + }, + "createdAt": { + "type": "date" + } + } + }, + "apm-indices": { + "dynamic": "false", + "type": "object" + }, + "apm-server-schema": { + "properties": { + "schemaJson": { + "index": false, + "type": "text" + } + } + }, + "apm-service-group": { + "properties": { + "color": { + "type": "text" + }, + "description": { + "type": "text" + }, + "groupName": { + "type": "keyword" + }, + "kuery": { + "type": "text" + } + } + }, + "apm-telemetry": { + "dynamic": "false", + "type": "object" + }, + "app_search_telemetry": { + "dynamic": "false", + "type": "object" + }, + "application_usage_daily": { + "dynamic": "false", + "properties": { + "timestamp": { + "type": "date" + } + } + }, + "application_usage_totals": { + "dynamic": "false", + "type": "object" + }, + "canvas-element": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "content": { + "type": "text" + }, + "help": { + "type": "text" + }, + "image": { + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "canvas-workpad": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "canvas-workpad-template": { + "dynamic": "false", + "properties": { + "help": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "tags": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "template_key": { + "type": "keyword" + } + } + }, + "cases": { + "dynamic": "false", + "properties": { + "assignees": { + "properties": { + "uid": { + "type": "keyword" + } + } + }, + "closed_at": { + "type": "date" + }, + "closed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "profile_uid": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "connector": { + "properties": { + "fields": { + "properties": { + "key": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "name": { + "type": "text" + }, + "type": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "profile_uid": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "description": { + "type": "text" + }, + "duration": { + "type": "unsigned_long" + }, + "external_service": { + "properties": { + "connector_name": { + "type": "keyword" + }, + "external_id": { + "type": "keyword" + }, + "external_title": { + "type": "text" + }, + "external_url": { + "type": "text" + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "profile_uid": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "owner": { + "type": "keyword" + }, + "settings": { + "properties": { + "syncAlerts": { + "type": "boolean" + } + } + }, + "severity": { + "type": "short" + }, + "status": { + "type": "short" + }, + "tags": { + "type": "keyword" + }, + "title": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "total_alerts": { + "type": "integer" + }, + "total_comments": { + "type": "integer" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "profile_uid": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-comments": { + "dynamic": "false", + "properties": { + "actions": { + "properties": { + "type": { + "type": "keyword" + } + } + }, + "alertId": { + "type": "keyword" + }, + "comment": { + "type": "text" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "username": { + "type": "keyword" + } + } + }, + "externalReferenceAttachmentTypeId": { + "type": "keyword" + }, + "owner": { + "type": "keyword" + }, + "persistableStateAttachmentTypeId": { + "type": "keyword" + }, + "pushed_at": { + "type": "date" + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + } + } + }, + "cases-configure": { + "dynamic": "false", + "properties": { + "closure_type": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "owner": { + "type": "keyword" + } + } + }, + "cases-connector-mappings": { + "dynamic": "false", + "properties": { + "owner": { + "type": "keyword" + } + } + }, + "cases-telemetry": { + "dynamic": "false", + "type": "object" + }, + "cases-user-actions": { + "dynamic": "false", + "properties": { + "action": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "username": { + "type": "keyword" + } + } + }, + "owner": { + "type": "keyword" + }, + "payload": { + "dynamic": "false", + "properties": { + "assignees": { + "properties": { + "uid": { + "type": "keyword" + } + } + }, + "comment": { + "properties": { + "externalReferenceAttachmentTypeId": { + "type": "keyword" + }, + "persistableStateAttachmentTypeId": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "connector": { + "properties": { + "type": { + "type": "keyword" + } + } + } + } + }, + "type": { + "type": "keyword" + } + } + }, + "config": { + "dynamic": "false", + "properties": { + "buildNum": { + "type": "keyword" + } + } + }, + "config-global": { + "dynamic": "false", + "properties": { + "buildNum": { + "type": "keyword" + } + } + }, + "connector_token": { + "dynamic": "false", + "properties": { + "connectorId": { + "type": "keyword" + }, + "tokenType": { + "type": "keyword" + } + } + }, + "core-usage-stats": { + "dynamic": "false", + "type": "object" + }, + "coreMigrationVersion": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "csp-rule-template": { + "dynamic": "false", + "properties": { + "metadata": { + "properties": { + "benchmark": { + "properties": { + "id": { + "type": "keyword" + } + } + }, + "name": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + } + } + }, + "dashboard": { + "properties": { + "controlGroupInput": { + "properties": { + "chainingSystem": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "controlStyle": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "ignoreParentSettingsJSON": { + "index": false, + "type": "text" + }, + "panelsJSON": { + "index": false, + "type": "text" + } + } + }, + "description": { + "type": "text" + }, + "hits": { + "doc_values": false, + "index": false, + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "index": false, + "type": "text" + } + } + }, + "optionsJSON": { + "index": false, + "type": "text" + }, + "panelsJSON": { + "index": false, + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "pause": { + "doc_values": false, + "index": false, + "type": "boolean" + }, + "section": { + "doc_values": false, + "index": false, + "type": "integer" + }, + "value": { + "doc_values": false, + "index": false, + "type": "integer" + } + } + }, + "timeFrom": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "timeRestore": { + "doc_values": false, + "index": false, + "type": "boolean" + }, + "timeTo": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "endpoint:user-artifact": { + "properties": { + "body": { + "type": "binary" + }, + "compressionAlgorithm": { + "index": false, + "type": "keyword" + }, + "created": { + "index": false, + "type": "date" + }, + "decodedSha256": { + "index": false, + "type": "keyword" + }, + "decodedSize": { + "index": false, + "type": "long" + }, + "encodedSha256": { + "type": "keyword" + }, + "encodedSize": { + "index": false, + "type": "long" + }, + "encryptionAlgorithm": { + "index": false, + "type": "keyword" + }, + "identifier": { + "type": "keyword" + } + } + }, + "endpoint:user-artifact-manifest": { + "properties": { + "artifacts": { + "properties": { + "artifactId": { + "index": false, + "type": "keyword" + }, + "policyId": { + "index": false, + "type": "keyword" + } + }, + "type": "nested" + }, + "created": { + "index": false, + "type": "date" + }, + "schemaVersion": { + "type": "keyword" + }, + "semanticVersion": { + "index": false, + "type": "keyword" + } + } + }, + "enterprise_search_telemetry": { + "dynamic": "false", + "type": "object" + }, + "epm-packages": { + "properties": { + "es_index_patterns": { + "dynamic": "false", + "type": "object" + }, + "experimental_data_stream_features": { + "properties": { + "data_stream": { + "type": "keyword" + }, + "features": { + "dynamic": "false", + "properties": { + "synthetic_source": { + "type": "boolean" + }, + "tsdb": { + "type": "boolean" + } + }, + "type": "nested" + } + }, + "type": "nested" + }, + "install_format_schema_version": { + "type": "version" + }, + "install_source": { + "type": "keyword" + }, + "install_started_at": { + "type": "date" + }, + "install_status": { + "type": "keyword" + }, + "install_version": { + "type": "keyword" + }, + "installed_es": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + }, + "type": "nested" + }, + "installed_kibana": { + "dynamic": "false", + "type": "object" + }, + "installed_kibana_space_id": { + "type": "keyword" + }, + "internal": { + "type": "boolean" + }, + "keep_policies_up_to_date": { + "index": false, + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "package_assets": { + "dynamic": "false", + "type": "object" + }, + "verification_key_id": { + "type": "keyword" + }, + "verification_status": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "epm-packages-assets": { + "properties": { + "asset_path": { + "type": "keyword" + }, + "data_base64": { + "type": "binary" + }, + "data_utf8": { + "index": false, + "type": "text" + }, + "install_source": { + "type": "keyword" + }, + "media_type": { + "type": "keyword" + }, + "package_name": { + "type": "keyword" + }, + "package_version": { + "type": "keyword" + } + } + }, + "event_loop_delays_daily": { + "dynamic": "false", + "properties": { + "lastUpdatedAt": { + "type": "date" + } + } + }, + "exception-list": { + "properties": { + "_tags": { + "type": "keyword" + }, + "comments": { + "properties": { + "comment": { + "type": "keyword" + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "updated_at": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "entries": { + "properties": { + "entries": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "field": { + "type": "keyword" + }, + "list": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "expire_time": { + "type": "date" + }, + "immutable": { + "type": "boolean" + }, + "item_id": { + "type": "keyword" + }, + "list_id": { + "type": "keyword" + }, + "list_type": { + "type": "keyword" + }, + "meta": { + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + }, + "os_types": { + "type": "keyword" + }, + "tags": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + }, + "tie_breaker_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "exception-list-agnostic": { + "properties": { + "_tags": { + "type": "keyword" + }, + "comments": { + "properties": { + "comment": { + "type": "keyword" + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "updated_at": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "entries": { + "properties": { + "entries": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "field": { + "type": "keyword" + }, + "list": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "expire_time": { + "type": "date" + }, + "immutable": { + "type": "boolean" + }, + "item_id": { + "type": "keyword" + }, + "list_id": { + "type": "keyword" + }, + "list_type": { + "type": "keyword" + }, + "meta": { + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + }, + "os_types": { + "type": "keyword" + }, + "tags": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + }, + "tie_breaker_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "file": { + "dynamic": "false", + "properties": { + "FileKind": { + "type": "keyword" + }, + "Meta": { + "type": "flattened" + }, + "Status": { + "type": "keyword" + }, + "Updated": { + "type": "date" + }, + "created": { + "type": "date" + }, + "extension": { + "type": "keyword" + }, + "mime_type": { + "type": "keyword" + }, + "name": { + "type": "text" + }, + "size": { + "type": "long" + }, + "user": { + "type": "flattened" + } + } + }, + "file-upload-usage-collection-telemetry": { + "properties": { + "file_upload": { + "properties": { + "index_creation_count": { + "type": "long" + } + } + } + } + }, + "fileShare": { + "dynamic": "false", + "properties": { + "created": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "token": { + "type": "keyword" + }, + "valid_until": { + "type": "long" + } + } + }, + "fleet-fleet-server-host": { + "properties": { + "host_urls": { + "index": false, + "type": "keyword" + }, + "is_default": { + "type": "boolean" + }, + "is_preconfigured": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "proxy_id": { + "type": "keyword" + } + } + }, + "fleet-message-signing-keys": { + "dynamic": "false", + "type": "object" + }, + "fleet-preconfiguration-deletion-record": { + "properties": { + "id": { + "type": "keyword" + } + } + }, + "fleet-proxy": { + "properties": { + "certificate": { + "index": false, + "type": "keyword" + }, + "certificate_authorities": { + "index": false, + "type": "keyword" + }, + "certificate_key": { + "index": false, + "type": "keyword" + }, + "is_preconfigured": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "proxy_headers": { + "index": false, + "type": "text" + }, + "url": { + "index": false, + "type": "keyword" + } + } + }, + "graph-workspace": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "legacyIndexPatternRef": { + "index": false, + "type": "text" + }, + "numLinks": { + "type": "integer" + }, + "numVertices": { + "type": "integer" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "wsState": { + "type": "text" + } + } + }, + "guided-onboarding-guide-state": { + "dynamic": "false", + "properties": { + "guideId": { + "type": "keyword" + }, + "isActive": { + "type": "boolean" + } + } + }, + "guided-onboarding-plugin-state": { + "dynamic": "false", + "type": "object" + }, + "index-pattern": { + "dynamic": "false", + "properties": { + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "title": { + "type": "text" + }, + "type": { + "type": "keyword" + } + } + }, + "infrastructure-monitoring-log-view": { + "dynamic": "false", + "properties": { + "name": { + "type": "text" + } + } + }, + "infrastructure-ui-source": { + "dynamic": "false", + "type": "object" + }, + "ingest-agent-policies": { + "properties": { + "agent_features": { + "properties": { + "enabled": { + "type": "boolean" + }, + "name": { + "type": "keyword" + } + } + }, + "data_output_id": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "download_source_id": { + "type": "keyword" + }, + "fleet_server_host_id": { + "type": "keyword" + }, + "inactivity_timeout": { + "type": "integer" + }, + "is_default": { + "type": "boolean" + }, + "is_default_fleet_server": { + "type": "boolean" + }, + "is_managed": { + "type": "boolean" + }, + "is_preconfigured": { + "type": "keyword" + }, + "monitoring_enabled": { + "index": false, + "type": "keyword" + }, + "monitoring_output_id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "revision": { + "type": "integer" + }, + "schema_version": { + "type": "version" + }, + "status": { + "type": "keyword" + }, + "unenroll_timeout": { + "type": "integer" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "ingest-download-sources": { + "properties": { + "host": { + "type": "keyword" + }, + "is_default": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "source_id": { + "index": false, + "type": "keyword" + } + } + }, + "ingest-outputs": { + "properties": { + "ca_sha256": { + "index": false, + "type": "keyword" + }, + "ca_trusted_fingerprint": { + "index": false, + "type": "keyword" + }, + "config": { + "type": "flattened" + }, + "config_yaml": { + "type": "text" + }, + "hosts": { + "type": "keyword" + }, + "is_default": { + "type": "boolean" + }, + "is_default_monitoring": { + "type": "boolean" + }, + "is_preconfigured": { + "index": false, + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "output_id": { + "index": false, + "type": "keyword" + }, + "proxy_id": { + "type": "keyword" + }, + "shipper": { + "dynamic": "false", + "type": "object" + }, + "ssl": { + "type": "binary" + }, + "type": { + "type": "keyword" + } + } + }, + "ingest-package-policies": { + "properties": { + "created_at": { + "type": "date" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "elasticsearch": { + "dynamic": "false", + "type": "object" + }, + "enabled": { + "type": "boolean" + }, + "inputs": { + "dynamic": "false", + "type": "object" + }, + "is_managed": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "package": { + "properties": { + "name": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "policy_id": { + "type": "keyword" + }, + "revision": { + "type": "integer" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + }, + "vars": { + "type": "flattened" + } + } + }, + "ingest_manager_settings": { + "properties": { + "fleet_server_hosts": { + "type": "keyword" + }, + "has_seen_add_data_notice": { + "index": false, + "type": "boolean" + }, + "prerelease_integrations_enabled": { + "type": "boolean" + } + } + }, + "inventory-view": { + "dynamic": "false", + "type": "object" + }, + "kql-telemetry": { + "dynamic": "false", + "type": "object" + }, + "legacy-url-alias": { + "dynamic": "false", + "properties": { + "disabled": { + "type": "boolean" + }, + "resolveCounter": { + "type": "long" + }, + "sourceId": { + "type": "keyword" + }, + "targetId": { + "type": "keyword" + }, + "targetNamespace": { + "type": "keyword" + }, + "targetType": { + "type": "keyword" + } + } + }, + "lens": { + "properties": { + "description": { + "type": "text" + }, + "state": { + "dynamic": "false", + "type": "object" + }, + "title": { + "type": "text" + }, + "visualizationType": { + "type": "keyword" + } + } + }, + "lens-ui-telemetry": { + "properties": { + "count": { + "type": "integer" + }, + "date": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "maintenance-window": { + "dynamic": "false", + "properties": { + "enabled": { + "type": "boolean" + }, + "events": { + "format": "epoch_millis||strict_date_optional_time", + "type": "date_range" + } + } + }, + "map": { + "properties": { + "bounds": { + "dynamic": "false", + "type": "object" + }, + "description": { + "type": "text" + }, + "layerListJSON": { + "type": "text" + }, + "mapStateJSON": { + "type": "text" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "metrics-explorer-view": { + "dynamic": "false", + "type": "object" + }, + "ml-job": { + "properties": { + "datafeed_id": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "job_id": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "type": { + "type": "keyword" + } + } + }, + "ml-module": { + "dynamic": "false", + "properties": { + "datafeeds": { + "type": "object" + }, + "defaultIndexPattern": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "description": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "id": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "jobs": { + "type": "object" + }, + "logo": { + "type": "object" + }, + "query": { + "type": "object" + }, + "title": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "type": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "ml-trained-model": { + "properties": { + "job": { + "properties": { + "create_time": { + "type": "date" + }, + "job_id": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "model_id": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "monitoring-telemetry": { + "properties": { + "reportedClusterUuids": { + "type": "keyword" + } + } + }, + "namespace": { + "type": "keyword" + }, + "namespaces": { + "type": "keyword" + }, + "originId": { + "type": "keyword" + }, + "osquery-manager-usage-metric": { + "properties": { + "count": { + "type": "long" + }, + "errors": { + "type": "long" + } + } + }, + "osquery-pack": { + "properties": { + "created_at": { + "type": "date" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "enabled": { + "type": "boolean" + }, + "name": { + "type": "text" + }, + "queries": { + "dynamic": "false", + "properties": { + "ecs_mapping": { + "dynamic": "false", + "type": "object" + }, + "id": { + "type": "keyword" + }, + "interval": { + "type": "text" + }, + "platform": { + "type": "keyword" + }, + "query": { + "type": "text" + }, + "version": { + "type": "keyword" + } + } + }, + "shards": { + "dynamic": "false", + "type": "object" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + }, + "version": { + "type": "long" + } + } + }, + "osquery-pack-asset": { + "dynamic": "false", + "properties": { + "description": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queries": { + "dynamic": "false", + "properties": { + "ecs_mapping": { + "dynamic": "false", + "type": "object" + }, + "id": { + "type": "keyword" + }, + "interval": { + "type": "text" + }, + "platform": { + "type": "keyword" + }, + "query": { + "type": "text" + }, + "version": { + "type": "keyword" + } + } + }, + "shards": { + "dynamic": "false", + "type": "object" + }, + "version": { + "type": "long" + } + } + }, + "osquery-saved-query": { + "dynamic": "false", + "properties": { + "created_at": { + "type": "date" + }, + "created_by": { + "type": "text" + }, + "description": { + "type": "text" + }, + "ecs_mapping": { + "dynamic": "false", + "type": "object" + }, + "id": { + "type": "keyword" + }, + "interval": { + "type": "keyword" + }, + "platform": { + "type": "keyword" + }, + "query": { + "type": "text" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "text" + }, + "version": { + "type": "keyword" + } + } + }, + "query": { + "dynamic": "false", + "properties": { + "description": { + "type": "text" + }, + "title": { + "type": "text" + } + } + }, + "references": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "rules-settings": { + "dynamic": "false", + "properties": { + "flapping": { + "type": "object" + } + } + }, + "sample-data-telemetry": { + "properties": { + "installCount": { + "type": "long" + }, + "unInstallCount": { + "type": "long" + } + } + }, + "search": { + "dynamic": "false", + "properties": { + "description": { + "type": "text" + }, + "title": { + "type": "text" + } + } + }, + "search-session": { + "dynamic": "false", + "properties": { + "created": { + "type": "date" + }, + "realmName": { + "type": "keyword" + }, + "realmType": { + "type": "keyword" + }, + "sessionId": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "search-telemetry": { + "dynamic": "false", + "type": "object" + }, + "security-rule": { + "dynamic": "false", + "properties": { + "rule_id": { + "type": "keyword" + }, + "version": { + "type": "long" + } + } + }, + "security-solution-signals-migration": { + "properties": { + "created": { + "index": false, + "type": "date" + }, + "createdBy": { + "index": false, + "type": "text" + }, + "destinationIndex": { + "index": false, + "type": "keyword" + }, + "error": { + "index": false, + "type": "text" + }, + "sourceIndex": { + "type": "keyword" + }, + "status": { + "index": false, + "type": "keyword" + }, + "taskId": { + "index": false, + "type": "keyword" + }, + "updated": { + "index": false, + "type": "date" + }, + "updatedBy": { + "index": false, + "type": "text" + }, + "version": { + "type": "long" + } + } + }, + "siem-detection-engine-rule-actions": { + "properties": { + "actions": { + "properties": { + "actionRef": { + "type": "keyword" + }, + "action_type_id": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "params": { + "dynamic": "false", + "type": "object" + } + } + }, + "alertThrottle": { + "type": "keyword" + }, + "ruleAlertId": { + "type": "keyword" + }, + "ruleThrottle": { + "type": "keyword" + } + } + }, + "siem-ui-timeline": { + "properties": { + "columns": { + "properties": { + "aggregatable": { + "type": "boolean" + }, + "category": { + "type": "keyword" + }, + "columnHeaderType": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "example": { + "type": "text" + }, + "id": { + "type": "keyword" + }, + "indexes": { + "type": "keyword" + }, + "name": { + "type": "text" + }, + "placeholder": { + "type": "text" + }, + "searchable": { + "type": "boolean" + }, + "type": { + "type": "keyword" + } + } + }, + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "dataProviders": { + "properties": { + "and": { + "properties": { + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "type": { + "type": "text" + } + } + }, + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "type": { + "type": "text" + } + } + }, + "dateRange": { + "properties": { + "end": { + "type": "date" + }, + "start": { + "type": "date" + } + } + }, + "description": { + "type": "text" + }, + "eqlOptions": { + "properties": { + "eventCategoryField": { + "type": "text" + }, + "query": { + "type": "text" + }, + "size": { + "type": "text" + }, + "tiebreakerField": { + "type": "text" + }, + "timestampField": { + "type": "text" + } + } + }, + "eventType": { + "type": "keyword" + }, + "excludedRowRendererIds": { + "type": "text" + }, + "favorite": { + "properties": { + "favoriteDate": { + "type": "date" + }, + "fullName": { + "type": "text" + }, + "keySearch": { + "type": "text" + }, + "userName": { + "type": "text" + } + } + }, + "filters": { + "properties": { + "exists": { + "type": "text" + }, + "match_all": { + "type": "text" + }, + "meta": { + "properties": { + "alias": { + "type": "text" + }, + "controlledBy": { + "type": "text" + }, + "disabled": { + "type": "boolean" + }, + "field": { + "type": "text" + }, + "formattedValue": { + "type": "text" + }, + "index": { + "type": "keyword" + }, + "key": { + "type": "keyword" + }, + "negate": { + "type": "boolean" + }, + "params": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "value": { + "type": "text" + } + } + }, + "missing": { + "type": "text" + }, + "query": { + "type": "text" + }, + "range": { + "type": "text" + }, + "script": { + "type": "text" + } + } + }, + "indexNames": { + "type": "text" + }, + "kqlMode": { + "type": "keyword" + }, + "kqlQuery": { + "properties": { + "filterQuery": { + "properties": { + "kuery": { + "properties": { + "expression": { + "type": "text" + }, + "kind": { + "type": "keyword" + } + } + }, + "serializedQuery": { + "type": "text" + } + } + } + } + }, + "sort": { + "dynamic": "false", + "properties": { + "columnId": { + "type": "keyword" + }, + "columnType": { + "type": "keyword" + }, + "sortDirection": { + "type": "keyword" + } + } + }, + "status": { + "type": "keyword" + }, + "templateTimelineId": { + "type": "text" + }, + "templateTimelineVersion": { + "type": "integer" + }, + "timelineType": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-note": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "note": { + "type": "text" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-pinned-event": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "slo": { + "dynamic": "false", + "properties": { + "budgetingMethod": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "enabled": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "indicator": { + "properties": { + "params": { + "type": "flattened" + }, + "type": { + "type": "keyword" + } + } + }, + "name": { + "type": "text" + }, + "tags": { + "type": "keyword" + } + } + }, + "space": { + "dynamic": "false", + "properties": { + "_reserved": { + "type": "boolean" + }, + "color": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "disabledFeatures": { + "type": "keyword" + }, + "initials": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "spaces-usage-stats": { + "dynamic": "false", + "type": "object" + }, + "synthetics-monitor": { + "dynamic": "false", + "properties": { + "alert": { + "properties": { + "status": { + "properties": { + "enabled": { + "type": "boolean" + } + } + } + } + }, + "custom_heartbeat_id": { + "type": "keyword" + }, + "enabled": { + "type": "boolean" + }, + "hash": { + "type": "keyword" + }, + "hosts": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "id": { + "type": "keyword" + }, + "journey_id": { + "type": "keyword" + }, + "locations": { + "properties": { + "id": { + "fields": { + "text": { + "type": "text" + } + }, + "ignore_above": 256, + "type": "keyword" + }, + "label": { + "type": "text" + } + } + }, + "name": { + "fields": { + "keyword": { + "ignore_above": 256, + "normalizer": "lowercase", + "type": "keyword" + } + }, + "type": "text" + }, + "origin": { + "type": "keyword" + }, + "project_id": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + }, + "schedule": { + "properties": { + "number": { + "type": "integer" + } + } + }, + "tags": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + }, + "type": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "urls": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "synthetics-param": { + "dynamic": "false", + "type": "object" + }, + "synthetics-privates-locations": { + "dynamic": "false", + "type": "object" + }, + "tag": { + "properties": { + "color": { + "type": "text" + }, + "description": { + "type": "text" + }, + "name": { + "type": "text" + } + } + }, + "telemetry": { + "dynamic": "false", + "type": "object" + }, + "type": { + "type": "keyword" + }, + "typeMigrationVersion": { + "type": "version" + }, + "ui-metric": { + "properties": { + "count": { + "type": "integer" + } + } + }, + "updated_at": { + "type": "date" + }, + "upgrade-assistant-ml-upgrade-operation": { + "dynamic": "false", + "properties": { + "snapshotId": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "upgrade-assistant-reindex-operation": { + "dynamic": "false", + "properties": { + "indexName": { + "type": "keyword" + }, + "status": { + "type": "integer" + } + } + }, + "uptime-dynamic-settings": { + "dynamic": "false", + "type": "object" + }, + "uptime-synthetics-api-key": { + "dynamic": "false", + "properties": { + "apiKey": { + "type": "binary" + } + } + }, + "url": { + "dynamic": "false", + "properties": { + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "slug": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "usage-counters": { + "dynamic": "false", + "properties": { + "domainId": { + "type": "keyword" + } + } + }, + "visualization": { + "dynamic": "false", + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "type": "object" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "workplace_search_telemetry": { + "dynamic": "false", + "type": "object" + } + } + }, + "settings": { + "index": { + "auto_expand_replicas": "0-1", + "hidden": "true", + "mapping": { + "total_fields": { + "limit": "1500" + } + }, + "number_of_replicas": "0", + "number_of_shards": "1", + "priority": "10", + "refresh_interval": "1s" + } + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/pre_calculated_histogram/data.json b/x-pack/test/functional/es_archives/pre_calculated_histogram/data.json index 121a4036aaacd..cf696a0691c4b 100644 --- a/x-pack/test/functional/es_archives/pre_calculated_histogram/data.json +++ b/x-pack/test/functional/es_archives/pre_calculated_histogram/data.json @@ -2,7 +2,7 @@ "type": "doc", "value": { "id": "index-pattern:histogram-test", - "index": ".kibana", + "index": ".kibana_analytics", "source": { "index-pattern": { "title": "histogram-test" diff --git a/x-pack/test/functional/es_archives/security_solution/legacy_actions/data.json b/x-pack/test/functional/es_archives/security_solution/legacy_actions/data.json index 8dbc0fbe883e8..1663b2e9c6bdf 100644 --- a/x-pack/test/functional/es_archives/security_solution/legacy_actions/data.json +++ b/x-pack/test/functional/es_archives/security_solution/legacy_actions/data.json @@ -1,7 +1,7 @@ { "type": "doc", "value": { - "index" : ".kibana", + "index" : ".kibana_alerting_cases", "id" : "action:c95cb100-b075-11ec-bb3f-1f063f8e06cf", "source" : { "action" : { @@ -23,9 +23,7 @@ "namespaces" : [ "default" ], - "migrationVersion" : { - "action" : "8.0.0" - }, + "typeMigrationVersion": "8.0.0", "coreMigrationVersion" : "8.1.2", "updated_at" : "2022-03-30T22:07:27.777Z" } @@ -35,26 +33,24 @@ { "type": "doc", "value": { - "index" : ".kibana", - "id" : "action:207fa0e0-c04e-11ec-8a52-4fb92379525a", - "source" : { - "action" : { - "actionTypeId" : ".slack", - "name" : "Slack connector", - "isMissingSecrets" : false, - "config" : { }, - "secrets" : "WTpvAWxAnR1yHdFqQrmGGeOmqGzeIDQnjGexz7F3JdFVEH0CJkPbiyEk9SiiBr1OPZ3s0LWqphjNREjyxSTCTuYTKaEIQ1+mJFQzjIulekMZklQdon7kNh4hOVHW832IX1Lpx9f5cgAPQst2bqJCehz+3BeZOh17Hgrvb/0zzKRCR4cvlhYbOUNUCNR/d6YoEniwUK8pO9xcel9hle7XY6MNfBGiMX8T41O87lvgp9xDjch3LpwqzDjUIFs=" + "index": ".kibana_alerting_cases", + "id": "action:207fa0e0-c04e-11ec-8a52-4fb92379525a", + "source": { + "action": { + "actionTypeId": ".slack", + "name": "Slack connector", + "isMissingSecrets": false, + "config": {}, + "secrets": "WTpvAWxAnR1yHdFqQrmGGeOmqGzeIDQnjGexz7F3JdFVEH0CJkPbiyEk9SiiBr1OPZ3s0LWqphjNREjyxSTCTuYTKaEIQ1+mJFQzjIulekMZklQdon7kNh4hOVHW832IX1Lpx9f5cgAPQst2bqJCehz+3BeZOh17Hgrvb/0zzKRCR4cvlhYbOUNUCNR/d6YoEniwUK8pO9xcel9hle7XY6MNfBGiMX8T41O87lvgp9xDjch3LpwqzDjUIFs=" }, - "type" : "action", - "references" : [ ], - "namespaces" : [ + "type": "action", + "references": [], + "namespaces": [ "default" ], - "migrationVersion" : { - "action" : "8.0.0" - }, - "coreMigrationVersion" : "8.1.2", - "updated_at" : "2022-03-30T22:07:27.777Z" + "typeMigrationVersion": "8.0.0", + "coreMigrationVersion": "8.1.2", + "updated_at": "2022-03-30T22:07:27.777Z" } } } @@ -62,45 +58,45 @@ { "type": "doc", "value": { - "index" : ".kibana", - "id" : "alert:9095ee90-b075-11ec-bb3f-1f063f8e06cf", - "source" : { - "alert" : { - "name" : "test w/ no actions", - "tags" : [ + "index": ".kibana_alerting_cases", + "id": "alert:9095ee90-b075-11ec-bb3f-1f063f8e06cf", + "source": { + "alert": { + "name": "test w/ no actions", + "tags": [ "__internal_rule_id:2297be91-894c-4831-830f-b424a0ec84f0", "__internal_immutable:false", "auto_disabled_8.0" ], - "alertTypeId" : "siem.queryRule", - "consumer" : "siem", + "alertTypeId": "siem.queryRule", + "consumer": "siem", "revision": 0, - "params" : { - "author" : [ ], - "description" : "a", - "ruleId" : "2297be91-894c-4831-830f-b424a0ec84f0", - "falsePositives" : [ ], - "from" : "now-360s", - "immutable" : false, - "license" : "", - "outputIndex" : "", - "meta" : { - "from" : "1m", - "kibana_siem_app_url" : "https://actions.kb.us-central1.gcp.cloud.es.io:9243/app/security" + "params": { + "author": [], + "description": "a", + "ruleId": "2297be91-894c-4831-830f-b424a0ec84f0", + "falsePositives": [], + "from": "now-360s", + "immutable": false, + "license": "", + "outputIndex": "", + "meta": { + "from": "1m", + "kibana_siem_app_url": "https://actions.kb.us-central1.gcp.cloud.es.io:9243/app/security" }, - "maxSignals" : 100, - "riskScore" : 21, - "riskScoreMapping" : [ ], - "severity" : "low", - "severityMapping" : [ ], - "threat" : [ ], - "to" : "now", - "references" : [ ], - "version" : 1, - "exceptionsList" : [ ], - "type" : "query", - "language" : "kuery", - "index" : [ + "maxSignals": 100, + "riskScore": 21, + "riskScoreMapping": [], + "severity": "low", + "severityMapping": [], + "threat": [], + "to": "now", + "references": [], + "version": 1, + "exceptionsList": [], + "type": "query", + "language": "kuery", + "index": [ "apm-*-transaction*", "traces-apm*", "auditbeat-*", @@ -110,46 +106,44 @@ "packetbeat-*", "winlogbeat-*" ], - "query" : "*:*", - "filters" : [ ] + "query": "*:*", + "filters": [] }, - "schedule" : { - "interval" : "5m" + "schedule": { + "interval": "5m" }, - "enabled" : true, - "actions" : [ ], - "throttle" : null, - "notifyWhen" : "onActiveAlert", - "apiKeyOwner" : null, - "apiKey" : null, - "createdBy" : "1527796724", - "updatedBy" : "1527796724", - "createdAt" : "2022-03-30T22:05:53.511Z", - "updatedAt" : "2022-03-30T22:05:53.511Z", - "muteAll" : false, - "mutedInstanceIds" : [ ], - "executionStatus" : { - "status" : "ok", - "lastExecutionDate" : "2022-03-31T19:53:37.507Z", - "error" : null, - "lastDuration" : 2377 + "enabled": true, + "actions": [], + "throttle": null, + "notifyWhen": "onActiveAlert", + "apiKeyOwner": null, + "apiKey": null, + "createdBy": "1527796724", + "updatedBy": "1527796724", + "createdAt": "2022-03-30T22:05:53.511Z", + "updatedAt": "2022-03-30T22:05:53.511Z", + "muteAll": false, + "mutedInstanceIds": [], + "executionStatus": { + "status": "ok", + "lastExecutionDate": "2022-03-31T19:53:37.507Z", + "error": null, + "lastDuration": 2377 }, - "meta" : { - "versionApiKeyLastmodified" : "7.15.2" + "meta": { + "versionApiKeyLastmodified": "7.15.2" }, - "scheduledTaskId" : null, - "legacyId" : "9095ee90-b075-11ec-bb3f-1f063f8e06cf" + "scheduledTaskId": null, + "legacyId": "9095ee90-b075-11ec-bb3f-1f063f8e06cf" }, - "type" : "alert", - "references" : [ ], - "namespaces" : [ + "type": "alert", + "references": [], + "namespaces": [ "default" ], - "migrationVersion" : { - "alert" : "8.0.1" - }, - "coreMigrationVersion" : "8.1.2", - "updated_at" : "2022-03-31T19:53:39.885Z" + "typeMigrationVersion": "8.0.1", + "coreMigrationVersion": "8.1.2", + "updated_at": "2022-03-31T19:53:39.885Z" } } } @@ -157,45 +151,45 @@ { "type": "doc", "value": { - "index" : ".kibana", - "id" : "alert:dc6595f0-b075-11ec-bb3f-1f063f8e06cf", - "source" : { - "alert" : { - "name" : "test w/ action - every rule run", - "tags" : [ + "index": ".kibana_alerting_cases", + "id": "alert:dc6595f0-b075-11ec-bb3f-1f063f8e06cf", + "source": { + "alert": { + "name": "test w/ action - every rule run", + "tags": [ "__internal_rule_id:72a0d429-363b-4f70-905e-c6019a224d40", "__internal_immutable:false", "auto_disabled_8.0" ], - "alertTypeId" : "siem.queryRule", - "consumer" : "siem", + "alertTypeId": "siem.queryRule", + "consumer": "siem", "revision": 0, - "params" : { - "author" : [ ], - "description" : "a", - "ruleId" : "72a0d429-363b-4f70-905e-c6019a224d40", - "falsePositives" : [ ], - "from" : "now-360s", - "immutable" : false, - "license" : "", - "outputIndex" : "", - "meta" : { - "from" : "1m", - "kibana_siem_app_url" : "https://actions.kb.us-central1.gcp.cloud.es.io:9243/app/security" + "params": { + "author": [], + "description": "a", + "ruleId": "72a0d429-363b-4f70-905e-c6019a224d40", + "falsePositives": [], + "from": "now-360s", + "immutable": false, + "license": "", + "outputIndex": "", + "meta": { + "from": "1m", + "kibana_siem_app_url": "https://actions.kb.us-central1.gcp.cloud.es.io:9243/app/security" }, - "maxSignals" : 100, - "riskScore" : 21, - "riskScoreMapping" : [ ], - "severity" : "low", - "severityMapping" : [ ], - "threat" : [ ], - "to" : "now", - "references" : [ ], - "version" : 1, - "exceptionsList" : [ ], - "type" : "query", - "language" : "kuery", - "index" : [ + "maxSignals": 100, + "riskScore": 21, + "riskScoreMapping": [], + "severity": "low", + "severityMapping": [], + "threat": [], + "to": "now", + "references": [], + "version": 1, + "exceptionsList": [], + "type": "query", + "language": "kuery", + "index": [ "apm-*-transaction*", "traces-apm*", "auditbeat-*", @@ -205,111 +199,109 @@ "packetbeat-*", "winlogbeat-*" ], - "query" : "*:* ", - "filters" : [ ] + "query": "*:* ", + "filters": [] }, - "schedule" : { - "interval" : "5m" + "schedule": { + "interval": "5m" }, - "enabled" : true, - "actions" : [ + "enabled": true, + "actions": [ { - "group" : "default", - "params" : { - "message" : "Rule {{context.rule.name}} generated {{state.signals_count}} alerts", - "to" : [ + "group": "default", + "params": { + "message": "Rule {{context.rule.name}} generated {{state.signals_count}} alerts", + "to": [ "test@test.com" ], - "subject" : "Test Actions" + "subject": "Test Actions" }, - "actionTypeId" : ".email", - "actionRef" : "action_0" + "actionTypeId": ".email", + "actionRef": "action_0" } ], - "throttle" : null, - "notifyWhen" : "onActiveAlert", - "apiKeyOwner" : null, - "apiKey" : null, - "createdBy" : "1527796724", - "updatedBy" : "1527796724", - "createdAt" : "2022-03-30T22:08:00.172Z", - "updatedAt" : "2022-03-30T22:08:00.172Z", - "muteAll" : false, - "mutedInstanceIds" : [ ], - "executionStatus" : { - "status" : "ok", - "lastExecutionDate" : "2022-03-31T19:55:43.502Z", - "error" : null, - "lastDuration" : 2700 + "throttle": null, + "notifyWhen": "onActiveAlert", + "apiKeyOwner": null, + "apiKey": null, + "createdBy": "1527796724", + "updatedBy": "1527796724", + "createdAt": "2022-03-30T22:08:00.172Z", + "updatedAt": "2022-03-30T22:08:00.172Z", + "muteAll": false, + "mutedInstanceIds": [], + "executionStatus": { + "status": "ok", + "lastExecutionDate": "2022-03-31T19:55:43.502Z", + "error": null, + "lastDuration": 2700 }, - "meta" : { - "versionApiKeyLastmodified" : "7.15.2" + "meta": { + "versionApiKeyLastmodified": "7.15.2" }, - "scheduledTaskId" : null, - "legacyId" : "dc6595f0-b075-11ec-bb3f-1f063f8e06cf" + "scheduledTaskId": null, + "legacyId": "dc6595f0-b075-11ec-bb3f-1f063f8e06cf" }, - "type" : "alert", - "references" : [ + "type": "alert", + "references": [ { - "id" : "c95cb100-b075-11ec-bb3f-1f063f8e06cf", - "name" : "action_0", - "type" : "action" + "id": "c95cb100-b075-11ec-bb3f-1f063f8e06cf", + "name": "action_0", + "type": "action" } ], - "namespaces" : [ + "namespaces": [ "default" ], - "migrationVersion" : { - "alert" : "8.0.1" - }, - "coreMigrationVersion" : "8.1.2", - "updated_at" : "2022-03-31T19:55:46.202Z" + "typeMigrationVersion": "8.0.1", + "coreMigrationVersion": "8.1.2", + "updated_at": "2022-03-31T19:55:46.202Z" } } } { "type": "doc", - "value": { - "index" : ".kibana", - "id" : "alert:064e3160-b076-11ec-bb3f-1f063f8e06cf", - "source" : { - "alert" : { - "name" : "test w/ action - every hour", - "tags" : [ + "value": { + "index": ".kibana_alerting_cases", + "id": "alert:064e3160-b076-11ec-bb3f-1f063f8e06cf", + "source": { + "alert": { + "name": "test w/ action - every hour", + "tags": [ "__internal_rule_id:4c056b05-75ac-4209-be32-82100f771eb4", "__internal_immutable:false", "auto_disabled_8.0" ], - "alertTypeId" : "siem.queryRule", - "consumer" : "siem", + "alertTypeId": "siem.queryRule", + "consumer": "siem", "revision": 0, - "params" : { - "author" : [ ], - "description" : "a", - "ruleId" : "4c056b05-75ac-4209-be32-82100f771eb4", - "falsePositives" : [ ], - "from" : "now-360s", - "immutable" : false, - "license" : "", - "outputIndex" : "", - "meta" : { - "from" : "1m", - "kibana_siem_app_url" : "https://actions.kb.us-central1.gcp.cloud.es.io:9243/app/security" + "params": { + "author": [], + "description": "a", + "ruleId": "4c056b05-75ac-4209-be32-82100f771eb4", + "falsePositives": [], + "from": "now-360s", + "immutable": false, + "license": "", + "outputIndex": "", + "meta": { + "from": "1m", + "kibana_siem_app_url": "https://actions.kb.us-central1.gcp.cloud.es.io:9243/app/security" }, - "maxSignals" : 100, - "riskScore" : 21, - "riskScoreMapping" : [ ], - "severity" : "low", - "severityMapping" : [ ], - "threat" : [ ], - "to" : "now", - "references" : [ ], - "version" : 1, - "exceptionsList" : [ ], - "type" : "query", - "language" : "kuery", - "index" : [ + "maxSignals": 100, + "riskScore": 21, + "riskScoreMapping": [], + "severity": "low", + "severityMapping": [], + "threat": [], + "to": "now", + "references": [], + "version": 1, + "exceptionsList": [], + "type": "query", + "language": "kuery", + "index": [ "apm-*-transaction*", "traces-apm*", "auditbeat-*", @@ -319,46 +311,44 @@ "packetbeat-*", "winlogbeat-*" ], - "query" : "*:*", - "filters" : [ ] + "query": "*:*", + "filters": [] }, - "schedule" : { - "interval" : "5m" + "schedule": { + "interval": "5m" }, - "enabled" : true, - "actions" : [ ], - "throttle" : null, - "notifyWhen" : "onActiveAlert", - "apiKeyOwner" : null, - "apiKey" : null, - "createdBy" : "1527796724", - "updatedBy" : "1527796724", - "createdAt" : "2022-03-30T22:09:10.261Z", - "updatedAt" : "2022-03-30T22:09:10.261Z", - "muteAll" : false, - "mutedInstanceIds" : [ ], - "executionStatus" : { - "status" : "ok", - "lastExecutionDate" : "2022-03-31T19:56:43.499Z", - "error" : null, - "lastDuration" : 2815 + "enabled": true, + "actions": [], + "throttle": null, + "notifyWhen": "onActiveAlert", + "apiKeyOwner": null, + "apiKey": null, + "createdBy": "1527796724", + "updatedBy": "1527796724", + "createdAt": "2022-03-30T22:09:10.261Z", + "updatedAt": "2022-03-30T22:09:10.261Z", + "muteAll": false, + "mutedInstanceIds": [], + "executionStatus": { + "status": "ok", + "lastExecutionDate": "2022-03-31T19:56:43.499Z", + "error": null, + "lastDuration": 2815 }, - "meta" : { - "versionApiKeyLastmodified" : "7.15.2" + "meta": { + "versionApiKeyLastmodified": "7.15.2" }, - "scheduledTaskId" : null, - "legacyId" : "064e3160-b076-11ec-bb3f-1f063f8e06cf" + "scheduledTaskId": null, + "legacyId": "064e3160-b076-11ec-bb3f-1f063f8e06cf" }, - "type" : "alert", - "references" : [ ], - "namespaces" : [ + "type": "alert", + "references": [], + "namespaces": [ "default" ], - "migrationVersion" : { - "alert" : "8.0.1" - }, - "coreMigrationVersion" : "8.1.2", - "updated_at" : "2022-03-31T19:56:46.314Z" + "typeMigrationVersion": "8.0.1", + "coreMigrationVersion": "8.1.2", + "updated_at": "2022-03-31T19:56:46.314Z" } } } @@ -366,45 +356,45 @@ { "type": "doc", "value": { - "index" : ".kibana", - "id" : "alert:27639570-b076-11ec-bb3f-1f063f8e06cf", - "source" : { - "alert" : { - "name" : "test w/ action - daily", - "tags" : [ + "index": ".kibana_alerting_cases", + "id": "alert:27639570-b076-11ec-bb3f-1f063f8e06cf", + "source": { + "alert": { + "name": "test w/ action - daily", + "tags": [ "__internal_rule_id:8e2c8550-f13f-4e21-be0c-92148d71a5f1", "__internal_immutable:false", "auto_disabled_8.0" ], - "alertTypeId" : "siem.queryRule", - "consumer" : "siem", + "alertTypeId": "siem.queryRule", + "consumer": "siem", "revision": 0, - "params" : { - "author" : [ ], - "description" : "a", - "ruleId" : "8e2c8550-f13f-4e21-be0c-92148d71a5f1", - "falsePositives" : [ ], - "from" : "now-360s", - "immutable" : false, - "license" : "", - "outputIndex" : "", - "meta" : { - "from" : "1m", - "kibana_siem_app_url" : "https://actions.kb.us-central1.gcp.cloud.es.io:9243/app/security" + "params": { + "author": [], + "description": "a", + "ruleId": "8e2c8550-f13f-4e21-be0c-92148d71a5f1", + "falsePositives": [], + "from": "now-360s", + "immutable": false, + "license": "", + "outputIndex": "", + "meta": { + "from": "1m", + "kibana_siem_app_url": "https://actions.kb.us-central1.gcp.cloud.es.io:9243/app/security" }, - "maxSignals" : 100, - "riskScore" : 21, - "riskScoreMapping" : [ ], - "severity" : "low", - "severityMapping" : [ ], - "threat" : [ ], - "to" : "now", - "references" : [ ], - "version" : 1, - "exceptionsList" : [ ], - "type" : "query", - "language" : "kuery", - "index" : [ + "maxSignals": 100, + "riskScore": 21, + "riskScoreMapping": [], + "severity": "low", + "severityMapping": [], + "threat": [], + "to": "now", + "references": [], + "version": 1, + "exceptionsList": [], + "type": "query", + "language": "kuery", + "index": [ "apm-*-transaction*", "traces-apm*", "auditbeat-*", @@ -414,46 +404,44 @@ "packetbeat-*", "winlogbeat-*" ], - "query" : "*:*", - "filters" : [ ] + "query": "*:*", + "filters": [] }, - "schedule" : { - "interval" : "5m" + "schedule": { + "interval": "5m" }, - "enabled" : true, - "actions" : [ ], - "throttle" : null, - "notifyWhen" : "onActiveAlert", - "apiKeyOwner" : null, - "apiKey" : null, - "createdBy" : "1527796724", - "updatedBy" : "1527796724", - "createdAt" : "2022-03-30T22:10:06.358Z", - "updatedAt" : "2022-03-30T22:10:06.358Z", - "muteAll" : false, - "mutedInstanceIds" : [ ], - "executionStatus" : { - "status" : "ok", - "lastExecutionDate" : "2022-03-31T19:58:13.480Z", - "error" : null, - "lastDuration" : 3023 + "enabled": true, + "actions": [], + "throttle": null, + "notifyWhen": "onActiveAlert", + "apiKeyOwner": null, + "apiKey": null, + "createdBy": "1527796724", + "updatedBy": "1527796724", + "createdAt": "2022-03-30T22:10:06.358Z", + "updatedAt": "2022-03-30T22:10:06.358Z", + "muteAll": false, + "mutedInstanceIds": [], + "executionStatus": { + "status": "ok", + "lastExecutionDate": "2022-03-31T19:58:13.480Z", + "error": null, + "lastDuration": 3023 }, - "meta" : { - "versionApiKeyLastmodified" : "7.15.2" + "meta": { + "versionApiKeyLastmodified": "7.15.2" }, - "scheduledTaskId" : null, - "legacyId" : "27639570-b076-11ec-bb3f-1f063f8e06cf" + "scheduledTaskId": null, + "legacyId": "27639570-b076-11ec-bb3f-1f063f8e06cf" }, - "type" : "alert", - "references" : [ ], - "namespaces" : [ + "type": "alert", + "references": [], + "namespaces": [ "default" ], - "migrationVersion" : { - "alert" : "8.0.1" - }, - "coreMigrationVersion" : "8.1.2", - "updated_at" : "2022-03-31T19:58:16.503Z" + "typeMigrationVersion": "8.0.1", + "coreMigrationVersion": "8.1.2", + "updated_at": "2022-03-31T19:58:16.503Z" } } } @@ -461,45 +449,45 @@ { "type": "doc", "value": { - "index" : ".kibana", - "id" : "alert:61ec7a40-b076-11ec-bb3f-1f063f8e06cf", - "source" : { - "alert" : { - "name" : "test w/ actions - weekly", - "tags" : [ + "index": ".kibana_alerting_cases", + "id": "alert:61ec7a40-b076-11ec-bb3f-1f063f8e06cf", + "source": { + "alert": { + "name": "test w/ actions - weekly", + "tags": [ "__internal_rule_id:05fbdd2a-e802-420b-bdc3-95ae0acca454", "__internal_immutable:false", "auto_disabled_8.0" ], - "alertTypeId" : "siem.queryRule", - "consumer" : "siem", + "alertTypeId": "siem.queryRule", + "consumer": "siem", "revision": 0, - "params" : { - "author" : [ ], - "description" : "a", - "ruleId" : "05fbdd2a-e802-420b-bdc3-95ae0acca454", - "falsePositives" : [ ], - "from" : "now-360s", - "immutable" : false, - "license" : "", - "outputIndex" : "", - "meta" : { - "from" : "1m", - "kibana_siem_app_url" : "https://actions.kb.us-central1.gcp.cloud.es.io:9243/app/security" + "params": { + "author": [], + "description": "a", + "ruleId": "05fbdd2a-e802-420b-bdc3-95ae0acca454", + "falsePositives": [], + "from": "now-360s", + "immutable": false, + "license": "", + "outputIndex": "", + "meta": { + "from": "1m", + "kibana_siem_app_url": "https://actions.kb.us-central1.gcp.cloud.es.io:9243/app/security" }, - "maxSignals" : 100, - "riskScore" : 21, - "riskScoreMapping" : [ ], - "severity" : "low", - "severityMapping" : [ ], - "threat" : [ ], - "to" : "now", - "references" : [ ], - "version" : 1, - "exceptionsList" : [ ], - "type" : "query", - "language" : "kuery", - "index" : [ + "maxSignals": 100, + "riskScore": 21, + "riskScoreMapping": [], + "severity": "low", + "severityMapping": [], + "threat": [], + "to": "now", + "references": [], + "version": 1, + "exceptionsList": [], + "type": "query", + "language": "kuery", + "index": [ "apm-*-transaction*", "traces-apm*", "auditbeat-*", @@ -509,46 +497,44 @@ "packetbeat-*", "winlogbeat-*" ], - "query" : "*:*", - "filters" : [ ] + "query": "*:*", + "filters": [] }, - "schedule" : { - "interval" : "5m" + "schedule": { + "interval": "5m" }, - "enabled" : true, - "actions" : [ ], - "throttle" : null, - "notifyWhen" : "onActiveAlert", - "apiKeyOwner" : null, - "apiKey" : null, - "createdBy" : "1527796724", - "updatedBy" : "1527796724", - "createdAt" : "2022-03-30T22:11:44.516Z", - "updatedAt" : "2022-03-30T22:11:44.516Z", - "muteAll" : false, - "mutedInstanceIds" : [ ], - "executionStatus" : { - "status" : "ok", - "lastExecutionDate" : "2022-03-31T19:54:22.442Z", - "error" : null, - "lastDuration" : 2612 + "enabled": true, + "actions": [], + "throttle": null, + "notifyWhen": "onActiveAlert", + "apiKeyOwner": null, + "apiKey": null, + "createdBy": "1527796724", + "updatedBy": "1527796724", + "createdAt": "2022-03-30T22:11:44.516Z", + "updatedAt": "2022-03-30T22:11:44.516Z", + "muteAll": false, + "mutedInstanceIds": [], + "executionStatus": { + "status": "ok", + "lastExecutionDate": "2022-03-31T19:54:22.442Z", + "error": null, + "lastDuration": 2612 }, - "meta" : { - "versionApiKeyLastmodified" : "7.15.2" + "meta": { + "versionApiKeyLastmodified": "7.15.2" }, - "scheduledTaskId" : null, - "legacyId" : "61ec7a40-b076-11ec-bb3f-1f063f8e06cf" + "scheduledTaskId": null, + "legacyId": "61ec7a40-b076-11ec-bb3f-1f063f8e06cf" }, - "type" : "alert", - "references" : [ ], - "namespaces" : [ + "type": "alert", + "references": [], + "namespaces": [ "default" ], - "migrationVersion" : { - "alert" : "8.0.1" - }, - "coreMigrationVersion" : "8.1.2", - "updated_at" : "2022-03-31T19:54:25.054Z" + "typeMigrationVersion": "8.0.1", + "coreMigrationVersion": "8.1.2", + "updated_at": "2022-03-31T19:54:25.054Z" } } } @@ -556,80 +542,78 @@ { "type": "doc", "value": { - "index" : ".kibana", - "id" : "alert:64492ef0-b076-11ec-bb3f-1f063f8e06cf", - "source" : { - "alert" : { - "name" : "test w/ actions - weekly", - "tags" : [ + "index": ".kibana_alerting_cases", + "id": "alert:64492ef0-b076-11ec-bb3f-1f063f8e06cf", + "source": { + "alert": { + "name": "test w/ actions - weekly", + "tags": [ "__internal_rule_alert_id:61ec7a40-b076-11ec-bb3f-1f063f8e06cf" ], - "alertTypeId" : "siem.notifications", - "consumer" : "siem", + "alertTypeId": "siem.notifications", + "consumer": "siem", "revision": 0, - "params" : { - "ruleAlertId" : "61ec7a40-b076-11ec-bb3f-1f063f8e06cf" + "params": { + "ruleAlertId": "61ec7a40-b076-11ec-bb3f-1f063f8e06cf" }, - "schedule" : { - "interval" : "7d" + "schedule": { + "interval": "7d" }, - "enabled" : true, - "actions" : [ + "enabled": true, + "actions": [ { - "group" : "default", - "params" : { - "message" : "Rule {{context.rule.name}} generated {{state.signals_count}} alerts", - "to" : [ + "group": "default", + "params": { + "message": "Rule {{context.rule.name}} generated {{state.signals_count}} alerts", + "to": [ "test@test.com" ], - "subject" : "Test Actions" + "subject": "Test Actions" }, - "actionTypeId" : ".email", - "actionRef" : "action_0" + "actionTypeId": ".email", + "actionRef": "action_0" } ], - "throttle" : null, - "notifyWhen" : "onActiveAlert", - "apiKeyOwner" : null, - "apiKey" : null, - "createdBy" : "1527796724", - "updatedBy" : "1527796724", - "createdAt" : "2022-03-30T22:11:48.661Z", - "updatedAt" : "2022-03-30T22:11:48.661Z", - "muteAll" : false, - "mutedInstanceIds" : [ ], - "executionStatus" : { - "status" : "ok", - "lastExecutionDate" : "2022-03-30T22:11:51.640Z", - "error" : null + "throttle": null, + "notifyWhen": "onActiveAlert", + "apiKeyOwner": null, + "apiKey": null, + "createdBy": "1527796724", + "updatedBy": "1527796724", + "createdAt": "2022-03-30T22:11:48.661Z", + "updatedAt": "2022-03-30T22:11:48.661Z", + "muteAll": false, + "mutedInstanceIds": [], + "executionStatus": { + "status": "ok", + "lastExecutionDate": "2022-03-30T22:11:51.640Z", + "error": null }, - "meta" : { - "versionApiKeyLastmodified" : "7.15.2" + "meta": { + "versionApiKeyLastmodified": "7.15.2" }, - "scheduledTaskId" : null, - "legacyId" : "64492ef0-b076-11ec-bb3f-1f063f8e06cf" + "scheduledTaskId": null, + "legacyId": "64492ef0-b076-11ec-bb3f-1f063f8e06cf" }, - "type" : "alert", - "references" : [ + "type": "alert", + "references": [ { - "id" : "c95cb100-b075-11ec-bb3f-1f063f8e06cf", - "name" : "action_0", - "type" : "action" + "id": "c95cb100-b075-11ec-bb3f-1f063f8e06cf", + "name": "action_0", + "type": "action" }, { - "id" : "61ec7a40-b076-11ec-bb3f-1f063f8e06cf", - "name" : "param:alert_0", - "type" : "alert" + "id": "61ec7a40-b076-11ec-bb3f-1f063f8e06cf", + "name": "param:alert_0", + "type": "alert" } ], - "namespaces" : [ + "namespaces": [ "default" ], - "migrationVersion" : { - "alert" : "8.0.1" - }, - "coreMigrationVersion" : "8.1.2", - "updated_at" : "2022-03-30T22:11:51.707Z" + "typeMigrationVersion": "8.0.1", + "coreMigrationVersion": "8.1.2", + "updated_at": "2022-03-30T22:11:51.707Z" } } } @@ -637,92 +621,90 @@ { "type": "doc", "value": { - "index" : ".kibana", - "id" : "alert:d42e8210-b076-11ec-bb3f-1f063f8e06cf", - "source" : { - "alert" : { - "name" : "test w/ action - every hour", - "tags" : [ - "__internal_rule_alert_id:064e3160-b076-11ec-bb3f-1f063f8e06cf" - ], - "alertTypeId" : "siem.notifications", - "consumer" : "siem", - "revision": 0, - "params" : { - "ruleAlertId" : "064e3160-b076-11ec-bb3f-1f063f8e06cf" - }, - "schedule" : { - "interval" : "1h" + "index": ".kibana_alerting_cases", + "id": "alert:d42e8210-b076-11ec-bb3f-1f063f8e06cf", + "source": { + "alert": { + "name": "test w/ action - every hour", + "tags": [ + "__internal_rule_alert_id:064e3160-b076-11ec-bb3f-1f063f8e06cf" + ], + "alertTypeId": "siem.notifications", + "consumer": "siem", + "revision": 0, + "params": { + "ruleAlertId": "064e3160-b076-11ec-bb3f-1f063f8e06cf" + }, + "schedule": { + "interval": "1h" + }, + "enabled": true, + "actions": [ + { + "group": "default", + "params": { + "message": "Rule {{context.rule.name}} generated {{state.signals_count}} alerts", + "to": [ + "test@test.com" + ], + "subject": "Rule email" + }, + "actionTypeId": ".email", + "actionRef": "action_0" + }, + { + "group": "default", + "params": { + "message": "Rule {{context.rule.name}} generated {{state.signals_count}} alerts" + }, + "actionTypeId": ".slack", + "actionRef": "action_1" + } + ], + "throttle": null, + "notifyWhen": "onActiveAlert", + "apiKeyOwner": null, + "apiKey": null, + "createdBy": "1527796724", + "updatedBy": "1527796724", + "createdAt": "2022-03-30T22:14:56.318Z", + "updatedAt": "2022-03-30T22:15:12.135Z", + "muteAll": false, + "mutedInstanceIds": [], + "executionStatus": { + "status": "pending", + "lastExecutionDate": "2022-03-30T22:14:56.318Z", + "error": null + }, + "meta": { + "versionApiKeyLastmodified": "7.15.2" + }, + "legacyId": "d42e8210-b076-11ec-bb3f-1f063f8e06cf" }, - "enabled" : true, - "actions" : [ + "type": "alert", + "references": [ { - "group" : "default", - "params" : { - "message" : "Rule {{context.rule.name}} generated {{state.signals_count}} alerts", - "to" : [ - "test@test.com" - ], - "subject" : "Rule email" - }, - "actionTypeId" : ".email", - "actionRef" : "action_0" + "id": "c95cb100-b075-11ec-bb3f-1f063f8e06cf", + "name": "action_0", + "type": "action" }, { - "group" : "default", - "params" : { - "message" : "Rule {{context.rule.name}} generated {{state.signals_count}} alerts" - }, - "actionTypeId" : ".slack", - "actionRef" : "action_1" + "id": "207fa0e0-c04e-11ec-8a52-4fb92379525a", + "name": "action_1", + "type": "action" + }, + { + "id": "064e3160-b076-11ec-bb3f-1f063f8e06cf", + "name": "param:alert_0", + "type": "alert" } ], - "throttle" : null, - "notifyWhen" : "onActiveAlert", - "apiKeyOwner" : null, - "apiKey" : null, - "createdBy" : "1527796724", - "updatedBy" : "1527796724", - "createdAt" : "2022-03-30T22:14:56.318Z", - "updatedAt" : "2022-03-30T22:15:12.135Z", - "muteAll" : false, - "mutedInstanceIds" : [ ], - "executionStatus" : { - "status" : "pending", - "lastExecutionDate" : "2022-03-30T22:14:56.318Z", - "error" : null - }, - "meta" : { - "versionApiKeyLastmodified" : "7.15.2" - }, - "legacyId" : "d42e8210-b076-11ec-bb3f-1f063f8e06cf" - }, - "type" : "alert", - "references" : [ - { - "id" : "c95cb100-b075-11ec-bb3f-1f063f8e06cf", - "name" : "action_0", - "type" : "action" - }, - { - "id" : "207fa0e0-c04e-11ec-8a52-4fb92379525a", - "name" : "action_1", - "type" : "action" - }, - { - "id" : "064e3160-b076-11ec-bb3f-1f063f8e06cf", - "name" : "param:alert_0", - "type" : "alert" - } - ], - "namespaces" : [ - "default" - ], - "migrationVersion" : { - "alert" : "8.0.1" - }, - "coreMigrationVersion" : "8.1.2", - "updated_at" : "2022-03-30T22:15:12.135Z" + "namespaces": [ + "default" + ], + "typeMigrationVersion": "8.0.1", + "coreMigrationVersion": "8.1.2", + "updated_at": "2022-03-30T22:15:12.135Z" } } } @@ -730,103 +712,101 @@ { "type": "doc", "value": { - "index" : ".kibana", - "id" : "alert:29ba2fa0-b076-11ec-bb3f-1f063f8e06cf", - "source" : { - "alert" : { - "name" : "test w/ action - daily", - "tags" : [ + "index": ".kibana_alerting_cases", + "id": "alert:29ba2fa0-b076-11ec-bb3f-1f063f8e06cf", + "source": { + "alert": { + "name": "test w/ action - daily", + "tags": [ "__internal_rule_alert_id:27639570-b076-11ec-bb3f-1f063f8e06cf" ], - "alertTypeId" : "siem.notifications", - "consumer" : "siem", + "alertTypeId": "siem.notifications", + "consumer": "siem", "revision": 0, - "params" : { - "ruleAlertId" : "27639570-b076-11ec-bb3f-1f063f8e06cf" + "params": { + "ruleAlertId": "27639570-b076-11ec-bb3f-1f063f8e06cf" }, - "schedule" : { - "interval" : "1d" + "schedule": { + "interval": "1d" }, - "enabled" : true, - "actions" : [ + "enabled": true, + "actions": [ { - "group" : "default", - "params" : { - "message" : "Rule {{context.rule.name}} generated {{state.signals_count}} alerts", - "to" : [ + "group": "default", + "params": { + "message": "Rule {{context.rule.name}} generated {{state.signals_count}} alerts", + "to": [ "test@test.com" ], - "subject" : "Test Actions" + "subject": "Test Actions" }, - "actionTypeId" : ".email", - "actionRef" : "action_0" + "actionTypeId": ".email", + "actionRef": "action_0" } ], - "throttle" : null, - "notifyWhen" : "onActiveAlert", - "apiKeyOwner" : null, - "apiKey" : null, - "createdBy" : "1527796724", - "updatedBy" : "1527796724", - "createdAt" : "2022-03-30T22:10:10.435Z", - "updatedAt" : "2022-03-30T22:10:10.435Z", - "muteAll" : false, - "mutedInstanceIds" : [ ], - "executionStatus" : { - "status" : "ok", - "lastExecutionDate" : "2022-04-01T22:10:15.478Z", - "error" : null, - "lastDuration" : 984 + "throttle": null, + "notifyWhen": "onActiveAlert", + "apiKeyOwner": null, + "apiKey": null, + "createdBy": "1527796724", + "updatedBy": "1527796724", + "createdAt": "2022-03-30T22:10:10.435Z", + "updatedAt": "2022-03-30T22:10:10.435Z", + "muteAll": false, + "mutedInstanceIds": [], + "executionStatus": { + "status": "ok", + "lastExecutionDate": "2022-04-01T22:10:15.478Z", + "error": null, + "lastDuration": 984 }, - "meta" : { - "versionApiKeyLastmodified" : "7.15.2" + "meta": { + "versionApiKeyLastmodified": "7.15.2" }, - "scheduledTaskId" : null, - "legacyId" : "29ba2fa0-b076-11ec-bb3f-1f063f8e06cf", - "monitoring" : { - "run" : { - "history" : [ + "scheduledTaskId": null, + "legacyId": "29ba2fa0-b076-11ec-bb3f-1f063f8e06cf", + "monitoring": { + "run": { + "history": [ { - "duration" : 111, - "success" : true, - "timestamp" : 1648764614215 + "duration": 111, + "success": true, + "timestamp": 1648764614215 }, { - "duration" : 984, - "success" : true, - "timestamp" : 1648851016462 + "duration": 984, + "success": true, + "timestamp": 1648851016462 } ], - "calculated_metrics" : { - "p99" : 984, - "success_ratio" : 1, - "p50" : 547.5, - "p95" : 984 + "calculated_metrics": { + "p99": 984, + "success_ratio": 1, + "p50": 547.5, + "p95": 984 } } } }, - "type" : "alert", - "references" : [ + "type": "alert", + "references": [ { - "id" : "c95cb100-b075-11ec-bb3f-1f063f8e06cf", - "name" : "action_0", - "type" : "action" + "id": "c95cb100-b075-11ec-bb3f-1f063f8e06cf", + "name": "action_0", + "type": "action" }, { - "id" : "27639570-b076-11ec-bb3f-1f063f8e06cf", - "name" : "param:alert_0", - "type" : "alert" + "id": "27639570-b076-11ec-bb3f-1f063f8e06cf", + "name": "param:alert_0", + "type": "alert" } ], - "namespaces" : [ + "namespaces": [ "default" ], - "migrationVersion" : { - "alert" : "8.0.1" - }, - "coreMigrationVersion" : "8.1.2", - "updated_at" : "2022-04-01T22:10:16.467Z" + "typeMigrationVersion": "8.0.1", + "coreMigrationVersion": "8.1.2", + "updated_at": "2022-04-01T22:10:16.467Z" } } } @@ -834,30 +814,28 @@ { "type": "doc", "value": { - "index" : ".kibana", - "id" : "siem-detection-engine-rule-actions:926668d0-b075-11ec-bb3f-1f063f8e06cf", - "source" : { - "siem-detection-engine-rule-actions" : { - "actions" : [ ], - "ruleThrottle" : "no_actions", - "alertThrottle" : null + "index": ".kibana_security_solution", + "id": "siem-detection-engine-rule-actions:926668d0-b075-11ec-bb3f-1f063f8e06cf", + "source": { + "siem-detection-engine-rule-actions": { + "actions": [], + "ruleThrottle": "no_actions", + "alertThrottle": null }, - "type" : "siem-detection-engine-rule-actions", - "references" : [ + "type": "siem-detection-engine-rule-actions", + "references": [ { - "id" : "9095ee90-b075-11ec-bb3f-1f063f8e06cf", - "type" : "alert", - "name" : "alert_0" + "id": "9095ee90-b075-11ec-bb3f-1f063f8e06cf", + "type": "alert", + "name": "alert_0" } ], - "namespaces" : [ + "namespaces": [ "default" ], - "migrationVersion" : { - "siem-detection-engine-rule-actions" : "8.0.0" - }, - "coreMigrationVersion" : "8.1.2", - "updated_at" : "2022-03-30T22:05:55.563Z" + "typeMigrationVersion": "8.0.0", + "coreMigrationVersion": "8.1.2", + "updated_at": "2022-03-30T22:05:55.563Z" } } } @@ -865,48 +843,46 @@ { "type": "doc", "value": { - "index" : ".kibana", - "id" : "siem-detection-engine-rule-actions:dde13970-b075-11ec-bb3f-1f063f8e06cf", - "source" : { - "siem-detection-engine-rule-actions" : { - "actions" : [ + "index": ".kibana_security_solution", + "id": "siem-detection-engine-rule-actions:dde13970-b075-11ec-bb3f-1f063f8e06cf", + "source": { + "siem-detection-engine-rule-actions": { + "actions": [ { - "actionRef" : "action_0", - "group" : "default", - "params" : { - "message" : "Rule {{context.rule.name}} generated {{state.signals_count}} alerts", - "to" : [ + "actionRef": "action_0", + "group": "default", + "params": { + "message": "Rule {{context.rule.name}} generated {{state.signals_count}} alerts", + "to": [ "test@test.com" ], - "subject" : "Test Actions" + "subject": "Test Actions" }, - "action_type_id" : ".email" + "action_type_id": ".email" } ], - "ruleThrottle" : "rule", - "alertThrottle" : null + "ruleThrottle": "rule", + "alertThrottle": null }, - "type" : "siem-detection-engine-rule-actions", - "references" : [ + "type": "siem-detection-engine-rule-actions", + "references": [ { - "id" : "dc6595f0-b075-11ec-bb3f-1f063f8e06cf", - "type" : "alert", - "name" : "alert_0" + "id": "dc6595f0-b075-11ec-bb3f-1f063f8e06cf", + "type": "alert", + "name": "alert_0" }, { - "id" : "c95cb100-b075-11ec-bb3f-1f063f8e06cf", - "type" : "action", - "name" : "action_0" + "id": "c95cb100-b075-11ec-bb3f-1f063f8e06cf", + "type": "action", + "name": "action_0" } ], - "namespaces" : [ + "namespaces": [ "default" ], - "migrationVersion" : { - "siem-detection-engine-rule-actions" : "8.0.0" - }, - "coreMigrationVersion" : "8.1.2", - "updated_at" : "2022-03-30T22:08:02.207Z" + "typeMigrationVersion": "8.0.0", + "coreMigrationVersion": "8.1.2", + "updated_at": "2022-03-30T22:08:02.207Z" } } } @@ -914,61 +890,59 @@ { "type": "doc", "value": { - "index" : ".kibana", - "id" : "siem-detection-engine-rule-actions:07aa8d10-b076-11ec-bb3f-1f063f8e06cf", - "source" : { - "siem-detection-engine-rule-actions" : { - "actions" : [ + "index": ".kibana_security_solution", + "id": "siem-detection-engine-rule-actions:07aa8d10-b076-11ec-bb3f-1f063f8e06cf", + "source": { + "siem-detection-engine-rule-actions": { + "actions": [ { - "actionRef" : "action_0", - "group" : "default", - "params" : { - "message" : "Rule {{context.rule.name}} generated {{state.signals_count}} alerts", - "to" : [ + "actionRef": "action_0", + "group": "default", + "params": { + "message": "Rule {{context.rule.name}} generated {{state.signals_count}} alerts", + "to": [ "test@test.com" ], - "subject" : "Rule email" + "subject": "Rule email" }, - "action_type_id" : ".email" + "action_type_id": ".email" }, { - "actionRef" : "action_1", - "group" : "default", - "params" : { - "message" : "Rule {{context.rule.name}} generated {{state.signals_count}} alerts" + "actionRef": "action_1", + "group": "default", + "params": { + "message": "Rule {{context.rule.name}} generated {{state.signals_count}} alerts" }, - "action_type_id" : ".slack" + "action_type_id": ".slack" } ], - "ruleThrottle" : "1h", - "alertThrottle" : "1h" + "ruleThrottle": "1h", + "alertThrottle": "1h" }, - "type" : "siem-detection-engine-rule-actions", - "references" : [ + "type": "siem-detection-engine-rule-actions", + "references": [ { - "id" : "064e3160-b076-11ec-bb3f-1f063f8e06cf", - "type" : "alert", - "name" : "alert_0" + "id": "064e3160-b076-11ec-bb3f-1f063f8e06cf", + "type": "alert", + "name": "alert_0" }, { - "id" : "c95cb100-b075-11ec-bb3f-1f063f8e06cf", - "type" : "action", - "name" : "action_0" + "id": "c95cb100-b075-11ec-bb3f-1f063f8e06cf", + "type": "action", + "name": "action_0" }, { - "id" : "207fa0e0-c04e-11ec-8a52-4fb92379525a", - "type" : "action", - "name" : "action_1" + "id": "207fa0e0-c04e-11ec-8a52-4fb92379525a", + "type": "action", + "name": "action_1" } ], - "namespaces" : [ + "namespaces": [ "default" ], - "migrationVersion" : { - "siem-detection-engine-rule-actions" : "8.0.0" - }, - "coreMigrationVersion" : "8.1.2", - "updated_at" : "2022-03-30T22:09:12.300Z" + "typeMigrationVersion": "8.0.0", + "coreMigrationVersion": "8.1.2", + "updated_at": "2022-03-30T22:09:12.300Z" } } } @@ -976,48 +950,46 @@ { "type": "doc", "value": { - "index" : ".kibana", - "id" : "siem-detection-engine-rule-actions:291ae260-b076-11ec-bb3f-1f063f8e06cf", - "source" : { - "siem-detection-engine-rule-actions" : { - "actions" : [ + "index": ".kibana_security_solution", + "id": "siem-detection-engine-rule-actions:291ae260-b076-11ec-bb3f-1f063f8e06cf", + "source": { + "siem-detection-engine-rule-actions": { + "actions": [ { - "actionRef" : "action_0", - "group" : "default", - "params" : { - "message" : "Rule {{context.rule.name}} generated {{state.signals_count}} alerts", - "to" : [ + "actionRef": "action_0", + "group": "default", + "params": { + "message": "Rule {{context.rule.name}} generated {{state.signals_count}} alerts", + "to": [ "test@test.com" ], - "subject" : "Test Actions" + "subject": "Test Actions" }, - "action_type_id" : ".email" + "action_type_id": ".email" } ], - "ruleThrottle" : "1d", - "alertThrottle" : "1d" + "ruleThrottle": "1d", + "alertThrottle": "1d" }, - "type" : "siem-detection-engine-rule-actions", - "references" : [ + "type": "siem-detection-engine-rule-actions", + "references": [ { - "id" : "27639570-b076-11ec-bb3f-1f063f8e06cf", - "type" : "alert", - "name" : "alert_0" + "id": "27639570-b076-11ec-bb3f-1f063f8e06cf", + "type": "alert", + "name": "alert_0" }, { - "id" : "c95cb100-b075-11ec-bb3f-1f063f8e06cf", - "type" : "action", - "name" : "action_0" + "id": "c95cb100-b075-11ec-bb3f-1f063f8e06cf", + "type": "action", + "name": "action_0" } ], - "namespaces" : [ + "namespaces": [ "default" ], - "migrationVersion" : { - "siem-detection-engine-rule-actions" : "8.0.0" - }, - "coreMigrationVersion" : "8.1.2", - "updated_at" : "2022-03-30T22:10:08.399Z" + "typeMigrationVersion": "8.0.0", + "coreMigrationVersion": "8.1.2", + "updated_at": "2022-03-30T22:10:08.399Z" } } } @@ -1025,48 +997,46 @@ { "type": "doc", "value": { - "index" : ".kibana", - "id" : "siem-detection-engine-rule-actions:63aa2fd0-b076-11ec-bb3f-1f063f8e06cf", - "source" : { - "siem-detection-engine-rule-actions" : { - "actions" : [ + "index": ".kibana_security_solution", + "id": "siem-detection-engine-rule-actions:63aa2fd0-b076-11ec-bb3f-1f063f8e06cf", + "source": { + "siem-detection-engine-rule-actions": { + "actions": [ { - "actionRef" : "action_0", - "group" : "default", - "params" : { - "message" : "Rule {{context.rule.name}} generated {{state.signals_count}} alerts", - "to" : [ + "actionRef": "action_0", + "group": "default", + "params": { + "message": "Rule {{context.rule.name}} generated {{state.signals_count}} alerts", + "to": [ "test@test.com" ], - "subject" : "Test Actions" + "subject": "Test Actions" }, - "action_type_id" : ".email" + "action_type_id": ".email" } ], - "ruleThrottle" : "7d", - "alertThrottle" : "7d" + "ruleThrottle": "7d", + "alertThrottle": "7d" }, - "type" : "siem-detection-engine-rule-actions", - "references" : [ + "type": "siem-detection-engine-rule-actions", + "references": [ { - "id" : "61ec7a40-b076-11ec-bb3f-1f063f8e06cf", - "type" : "alert", - "name" : "alert_0" + "id": "61ec7a40-b076-11ec-bb3f-1f063f8e06cf", + "type": "alert", + "name": "alert_0" }, { - "id" : "c95cb100-b075-11ec-bb3f-1f063f8e06cf", - "type" : "action", - "name" : "action_0" + "id": "c95cb100-b075-11ec-bb3f-1f063f8e06cf", + "type": "action", + "name": "action_0" } ], - "namespaces" : [ + "namespaces": [ "default" ], - "migrationVersion" : { - "siem-detection-engine-rule-actions" : "8.0.0" - }, - "coreMigrationVersion" : "8.1.2", - "updated_at" : "2022-03-30T22:11:46.643Z" + "typeMigrationVersion": "8.0.0", + "coreMigrationVersion": "8.1.2", + "updated_at": "2022-03-30T22:11:46.643Z" } } } diff --git a/x-pack/test/saved_object_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json b/x-pack/test/saved_object_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json index ad130efa1f4d8..4ab39d75c5d02 100644 --- a/x-pack/test/saved_object_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json +++ b/x-pack/test/saved_object_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json @@ -54,7 +54,7 @@ "type": "doc", "value": { "id": "index-pattern:defaultspace-index-pattern-id", - "index": ".kibana", + "index": ".kibana_analytics", "source": { "index-pattern": { "timeFieldName": "@timestamp", @@ -113,7 +113,7 @@ "type": "doc", "value": { "id": "dashboard:defaultspace-dashboard-id", - "index": ".kibana", + "index": ".kibana_analytics", "source": { "dashboard": { "description": "", @@ -147,7 +147,7 @@ "type": "doc", "value": { "id": "index-pattern:space1-index-pattern-id", - "index": ".kibana", + "index": ".kibana_analytics", "source": { "index-pattern": { "timeFieldName": "@timestamp", @@ -208,7 +208,7 @@ "type": "doc", "value": { "id": "dashboard:space1-dashboard-id", - "index": ".kibana", + "index": ".kibana_analytics", "source": { "dashboard": { "description": "", @@ -242,7 +242,7 @@ "type": "doc", "value": { "id": "index-pattern:space2-index-pattern-id", - "index": ".kibana", + "index": ".kibana_analytics", "source": { "index-pattern": { "timeFieldName": "@timestamp", @@ -303,7 +303,7 @@ "type": "doc", "value": { "id": "dashboard:space2-dashboard-id", - "index": ".kibana", + "index": ".kibana_analytics", "source": { "dashboard": { "description": "", @@ -770,7 +770,7 @@ "type": "doc", "value": { "id": "index-pattern:inbound-reference-origin-match-1-newId", - "index": ".kibana", + "index": ".kibana_analytics", "source": { "originId": "inbound-reference-origin-match-1", "index-pattern": { @@ -789,7 +789,7 @@ "type": "doc", "value": { "id": "index-pattern:inbound-reference-origin-match-2a", - "index": ".kibana", + "index": ".kibana_analytics", "source": { "originId": "inbound-reference-origin-match-2", "index-pattern": { @@ -808,7 +808,7 @@ "type": "doc", "value": { "id": "index-pattern:inbound-reference-origin-match-2b", - "index": ".kibana", + "index": ".kibana_analytics", "source": { "originId": "inbound-reference-origin-match-2", "index-pattern": { diff --git a/x-pack/test/saved_object_api_integration/common/fixtures/es_archiver/saved_objects/spaces/mappings.json b/x-pack/test/saved_object_api_integration/common/fixtures/es_archiver/saved_objects/spaces/mappings.json index 34ddbc9ac7846..f1722d291fc1b 100644 --- a/x-pack/test/saved_object_api_integration/common/fixtures/es_archiver/saved_objects/spaces/mappings.json +++ b/x-pack/test/saved_object_api_integration/common/fixtures/es_archiver/saved_objects/spaces/mappings.json @@ -8,12 +8,281 @@ "index": ".kibana_$KIBANA_PACKAGE_VERSION_001", "mappings": { "dynamic": "false", + "_meta": { + "indexTypesMap": { + ".kibana_task_manager": [ + "task" + ], + ".kibana": [ + "apm-indices", + "apm-server-schema", + "apm-service-group", + "apm-telemetry", + "app_search_telemetry", + "application_usage_daily", + "application_usage_totals", + "config", + "config-global", + "core-usage-stats", + "enterprise_search_telemetry", + "event_loop_delays_daily", + "file", + "file-upload-usage-collection-telemetry", + "fileShare", + "guided-onboarding-guide-state", + "guided-onboarding-plugin-state", + "infrastructure-monitoring-log-view", + "infrastructure-ui-source", + "inventory-view", + "legacy-url-alias", + "metrics-explorer-view", + "ml-job", + "ml-module", + "ml-trained-model", + "monitoring-telemetry", + "sample-data-telemetry", + "slo", + "space", + "spaces-usage-stats", + "synthetics-monitor", + "synthetics-param", + "synthetics-privates-locations", + "tag", + "telemetry", + "ui-metric", + "upgrade-assistant-ml-upgrade-operation", + "upgrade-assistant-reindex-operation", + "uptime-dynamic-settings", + "uptime-synthetics-api-key", + "url", + "usage-counters", + "workplace_search_telemetry" + ], + ".kibana_ingest": [ + "epm-packages", + "epm-packages-assets", + "fleet-fleet-server-host", + "fleet-message-signing-keys", + "fleet-preconfiguration-deletion-record", + "fleet-proxy", + "ingest-agent-policies", + "ingest-download-sources", + "ingest-outputs", + "ingest-package-policies", + "ingest_manager_settings" + ], + ".kibana_security_solution": [ + "csp-rule-template", + "endpoint:user-artifact", + "endpoint:user-artifact-manifest", + "exception-list", + "exception-list-agnostic", + "osquery-manager-usage-metric", + "osquery-pack", + "osquery-pack-asset", + "osquery-saved-query", + "security-rule", + "security-solution-signals-migration", + "siem-detection-engine-rule-actions", + "siem-ui-timeline", + "siem-ui-timeline-note", + "siem-ui-timeline-pinned-event" + ], + ".kibana_alerting_cases": [ + "action", + "action_task_params", + "alert", + "api_key_pending_invalidation", + "cases", + "cases-comments", + "cases-configure", + "cases-connector-mappings", + "cases-telemetry", + "cases-user-actions", + "connector_token", + "maintenance-window", + "rules-settings" + ], + ".kibana_analytics": [ + "canvas-element", + "canvas-workpad", + "canvas-workpad-template", + "dashboard", + "graph-workspace", + "index-pattern", + "kql-telemetry", + "lens", + "lens-ui-telemetry", + "map", + "query", + "search", + "search-session", + "search-telemetry", + "visualization" + ] + } + }, "properties": { "space": { "dynamic": false, "type": "object" }, - "type": { "type": "keyword" }, + "type": { + "type": "keyword" + }, + "migrationVersion": { + "dynamic": "true", + "type": "object" + } + } + }, + "settings": { + "index": { + "auto_expand_replicas": "0-1", + "number_of_replicas": "0", + "number_of_shards": "1", + "mapping": { + "total_fields": { + "limit": 1500 + } + } + } + } + } +} + +{ + "type": "index", + "value": { + "aliases": { + ".kibana_analytics_$KIBANA_PACKAGE_VERSION": {}, + ".kibana_analytics": {} + }, + "index": ".kibana_analytics_$KIBANA_PACKAGE_VERSION_001", + "mappings": { + "dynamic": "false", + "_meta": { + "indexTypesMap": { + ".kibana_task_manager": [ + "task" + ], + ".kibana": [ + "apm-indices", + "apm-server-schema", + "apm-service-group", + "apm-telemetry", + "app_search_telemetry", + "application_usage_daily", + "application_usage_totals", + "config", + "config-global", + "core-usage-stats", + "enterprise_search_telemetry", + "event_loop_delays_daily", + "file", + "file-upload-usage-collection-telemetry", + "fileShare", + "guided-onboarding-guide-state", + "guided-onboarding-plugin-state", + "infrastructure-monitoring-log-view", + "infrastructure-ui-source", + "inventory-view", + "legacy-url-alias", + "metrics-explorer-view", + "ml-job", + "ml-module", + "ml-trained-model", + "monitoring-telemetry", + "sample-data-telemetry", + "slo", + "space", + "spaces-usage-stats", + "synthetics-monitor", + "synthetics-param", + "synthetics-privates-locations", + "tag", + "telemetry", + "ui-metric", + "upgrade-assistant-ml-upgrade-operation", + "upgrade-assistant-reindex-operation", + "uptime-dynamic-settings", + "uptime-synthetics-api-key", + "url", + "usage-counters", + "workplace_search_telemetry" + ], + ".kibana_ingest": [ + "epm-packages", + "epm-packages-assets", + "fleet-fleet-server-host", + "fleet-message-signing-keys", + "fleet-preconfiguration-deletion-record", + "fleet-proxy", + "ingest-agent-policies", + "ingest-download-sources", + "ingest-outputs", + "ingest-package-policies", + "ingest_manager_settings" + ], + ".kibana_security_solution": [ + "csp-rule-template", + "endpoint:user-artifact", + "endpoint:user-artifact-manifest", + "exception-list", + "exception-list-agnostic", + "osquery-manager-usage-metric", + "osquery-pack", + "osquery-pack-asset", + "osquery-saved-query", + "security-rule", + "security-solution-signals-migration", + "siem-detection-engine-rule-actions", + "siem-ui-timeline", + "siem-ui-timeline-note", + "siem-ui-timeline-pinned-event" + ], + ".kibana_alerting_cases": [ + "action", + "action_task_params", + "alert", + "api_key_pending_invalidation", + "cases", + "cases-comments", + "cases-configure", + "cases-connector-mappings", + "cases-telemetry", + "cases-user-actions", + "connector_token", + "maintenance-window", + "rules-settings" + ], + ".kibana_analytics": [ + "canvas-element", + "canvas-workpad", + "canvas-workpad-template", + "dashboard", + "graph-workspace", + "index-pattern", + "kql-telemetry", + "lens", + "lens-ui-telemetry", + "map", + "query", + "search", + "search-session", + "search-telemetry", + "visualization" + ] + } + }, + "properties": { + "space": { + "dynamic": false, + "type": "object" + }, + "type": { + "type": "keyword" + }, "migrationVersion": { "dynamic": "true", "type": "object" @@ -26,7 +295,9 @@ "number_of_replicas": "0", "number_of_shards": "1", "mapping": { - "total_fields": { "limit": 1500 } + "total_fields": { + "limit": 1500 + } } } } diff --git a/x-pack/test/spaces_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json b/x-pack/test/spaces_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json index 6d37b745fcde7..3271ff05975f4 100644 --- a/x-pack/test/spaces_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json +++ b/x-pack/test/spaces_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json @@ -98,7 +98,7 @@ "type": "_doc", "value": { "id": "dashboard:cts_dashboard_default", - "index": ".kibana", + "index": ".kibana_analytics", "source": { "originId": "cts_dashboard", "dashboard": { @@ -130,7 +130,7 @@ "type": "_doc", "value": { "id": "visualization:cts_vis_1_default", - "index": ".kibana", + "index": ".kibana_analytics", "source": { "visualization": { "title": "CTS vis 1 from default space", @@ -159,7 +159,7 @@ "type": "_doc", "value": { "id": "visualization:cts_vis_2_default", - "index": ".kibana", + "index": ".kibana_analytics", "source": { "visualization": { "title": "CTS vis 2 from default space", @@ -188,7 +188,7 @@ "type": "_doc", "value": { "id": "visualization:cts_vis_3_default", - "index": ".kibana", + "index": ".kibana_analytics", "source": { "originId": "cts_vis_3", "visualization": { @@ -218,7 +218,7 @@ "type": "_doc", "value": { "id": "dashboard:cts_dashboard_space_1", - "index": ".kibana", + "index": ".kibana_analytics", "source": { "originId": "cts_dashboard", "dashboard": { @@ -254,7 +254,7 @@ "type": "_doc", "value": { "id": "visualization:cts_vis_1_space_1", - "index": ".kibana", + "index": ".kibana_analytics", "source": { "visualization": { "title": "CTS vis 1 from space_1 space", @@ -283,7 +283,7 @@ "type": "_doc", "value": { "id": "visualization:cts_vis_2_space_1", - "index": ".kibana", + "index": ".kibana_analytics", "source": { "visualization": { "title": "CTS vis 2 from space_1 space", @@ -312,7 +312,7 @@ "type": "_doc", "value": { "id": "visualization:cts_vis_3_space_1", - "index": ".kibana", + "index": ".kibana_analytics", "source": { "originId": "cts_vis_3", "visualization": { @@ -342,7 +342,7 @@ "type": "_doc", "value": { "id": "index-pattern:cts_ip_1_default", - "index": ".kibana", + "index": ".kibana_analytics", "source": { "originId": "cts_ip_1", "index-pattern": { @@ -361,7 +361,7 @@ "type": "_doc", "value": { "id": "index-pattern:cts_ip_1_space_1", - "index": ".kibana", + "index": ".kibana_analytics", "source": { "originId": "cts_ip_1", "index-pattern": { diff --git a/x-pack/test/spaces_api_integration/common/fixtures/es_archiver/saved_objects/spaces/mappings.json b/x-pack/test/spaces_api_integration/common/fixtures/es_archiver/saved_objects/spaces/mappings.json index 3f837ae5fa208..a787729a16c35 100644 --- a/x-pack/test/spaces_api_integration/common/fixtures/es_archiver/saved_objects/spaces/mappings.json +++ b/x-pack/test/spaces_api_integration/common/fixtures/es_archiver/saved_objects/spaces/mappings.json @@ -7,7 +7,261 @@ }, "index": ".kibana_$KIBANA_PACKAGE_VERSION_001", "mappings": { - "dynamic": "false" + "dynamic": "false", + "_meta": { + "indexTypesMap": { + ".kibana_task_manager": [ + "task" + ], + ".kibana": [ + "apm-indices", + "apm-server-schema", + "apm-service-group", + "apm-telemetry", + "app_search_telemetry", + "application_usage_daily", + "application_usage_totals", + "config", + "config-global", + "core-usage-stats", + "enterprise_search_telemetry", + "event_loop_delays_daily", + "file", + "file-upload-usage-collection-telemetry", + "fileShare", + "guided-onboarding-guide-state", + "guided-onboarding-plugin-state", + "infrastructure-monitoring-log-view", + "infrastructure-ui-source", + "inventory-view", + "legacy-url-alias", + "metrics-explorer-view", + "ml-job", + "ml-module", + "ml-trained-model", + "monitoring-telemetry", + "sample-data-telemetry", + "slo", + "space", + "spaces-usage-stats", + "synthetics-monitor", + "synthetics-param", + "synthetics-privates-locations", + "tag", + "telemetry", + "ui-metric", + "upgrade-assistant-ml-upgrade-operation", + "upgrade-assistant-reindex-operation", + "uptime-dynamic-settings", + "uptime-synthetics-api-key", + "url", + "usage-counters", + "workplace_search_telemetry" + ], + ".kibana_ingest": [ + "epm-packages", + "epm-packages-assets", + "fleet-fleet-server-host", + "fleet-message-signing-keys", + "fleet-preconfiguration-deletion-record", + "fleet-proxy", + "ingest-agent-policies", + "ingest-download-sources", + "ingest-outputs", + "ingest-package-policies", + "ingest_manager_settings" + ], + ".kibana_security_solution": [ + "csp-rule-template", + "endpoint:user-artifact", + "endpoint:user-artifact-manifest", + "exception-list", + "exception-list-agnostic", + "osquery-manager-usage-metric", + "osquery-pack", + "osquery-pack-asset", + "osquery-saved-query", + "security-rule", + "security-solution-signals-migration", + "siem-detection-engine-rule-actions", + "siem-ui-timeline", + "siem-ui-timeline-note", + "siem-ui-timeline-pinned-event" + ], + ".kibana_alerting_cases": [ + "action", + "action_task_params", + "alert", + "api_key_pending_invalidation", + "cases", + "cases-comments", + "cases-configure", + "cases-connector-mappings", + "cases-telemetry", + "cases-user-actions", + "connector_token", + "maintenance-window", + "rules-settings" + ], + ".kibana_analytics": [ + "canvas-element", + "canvas-workpad", + "canvas-workpad-template", + "dashboard", + "graph-workspace", + "index-pattern", + "kql-telemetry", + "lens", + "lens-ui-telemetry", + "map", + "query", + "search", + "search-session", + "search-telemetry", + "visualization" + ] + } + } + }, + "settings": { + "index": { + "auto_expand_replicas": "0-1", + "number_of_replicas": "0", + "number_of_shards": "1", + "mapping": { + "total_fields": { + "limit": 1500 + } + } + } + } + } +} + +{ + "type": "index", + "value": { + "aliases": { + ".kibana_analytics_$KIBANA_PACKAGE_VERSION": {}, + ".kibana_analytics": {} + }, + "index": ".kibana_analytics_$KIBANA_PACKAGE_VERSION_001", + "mappings": { + "dynamic": "false", + "_meta": { + "indexTypesMap": { + ".kibana_task_manager": [ + "task" + ], + ".kibana": [ + "apm-indices", + "apm-server-schema", + "apm-service-group", + "apm-telemetry", + "app_search_telemetry", + "application_usage_daily", + "application_usage_totals", + "config", + "config-global", + "core-usage-stats", + "enterprise_search_telemetry", + "event_loop_delays_daily", + "file", + "file-upload-usage-collection-telemetry", + "fileShare", + "guided-onboarding-guide-state", + "guided-onboarding-plugin-state", + "infrastructure-monitoring-log-view", + "infrastructure-ui-source", + "inventory-view", + "legacy-url-alias", + "metrics-explorer-view", + "ml-job", + "ml-module", + "ml-trained-model", + "monitoring-telemetry", + "sample-data-telemetry", + "slo", + "space", + "spaces-usage-stats", + "synthetics-monitor", + "synthetics-param", + "synthetics-privates-locations", + "tag", + "telemetry", + "ui-metric", + "upgrade-assistant-ml-upgrade-operation", + "upgrade-assistant-reindex-operation", + "uptime-dynamic-settings", + "uptime-synthetics-api-key", + "url", + "usage-counters", + "workplace_search_telemetry" + ], + ".kibana_ingest": [ + "epm-packages", + "epm-packages-assets", + "fleet-fleet-server-host", + "fleet-message-signing-keys", + "fleet-preconfiguration-deletion-record", + "fleet-proxy", + "ingest-agent-policies", + "ingest-download-sources", + "ingest-outputs", + "ingest-package-policies", + "ingest_manager_settings" + ], + ".kibana_security_solution": [ + "csp-rule-template", + "endpoint:user-artifact", + "endpoint:user-artifact-manifest", + "exception-list", + "exception-list-agnostic", + "osquery-manager-usage-metric", + "osquery-pack", + "osquery-pack-asset", + "osquery-saved-query", + "security-rule", + "security-solution-signals-migration", + "siem-detection-engine-rule-actions", + "siem-ui-timeline", + "siem-ui-timeline-note", + "siem-ui-timeline-pinned-event" + ], + ".kibana_alerting_cases": [ + "action", + "action_task_params", + "alert", + "api_key_pending_invalidation", + "cases", + "cases-comments", + "cases-configure", + "cases-connector-mappings", + "cases-telemetry", + "cases-user-actions", + "connector_token", + "maintenance-window", + "rules-settings" + ], + ".kibana_analytics": [ + "canvas-element", + "canvas-workpad", + "canvas-workpad-template", + "dashboard", + "graph-workspace", + "index-pattern", + "kql-telemetry", + "lens", + "lens-ui-telemetry", + "map", + "query", + "search", + "search-session", + "search-telemetry", + "visualization" + ] + } + } }, "settings": { "index": { @@ -15,7 +269,9 @@ "number_of_replicas": "0", "number_of_shards": "1", "mapping": { - "total_fields": { "limit": 1500 } + "total_fields": { + "limit": 1500 + } } } } diff --git a/x-pack/test/spaces_api_integration/common/lib/space_test_utils.ts b/x-pack/test/spaces_api_integration/common/lib/space_test_utils.ts index b7e698244ee29..8de4482474d8e 100644 --- a/x-pack/test/spaces_api_integration/common/lib/space_test_utils.ts +++ b/x-pack/test/spaces_api_integration/common/lib/space_test_utils.ts @@ -6,6 +6,7 @@ */ import type { Client } from '@elastic/elasticsearch'; +import { ALL_SAVED_OBJECT_INDICES } from '@kbn/core-saved-objects-server'; import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common/constants'; export function getUrlPrefix(spaceId?: string) { @@ -39,7 +40,7 @@ export function getTestScenariosForSpace(spaceId: string) { export function getAggregatedSpaceData(es: Client, objectTypes: string[]) { return es.search({ - index: '.kibana', + index: ALL_SAVED_OBJECT_INDICES, body: { size: 0, runtime_mappings: { diff --git a/x-pack/test/spaces_api_integration/common/suites/delete.ts b/x-pack/test/spaces_api_integration/common/suites/delete.ts index a1c73125ede28..b1ad6241e11df 100644 --- a/x-pack/test/spaces_api_integration/common/suites/delete.ts +++ b/x-pack/test/spaces_api_integration/common/suites/delete.ts @@ -8,6 +8,7 @@ import expect from '@kbn/expect'; import { SuperTest } from 'supertest'; import type { Client } from '@elastic/elasticsearch'; +import { ALL_SAVED_OBJECT_INDICES } from '@kbn/core-saved-objects-server'; import { getAggregatedSpaceData, getTestScenariosForSpace } from '../lib/space_test_utils'; import { MULTI_NAMESPACE_SAVED_OBJECT_TEST_CASES as CASES } from '../lib/saved_object_test_cases'; import { DescribeFn, TestDefinitionAuthentication } from '../lib/types'; @@ -105,7 +106,7 @@ export function deleteTestSuiteFactory(es: Client, esArchiver: any, supertest: S // Since Space 2 was deleted, any multi-namespace objects that existed in that space // are updated to remove it, and of those, any that don't exist in any space are deleted. const multiNamespaceResponse = await es.search>({ - index: '.kibana', + index: ALL_SAVED_OBJECT_INDICES, size: 100, body: { query: { terms: { type: ['sharedtype'] } } }, }); diff --git a/x-pack/test/spaces_api_integration/common/suites/disable_legacy_url_aliases.ts b/x-pack/test/spaces_api_integration/common/suites/disable_legacy_url_aliases.ts index 4719d0e5164a6..002ed6c3e6515 100644 --- a/x-pack/test/spaces_api_integration/common/suites/disable_legacy_url_aliases.ts +++ b/x-pack/test/spaces_api_integration/common/suites/disable_legacy_url_aliases.ts @@ -6,12 +6,13 @@ */ import expect from '@kbn/expect'; -import { SuperTest } from 'supertest'; +import type { SuperTest } from 'supertest'; import type { Client } from '@elastic/elasticsearch'; import type { LegacyUrlAlias } from '@kbn/core-saved-objects-base-server-internal'; +import { MAIN_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { SPACES } from '../lib/spaces'; import { getUrlPrefix } from '../../../saved_object_api_integration/common/lib/saved_object_test_utils'; -import { +import type { ExpectResponseBody, TestDefinition, TestSuite, @@ -62,7 +63,8 @@ export function disableLegacyUrlAliasesTestSuiteFactory( } const esResponse = await es.get( { - index: '.kibana', + // affected by the .kibana split, assumes LEGACY_URL_ALIAS_TYPE is stored in .kibana + index: MAIN_SAVED_OBJECT_INDEX, id: `${LEGACY_URL_ALIAS_TYPE}:${targetSpace}:${targetType}:${sourceId}`, }, { ignore: [404] } diff --git a/x-pack/test/spaces_api_integration/common/suites/update_objects_spaces.ts b/x-pack/test/spaces_api_integration/common/suites/update_objects_spaces.ts index 5528ceaa27602..14eb97c38c6ee 100644 --- a/x-pack/test/spaces_api_integration/common/suites/update_objects_spaces.ts +++ b/x-pack/test/spaces_api_integration/common/suites/update_objects_spaces.ts @@ -14,6 +14,7 @@ import { SavedObjectsErrorHelpers, SavedObjectsUpdateObjectsSpacesResponse, } from '@kbn/core/server'; +import { ALL_SAVED_OBJECT_INDICES } from '@kbn/core-saved-objects-server'; import { SPACES } from '../lib/spaces'; import { expectResponses, @@ -98,11 +99,11 @@ export function updateObjectsSpacesTestSuiteFactory( if (expectAliasDifference !== undefined) { // if we deleted an object that had an alias pointing to it, the alias should have been deleted as well if (!hasRefreshed) { - await es.indices.refresh({ index: '.kibana' }); // alias deletion uses refresh: false, so we need to manually refresh the index before searching + await es.indices.refresh({ index: ALL_SAVED_OBJECT_INDICES }); // alias deletion uses refresh: false, so we need to manually refresh the index before searching hasRefreshed = true; } const searchResponse = await es.search({ - index: '.kibana', + index: ALL_SAVED_OBJECT_INDICES, body: { size: 0, query: { terms: { type: ['legacy-url-alias'] } }, From aae33d09192f0a6143e8d658b49235ada87cd04b Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Tue, 25 Apr 2023 10:02:07 +0200 Subject: [PATCH 24/52] [Discover] Improve the copy when reaching the limit of discover:sampleSize (#155533) Closes https://github.com/elastic/kibana/issues/154186 This PR removes a link to Advanced Settings from the copy. Screenshot 2023-04-21 at 18 01 14 --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../discover_grid/discover_grid.tsx | 19 ++----------------- .../discover/group2/_data_grid_pagination.ts | 1 - .../translations/translations/fr-FR.json | 2 -- .../translations/translations/ja-JP.json | 2 -- .../translations/translations/zh-CN.json | 2 -- 5 files changed, 2 insertions(+), 24 deletions(-) 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 d571de95f074d..2537bc6be16ee 100644 --- a/src/plugins/discover/public/components/discover_grid/discover_grid.tsx +++ b/src/plugins/discover/public/components/discover_grid/discover_grid.tsx @@ -20,7 +20,6 @@ import { EuiLoadingSpinner, EuiIcon, EuiDataGridRefProps, - EuiLink, } from '@elastic/eui'; import type { DataView } from '@kbn/data-views-plugin/public'; import type { SortOrder } from '@kbn/saved-search-plugin/public'; @@ -43,7 +42,6 @@ import { GRID_STYLE, toolbarVisibility as toolbarVisibilityDefaults } from './co import { getDisplayedColumns } from '../../utils/columns'; import { DOC_HIDE_TIME_COLUMN_SETTING, - SAMPLE_SIZE_SETTING, MAX_DOC_FIELDS_DISPLAYED, SHOW_MULTIFIELDS, } from '../../../common'; @@ -613,23 +611,10 @@ export const DiscoverGrid = ({ {showDisclaimer && (

- - - ), }} />

diff --git a/test/functional/apps/discover/group2/_data_grid_pagination.ts b/test/functional/apps/discover/group2/_data_grid_pagination.ts index c79bf291aa2e4..7da02308c73be 100644 --- a/test/functional/apps/discover/group2/_data_grid_pagination.ts +++ b/test/functional/apps/discover/group2/_data_grid_pagination.ts @@ -64,7 +64,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // footer is shown now await retry.try(async function () { await testSubjects.existOrFail('discoverTableFooter'); - await testSubjects.existOrFail('discoverTableSampleSizeSettingsLink'); }); }); diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index feb19c34d755d..d08257e729f6a 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -2151,7 +2151,6 @@ "discover.grid.copyColumnValuesToClipboard.toastTitle": "Valeurs de la colonne \"{column}\" copiées dans le presse-papiers", "discover.grid.filterForAria": "Filtrer sur cette {value}", "discover.grid.filterOutAria": "Exclure cette {value}", - "discover.gridSampleSize.description": "Vous voyez les {sampleSize} premiers documents qui correspondent à votre recherche. Pour modifier cette valeur, accédez à {advancedSettingsLink}.", "discover.howToSeeOtherMatchingDocumentsDescription": "Voici les {sampleSize} premiers documents correspondant à votre recherche. Veuillez affiner cette dernière pour en voir davantage.", "discover.noMatchRoute.bannerText": "L'application Discover ne reconnaît pas cet itinéraire : {route}", "discover.noResults.kqlExamples.kqlDescription": "En savoir plus sur {kqlLink}", @@ -2371,7 +2370,6 @@ "discover.grid.tableRow.viewSurroundingDocumentsLinkTextSimple": "Documents relatifs", "discover.grid.tableRow.viewText": "Afficher :", "discover.grid.viewDoc": "Afficher/Masquer les détails de la boîte de dialogue", - "discover.gridSampleSize.advancedSettingsLinkLabel": "Paramètres avancés", "discover.helpMenu.appName": "Découverte", "discover.inspectorRequestDataTitleDocuments": "Documents", "discover.inspectorRequestDescriptionDocument": "Cette requête interroge Elasticsearch afin de récupérer les documents.", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 8ccd8277ac4eb..1ebc1aeae602e 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -2151,7 +2151,6 @@ "discover.grid.copyColumnValuesToClipboard.toastTitle": "\"{column}\"列の値がクリップボードにコピーされました", "discover.grid.filterForAria": "この{value}でフィルターを適用", "discover.grid.filterOutAria": "この{value}を除外", - "discover.gridSampleSize.description": "検索と一致する最初の{sampleSize}ドキュメントを表示しています。この値を変更するには、{advancedSettingsLink}に移動します。", "discover.howToSeeOtherMatchingDocumentsDescription": "これらは検索条件に一致した初めの{sampleSize}件のドキュメントです。他の結果を表示するには検索条件を絞ってください。", "discover.noMatchRoute.bannerText": "Discoverアプリケーションはこのルートを認識できません:{route}", "discover.noResults.kqlExamples.kqlDescription": "{kqlLink}の詳細", @@ -2371,7 +2370,6 @@ "discover.grid.tableRow.viewSurroundingDocumentsLinkTextSimple": "周りのドキュメント", "discover.grid.tableRow.viewText": "表示:", "discover.grid.viewDoc": "詳細ダイアログを切り替え", - "discover.gridSampleSize.advancedSettingsLinkLabel": "高度な設定", "discover.helpMenu.appName": "Discover", "discover.inspectorRequestDataTitleDocuments": "ドキュメント", "discover.inspectorRequestDescriptionDocument": "このリクエストはElasticsearchにクエリをかけ、ドキュメントを取得します。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index f74700f6ed57e..0c2b495e1e3fa 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -2151,7 +2151,6 @@ "discover.grid.copyColumnValuesToClipboard.toastTitle": "“{column}”列的值已复制到剪贴板", "discover.grid.filterForAria": "筛留此 {value}", "discover.grid.filterOutAria": "筛除此 {value}", - "discover.gridSampleSize.description": "您正查看与您的搜索相匹配的前 {sampleSize} 个文档。要更改此值,请前往 {advancedSettingsLink}", "discover.howToSeeOtherMatchingDocumentsDescription": "以下是匹配您的搜索的前 {sampleSize} 个文档,请优化您的搜索以查看其他文档。", "discover.noMatchRoute.bannerText": "Discover 应用程序无法识别此路由:{route}", "discover.noResults.kqlExamples.kqlDescription": "详细了解 {kqlLink}", @@ -2371,7 +2370,6 @@ "discover.grid.tableRow.viewSurroundingDocumentsLinkTextSimple": "周围文档", "discover.grid.tableRow.viewText": "视图:", "discover.grid.viewDoc": "切换具有详情的对话框", - "discover.gridSampleSize.advancedSettingsLinkLabel": "高级设置", "discover.helpMenu.appName": "Discover", "discover.inspectorRequestDataTitleDocuments": "文档", "discover.inspectorRequestDescriptionDocument": "此请求将查询 Elasticsearch 以获取文档。", From 2a6f46a93db5d50028ddfa65ed3e4f7356cd4cf3 Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Tue, 25 Apr 2023 10:02:58 +0200 Subject: [PATCH 25/52] [Discover] Show loading indicator during Discover table updates (#155505) Related to https://github.com/elastic/kibana/pull/152342 ## Summary This PR adds a horizontal loading indicator to Discover table view. It will appear when data is being refetched. Wdyt?
Preview ![main](https://user-images.githubusercontent.com/1415710/233641932-8514593c-e51c-4502-a226-b0e4df34313e.gif) ![sql](https://user-images.githubusercontent.com/1415710/233641936-2e99b0c5-de24-4133-bd7f-2b7f8c5695e2.gif)
--- .../main/components/layout/discover_documents.tsx | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) 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 9804a093113a7..77d6e36715dd7 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 @@ -9,11 +9,13 @@ import React, { memo, useCallback, useMemo } from 'react'; import { EuiFlexItem, EuiLoadingSpinner, + EuiProgress, EuiScreenReaderOnly, EuiSpacer, EuiText, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; +import { css } from '@emotion/react'; import { DataView } from '@kbn/data-views-plugin/public'; import { SavedSearch, SortOrder } from '@kbn/saved-search-plugin/public'; import { DataTableRecord } from '../../../../types'; @@ -42,6 +44,14 @@ import { getRawRecordType } from '../../utils/get_raw_record_type'; import { DiscoverGridFlyout } from '../../../../components/discover_grid/discover_grid_flyout'; import { DocViewer } from '../../../../services/doc_views/components/doc_viewer'; +const containerStyles = css` + position: relative; +`; + +const progressStyle = css` + z-index: 2; +`; + const DocTableInfiniteMemoized = React.memo(DocTableInfinite); const DataGridMemoized = React.memo(DiscoverGrid); @@ -182,7 +192,7 @@ function DiscoverDocumentsComponent({ } return ( - +

@@ -253,6 +263,9 @@ function DiscoverDocumentsComponent({

)} + {isDataLoading && ( + + )} ); } From c5d636437382e24c988a81f759bd9f1c0853dbca Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Tue, 25 Apr 2023 10:06:11 +0200 Subject: [PATCH 26/52] [UnifiedFieldList] Allow wildcards in field search (#155540) Closes https://github.com/elastic/kibana/issues/97459 ## Summary This PR allows to search by field names with wildcard. Screenshot 2023-04-24 at 10 47 18 ### 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 --- .../field_item_button.test.tsx.snap | 29 ++++++++++++++ .../field_item_button.test.tsx | 14 +++++++ .../field_item_button/field_item_button.tsx | 15 +++++++- .../public/hooks/use_field_filters.test.tsx | 27 +++++++++++++ .../public/hooks/use_field_filters.ts | 7 +--- .../field_name_wildcard_matcher.test.tsx | 38 +++++++++++++++++++ .../utils/field_name_wildcard_matcher.ts | 34 +++++++++++++++++ 7 files changed, 158 insertions(+), 6 deletions(-) create mode 100644 src/plugins/unified_field_list/public/utils/field_name_wildcard_matcher.test.tsx create mode 100644 src/plugins/unified_field_list/public/utils/field_name_wildcard_matcher.ts diff --git a/src/plugins/unified_field_list/public/components/field_item_button/__snapshots__/field_item_button.test.tsx.snap b/src/plugins/unified_field_list/public/components/field_item_button/__snapshots__/field_item_button.test.tsx.snap index d1068660c1eaa..899d6a7579123 100644 --- a/src/plugins/unified_field_list/public/components/field_item_button/__snapshots__/field_item_button.test.tsx.snap +++ b/src/plugins/unified_field_list/public/components/field_item_button/__snapshots__/field_item_button.test.tsx.snap @@ -88,6 +88,35 @@ exports[`UnifiedFieldList renders properly for text-based co /> `; +exports[`UnifiedFieldList renders properly for wildcard search 1`] = ` + + } + fieldName={ + + script date + + } + isActive={false} + key="field-item-button-script date" + size="s" +/> +`; + exports[`UnifiedFieldList renders properly when a conflict field 1`] = ` ', () => { ); expect(component).toMatchSnapshot(); }); + + test('renders properly for wildcard search', () => { + const component = shallow( + + ); + expect(component).toMatchSnapshot(); + }); }); diff --git a/src/plugins/unified_field_list/public/components/field_item_button/field_item_button.tsx b/src/plugins/unified_field_list/public/components/field_item_button/field_item_button.tsx index df24125027e2f..ec0078431a8bc 100644 --- a/src/plugins/unified_field_list/public/components/field_item_button/field_item_button.tsx +++ b/src/plugins/unified_field_list/public/components/field_item_button/field_item_button.tsx @@ -14,6 +14,7 @@ import { EuiButtonIcon, EuiButtonIconProps, EuiHighlight, EuiIcon, EuiToolTip } import type { DataViewField } from '@kbn/data-views-plugin/common'; import { type FieldListItem, type GetCustomFieldType } from '../../types'; import { FieldIcon, getFieldIconProps } from '../field_icon'; +import { fieldNameWildcardMatcher } from '../../utils/field_name_wildcard_matcher'; import './field_item_button.scss'; /** @@ -194,7 +195,7 @@ export function FieldItemButton({ fieldIcon={} fieldName={ @@ -230,3 +231,15 @@ function FieldConflictInfoIcon() { ); } + +function getSearchHighlight(displayName: string, fieldSearchHighlight?: string): string { + const searchHighlight = fieldSearchHighlight || ''; + if ( + searchHighlight.includes('*') && + fieldNameWildcardMatcher({ name: displayName }, searchHighlight) + ) { + return displayName; + } + + return searchHighlight; +} diff --git a/src/plugins/unified_field_list/public/hooks/use_field_filters.test.tsx b/src/plugins/unified_field_list/public/hooks/use_field_filters.test.tsx index 77327fcbee402..1295d14f374b2 100644 --- a/src/plugins/unified_field_list/public/hooks/use_field_filters.test.tsx +++ b/src/plugins/unified_field_list/public/hooks/use_field_filters.test.tsx @@ -68,6 +68,33 @@ describe('UnifiedFieldList useFieldFilters()', () => { expect(result.current.onFilterField!(dataView.getFieldByName('bytes')!)).toBe(false); }); + it('should update correctly on search by name which has a wildcard', async () => { + const props: FieldFiltersParams = { + allFields: dataView.fields, + services: mockedServices, + }; + const { result } = renderHook(useFieldFilters, { + initialProps: props, + }); + + expect(result.current.fieldSearchHighlight).toBe(''); + expect(result.current.onFilterField).toBeUndefined(); + + act(() => { + result.current.fieldListFiltersProps.onChangeNameFilter('message*me1'); + }); + + expect(result.current.fieldSearchHighlight).toBe('message*me1'); + expect(result.current.onFilterField).toBeDefined(); + expect(result.current.onFilterField!({ displayName: 'test' } as DataViewField)).toBe(false); + expect(result.current.onFilterField!({ displayName: 'message' } as DataViewField)).toBe(false); + expect(result.current.onFilterField!({ displayName: 'message.name1' } as DataViewField)).toBe( + true + ); + expect(result.current.onFilterField!({ name: 'messagename10' } as DataViewField)).toBe(false); + expect(result.current.onFilterField!({ name: 'message.test' } as DataViewField)).toBe(false); + }); + it('should update correctly on filter by type', async () => { const props: FieldFiltersParams = { allFields: dataView.fields, diff --git a/src/plugins/unified_field_list/public/hooks/use_field_filters.ts b/src/plugins/unified_field_list/public/hooks/use_field_filters.ts index 803739caba7c6..c3e08ff335602 100644 --- a/src/plugins/unified_field_list/public/hooks/use_field_filters.ts +++ b/src/plugins/unified_field_list/public/hooks/use_field_filters.ts @@ -13,6 +13,7 @@ import type { CoreStart } from '@kbn/core-lifecycle-browser'; import { type FieldListFiltersProps } from '../components/field_list_filters'; import { type FieldListItem, type FieldTypeKnown, GetCustomFieldType } from '../types'; import { getFieldIconType } from '../utils/field_types'; +import { fieldNameWildcardMatcher } from '../utils/field_name_wildcard_matcher'; const htmlId = htmlIdGenerator('fieldList'); @@ -74,11 +75,7 @@ export function useFieldFilters({ onFilterField: fieldSearchHighlight?.length || selectedFieldTypes.length > 0 ? (field: T) => { - if ( - fieldSearchHighlight?.length && - !field.name?.toLowerCase().includes(fieldSearchHighlight) && - !field.displayName?.toLowerCase().includes(fieldSearchHighlight) - ) { + if (fieldSearchHighlight && !fieldNameWildcardMatcher(field, fieldSearchHighlight)) { return false; } if (selectedFieldTypes.length > 0) { diff --git a/src/plugins/unified_field_list/public/utils/field_name_wildcard_matcher.test.tsx b/src/plugins/unified_field_list/public/utils/field_name_wildcard_matcher.test.tsx new file mode 100644 index 0000000000000..2637ddf4046d2 --- /dev/null +++ b/src/plugins/unified_field_list/public/utils/field_name_wildcard_matcher.test.tsx @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { type DataViewField } from '@kbn/data-views-plugin/common'; +import { fieldNameWildcardMatcher } from './field_name_wildcard_matcher'; + +describe('UnifiedFieldList fieldNameWildcardMatcher()', () => { + it('should work correctly', async () => { + expect(fieldNameWildcardMatcher({ displayName: 'test' } as DataViewField, 'no')).toBe(false); + expect( + fieldNameWildcardMatcher({ displayName: 'test', name: 'yes' } as DataViewField, 'yes') + ).toBe(true); + + const search = 'test*ue'; + expect(fieldNameWildcardMatcher({ displayName: 'test' } as DataViewField, search)).toBe(false); + expect(fieldNameWildcardMatcher({ displayName: 'test.value' } as DataViewField, search)).toBe( + true + ); + expect(fieldNameWildcardMatcher({ name: 'test.this_value' } as DataViewField, search)).toBe( + true + ); + expect(fieldNameWildcardMatcher({ name: 'message.test' } as DataViewField, search)).toBe(false); + expect( + fieldNameWildcardMatcher({ name: 'test.this_value.maybe' } as DataViewField, search) + ).toBe(false); + expect( + fieldNameWildcardMatcher({ name: 'test.this_value.maybe' } as DataViewField, `${search}*`) + ).toBe(true); + expect( + fieldNameWildcardMatcher({ name: 'test.this_value.maybe' } as DataViewField, '*value*') + ).toBe(true); + }); +}); diff --git a/src/plugins/unified_field_list/public/utils/field_name_wildcard_matcher.ts b/src/plugins/unified_field_list/public/utils/field_name_wildcard_matcher.ts new file mode 100644 index 0000000000000..98b0e64b7bc78 --- /dev/null +++ b/src/plugins/unified_field_list/public/utils/field_name_wildcard_matcher.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 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 { escapeRegExp, memoize } from 'lodash'; + +const makeRegEx = memoize(function makeRegEx(glob: string) { + const globRegex = glob.split('*').map(escapeRegExp).join('.*'); + return new RegExp(globRegex.includes('*') ? `^${globRegex}$` : globRegex, 'i'); +}); + +/** + * Checks if field displayName or name matches the provided search string. + * The search string can have wildcard. + * @param field + * @param fieldSearchHighlight + */ +export const fieldNameWildcardMatcher = ( + field: { name: string; displayName?: string }, + fieldSearchHighlight: string +): boolean => { + if (!fieldSearchHighlight) { + return false; + } + + return ( + (!!field.displayName && makeRegEx(fieldSearchHighlight).test(field.displayName)) || + makeRegEx(fieldSearchHighlight).test(field.name) + ); +}; From d01fe0777bed573cd2a795b73276e9733a78dd28 Mon Sep 17 00:00:00 2001 From: Joseph McElroy Date: Tue, 25 Apr 2023 09:26:36 +0100 Subject: [PATCH 27/52] [Behavioral Analytics] Fixes to Overview events, Integration Page (#155646) Number of fixes to issues I spotted whilst testing behavioral Analytics **Integration Page** - [x] Update the Enterprise search URL for Elasticsearch URL - [x] Add "requires CORS" explanation in the integration page - [x] update the CDN script URL ![image](https://user-images.githubusercontent.com/49480/234048809-859d1ce2-85ef-479e-a449-27896cdcc52d.png) **Graphs + tables** - [x] Search counts are incorrect (search_click is counted as a search) - [x] Search queries are not narrowed to `search` events **Integration Onboarding experience** - [x] Bug: Install our tracker banner is still present even with events - [x] Hide the banner until loading is complete to whether there are any events in the index. ![image](https://user-images.githubusercontent.com/49480/234049110-26ef3ab4-e3e8-473e-9193-ce7b3ba18af6.png) ![image](https://user-images.githubusercontent.com/49480/234049458-12cc42dd-e586-4435-9c14-3ba774c09af7.png) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../common/types/analytics.ts | 2 +- ...ytics_collection_explore_table_formulas.ts | 7 +- ...nalytics_collection_explore_table_logic.ts | 8 +- .../analytics_collection_integrate.test.tsx | 22 +++-- ..._collection_integrate_javascript_embed.tsx | 10 ++- ...nalytics_collection_integrate_searchui.tsx | 2 +- .../analytics_collection_integrate_view.tsx | 82 +++++++++++++++++-- ...analytics_collection_no_events_callout.tsx | 6 +- ...collection_no_events_callout_logic.test.ts | 6 +- ...ics_collection_no_events_callout_logic.tsx | 12 ++- .../utils/get_formula_by_filter.test.ts | 6 +- .../analytics/utils/get_formula_by_filter.ts | 4 +- .../routes/enterprise_search/analytics.ts | 7 +- 13 files changed, 134 insertions(+), 40 deletions(-) diff --git a/x-pack/plugins/enterprise_search/common/types/analytics.ts b/x-pack/plugins/enterprise_search/common/types/analytics.ts index d50b4dc6e98c0..6d9db14ff729c 100644 --- a/x-pack/plugins/enterprise_search/common/types/analytics.ts +++ b/x-pack/plugins/enterprise_search/common/types/analytics.ts @@ -11,7 +11,7 @@ export interface AnalyticsCollection { } export interface AnalyticsEventsExist { - exist: boolean; + exists: boolean; } export interface AnalyticsCollectionDataViewId { diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table_formulas.ts b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table_formulas.ts index b628d1d8e633f..87e9812b94ac7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table_formulas.ts +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table_formulas.ts @@ -45,7 +45,11 @@ export const getPaginationRequestParams = (pageIndex: number, pageSize: number) export const getBaseSearchTemplate = ( aggregationFieldName: string, - { search, timeRange }: { search: string; timeRange: TimeRange }, + { + search, + timeRange, + eventType, + }: { search: string; timeRange: TimeRange; eventType?: 'search' | 'search_click' | 'page_view' }, aggs: IKibanaSearchRequest['params']['aggs'] ): IKibanaSearchRequest => ({ params: { @@ -61,6 +65,7 @@ export const getBaseSearchTemplate = ( }, }, }, + ...(eventType ? [{ term: { 'event.action': eventType } }] : []), ...(search ? [getSearchQueryRequestParams(aggregationFieldName, search)] : []), ], }, diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table_logic.ts b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table_logic.ts index cce07541a9a2e..101ebdb7a76bf 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table_logic.ts @@ -80,7 +80,7 @@ const tablesParams: { ) => getBaseSearchTemplate( aggregationFieldName, - { search, timeRange }, + { search, timeRange, eventType: 'search' }, { searches: { terms: { @@ -122,7 +122,7 @@ const tablesParams: { ) => getBaseSearchTemplate( aggregationFieldName, - { search, timeRange }, + { search, timeRange, eventType: 'search' }, { formula: { aggs: { @@ -169,7 +169,7 @@ const tablesParams: { ) => getBaseSearchTemplate( aggregationFieldName, - { search, timeRange }, + { search, timeRange, eventType: 'search_click' }, { formula: { aggs: { @@ -216,7 +216,7 @@ const tablesParams: { ) => getBaseSearchTemplate( aggregationFieldName, - { search, timeRange }, + { search, timeRange, eventType: 'page_view' }, { formula: { aggs: { diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate/analytics_collection_integrate.test.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate/analytics_collection_integrate.test.tsx index f1c66a8cedbb6..df2dbade8c852 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate/analytics_collection_integrate.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate/analytics_collection_integrate.test.tsx @@ -36,28 +36,32 @@ describe('AnalyticsCollectionIntegrate', () => { const wrapper = shallow( ); - expect(wrapper.find(EuiSteps).dive().find(EuiCodeBlock)).toHaveLength(3); + expect(wrapper.find(EuiSteps).dive().find(EuiCodeBlock)).toHaveLength(4); wrapper.find('[data-test-subj="searchuiEmbed"]').at(0).simulate('click'); expect(wrapper.find(EuiSteps).dive().find(EuiCodeBlock)).toHaveLength(3); wrapper.find('[data-test-subj="javascriptClientEmbed"]').at(0).simulate('click'); - expect(wrapper.find(EuiSteps).dive().find(EuiCodeBlock)).toHaveLength(5); + expect(wrapper.find(EuiSteps).dive().find(EuiCodeBlock)).toHaveLength(6); }); it('check value of config & webClientSrc', () => { const wrapper = shallow( ); - expect(wrapper.find(EuiSteps).dive().find(EuiCodeBlock).at(0).dive().text()).toContain( - 'https://cdn.jsdelivr.net/npm/@elastic/behavioral-analytics-browser-tracker@2/dist/umd/index.global.js' + expect(wrapper.find(EuiSteps).dive().find(EuiCodeBlock).at(1).dive().text()).toContain( + 'https://cdn.jsdelivr.net/npm/@elastic/behavioral-analytics-browser-tracker@2' ); - expect(wrapper.find(EuiSteps).dive().find(EuiCodeBlock).at(1).dive().text()) + expect(wrapper.find(EuiSteps).dive().find(EuiCodeBlock).at(2).dive().text()) .toMatchInlineSnapshot(` - "" + apiKey: \\"########\\", + // Optional: sampling rate percentage: 0-1, 0 = no events, 1 = all events + // sampling: 1, + }); + " `); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate/analytics_collection_integrate_javascript_embed.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate/analytics_collection_integrate_javascript_embed.tsx index 632a6626404cd..b44b19042d60c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate/analytics_collection_integrate_javascript_embed.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate/analytics_collection_integrate_javascript_embed.tsx @@ -63,11 +63,15 @@ export const javascriptEmbedSteps = (webClientSrc: string, analyticsConfig: Anal )}

- {``} + apiKey: "${analyticsConfig.apiKey}", + // Optional: sampling rate percentage: 0-1, 0 = no events, 1 = all events + // sampling: 1, +}); +`} diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate/analytics_collection_integrate_searchui.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate/analytics_collection_integrate_searchui.tsx index c52fbdbff01f5..74a08f90b1178 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate/analytics_collection_integrate_searchui.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate/analytics_collection_integrate_searchui.tsx @@ -118,7 +118,7 @@ const searchUIConfig = { ... plugins: [ AnalyticsPlugin({ - client: getTracker(); + client: getTracker() }) ], ... diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate/analytics_collection_integrate_view.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate/analytics_collection_integrate_view.tsx index 550a16e7a78cc..2b69286d43f77 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate/analytics_collection_integrate_view.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate/analytics_collection_integrate_view.tsx @@ -19,6 +19,7 @@ import { EuiTabs, EuiLink, EuiText, + EuiCodeBlock, } from '@elastic/eui'; import { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps'; @@ -26,10 +27,10 @@ import { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps'; import { i18n } from '@kbn/i18n'; import { AnalyticsCollection } from '../../../../../../common/types/analytics'; +import { useCloudDetails } from '../../../../shared/cloud_details/cloud_details'; +import { decodeCloudId } from '../../../../shared/decode_cloud_id/decode_cloud_id'; import { docLinks } from '../../../../shared/doc_links'; -import { getEnterpriseSearchUrl } from '../../../../shared/enterprise_search_url'; - import { KibanaLogic } from '../../../../shared/kibana'; import { EnterpriseSearchAnalyticsPageTemplate } from '../../layout/page_template'; @@ -51,6 +52,62 @@ export interface AnalyticsConfig { endpoint: string; } +const CORSStep = (): EuiContainedStepProps => ({ + title: i18n.translate( + 'xpack.enterpriseSearch.analytics.collections.collectionsView.corsStep.title', + { + defaultMessage: 'Configure CORS', + } + ), + children: ( + <> + + <> +

+ {i18n.translate( + 'xpack.enterpriseSearch.analytics.collectionsView.integration.corsStep.description', + { + defaultMessage: + "You must configure CORS to allow requests from your website's domain to the Analytics API endpoint. You can do this by adding the following to your Elasticsearch configuration file:", + } + )} +

+ + + {`http.cors.allow-origin: "*" +http.cors.enabled: true +http.cors.allow-credentials: true +http.cors.allow-methods: OPTIONS, POST +http.cors.allow-headers: X-Requested-With, X-Auth-Token, Content-Type, Content-Length, Authorization, Access-Control-Allow-Headers, Accept`} + +

+ {i18n.translate( + 'xpack.enterpriseSearch.analytics.collectionsView.integration.corsStep.descriptionTwo', + { + defaultMessage: + "Alternatively you can use a proxy server to route analytic requests from your website's domain to the Analytics API endpoint which will allow you to avoid configuring CORS.", + } + )} +

+ + {i18n.translate( + 'xpack.enterpriseSearch.analytics.collectionsView.integration.corsStep.learnMoreLink', + { + defaultMessage: 'Learn more about CORS for Behavioral Analytics.', + } + )} + + +
+ + ), +}); + const apiKeyStep = ( openApiKeyModal: () => void, navigateToUrl: typeof KibanaLogic.values.navigateToUrl @@ -135,13 +192,18 @@ export const AnalyticsCollectionIntegrateView: React.FC(false); const { navigateToUrl } = useValues(KibanaLogic); const { apiKey } = useValues(GenerateApiKeyModalLogic); + const DEFAULT_URL = 'https://localhost:9200'; + const cloudContext = useCloudDetails(); + + const baseUrl = + (cloudContext.cloudId && decodeCloudId(cloudContext.cloudId)?.elasticsearchUrl) || DEFAULT_URL; const analyticsConfig: AnalyticsConfig = { apiKey: apiKey || '########', collectionName: analyticsCollection?.name, - endpoint: getEnterpriseSearchUrl(), + endpoint: baseUrl, }; - const webClientSrc = `https://cdn.jsdelivr.net/npm/@elastic/behavioral-analytics-browser-tracker@2/dist/umd/index.global.js`; + const webClientSrc = `https://cdn.jsdelivr.net/npm/@elastic/behavioral-analytics-browser-tracker@2`; const tabs: Array<{ key: TabKey; @@ -179,8 +241,16 @@ export const AnalyticsCollectionIntegrateView: React.FC setApiKeyModalOpen(true), navigateToUrl); const steps: Record = { - javascriptClientEmbed: [apiKeyStepGuide, ...javascriptClientEmbedSteps(analyticsConfig)], - javascriptEmbed: [apiKeyStepGuide, ...javascriptEmbedSteps(webClientSrc, analyticsConfig)], + javascriptClientEmbed: [ + apiKeyStepGuide, + CORSStep(), + ...javascriptClientEmbedSteps(analyticsConfig), + ], + javascriptEmbed: [ + apiKeyStepGuide, + CORSStep(), + ...javascriptEmbedSteps(webClientSrc, analyticsConfig), + ], searchuiEmbed: searchUIEmbedSteps(setSelectedTab), }; diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_no_events_callout/analytics_collection_no_events_callout.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_no_events_callout/analytics_collection_no_events_callout.tsx index 7e5ed567bb846..1340a5105dae4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_no_events_callout/analytics_collection_no_events_callout.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_no_events_callout/analytics_collection_no_events_callout.tsx @@ -31,13 +31,13 @@ export const AnalyticsCollectionNoEventsCallout: React.FC< > = ({ analyticsCollection }) => { const { navigateToUrl } = useValues(KibanaLogic); const { analyticsEventsExist } = useActions(AnalyticsCollectionNoEventsCalloutLogic); - const { hasEvents } = useValues(AnalyticsCollectionNoEventsCalloutLogic); + const { hasEvents, isLoading } = useValues(AnalyticsCollectionNoEventsCalloutLogic); useEffect(() => { - analyticsEventsExist(analyticsCollection.name); + analyticsEventsExist(analyticsCollection.events_datastream); }, []); - return hasEvents ? null : ( + return hasEvents || isLoading ? null : ( { const DEFAULT_VALUES = { data: undefined, hasEvents: false, + isLoading: false, status: Status.IDLE, }; @@ -33,13 +34,14 @@ describe('analyticsEventsExistLogic', () => { describe('selectors', () => { it('updates when apiSuccess listener triggered', () => { - AnalyticsCollectionNoEventsCalloutLogic.actions.apiSuccess({ exist: indexName }); + AnalyticsCollectionNoEventsCalloutLogic.actions.apiSuccess({ exists: indexName }); expect(AnalyticsCollectionNoEventsCalloutLogic.values).toEqual({ ...DEFAULT_VALUES, + data: { exists: indexName }, hasEvents: true, + isLoading: false, status: Status.SUCCESS, - data: { exist: indexName }, }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_no_events_callout/analytics_collection_no_events_callout_logic.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_no_events_callout/analytics_collection_no_events_callout_logic.tsx index c63ddc97cc536..680c4715621a2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_no_events_callout/analytics_collection_no_events_callout_logic.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_no_events_callout/analytics_collection_no_events_callout_logic.tsx @@ -22,6 +22,7 @@ export interface AnalyticsCollectionNoEventsCalloutActions { export interface AnalyticsCollectionNoEventsCalloutValues { hasEvents: boolean; status: Status; + isLoading: boolean; data: typeof AnalyticsEventsExistAPILogic.values.data; } @@ -40,8 +41,15 @@ export const AnalyticsCollectionNoEventsCalloutLogic = kea< actions.makeRequest({ indexName }); }, }), - path: ['enterprise_search', 'analytics', 'events_exist'], + path: ['enterprise_search', 'analytics', 'collection', 'events_exist'], selectors: ({ selectors }) => ({ - hasEvents: [() => [selectors.data], (data) => data?.exist === true], + hasEvents: [ + () => [selectors.data], + (data: AnalyticsCollectionNoEventsCalloutValues['data']) => data?.exists === true, + ], + isLoading: [ + () => [selectors.status], + (status: AnalyticsCollectionNoEventsCalloutValues['status']) => status === Status.LOADING, + ], }), }); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/utils/get_formula_by_filter.test.ts b/x-pack/plugins/enterprise_search/public/applications/analytics/utils/get_formula_by_filter.test.ts index 5ced11f776aab..a50500f751472 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/utils/get_formula_by_filter.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/utils/get_formula_by_filter.test.ts @@ -10,12 +10,14 @@ import { FilterBy, getFormulaByFilter } from './get_formula_by_filter'; describe('getFormulaByFilter', () => { test('should return the correct formula for Searches filter without shift', () => { const formula = getFormulaByFilter(FilterBy.Searches); - expect(formula).toBe('count(search.query)'); + expect(formula).toBe("count(search.query, kql='event.action: search')"); }); test('should return the correct formula for NoResults filter with shift', () => { const formula = getFormulaByFilter(FilterBy.NoResults, '1d'); - expect(formula).toBe("count(kql='search.results.total_results : 0', shift='1d')"); + expect(formula).toBe( + "count(kql='search.results.total_results : 0 and event.action: search', shift='1d')" + ); }); test('should return the correct formula for Clicks filter without shift', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/utils/get_formula_by_filter.ts b/x-pack/plugins/enterprise_search/public/applications/analytics/utils/get_formula_by_filter.ts index 2ef4ce5790459..9895f3a0ddc55 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/utils/get_formula_by_filter.ts +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/utils/get_formula_by_filter.ts @@ -13,8 +13,8 @@ export enum FilterBy { } export const getFormulaByFilter = (filter: FilterBy, shift?: string): string => { const mapFilterByToFormula: { [key in FilterBy]: string } = { - [FilterBy.Searches]: 'count(search.query', - [FilterBy.NoResults]: "count(kql='search.results.total_results : 0'", + [FilterBy.Searches]: "count(search.query, kql='event.action: search'", + [FilterBy.NoResults]: "count(kql='search.results.total_results : 0 and event.action: search'", [FilterBy.Clicks]: "count(kql='event.action: search_click'", [FilterBy.Sessions]: 'unique_count(session.id', }; diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/analytics.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/analytics.ts index 4bc99895a6d44..48a7a68084dea 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/analytics.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/analytics.ts @@ -12,6 +12,7 @@ import { SavedObjectsServiceStart } from '@kbn/core-saved-objects-server'; import { DataPluginStart } from '@kbn/data-plugin/server/plugin'; import { i18n } from '@kbn/i18n'; +import { AnalyticsEventsExist } from '../../../common/types/analytics'; import { ErrorCode } from '../../../common/types/error_codes'; import { addAnalyticsCollection } from '../../lib/analytics/add_analytics_collection'; import { analyticsEventsExist } from '../../lib/analytics/analytics_events_exist'; @@ -193,11 +194,9 @@ export function registerAnalyticsRoutes({ const eventsIndexExists = await analyticsEventsExist(client, request.params.name); - if (!eventsIndexExists) { - return response.ok({ body: { exists: false } }); - } + const body: AnalyticsEventsExist = { exists: eventsIndexExists }; - return response.ok({ body: { exists: true } }); + return response.ok({ body }); }) ); } From 7c920d18d5e1ce815581c229b99d5eebc5b08a85 Mon Sep 17 00:00:00 2001 From: Yan Savitski Date: Tue, 25 Apr 2023 10:41:58 +0200 Subject: [PATCH 28/52] [Enterprise Search] [Behavioral analytics] Add search to collection list (#155524) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ✔️ Implement search for collection list Page ✔️ Add search query to fetch collection request ✔️ Update server-side to support new search query image --------- Co-authored-by: Joseph McElroy --- ...ch_analytics_collections_api_logic.test.ts | 6 +- .../fetch_analytics_collections_api_logic.ts | 14 ++- .../analytics_collection_data_view_logic.ts | 4 +- ...ics_collection_explore_table_logic.test.ts | 17 +++- ...nalytics_collection_explore_table_logic.ts | 47 +++++++--- .../analytics_collection_explorer_table.tsx | 1 + .../analytics_collection_toolbar_logic.ts | 2 +- .../delete_analytics_collection_logic.ts | 2 +- .../analytics_collection_not_found.tsx | 43 +++++++++ .../analytics_collection_table.test.tsx | 22 +++-- .../analytics_collection_table.tsx | 53 +++++++---- .../analytics_collections_logic.test.ts | 93 +++++++++++++++++-- .../analytics_collections_logic.ts | 49 ++++++++-- .../analytics_overview/analytics_overview.tsx | 15 ++- .../enterprise_search/analytics.test.ts | 74 +++++++++++++++ .../routes/enterprise_search/analytics.ts | 19 +++- 16 files changed, 395 insertions(+), 66 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_overview/analytics_collection_not_found.tsx diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/api/index/fetch_analytics_collections_api_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/analytics/api/index/fetch_analytics_collections_api_logic.test.ts index 08478bf32b362..15d73d21ff890 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/api/index/fetch_analytics_collections_api_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/api/index/fetch_analytics_collections_api_logic.test.ts @@ -21,9 +21,11 @@ describe('FetchAnalyticsCollectionsApiLogic', () => { it('calls the analytics collections list api', async () => { const promise = Promise.resolve([{ name: 'result' }]); http.get.mockReturnValue(promise); - const result = fetchAnalyticsCollections(); + const result = fetchAnalyticsCollections({}); await nextTick(); - expect(http.get).toHaveBeenCalledWith('/internal/enterprise_search/analytics/collections'); + expect(http.get).toHaveBeenCalledWith('/internal/enterprise_search/analytics/collections', { + query: { query: '' }, + }); await expect(result).resolves.toEqual([{ name: 'result' }]); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/api/index/fetch_analytics_collections_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/analytics/api/index/fetch_analytics_collections_api_logic.ts index 399038a776c10..45567d9202639 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/api/index/fetch_analytics_collections_api_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/api/index/fetch_analytics_collections_api_logic.ts @@ -12,10 +12,20 @@ import { HttpLogic } from '../../../shared/http'; export type FetchAnalyticsCollectionsApiLogicResponse = AnalyticsCollection[]; -export const fetchAnalyticsCollections = async () => { +interface FetchAnalyticsCollectionsApiLogicArgs { + query?: string; +} + +export const fetchAnalyticsCollections = async ({ + query = '', +}: FetchAnalyticsCollectionsApiLogicArgs) => { const { http } = HttpLogic.values; const route = '/internal/enterprise_search/analytics/collections'; - const response = await http.get(route); + const response = await http.get(route, { + query: { + query, + }, + }); return response; }; diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_data_view_logic.ts b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_data_view_logic.ts index 8d53757db78b6..f00ef0fecc1ed 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_data_view_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_data_view_logic.ts @@ -16,7 +16,7 @@ import { FetchAnalyticsCollectionLogic, } from './fetch_analytics_collection_logic'; -interface AnalyticsCollectionDataViewLogicValues { +export interface AnalyticsCollectionDataViewLogicValues { dataView: DataView | null; } @@ -39,7 +39,7 @@ export const AnalyticsCollectionDataViewLogic = kea< actions.setDataView(await findOrCreateDataView(collection)); }, }), - path: ['enterprise_search', 'analytics', 'collections', 'dataView'], + path: ['enterprise_search', 'analytics', 'collection', 'dataView'], reducers: () => ({ dataView: [null, { setDataView: (_, { dataView }) => dataView }], }), diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table_logic.test.ts index 6f02ab06fedfb..226c521c44894 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table_logic.test.ts @@ -7,6 +7,8 @@ import { LogicMounter } from '../../../__mocks__/kea_logic'; +import { nextTick } from '@kbn/test-jest-helpers'; + import { KibanaLogic } from '../../../shared/kibana/kibana_logic'; import { @@ -38,6 +40,7 @@ describe('AnalyticsCollectionExplorerTablesLogic', () => { }); const defaultProps = { + dataView: null, isLoading: false, items: [], pageIndex: 0, @@ -45,6 +48,10 @@ describe('AnalyticsCollectionExplorerTablesLogic', () => { search: '', selectedTable: null, sorting: null, + timeRange: { + from: 'now-7d', + to: 'now', + }, totalItemsCount: 0, }; @@ -79,6 +86,10 @@ describe('AnalyticsCollectionExplorerTablesLogic', () => { }); describe('isLoading', () => { + beforeEach(() => { + mount({ selectedTable: ExploreTables.TopReferrers }); + }); + it('should handle onTableChange', () => { AnalyticsCollectionExploreTableLogic.actions.onTableChange({ page: { index: 2, size: 10 }, @@ -241,11 +252,15 @@ describe('AnalyticsCollectionExplorerTablesLogic', () => { }); }); - it('should fetch items when search changes', () => { + it('should fetch items when search changes', async () => { + jest.useFakeTimers({ legacyFakeTimers: true }); AnalyticsCollectionExploreTableLogic.actions.setSelectedTable(ExploreTables.WorsePerformers); (KibanaLogic.values.data.search.search as jest.Mock).mockClear(); AnalyticsCollectionExploreTableLogic.actions.setSearch('test'); + jest.advanceTimersByTime(200); + await nextTick(); + expect(KibanaLogic.values.data.search.search).toHaveBeenCalledWith(expect.any(Object), { indexPattern: undefined, sessionId: undefined, diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table_logic.ts b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table_logic.ts index 101ebdb7a76bf..e5e181ccfa266 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table_logic.ts @@ -16,7 +16,10 @@ import { import { KibanaLogic } from '../../../shared/kibana/kibana_logic'; -import { AnalyticsCollectionDataViewLogic } from './analytics_collection_data_view_logic'; +import { + AnalyticsCollectionDataViewLogic, + AnalyticsCollectionDataViewLogicValues, +} from './analytics_collection_data_view_logic'; import { getBaseSearchTemplate, @@ -33,9 +36,13 @@ import { TopReferrersTable, WorsePerformersTable, } from './analytics_collection_explore_table_types'; -import { AnalyticsCollectionToolbarLogic } from './analytics_collection_toolbar/analytics_collection_toolbar_logic'; +import { + AnalyticsCollectionToolbarLogic, + AnalyticsCollectionToolbarLogicValues, +} from './analytics_collection_toolbar/analytics_collection_toolbar_logic'; const BASE_PAGE_SIZE = 10; +const SEARCH_COOLDOWN = 200; export interface Sorting { direction: 'asc' | 'desc'; @@ -243,13 +250,16 @@ const tablesParams: { }; export interface AnalyticsCollectionExploreTableLogicValues { + dataView: AnalyticsCollectionDataViewLogicValues['dataView']; isLoading: boolean; items: ExploreTableItem[]; pageIndex: number; pageSize: number; search: string; + searchSessionId: AnalyticsCollectionToolbarLogicValues['searchSessionId']; selectedTable: ExploreTables | null; sorting: Sorting | null; + timeRange: AnalyticsCollectionToolbarLogicValues['timeRange']; totalItemsCount: number; } @@ -282,14 +292,26 @@ export const AnalyticsCollectionExploreTableLogic = kea< setSelectedTable: (id, sorting) => ({ id, sorting }), setTotalItemsCount: (count) => ({ count }), }, + connect: { + actions: [AnalyticsCollectionToolbarLogic, ['setTimeRange', 'setSearchSessionId']], + values: [ + AnalyticsCollectionDataViewLogic, + ['dataView'], + AnalyticsCollectionToolbarLogic, + ['timeRange', 'searchSessionId'], + ], + }, listeners: ({ actions, values }) => { const fetchItems = () => { if (values.selectedTable === null || !(values.selectedTable in tablesParams)) { + actions.setItems([]); + actions.setTotalItemsCount(0); + return; } const { requestParams, parseResponse } = tablesParams[values.selectedTable] as TableParams; - const timeRange = AnalyticsCollectionToolbarLogic.values.timeRange; + const timeRange = values.timeRange; const search$ = KibanaLogic.values.data.search .search( @@ -301,8 +323,8 @@ export const AnalyticsCollectionExploreTableLogic = kea< timeRange, }), { - indexPattern: AnalyticsCollectionDataViewLogic.values.dataView || undefined, - sessionId: AnalyticsCollectionToolbarLogic.values.searchSessionId, + indexPattern: values.dataView || undefined, + sessionId: values.searchSessionId, } ) .subscribe({ @@ -323,13 +345,16 @@ export const AnalyticsCollectionExploreTableLogic = kea< return { onTableChange: fetchItems, - setSearch: fetchItems, + setSearch: async (_, breakpoint) => { + await breakpoint(SEARCH_COOLDOWN); + fetchItems(); + }, + setSearchSessionId: fetchItems, setSelectedTable: fetchItems, - [AnalyticsCollectionToolbarLogic.actionTypes.setTimeRange]: fetchItems, - [AnalyticsCollectionToolbarLogic.actionTypes.setSearchSessionId]: fetchItems, + setTimeRange: fetchItems, }; }, - path: ['enterprise_search', 'analytics', 'collections', 'explore', 'table'], + path: ['enterprise_search', 'analytics', 'collection', 'explore', 'table'], reducers: () => ({ isLoading: [ false, @@ -337,10 +362,10 @@ export const AnalyticsCollectionExploreTableLogic = kea< onTableChange: () => true, setItems: () => false, setSearch: () => true, + setSearchSessionId: () => true, setSelectedTable: () => true, setTableState: () => true, - [AnalyticsCollectionToolbarLogic.actionTypes.setTimeRange]: () => true, - [AnalyticsCollectionToolbarLogic.actionTypes.setSearchSessionId]: () => true, + setTimeRange: () => true, }, ], items: [[], { setItems: (_, { items }) => items }], diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explorer/analytics_collection_explorer_table.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explorer/analytics_collection_explorer_table.tsx index fe55aae3d1692..cc104fe93b7ba 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explorer/analytics_collection_explorer_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explorer/analytics_collection_explorer_table.tsx @@ -277,6 +277,7 @@ export const AnalyticsCollectionExplorerTable = () => { value={search} onChange={(event) => setSearch(event.target.value)} isClearable + isLoading={isLoading} incremental fullWidth /> diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_toolbar/analytics_collection_toolbar_logic.ts b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_toolbar/analytics_collection_toolbar_logic.ts index 412e3c502ab44..cfbe44b64d82c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_toolbar/analytics_collection_toolbar_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_toolbar/analytics_collection_toolbar_logic.ts @@ -55,7 +55,7 @@ export const AnalyticsCollectionToolbarLogic = kea< actions.setSearchSessionId(null); }, }), - path: ['enterprise_search', 'analytics', 'collections', 'toolbar'], + path: ['enterprise_search', 'analytics', 'collection', 'toolbar'], reducers: () => ({ _searchSessionId: [ null, diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/delete_analytics_collection_logic.ts b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/delete_analytics_collection_logic.ts index ea0cf4476ac54..6913739863efa 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/delete_analytics_collection_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/delete_analytics_collection_logic.ts @@ -48,7 +48,7 @@ export const DeleteAnalyticsCollectionLogic = kea< actions.makeRequest({ name }); }, }), - path: ['enterprise_search', 'analytics', 'collections', 'delete'], + path: ['enterprise_search', 'analytics', 'collection', 'delete'], selectors: ({ selectors }) => ({ isLoading: [ () => [selectors.status], diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_overview/analytics_collection_not_found.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_overview/analytics_collection_not_found.tsx new file mode 100644 index 0000000000000..8f3359e764b5b --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_overview/analytics_collection_not_found.tsx @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { EuiEmptyPrompt, EuiImage } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import noMlModelsGraphicDark from '../../../../assets/images/no_ml_models_dark.svg'; + +const ICON_WIDTH = 294; + +interface AnalyticsCollectionNotFoundProps { + query: string; +} + +export const AnalyticsCollectionNotFound: React.FC = ({ + query, +}) => ( + } + title={ +

+ {i18n.translate('xpack.enterpriseSearch.analytics.collections.notFound.headingTitle', { + defaultMessage: 'No results found for “{query}”', + values: { query }, + })} +

+ } + body={ +

+ {i18n.translate('xpack.enterpriseSearch.analytics.collections.notFound.subHeading', { + defaultMessage: 'Try searching for another term.', + })} +

+ } + /> +); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_overview/analytics_collection_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_overview/analytics_collection_table.test.tsx index 461f24df6a1ca..af6c9a4ebb73c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_overview/analytics_collection_table.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_overview/analytics_collection_table.test.tsx @@ -16,6 +16,7 @@ import { EuiButtonGroup, EuiSuperDatePicker } from '@elastic/eui'; import { AnalyticsCollection } from '../../../../../common/types/analytics'; import { AnalyticsCollectionCardWithLens } from './analytics_collection_card/analytics_collection_card'; +import { AnalyticsCollectionNotFound } from './analytics_collection_not_found'; import { AnalyticsCollectionTable } from './analytics_collection_table'; @@ -30,13 +31,18 @@ describe('AnalyticsCollectionTable', () => { name: 'example2', }, ]; + const props = { + collections: analyticsCollections, + isSearching: false, + onSearch: jest.fn(), + }; beforeEach(() => { jest.clearAllMocks(); }); it('renders cards', () => { - const wrapper = shallow(); + const wrapper = shallow(); const collectionCards = wrapper.find(AnalyticsCollectionCardWithLens); expect(collectionCards).toHaveLength(analyticsCollections.length); @@ -44,9 +50,7 @@ describe('AnalyticsCollectionTable', () => { }); it('renders filters', () => { - const buttonGroup = shallow( - - ).find(EuiButtonGroup); + const buttonGroup = shallow().find(EuiButtonGroup); expect(buttonGroup).toHaveLength(1); expect(buttonGroup.prop('options')).toHaveLength(4); @@ -54,12 +58,16 @@ describe('AnalyticsCollectionTable', () => { }); it('renders datePick', () => { - const datePicker = shallow( - - ).find(EuiSuperDatePicker); + const datePicker = shallow().find(EuiSuperDatePicker); expect(datePicker).toHaveLength(1); expect(datePicker.prop('start')).toEqual('now-7d'); expect(datePicker.prop('end')).toEqual('now'); }); + + it('renders not found page', () => { + const wrapper = shallow(); + + expect(wrapper.find(AnalyticsCollectionNotFound)).toHaveLength(1); + }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_overview/analytics_collection_table.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_overview/analytics_collection_table.tsx index 2d52cf22f6ca2..705ddf29145ad 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_overview/analytics_collection_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_overview/analytics_collection_table.tsx @@ -13,15 +13,16 @@ import { EuiPanel, EuiSuperDatePicker, EuiSuperDatePickerCommonRange, - EuiSearchBar, EuiFlexGroup, EuiSpacer, EuiButtonGroup, useEuiTheme, EuiButton, + EuiFieldSearch, } from '@elastic/eui'; import { OnTimeChangeProps } from '@elastic/eui/src/components/date_picker/super_date_picker/super_date_picker'; + import { i18n } from '@kbn/i18n'; import { AnalyticsCollection } from '../../../../../common/types/analytics'; @@ -30,6 +31,7 @@ import { AddAnalyticsCollection } from '../add_analytics_collections/add_analyti import { AnalyticsCollectionCardWithLens } from './analytics_collection_card/analytics_collection_card'; +import { AnalyticsCollectionNotFound } from './analytics_collection_not_found'; import { AnalyticsCollectionTableStyles } from './analytics_collection_table.styles'; const defaultQuickRanges: EuiSuperDatePickerCommonRange[] = [ @@ -72,10 +74,14 @@ const defaultQuickRanges: EuiSuperDatePickerCommonRange[] = [ interface AnalyticsCollectionTableProps { collections: AnalyticsCollection[]; + isSearching: boolean; + onSearch: (query: string) => void; } export const AnalyticsCollectionTable: React.FC = ({ collections, + isSearching, + onSearch, }) => { const { euiTheme } = useEuiTheme(); const analyticsCollectionTableStyles = AnalyticsCollectionTableStyles(euiTheme); @@ -113,6 +119,7 @@ export const AnalyticsCollectionTable: React.FC = [analyticsCollectionTableStyles.button] ); const [filterId, setFilterId] = useState(filterOptions[0].id); + const [query, setQuery] = useState(''); const [timeRange, setTimeRange] = useState<{ from: string; to: string }>({ from: defaultQuickRanges[0].start, to: defaultQuickRanges[0].end, @@ -127,12 +134,18 @@ export const AnalyticsCollectionTable: React.FC = - { + setQuery(e.target.value); }} + isLoading={isSearching} + onSearch={onSearch} + incremental + fullWidth /> @@ -162,18 +175,22 @@ export const AnalyticsCollectionTable: React.FC = - - {collections.map((collection) => ( - - ))} - + {collections.length ? ( + + {collections.map((collection) => ( + + ))} + + ) : ( + + )} ( { const DEFAULT_VALUES = { analyticsCollections: [], data: undefined, - hasNoAnalyticsCollections: false, - isLoading: true, + hasNoAnalyticsCollections: true, + isFetching: true, + isSearchRequest: false, + isSearching: false, + searchQuery: '', status: Status.IDLE, }; @@ -45,9 +50,9 @@ describe('analyticsCollectionsLogic', () => { expect(AnalyticsCollectionsLogic.values).toEqual({ ...DEFAULT_VALUES, analyticsCollections: [], - hasNoAnalyticsCollections: true, data: [], - isLoading: false, + hasNoAnalyticsCollections: true, + isFetching: false, status: Status.SUCCESS, }); }); @@ -64,12 +69,23 @@ describe('analyticsCollectionsLogic', () => { expect(AnalyticsCollectionsLogic.values).toEqual({ ...DEFAULT_VALUES, analyticsCollections: collections, + hasNoAnalyticsCollections: false, data: collections, - isLoading: false, + isFetching: false, status: Status.SUCCESS, }); }); }); + + it('updates searchQuery when searchAnalyticsCollections is called', () => { + AnalyticsCollectionsLogic.actions.searchAnalyticsCollections('test'); + expect(AnalyticsCollectionsLogic.values.searchQuery).toBe('test'); + }); + + it('updates isSearchRequest when searchAnalyticsCollections is called', () => { + AnalyticsCollectionsLogic.actions.searchAnalyticsCollections('test'); + expect(AnalyticsCollectionsLogic.values.isSearchRequest).toBe(true); + }); }); describe('listeners', () => { @@ -84,11 +100,20 @@ describe('analyticsCollectionsLogic', () => { expect(mockFlashMessageHelpers.flashAPIErrors).toHaveBeenCalledWith({}); }); - it('calls makeRequest on fetchAnalyticsCollections', async () => { + it('calls makeRequest on fetchAnalyticsCollections', () => { AnalyticsCollectionsLogic.actions.makeRequest = jest.fn(); AnalyticsCollectionsLogic.actions.fetchAnalyticsCollections(); expect(AnalyticsCollectionsLogic.actions.makeRequest).toHaveBeenCalledWith({}); }); + + it('calls makeRequest query on searchAnalyticsCollections', async () => { + jest.useFakeTimers({ legacyFakeTimers: true }); + AnalyticsCollectionsLogic.actions.makeRequest = jest.fn(); + AnalyticsCollectionsLogic.actions.searchAnalyticsCollections('test'); + jest.advanceTimersByTime(200); + await nextTick(); + expect(AnalyticsCollectionsLogic.actions.makeRequest).toHaveBeenCalledWith({ query: 'test' }); + }); }); describe('selectors', () => { @@ -101,10 +126,64 @@ describe('analyticsCollectionsLogic', () => { analyticsCollections: [], data: [], hasNoAnalyticsCollections: true, - isLoading: false, + isFetching: false, status: Status.SUCCESS, }); }); }); + + describe('isFetching', () => { + it('updates on initialState', () => { + expect(AnalyticsCollectionsLogic.values.isFetching).toBe(true); + }); + + it('updates when fetchAnalyticsCollections listener triggered', () => { + AnalyticsCollectionsLogic.actions.fetchAnalyticsCollections(); + expect(AnalyticsCollectionsLogic.values.isFetching).toBe(true); + }); + + it('updates when apiSuccess listener triggered', () => { + FetchAnalyticsCollectionsAPILogic.actions.apiSuccess([]); + expect(AnalyticsCollectionsLogic.values.isFetching).toBe(false); + }); + + it('updates when search request triggered', () => { + AnalyticsCollectionsLogic.actions.searchAnalyticsCollections('test'); + expect(AnalyticsCollectionsLogic.values.isFetching).toBe(false); + }); + }); + + describe('isSearching', () => { + it('updates on initialState', () => { + expect(AnalyticsCollectionsLogic.values.isSearching).toBe(false); + }); + + it('updates when fetchAnalyticsCollections listener triggered', () => { + AnalyticsCollectionsLogic.actions.fetchAnalyticsCollections(); + expect(AnalyticsCollectionsLogic.values.isSearching).toBe(false); + }); + + it('updates when apiSuccess listener triggered', () => { + FetchAnalyticsCollectionsAPILogic.actions.apiSuccess([]); + expect(AnalyticsCollectionsLogic.values.isSearching).toBe(false); + }); + }); + + describe('hasNoAnalyticsCollections', () => { + it('returns false when no items and search query is not empty', () => { + AnalyticsCollectionsLogic.actions.searchAnalyticsCollections('test'); + expect(AnalyticsCollectionsLogic.values.searchQuery).toBe('test'); + expect(AnalyticsCollectionsLogic.values.hasNoAnalyticsCollections).toBe(false); + }); + + it('returns true when no items and search query is empty', () => { + AnalyticsCollectionsLogic.actions.searchAnalyticsCollections(''); + expect(AnalyticsCollectionsLogic.values.hasNoAnalyticsCollections).toBeTruthy(); + }); + + it('returns true when no items and search query is undefined', () => { + expect(AnalyticsCollectionsLogic.values.hasNoAnalyticsCollections).toBeTruthy(); + }); + }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_overview/analytics_collections_logic.ts b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_overview/analytics_collections_logic.ts index aa12b92b0f177..bfe86f2874a60 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_overview/analytics_collections_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_overview/analytics_collections_logic.ts @@ -15,15 +15,21 @@ import { FetchAnalyticsCollectionsApiLogicResponse, } from '../../api/index/fetch_analytics_collections_api_logic'; +const SEARCH_COOLDOWN = 200; + export interface AnalyticsCollectionsActions { fetchAnalyticsCollections(): void; makeRequest: Actions<{}, FetchAnalyticsCollectionsApiLogicResponse>['makeRequest']; + searchAnalyticsCollections(query?: string): { query: string }; } export interface AnalyticsCollectionsValues { analyticsCollections: AnalyticsCollection[]; data: typeof FetchAnalyticsCollectionsAPILogic.values.data; hasNoAnalyticsCollections: boolean; - isLoading: boolean; + isFetching: boolean; + isSearchRequest: boolean; + isSearching: boolean; + searchQuery: string; status: Status; } @@ -31,7 +37,10 @@ export const AnalyticsCollectionsLogic = kea< MakeLogicType >({ actions: { - fetchAnalyticsCollections: () => {}, + fetchAnalyticsCollections: true, + searchAnalyticsCollections: (query) => ({ + query, + }), }, connect: { actions: [FetchAnalyticsCollectionsAPILogic, ['makeRequest']], @@ -41,14 +50,42 @@ export const AnalyticsCollectionsLogic = kea< fetchAnalyticsCollections: () => { actions.makeRequest({}); }, + searchAnalyticsCollections: async ({ query }, breakpoint) => { + if (query) { + await breakpoint(SEARCH_COOLDOWN); + } + actions.makeRequest({ query }); + }, }), path: ['enterprise_search', 'analytics', 'collections'], + reducers: { + isSearchRequest: [ + false, + { + searchAnalyticsCollections: () => true, + }, + ], + searchQuery: [ + '', + { + searchAnalyticsCollections: (_, { query }) => query, + }, + ], + }, selectors: ({ selectors }) => ({ analyticsCollections: [() => [selectors.data], (data) => data || []], - hasNoAnalyticsCollections: [() => [selectors.data], (data) => data?.length === 0], - isLoading: [ - () => [selectors.status], - (status) => [Status.LOADING, Status.IDLE].includes(status), + hasNoAnalyticsCollections: [ + () => [selectors.analyticsCollections, selectors.searchQuery], + (analyticsCollections, searchQuery) => analyticsCollections.length === 0 && !searchQuery, + ], + isFetching: [ + () => [selectors.status, selectors.isSearchRequest], + (status, isSearchRequest) => + [Status.LOADING, Status.IDLE].includes(status) && !isSearchRequest, + ], + isSearching: [ + () => [selectors.status, selectors.isSearchRequest], + (status, isSearchRequest) => Status.LOADING === status && isSearchRequest, ], }), }); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_overview/analytics_overview.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_overview/analytics_overview.tsx index 562f587023b27..33bfa6d504bac 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_overview/analytics_overview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_overview/analytics_overview.tsx @@ -28,8 +28,9 @@ import { AnalyticsCollectionsLogic } from './analytics_collections_logic'; import { AnalyticsOverviewEmptyPage } from './analytics_overview_empty_page'; export const AnalyticsOverview: React.FC = () => { - const { fetchAnalyticsCollections } = useActions(AnalyticsCollectionsLogic); - const { analyticsCollections, isLoading, hasNoAnalyticsCollections } = + const { fetchAnalyticsCollections, searchAnalyticsCollections } = + useActions(AnalyticsCollectionsLogic); + const { analyticsCollections, hasNoAnalyticsCollections, isFetching, isSearching } = useValues(AnalyticsCollectionsLogic); const { isCloud } = useValues(KibanaLogic); @@ -46,7 +47,7 @@ export const AnalyticsOverview: React.FC = () => { { - ) : hasNoAnalyticsCollections ? ( + ) : hasNoAnalyticsCollections && !isSearching ? ( <> ) : ( - + )} ); diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/analytics.test.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/analytics.test.ts index 2f97e6362a9ee..690379fc0c4c3 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/analytics.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/analytics.test.ts @@ -25,6 +25,80 @@ describe('Enterprise Search Analytics API', () => { let mockRouter: MockRouter; const mockClient = {}; + describe('GET /internal/enterprise_search/analytics/collections', () => { + beforeEach(() => { + const context = { + core: Promise.resolve({ elasticsearch: { client: mockClient } }), + } as jest.Mocked; + + mockRouter = new MockRouter({ + context, + method: 'get', + path: '/internal/enterprise_search/analytics/collections', + }); + + const mockDataPlugin = { + indexPatterns: { + dataViewsServiceFactory: jest.fn(), + }, + }; + + const mockedSavedObjects = { + getScopedClient: jest.fn(), + }; + + registerAnalyticsRoutes({ + ...mockDependencies, + data: mockDataPlugin as unknown as DataPluginStart, + savedObjects: mockedSavedObjects as unknown as SavedObjectsServiceStart, + router: mockRouter.router, + }); + }); + + it('fetches a defined analytics collections', async () => { + const mockData: AnalyticsCollection[] = [ + { + events_datastream: 'logs-elastic_analytics.events-example', + name: 'my_collection', + }, + { + events_datastream: 'logs-elastic_analytics.events-example2', + name: 'my_collection2', + }, + { + events_datastream: 'logs-elastic_analytics.events-example2', + name: 'my_collection3', + }, + ]; + + (fetchAnalyticsCollections as jest.Mock).mockImplementationOnce(() => { + return Promise.resolve(mockData); + }); + await mockRouter.callRoute({}); + + expect(mockRouter.response.ok).toHaveBeenCalledWith({ + body: mockData, + }); + }); + + it('passes the query string to the fetch function', async () => { + await mockRouter.callRoute({ query: { query: 'my_collection2' } }); + + expect(fetchAnalyticsCollections).toHaveBeenCalledWith(mockClient, 'my_collection2*'); + }); + + it('returns an empty obj when fetchAnalyticsCollections returns not found error', async () => { + (fetchAnalyticsCollections as jest.Mock).mockImplementationOnce(() => { + throw new Error(ErrorCode.ANALYTICS_COLLECTION_NOT_FOUND); + }); + await mockRouter.callRoute({}); + + expect(mockRouter.response.ok).toHaveBeenCalledWith({ + body: [], + }); + }); + }); + describe('GET /internal/enterprise_search/analytics/collections/{id}', () => { beforeEach(() => { const context = { diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/analytics.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/analytics.ts index 48a7a68084dea..abdf0673665c8 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/analytics.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/analytics.ts @@ -51,12 +51,25 @@ export function registerAnalyticsRoutes({ router.get( { path: '/internal/enterprise_search/analytics/collections', - validate: {}, + validate: { + query: schema.object({ + query: schema.maybe(schema.string()), + }), + }, }, elasticsearchErrorHandler(log, async (context, request, response) => { const { client } = (await context.core).elasticsearch; - const collections = await fetchAnalyticsCollections(client); - return response.ok({ body: collections }); + try { + const query = request.query.query && request.query.query + '*'; + const collections = await fetchAnalyticsCollections(client, query); + return response.ok({ body: collections }); + } catch (error) { + if ((error as Error).message === ErrorCode.ANALYTICS_COLLECTION_NOT_FOUND) { + return response.ok({ body: [] }); + } + + throw error; + } }) ); From 862103aaa6537bb776c4c68cee813e5ffbe85f50 Mon Sep 17 00:00:00 2001 From: Konrad Szwarc Date: Tue, 25 Apr 2023 11:09:23 +0200 Subject: [PATCH 29/52] [Defend Workflows][E2E]Endpoint isolate e2e coverage multipass backport (#155360) Depends on https://github.com/elastic/kibana/pull/154465 This PR backports `isolate` command e2e tests to be used with multipass. --- .../cypress/e2e/endpoint/isolate.cy.ts | 286 ++++++++++++++++++ .../cypress/e2e/mocked_data/isolate.cy.ts | 30 +- .../management/cypress/tasks/api_fixtures.ts | 45 ++- .../management/cypress/tasks/isolate.ts | 72 +++++ .../endpoint_agent_runner/elastic_endpoint.ts | 2 +- 5 files changed, 401 insertions(+), 34 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/isolate.cy.ts diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/isolate.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/isolate.cy.ts new file mode 100644 index 0000000000000..db486a6478e1a --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/isolate.cy.ts @@ -0,0 +1,286 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { Agent } from '@kbn/fleet-plugin/common'; +import { APP_CASES_PATH, APP_ENDPOINTS_PATH } from '../../../../../common/constants'; +import { closeAllToasts } from '../../tasks/close_all_toasts'; +import { + checkEndpointListForIsolatedHosts, + checkFlyoutEndpointIsolation, + createAgentPolicyTask, + filterOutEndpoints, + filterOutIsolatedHosts, + isolateHostWithComment, + openAlertDetails, + openCaseAlertDetails, + releaseHostWithComment, + toggleRuleOffAndOn, + visitRuleAlerts, + waitForReleaseOption, +} from '../../tasks/isolate'; +import { cleanupCase, cleanupRule, loadCase, loadRule } from '../../tasks/api_fixtures'; +import { ENDPOINT_VM_NAME } from '../../tasks/common'; +import { login } from '../../tasks/login'; +import type { IndexedFleetEndpointPolicyResponse } from '../../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy'; +import { + getAgentByHostName, + getEndpointIntegrationVersion, + reassignAgentPolicy, +} from '../../tasks/fleet'; + +describe('Isolate command', () => { + const endpointHostname = Cypress.env(ENDPOINT_VM_NAME); + const isolateComment = `Isolating ${endpointHostname}`; + const releaseComment = `Releasing ${endpointHostname}`; + + beforeEach(() => { + login(); + }); + + describe('From manage', () => { + let response: IndexedFleetEndpointPolicyResponse; + let initialAgentData: Agent; + + before(() => { + getAgentByHostName(endpointHostname).then((agentData) => { + initialAgentData = agentData; + }); + + getEndpointIntegrationVersion().then((version) => { + createAgentPolicyTask(version, (data) => { + response = data; + }); + }); + }); + + after(() => { + if (initialAgentData?.policy_id) { + reassignAgentPolicy(initialAgentData.id, initialAgentData.policy_id); + } + if (response) { + cy.task('deleteIndexedFleetEndpointPolicies', response); + } + }); + + it('should allow filtering endpoint by Isolated status', () => { + cy.visit(APP_ENDPOINTS_PATH); + closeAllToasts(); + checkEndpointListForIsolatedHosts(false); + + filterOutIsolatedHosts(); + cy.contains('No items found'); + cy.getByTestSubj('adminSearchBar').click().type('{selectall}{backspace}'); + cy.getByTestSubj('querySubmitButton').click(); + cy.getByTestSubj('endpointTableRowActions').click(); + cy.getByTestSubj('isolateLink').click(); + + cy.contains(`Isolate host ${endpointHostname} from network.`); + cy.getByTestSubj('endpointHostIsolationForm'); + cy.getByTestSubj('host_isolation_comment').type(isolateComment); + cy.getByTestSubj('hostIsolateConfirmButton').click(); + cy.contains(`Isolation on host ${endpointHostname} successfully submitted`); + cy.getByTestSubj('euiFlyoutCloseButton').click(); + cy.getByTestSubj('rowHostStatus-actionStatuses').should('contain.text', 'Isolated'); + filterOutIsolatedHosts(); + + checkEndpointListForIsolatedHosts(); + + cy.getByTestSubj('endpointTableRowActions').click(); + cy.getByTestSubj('unIsolateLink').click(); + releaseHostWithComment(releaseComment, endpointHostname); + cy.contains('Confirm').click(); + cy.getByTestSubj('euiFlyoutCloseButton').click(); + cy.getByTestSubj('adminSearchBar').click().type('{selectall}{backspace}'); + cy.getByTestSubj('querySubmitButton').click(); + checkEndpointListForIsolatedHosts(false); + }); + }); + + describe('From alerts', () => { + let response: IndexedFleetEndpointPolicyResponse; + let initialAgentData: Agent; + let ruleId: string; + let ruleName: string; + + before(() => { + getAgentByHostName(endpointHostname).then((agentData) => { + initialAgentData = agentData; + }); + + getEndpointIntegrationVersion().then((version) => { + createAgentPolicyTask(version, (data) => { + response = data; + }); + }); + loadRule(false).then((data) => { + ruleId = data.id; + ruleName = data.name; + }); + }); + + after(() => { + if (initialAgentData?.policy_id) { + reassignAgentPolicy(initialAgentData.id, initialAgentData.policy_id); + } + if (response) { + cy.task('deleteIndexedFleetEndpointPolicies', response); + } + if (ruleId) { + cleanupRule(ruleId); + } + }); + + it('should have generated endpoint and rule', () => { + cy.visit(APP_ENDPOINTS_PATH); + cy.contains(endpointHostname).should('exist'); + + toggleRuleOffAndOn(ruleName); + }); + + it('should isolate and release host', () => { + visitRuleAlerts(ruleName); + + filterOutEndpoints(endpointHostname); + + closeAllToasts(); + openAlertDetails(); + + isolateHostWithComment(isolateComment, endpointHostname); + + cy.getByTestSubj('hostIsolateConfirmButton').click(); + cy.contains(`Isolation on host ${endpointHostname} successfully submitted`); + + cy.getByTestSubj('euiFlyoutCloseButton').click(); + openAlertDetails(); + + checkFlyoutEndpointIsolation(); + + releaseHostWithComment(releaseComment, endpointHostname); + cy.contains('Confirm').click(); + + cy.contains(`Release on host ${endpointHostname} successfully submitted`); + cy.getByTestSubj('euiFlyoutCloseButton').click(); + openAlertDetails(); + cy.getByTestSubj('event-field-agent.status').within(() => { + cy.get('[title="Isolated"]').should('not.exist'); + }); + }); + }); + + describe('From cases', () => { + let response: IndexedFleetEndpointPolicyResponse; + let initialAgentData: Agent; + let ruleId: string; + let ruleName: string; + let caseId: string; + + const caseOwner = 'securitySolution'; + + before(() => { + getAgentByHostName(endpointHostname).then((agentData) => { + initialAgentData = agentData; + }); + getEndpointIntegrationVersion().then((version) => { + createAgentPolicyTask(version, (data) => { + response = data; + }); + }); + + loadRule(false).then((data) => { + ruleId = data.id; + ruleName = data.name; + }); + loadCase(caseOwner).then((data) => { + caseId = data.id; + }); + }); + + beforeEach(() => { + login(); + }); + + after(() => { + if (initialAgentData?.policy_id) { + reassignAgentPolicy(initialAgentData.id, initialAgentData.policy_id); + } + if (response) { + cy.task('deleteIndexedFleetEndpointPolicies', response); + } + if (ruleId) { + cleanupRule(ruleId); + } + if (caseId) { + cleanupCase(caseId); + } + }); + + it('should have generated endpoint and rule', () => { + cy.visit(APP_ENDPOINTS_PATH); + cy.contains(endpointHostname).should('exist'); + + toggleRuleOffAndOn(ruleName); + }); + + it('should isolate and release host', () => { + visitRuleAlerts(ruleName); + filterOutEndpoints(endpointHostname); + closeAllToasts(); + + openAlertDetails(); + + cy.getByTestSubj('add-to-existing-case-action').click(); + cy.getByTestSubj(`cases-table-row-select-${caseId}`).click(); + cy.contains(`An alert was added to \"Test ${caseOwner} case`); + + cy.intercept('GET', `/api/cases/${caseId}/user_actions/_find*`).as('case'); + cy.visit(`${APP_CASES_PATH}/${caseId}`); + cy.wait('@case', { timeout: 30000 }).then(({ response: res }) => { + const caseAlertId = res?.body.userActions[1].id; + + closeAllToasts(); + openCaseAlertDetails(caseAlertId); + isolateHostWithComment(isolateComment, endpointHostname); + cy.getByTestSubj('hostIsolateConfirmButton').click(); + + cy.getByTestSubj('euiFlyoutCloseButton').click(); + + cy.getByTestSubj('user-actions-list').within(() => { + cy.contains(isolateComment); + cy.get('[aria-label="lock"]').should('exist'); + cy.get('[aria-label="lockOpen"]').should('not.exist'); + }); + + waitForReleaseOption(caseAlertId); + + releaseHostWithComment(releaseComment, endpointHostname); + + cy.contains('Confirm').click(); + + cy.contains(`Release on host ${endpointHostname} successfully submitted`); + cy.getByTestSubj('euiFlyoutCloseButton').click(); + + cy.getByTestSubj('user-actions-list').within(() => { + cy.contains(releaseComment); + cy.contains(isolateComment); + cy.get('[aria-label="lock"]').should('exist'); + cy.get('[aria-label="lockOpen"]').should('exist'); + }); + + openCaseAlertDetails(caseAlertId); + + cy.getByTestSubj('event-field-agent.status').then(($status) => { + if ($status.find('[title="Isolated"]').length > 0) { + cy.getByTestSubj('euiFlyoutCloseButton').click(); + cy.getByTestSubj(`comment-action-show-alert-${caseAlertId}`).click(); + cy.getByTestSubj('take-action-dropdown-btn').click(); + } + cy.get('[title="Isolated"]').should('not.exist'); + }); + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/isolate.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/isolate.cy.ts index 579c0cab8c540..416987432fce3 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/isolate.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/isolate.cy.ts @@ -7,6 +7,9 @@ import { getEndpointListPath } from '../../../common/routing'; import { + checkEndpointListForIsolatedHosts, + checkFlyoutEndpointIsolation, + filterOutIsolatedHosts, interceptActionRequests, isolateHostWithComment, openAlertDetails, @@ -67,18 +70,9 @@ describe('Isolate command', () => { it('should allow filtering endpoint by Isolated status', () => { cy.visit(APP_PATH + getEndpointListPath({ name: 'endpointList' })); closeAllToasts(); - cy.getByTestSubj('adminSearchBar') - .click() - .type('united.endpoint.Endpoint.state.isolation: true'); - cy.getByTestSubj('querySubmitButton').click(); + filterOutIsolatedHosts(); cy.contains('Showing 2 endpoints'); - cy.getByTestSubj('endpointListTable').within(() => { - cy.get('tbody tr').each(($tr) => { - cy.wrap($tr).within(() => { - cy.get('td').eq(1).should('contain.text', 'Isolated'); - }); - }); - }); + checkEndpointListForIsolatedHosts(); }); }); @@ -161,18 +155,8 @@ describe('Isolate command', () => { cy.getByTestSubj('euiFlyoutCloseButton').click(); cy.wait(1000); openAlertDetails(); - cy.getByTestSubj('event-field-agent.status').then(($status) => { - if ($status.find('[title="Isolated"]').length > 0) { - cy.contains('Release host').click(); - } else { - cy.getByTestSubj('euiFlyoutCloseButton').click(); - openAlertDetails(); - cy.getByTestSubj('event-field-agent.status').within(() => { - cy.contains('Isolated'); - }); - cy.contains('Release host').click(); - } - }); + + checkFlyoutEndpointIsolation(); releaseHostWithComment(releaseComment, hostname); diff --git a/x-pack/plugins/security_solution/public/management/cypress/tasks/api_fixtures.ts b/x-pack/plugins/security_solution/public/management/cypress/tasks/api_fixtures.ts index 8d8df6318d215..3b8b7cfae6340 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/tasks/api_fixtures.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/tasks/api_fixtures.ts @@ -5,10 +5,8 @@ * 2.0. */ -import type { - RuleCreateProps, - RuleResponse, -} from '../../../../common/detection_engine/rule_schema'; +import type { CaseResponse } from '@kbn/cases-plugin/common'; +import type { RuleResponse } from '../../../../common/detection_engine/rule_schema'; import { request } from './common'; export const generateRandomStringName = (length: number) => @@ -18,9 +16,10 @@ export const cleanupRule = (id: string) => { request({ method: 'DELETE', url: `/api/detection_engine/rules?id=${id}` }); }; -export const loadRule = () => +export const loadRule = (includeResponseActions = true) => request({ method: 'POST', + url: `/api/detection_engine/rules`, body: { type: 'query', index: [ @@ -56,9 +55,35 @@ export const loadRule = () => actions: [], enabled: true, throttle: 'no_actions', - response_actions: [ - { params: { command: 'isolate', comment: 'Isolate host' }, action_type_id: '.endpoint' }, - ], - } as RuleCreateProps, - url: `/api/detection_engine/rules`, + ...(includeResponseActions + ? { + response_actions: [ + { + params: { command: 'isolate', comment: 'Isolate host' }, + action_type_id: '.endpoint', + }, + ], + } + : {}), + }, }).then((response) => response.body); + +export const loadCase = (owner: string) => + request({ + method: 'POST', + url: '/api/cases', + body: { + title: `Test ${owner} case ${generateRandomStringName(1)[0]}`, + tags: [], + severity: 'low', + description: 'Test security case', + assignees: [], + connector: { id: 'none', name: 'none', type: '.none', fields: null }, + settings: { syncAlerts: true }, + owner, + }, + }).then((response) => response.body); + +export const cleanupCase = (id: string) => { + request({ method: 'DELETE', url: '/api/cases', qs: { ids: JSON.stringify([id]) } }); +}; diff --git a/x-pack/plugins/security_solution/public/management/cypress/tasks/isolate.ts b/x-pack/plugins/security_solution/public/management/cypress/tasks/isolate.ts index b00b567852026..4644faaca2abf 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/tasks/isolate.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/tasks/isolate.ts @@ -5,6 +5,7 @@ * 2.0. */ +import type { IndexedFleetEndpointPolicyResponse } from '../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy'; import type { ActionDetails } from '../../../../common/endpoint/types'; const API_ENDPOINT_ACTION_PATH = '/api/endpoint/action/*'; @@ -52,6 +53,7 @@ export const openCaseAlertDetails = (alertId: string): void => { cy.getByTestSubj(`comment-action-show-alert-${alertId}`).click(); cy.getByTestSubj('take-action-dropdown-btn').click(); }; + export const waitForReleaseOption = (alertId: string): void => { openCaseAlertDetails(alertId); cy.getByTestSubj('event-field-agent.status').then(($status) => { @@ -67,3 +69,73 @@ export const waitForReleaseOption = (alertId: string): void => { } }); }; + +export const visitRuleAlerts = (ruleName: string) => { + cy.visit('/app/security/rules'); + cy.contains(ruleName).click(); +}; +export const checkFlyoutEndpointIsolation = (): void => { + cy.getByTestSubj('event-field-agent.status').then(($status) => { + if ($status.find('[title="Isolated"]').length > 0) { + cy.contains('Release host').click(); + } else { + cy.getByTestSubj('euiFlyoutCloseButton').click(); + cy.wait(5000); + openAlertDetails(); + cy.getByTestSubj('event-field-agent.status').within(() => { + cy.contains('Isolated'); + }); + cy.contains('Release host').click(); + } + }); +}; + +export const toggleRuleOffAndOn = (ruleName: string): void => { + cy.visit('/app/security/rules'); + cy.wait(2000); + cy.contains(ruleName) + .parents('tr') + .within(() => { + cy.getByTestSubj('ruleSwitch').should('have.attr', 'aria-checked', 'true'); + cy.getByTestSubj('ruleSwitch').click(); + cy.getByTestSubj('ruleSwitch').should('have.attr', 'aria-checked', 'false'); + cy.getByTestSubj('ruleSwitch').click(); + cy.getByTestSubj('ruleSwitch').should('have.attr', 'aria-checked', 'true'); + }); +}; + +export const filterOutEndpoints = (endpointHostname: string): void => { + cy.getByTestSubj('filters-global-container').within(() => { + cy.getByTestSubj('queryInput').click().type(`host.hostname : "${endpointHostname}"`); + cy.getByTestSubj('querySubmitButton').click(); + }); +}; + +export const createAgentPolicyTask = ( + version: string, + cb: (response: IndexedFleetEndpointPolicyResponse) => void +) => { + const policyName = `Reassign ${Math.random().toString(36).substring(2, 7)}`; + + cy.task('indexFleetEndpointPolicy', { + policyName, + endpointPackageVersion: version, + agentPolicyName: policyName, + }).then(cb); +}; + +export const filterOutIsolatedHosts = (): void => { + cy.getByTestSubj('adminSearchBar').click().type('united.endpoint.Endpoint.state.isolation: true'); + cy.getByTestSubj('querySubmitButton').click(); +}; + +export const checkEndpointListForIsolatedHosts = (expectIsolated = true): void => { + const chainer = expectIsolated ? 'contain.text' : 'not.contain.text'; + cy.getByTestSubj('endpointListTable').within(() => { + cy.get('tbody tr').each(($tr) => { + cy.wrap($tr).within(() => { + cy.get('td').eq(1).should(chainer, 'Isolated'); + }); + }); + }); +}; diff --git a/x-pack/plugins/security_solution/scripts/endpoint/endpoint_agent_runner/elastic_endpoint.ts b/x-pack/plugins/security_solution/scripts/endpoint/endpoint_agent_runner/elastic_endpoint.ts index e6de63c7ba510..68ff4951f77d4 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/endpoint_agent_runner/elastic_endpoint.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/endpoint_agent_runner/elastic_endpoint.ts @@ -57,7 +57,7 @@ export const enrollEndpointHost = async (): Promise => { try { const uniqueId = Math.random().toString().substring(2, 6); - const username = userInfo().username.toLowerCase(); + const username = userInfo().username.toLowerCase().replace('.', '-'); // Multipass doesn't like periods in username const policyId: string = policy || (await getOrCreateAgentPolicyId()); if (!policyId) { From 0ecb2cb4492775b5651793aa7f7ab5917de44c88 Mon Sep 17 00:00:00 2001 From: Pablo Machado Date: Tue, 25 Apr 2023 11:13:46 +0200 Subject: [PATCH 30/52] [Security Solutions] Display additional anomaly jobs in Entity Analytics Dashboard (#155520) issue: https://github.com/elastic/security-team/issues/6161 ## Summary * Adds more hardcoded jobs to the list of jobs displayed on the Notable anomalies table * Add pagination to the table * Remove the logic that refreshes the table when a job is installed * Move enableDataFeed logic to `` and use the response from the API to determine if the job was successfully installed. * Recently installed jobs are no longer sorted so users can find the jobs they have just installed. * When the page refreshes all jobs are sorted ![Apr-21-2023 17-47-28](https://user-images.githubusercontent.com/1490444/233953871-e2583aa8-4d7b-402a-aef3-e001dfc7ae18.gif) * I also replaced the loading spinner with a "Waiting" status when jobs are waiting for machine learning nodes to start because the loading spinner gave the false impression that the table would update at any moment. Screenshot 2023-04-24 at 11 22 57 TODO - [x] Cypress tests ### 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 --- .../e2e/dashboards/entity_analytics.cy.ts | 35 +- .../cypress/screens/entity_analytics.ts | 9 + .../ml/anomaly/use_anomalies_search.test.ts | 11 +- .../hooks/use_enable_data_feed.test.tsx | 320 +++++++++++------- .../ml_popover/hooks/use_enable_data_feed.ts | 148 ++++---- .../components/ml_popover/ml_popover.tsx | 16 +- .../logic/use_start_ml_jobs.tsx | 2 +- ...admin_job_description.integration.test.tsx | 3 +- .../admin/ml_admin_job_description.tsx | 15 +- .../entity_analytics/anomalies/columns.tsx | 60 +--- .../components/anomalies_tab_link.tsx | 73 ++++ .../anomalies/components/enable_job.test.tsx | 66 ++++ .../anomalies/components/enable_job.tsx | 41 +++ .../components/total_anomalies.test.tsx | 38 +++ .../anomalies/components/total_anomalies.tsx | 51 +++ .../entity_analytics/anomalies/config.ts | 49 ++- .../entity_analytics/anomalies/index.test.tsx | 35 +- .../entity_analytics/anomalies/index.tsx | 34 +- .../anomalies/translations.ts | 7 + 19 files changed, 744 insertions(+), 269 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/components/anomalies_tab_link.tsx create mode 100644 x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/components/enable_job.test.tsx create mode 100644 x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/components/enable_job.tsx create mode 100644 x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/components/total_anomalies.test.tsx create mode 100644 x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/components/total_anomalies.tsx diff --git a/x-pack/plugins/security_solution/cypress/e2e/dashboards/entity_analytics.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/dashboards/entity_analytics.cy.ts index 66820050516be..b8f8243ff9f87 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/dashboards/entity_analytics.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/dashboards/entity_analytics.cy.ts @@ -28,6 +28,10 @@ import { USERS_TABLE_ALERT_CELL, HOSTS_TABLE_ALERT_CELL, HOSTS_TABLE, + ANOMALIES_TABLE_NEXT_PAGE_BUTTON, + ANOMALIES_TABLE_ENABLE_JOB_BUTTON, + ANOMALIES_TABLE_ENABLE_JOB_LOADER, + ANOMALIES_TABLE_COUNT_COLUMN, } from '../../screens/entity_analytics'; import { openRiskTableFilterAndSelectTheLowOption } from '../../tasks/host_risk'; import { createRule } from '../../tasks/api_calls/rules'; @@ -35,6 +39,7 @@ import { waitForAlertsToPopulate } from '../../tasks/create_new_rule'; import { getNewRule } from '../../objects/rule'; import { clickOnFirstHostsAlerts, clickOnFirstUsersAlerts } from '../../tasks/risk_scores'; import { OPTION_LIST_LABELS, OPTION_LIST_VALUES } from '../../screens/common/filter_group'; +import { setRowsPerPageTo } from '../../tasks/table_pagination'; const TEST_USER_ALERTS = 2; const TEST_USER_NAME = 'test'; @@ -239,13 +244,39 @@ describe('Entity Analytics Dashboard', () => { }); describe('With anomalies data', () => { + before(() => { + esArchiverLoad('network'); + }); + + after(() => { + esArchiverUnload('network'); + }); + beforeEach(() => { visit(ENTITY_ANALYTICS_URL); }); - it('renders table', () => { + it('renders table with pagination', () => { cy.get(ANOMALIES_TABLE).should('be.visible'); - cy.get(ANOMALIES_TABLE_ROWS).should('have.length', 6); + cy.get(ANOMALIES_TABLE_ROWS).should('have.length', 10); + + // navigates to next page + cy.get(ANOMALIES_TABLE_NEXT_PAGE_BUTTON).click(); + cy.get(ANOMALIES_TABLE_ROWS).should('have.length', 10); + + // updates rows per page to 25 items + setRowsPerPageTo(25); + cy.get(ANOMALIES_TABLE_ROWS).should('have.length', 25); + }); + + it('enables a job', () => { + cy.get(ANOMALIES_TABLE_ROWS) + .eq(5) + .within(() => { + cy.get(ANOMALIES_TABLE_ENABLE_JOB_BUTTON).click(); + cy.get(ANOMALIES_TABLE_ENABLE_JOB_LOADER).should('be.visible'); + cy.get(ANOMALIES_TABLE_COUNT_COLUMN).should('include.text', '0'); + }); }); }); }); diff --git a/x-pack/plugins/security_solution/cypress/screens/entity_analytics.ts b/x-pack/plugins/security_solution/cypress/screens/entity_analytics.ts index 6095151fee57b..f41ae2a175f05 100644 --- a/x-pack/plugins/security_solution/cypress/screens/entity_analytics.ts +++ b/x-pack/plugins/security_solution/cypress/screens/entity_analytics.ts @@ -47,6 +47,15 @@ export const ANOMALIES_TABLE = export const ANOMALIES_TABLE_ROWS = '[data-test-subj="entity_analytics_anomalies"] .euiTableRow'; +export const ANOMALIES_TABLE_ENABLE_JOB_BUTTON = '[data-test-subj="enable-job"]'; + +export const ANOMALIES_TABLE_ENABLE_JOB_LOADER = '[data-test-subj="job-switch-loader"]'; + +export const ANOMALIES_TABLE_COUNT_COLUMN = '[data-test-subj="anomalies-table-column-count"]'; + +export const ANOMALIES_TABLE_NEXT_PAGE_BUTTON = + '[data-test-subj="entity_analytics_anomalies"] [data-test-subj="pagination-button-next"]'; + export const UPGRADE_CONFIRMATION_MODAL = (riskScoreEntity: RiskScoreEntity) => `[data-test-subj="${riskScoreEntity}-risk-score-upgrade-confirmation-modal"]`; diff --git a/x-pack/plugins/security_solution/public/common/components/ml/anomaly/use_anomalies_search.test.ts b/x-pack/plugins/security_solution/public/common/components/ml/anomaly/use_anomalies_search.test.ts index fa193a50afc8a..f4959bb223155 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml/anomaly/use_anomalies_search.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/ml/anomaly/use_anomalies_search.test.ts @@ -162,14 +162,9 @@ describe('useNotableAnomaliesSearch', () => { await waitForNextUpdate(); const names = result.current.data.map(({ name }) => name); - expect(names).toEqual([ - firstJobSecurityName, - secondJobSecurityName, - 'packetbeat_dns_tunneling', - 'packetbeat_rare_dns_question', - 'packetbeat_rare_server_domain', - 'suspicious_login_activity', - ]); + + expect(names[0]).toEqual(firstJobSecurityName); + expect(names[1]).toEqual(secondJobSecurityName); }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_enable_data_feed.test.tsx b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_enable_data_feed.test.tsx index b4c8265d23242..52d955414a099 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_enable_data_feed.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_enable_data_feed.test.tsx @@ -43,9 +43,9 @@ const JOB = { isCompatible: true, } as SecurityJob; -const mockSetupMlJob = jest.fn().mockReturnValue(Promise.resolve()); -const mockStartDatafeeds = jest.fn().mockReturnValue(Promise.resolve()); -const mockStopDatafeeds = jest.fn().mockReturnValue(Promise.resolve()); +const mockSetupMlJob = jest.fn(); +const mockStartDatafeeds = jest.fn(); +const mockStopDatafeeds = jest.fn(); jest.mock('../api', () => ({ setupMlJob: () => mockSetupMlJob(), @@ -69,186 +69,266 @@ jest.mock('../../../lib/kibana', () => { describe('useSecurityJobsHelpers', () => { afterEach(() => { - mockSetupMlJob.mockReset(); mockStartDatafeeds.mockReset(); mockStopDatafeeds.mockReset(); mockSetupMlJob.mockReset(); - }); - it('renders isLoading=true when installing job', async () => { - let resolvePromiseCb: (value: unknown) => void; - mockSetupMlJob.mockReturnValue( - new Promise((resolve) => { - resolvePromiseCb = resolve; - }) + mockStartDatafeeds.mockReturnValue( + Promise.resolve({ [`datafeed-${jobId}`]: { started: true } }) ); - const { result, waitForNextUpdate } = renderHook(() => useEnableDataFeed(), { - wrapper, - }); - expect(result.current.isLoading).toBe(false); + mockStopDatafeeds.mockReturnValue( + Promise.resolve([{ [`datafeed-${jobId}`]: { stopped: true } }]) + ); + mockSetupMlJob.mockReturnValue(Promise.resolve()); + }); - await act(async () => { - const enableDataFeedPromise = result.current.enableDatafeed(JOB, TIMESTAMP, false); + describe('enableDatafeed', () => { + it('renders isLoading=true when installing job', async () => { + let resolvePromiseCb: (value: unknown) => void; + mockSetupMlJob.mockReturnValue( + new Promise((resolve) => { + resolvePromiseCb = resolve; + }) + ); + const { result, waitForNextUpdate } = renderHook(() => useEnableDataFeed(), { + wrapper, + }); + expect(result.current.isLoading).toBe(false); - await waitForNextUpdate(); - expect(result.current.isLoading).toBe(true); + await act(async () => { + const enableDataFeedPromise = result.current.enableDatafeed(JOB, TIMESTAMP); - resolvePromiseCb({}); - await enableDataFeedPromise; - expect(result.current.isLoading).toBe(false); - }); - }); + await waitForNextUpdate(); + expect(result.current.isLoading).toBe(true); - it('does not call setupMlJob if job is already installed', async () => { - mockSetupMlJob.mockReturnValue(Promise.resolve()); - const { result } = renderHook(() => useEnableDataFeed(), { - wrapper, + resolvePromiseCb({}); + await enableDataFeedPromise; + expect(result.current.isLoading).toBe(false); + }); }); - await act(async () => { - await result.current.enableDatafeed({ ...JOB, isInstalled: true }, TIMESTAMP, false); - }); + it('does not call setupMlJob if job is already installed', async () => { + const { result } = renderHook(() => useEnableDataFeed(), { + wrapper, + }); - expect(mockSetupMlJob).not.toBeCalled(); - }); + await act(async () => { + await result.current.enableDatafeed({ ...JOB, isInstalled: true }, TIMESTAMP); + }); - it('calls setupMlJob if job is uninstalled', async () => { - mockSetupMlJob.mockReturnValue(Promise.resolve()); - const { result } = renderHook(() => useEnableDataFeed(), { - wrapper, - }); - await act(async () => { - await result.current.enableDatafeed({ ...JOB, isInstalled: false }, TIMESTAMP, false); + expect(mockSetupMlJob).not.toBeCalled(); }); - expect(mockSetupMlJob).toBeCalled(); - }); - it('calls startDatafeeds if enable param is true', async () => { - const { result } = renderHook(() => useEnableDataFeed(), { - wrapper, - }); - await act(async () => { - await result.current.enableDatafeed(JOB, TIMESTAMP, true); + it('calls setupMlJob if job is uninstalled', async () => { + const { result } = renderHook(() => useEnableDataFeed(), { + wrapper, + }); + await act(async () => { + await result.current.enableDatafeed({ ...JOB, isInstalled: false }, TIMESTAMP); + }); + expect(mockSetupMlJob).toBeCalled(); }); - expect(mockStartDatafeeds).toBeCalled(); - expect(mockStopDatafeeds).not.toBeCalled(); - }); - it('calls stopDatafeeds if enable param is false', async () => { - const { result } = renderHook(() => useEnableDataFeed(), { - wrapper, - }); - await act(async () => { - await result.current.enableDatafeed(JOB, TIMESTAMP, false); + it('calls startDatafeeds when enableDatafeed is called', async () => { + const { result } = renderHook(() => useEnableDataFeed(), { + wrapper, + }); + await act(async () => { + await result.current.enableDatafeed(JOB, TIMESTAMP); + }); + expect(mockStartDatafeeds).toBeCalled(); + expect(mockStopDatafeeds).not.toBeCalled(); }); - expect(mockStartDatafeeds).not.toBeCalled(); - expect(mockStopDatafeeds).toBeCalled(); - }); - it('calls startDatafeeds with 2 weeks old start date', async () => { - jest.useFakeTimers().setSystemTime(new Date('1989-03-07')); + it('calls startDatafeeds with 2 weeks old start date', async () => { + jest.useFakeTimers().setSystemTime(new Date('1989-03-07')); - const { result } = renderHook(() => useEnableDataFeed(), { - wrapper, - }); - await act(async () => { - await result.current.enableDatafeed(JOB, TIMESTAMP, true); - }); - expect(mockStartDatafeeds).toBeCalledWith({ - datafeedIds: [`datafeed-test_job_id`], - start: new Date('1989-02-21').getTime(), + const { result } = renderHook(() => useEnableDataFeed(), { + wrapper, + }); + await act(async () => { + await result.current.enableDatafeed(JOB, TIMESTAMP); + }); + expect(mockStartDatafeeds).toBeCalledWith({ + datafeedIds: [`datafeed-test_job_id`], + start: new Date('1989-02-21').getTime(), + }); }); - }); - describe('telemetry', () => { - it('reports telemetry when installing and enabling a job', async () => { - mockSetupMlJob.mockReturnValue(new Promise((resolve) => resolve({}))); + it('return enabled:true when startDataFeed successfully installed the job', async () => { const { result } = renderHook(() => useEnableDataFeed(), { wrapper, }); - await act(async () => { - await result.current.enableDatafeed(JOB, TIMESTAMP, true); + const response = await result.current.enableDatafeed(JOB, TIMESTAMP); + expect(response.enabled).toBeTruthy(); }); + }); - expect(mockedTelemetry.reportMLJobUpdate).toHaveBeenCalledWith({ - status: ML_JOB_TELEMETRY_STATUS.moduleInstalled, - isElasticJob: true, - jobId, - moduleId, + it('return enabled:false when startDataFeed promise is rejected while installing a job', async () => { + mockStartDatafeeds.mockReturnValue(Promise.reject(new Error('test_error'))); + const { result } = renderHook(() => useEnableDataFeed(), { + wrapper, }); - - expect(mockedTelemetry.reportMLJobUpdate).toHaveBeenCalledWith({ - status: ML_JOB_TELEMETRY_STATUS.started, - isElasticJob: true, - jobId, + await act(async () => { + const response = await result.current.enableDatafeed(JOB, TIMESTAMP); + expect(response.enabled).toBeFalsy(); }); }); - it('reports telemetry when stopping a job', async () => { + it('return enabled:false when startDataFeed failed to install the job', async () => { + mockStartDatafeeds.mockReturnValue( + Promise.resolve({ [`datafeed-${jobId}`]: { started: false, error: 'test_error' } }) + ); + const { result } = renderHook(() => useEnableDataFeed(), { wrapper, }); await act(async () => { - await result.current.enableDatafeed({ ...JOB, isInstalled: true }, TIMESTAMP, false); + const response = await result.current.enableDatafeed(JOB, TIMESTAMP); + expect(response.enabled).toBeFalsy(); + }); + }); + + describe('telemetry', () => { + it('reports telemetry when installing and enabling a job', async () => { + const { result } = renderHook(() => useEnableDataFeed(), { + wrapper, + }); + + await act(async () => { + await result.current.enableDatafeed(JOB, TIMESTAMP); + }); + + expect(mockedTelemetry.reportMLJobUpdate).toHaveBeenCalledWith({ + status: ML_JOB_TELEMETRY_STATUS.moduleInstalled, + isElasticJob: true, + jobId, + moduleId, + }); + + expect(mockedTelemetry.reportMLJobUpdate).toHaveBeenCalledWith({ + status: ML_JOB_TELEMETRY_STATUS.started, + isElasticJob: true, + jobId, + }); }); - expect(mockedTelemetry.reportMLJobUpdate).toHaveBeenCalledWith({ - status: ML_JOB_TELEMETRY_STATUS.stopped, - isElasticJob: true, - jobId, + it('reports telemetry when starting a job fails', async () => { + mockStartDatafeeds.mockReturnValue(Promise.reject(new Error('test_error'))); + const { result } = renderHook(() => useEnableDataFeed(), { + wrapper, + }); + await act(async () => { + await result.current.enableDatafeed({ ...JOB, isInstalled: true }, TIMESTAMP); + }); + + expect(mockedTelemetry.reportMLJobUpdate).toHaveBeenCalledWith({ + status: ML_JOB_TELEMETRY_STATUS.startError, + errorMessage: 'Start job failure - test_error', + isElasticJob: true, + jobId, + }); + }); + + it('reports telemetry when installing a module fails', async () => { + mockSetupMlJob.mockReturnValue(Promise.reject(new Error('test_error'))); + const { result } = renderHook(() => useEnableDataFeed(), { + wrapper, + }); + await act(async () => { + await result.current.enableDatafeed(JOB, TIMESTAMP); + }); + + expect(mockedTelemetry.reportMLJobUpdate).toHaveBeenCalledWith({ + status: ML_JOB_TELEMETRY_STATUS.installationError, + errorMessage: 'Create job failure - test_error', + isElasticJob: true, + jobId, + moduleId, + }); }); }); + }); - it('reports telemetry when stopping a job fails', async () => { - mockStopDatafeeds.mockReturnValue(Promise.reject(new Error('test_error'))); + describe('disableDatafeed', () => { + it('return enabled:false when disableDatafeed successfully uninstalled the job', async () => { const { result } = renderHook(() => useEnableDataFeed(), { wrapper, }); await act(async () => { - await result.current.enableDatafeed({ ...JOB, isInstalled: true }, TIMESTAMP, false); - }); - - expect(mockedTelemetry.reportMLJobUpdate).toHaveBeenCalledWith({ - status: ML_JOB_TELEMETRY_STATUS.stopError, - errorMessage: 'Stop job failure - test_error', - isElasticJob: true, - jobId, + const response = await result.current.disableDatafeed(JOB); + expect(response.enabled).toBeFalsy(); }); }); - it('reports telemetry when starting a job fails', async () => { - mockStartDatafeeds.mockReturnValue(Promise.reject(new Error('test_error'))); + it('return enabled:true when promise is rejected while uninstalling the job', async () => { + mockStopDatafeeds.mockReturnValue(Promise.reject(new Error('test_error'))); const { result } = renderHook(() => useEnableDataFeed(), { wrapper, }); await act(async () => { - await result.current.enableDatafeed({ ...JOB, isInstalled: true }, TIMESTAMP, true); + const response = await result.current.disableDatafeed(JOB); + expect(response.enabled).toBeTruthy(); }); + }); + + it('return enabled:true when disableDatafeed fails to uninstall the job', async () => { + mockStopDatafeeds.mockReturnValue( + Promise.resolve([{ [`datafeed-${jobId}`]: { stopped: false, error: 'test_error' } }]) + ); - expect(mockedTelemetry.reportMLJobUpdate).toHaveBeenCalledWith({ - status: ML_JOB_TELEMETRY_STATUS.startError, - errorMessage: 'Start job failure - test_error', - isElasticJob: true, - jobId, + const { result } = renderHook(() => useEnableDataFeed(), { + wrapper, + }); + await act(async () => { + const response = await result.current.disableDatafeed(JOB); + expect(response.enabled).toBeTruthy(); }); }); - it('reports telemetry when installing a module fails', async () => { - mockSetupMlJob.mockReturnValue(Promise.reject(new Error('test_error'))); + it('calls stopDatafeeds when disableDatafeed is called', async () => { const { result } = renderHook(() => useEnableDataFeed(), { wrapper, }); await act(async () => { - await result.current.enableDatafeed(JOB, TIMESTAMP, true); + await result.current.disableDatafeed(JOB); + }); + expect(mockStartDatafeeds).not.toBeCalled(); + expect(mockStopDatafeeds).toBeCalled(); + }); + + describe('telemetry', () => { + it('reports telemetry when stopping a job', async () => { + const { result } = renderHook(() => useEnableDataFeed(), { + wrapper, + }); + await act(async () => { + await result.current.disableDatafeed({ ...JOB, isInstalled: true }); + }); + + expect(mockedTelemetry.reportMLJobUpdate).toHaveBeenCalledWith({ + status: ML_JOB_TELEMETRY_STATUS.stopped, + isElasticJob: true, + jobId, + }); }); - expect(mockedTelemetry.reportMLJobUpdate).toHaveBeenCalledWith({ - status: ML_JOB_TELEMETRY_STATUS.installationError, - errorMessage: 'Create job failure - test_error', - isElasticJob: true, - jobId, - moduleId, + it('reports telemetry when stopping a job fails', async () => { + mockStopDatafeeds.mockReturnValue(Promise.reject(new Error('test_error'))); + const { result } = renderHook(() => useEnableDataFeed(), { + wrapper, + }); + await act(async () => { + await result.current.disableDatafeed({ ...JOB, isInstalled: true }); + }); + + expect(mockedTelemetry.reportMLJobUpdate).toHaveBeenCalledWith({ + status: ML_JOB_TELEMETRY_STATUS.stopError, + errorMessage: 'Stop job failure - test_error', + isElasticJob: true, + jobId, + }); }); }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_enable_data_feed.ts b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_enable_data_feed.ts index 48b7a918af26c..393e132436c38 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_enable_data_feed.ts +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_enable_data_feed.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { isEmpty } from 'lodash/fp'; import { useCallback, useState } from 'react'; import { useAppToasts } from '../../../hooks/use_app_toasts'; import { useKibana } from '../../../lib/kibana'; @@ -16,10 +17,10 @@ import { } from '../../../lib/telemetry'; import { setupMlJob, startDatafeeds, stopDatafeeds } from '../api'; -import type { SecurityJob } from '../types'; +import type { ErrorResponse, SecurityJob } from '../types'; import * as i18n from './translations'; -// Enable/Disable Job & Datafeed -- passed to JobsTable for use as callback on JobSwitch +// Enable/Disable Job & Datafeed export const useEnableDataFeed = () => { const { telemetry } = useKibana().services; @@ -27,11 +28,14 @@ export const useEnableDataFeed = () => { const [isLoading, setIsLoading] = useState(false); const enableDatafeed = useCallback( - async (job: SecurityJob, latestTimestampMs: number, enable: boolean) => { - submitTelemetry(job, enable); + async (job: SecurityJob, latestTimestampMs: number) => { + setIsLoading(true); + track( + METRIC_TYPE.COUNT, + job.isElasticJob ? TELEMETRY_EVENT.SIEM_JOB_ENABLED : TELEMETRY_EVENT.CUSTOM_JOB_ENABLED + ); if (!job.isInstalled) { - setIsLoading(true); try { await setupMlJob({ configTemplate: job.moduleId, @@ -39,7 +43,6 @@ export const useEnableDataFeed = () => { jobIdErrorFilter: [job.id], groups: job.groups, }); - setIsLoading(false); telemetry.reportMLJobUpdate({ jobId: job.id, isElasticJob: job.isElasticJob, @@ -47,8 +50,8 @@ export const useEnableDataFeed = () => { status: ML_JOB_TELEMETRY_STATUS.moduleInstalled, }); } catch (error) { - addError(error, { title: i18n.CREATE_JOB_FAILURE }); setIsLoading(false); + addError(error, { title: i18n.CREATE_JOB_FAILURE }); telemetry.reportMLJobUpdate({ jobId: job.id, isElasticJob: job.isElasticJob, @@ -56,7 +59,8 @@ export const useEnableDataFeed = () => { status: ML_JOB_TELEMETRY_STATUS.installationError, errorMessage: `${i18n.CREATE_JOB_FAILURE} - ${error.message}`, }); - return; + + return { enabled: false }; } } @@ -64,63 +68,89 @@ export const useEnableDataFeed = () => { const date = new Date(); const maxStartTime = date.setDate(date.getDate() - 14); - setIsLoading(true); - if (enable) { - const startTime = Math.max(latestTimestampMs, maxStartTime); - try { - await startDatafeeds({ datafeedIds: [`datafeed-${job.id}`], start: startTime }); - telemetry.reportMLJobUpdate({ - jobId: job.id, - isElasticJob: job.isElasticJob, - status: ML_JOB_TELEMETRY_STATUS.started, - }); - } catch (error) { - track(METRIC_TYPE.COUNT, TELEMETRY_EVENT.JOB_ENABLE_FAILURE); - addError(error, { title: i18n.START_JOB_FAILURE }); - telemetry.reportMLJobUpdate({ - jobId: job.id, - isElasticJob: job.isElasticJob, - status: ML_JOB_TELEMETRY_STATUS.startError, - errorMessage: `${i18n.START_JOB_FAILURE} - ${error.message}`, - }); - } - } else { - try { - await stopDatafeeds({ datafeedIds: [`datafeed-${job.id}`] }); - telemetry.reportMLJobUpdate({ - jobId: job.id, - isElasticJob: job.isElasticJob, - status: ML_JOB_TELEMETRY_STATUS.stopped, - }); - } catch (error) { - track(METRIC_TYPE.COUNT, TELEMETRY_EVENT.JOB_DISABLE_FAILURE); - addError(error, { title: i18n.STOP_JOB_FAILURE }); - telemetry.reportMLJobUpdate({ - jobId: job.id, - isElasticJob: job.isElasticJob, - status: ML_JOB_TELEMETRY_STATUS.stopError, - errorMessage: `${i18n.STOP_JOB_FAILURE} - ${error.message}`, - }); + const datafeedId = `datafeed-${job.id}`; + + const startTime = Math.max(latestTimestampMs, maxStartTime); + + try { + const response = await startDatafeeds({ + datafeedIds: [datafeedId], + start: startTime, + }); + + if (response[datafeedId]?.error) { + throw new Error(response[datafeedId].error); } + + telemetry.reportMLJobUpdate({ + jobId: job.id, + isElasticJob: job.isElasticJob, + status: ML_JOB_TELEMETRY_STATUS.started, + }); + + return { enabled: response[datafeedId] ? response[datafeedId].started : false }; + } catch (error) { + track(METRIC_TYPE.COUNT, TELEMETRY_EVENT.JOB_ENABLE_FAILURE); + addError(error, { title: i18n.START_JOB_FAILURE }); + telemetry.reportMLJobUpdate({ + jobId: job.id, + isElasticJob: job.isElasticJob, + status: ML_JOB_TELEMETRY_STATUS.startError, + errorMessage: `${i18n.START_JOB_FAILURE} - ${error.message}`, + }); + } finally { + setIsLoading(false); } - setIsLoading(false); + + return { enabled: false }; }, [addError, telemetry] ); - return { enableDatafeed, isLoading }; -}; + const disableDatafeed = useCallback( + async (job: SecurityJob) => { + track( + METRIC_TYPE.COUNT, + job.isElasticJob ? TELEMETRY_EVENT.SIEM_JOB_DISABLED : TELEMETRY_EVENT.CUSTOM_JOB_DISABLED + ); + setIsLoading(true); -const submitTelemetry = (job: SecurityJob, enabled: boolean) => { - // Report type of job enabled/disabled - track( - METRIC_TYPE.COUNT, - job.isElasticJob - ? enabled - ? TELEMETRY_EVENT.SIEM_JOB_ENABLED - : TELEMETRY_EVENT.SIEM_JOB_DISABLED - : enabled - ? TELEMETRY_EVENT.CUSTOM_JOB_ENABLED - : TELEMETRY_EVENT.CUSTOM_JOB_DISABLED + const datafeedId = `datafeed-${job.id}`; + + try { + const [response] = await stopDatafeeds({ datafeedIds: [datafeedId] }); + + if (isErrorResponse(response)) { + throw new Error(response.error); + } + + telemetry.reportMLJobUpdate({ + jobId: job.id, + isElasticJob: job.isElasticJob, + status: ML_JOB_TELEMETRY_STATUS.stopped, + }); + + return { enabled: response[datafeedId] ? !response[datafeedId].stopped : true }; + } catch (error) { + track(METRIC_TYPE.COUNT, TELEMETRY_EVENT.JOB_DISABLE_FAILURE); + addError(error, { title: i18n.STOP_JOB_FAILURE }); + telemetry.reportMLJobUpdate({ + jobId: job.id, + isElasticJob: job.isElasticJob, + status: ML_JOB_TELEMETRY_STATUS.stopError, + errorMessage: `${i18n.STOP_JOB_FAILURE} - ${error.message}`, + }); + } finally { + setIsLoading(false); + } + + return { enabled: true }; + }, + [addError, telemetry] ); + + return { enableDatafeed, disableDatafeed, isLoading }; }; + +const isErrorResponse = (response: ErrorResponse): response is ErrorResponse => + !isEmpty(response.error); diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/ml_popover.tsx b/x-pack/plugins/security_solution/public/common/components/ml_popover/ml_popover.tsx index d518a42f2907d..5f3b834d777b8 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/ml_popover.tsx +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/ml_popover.tsx @@ -59,14 +59,22 @@ export const MlPopover = React.memo(() => { } = useSecurityJobs(); const docLinks = useKibana().services.docLinks; - const { enableDatafeed, isLoading: isLoadingEnableDataFeed } = useEnableDataFeed(); + const { + enableDatafeed, + disableDatafeed, + isLoading: isLoadingEnableDataFeed, + } = useEnableDataFeed(); const handleJobStateChange = useCallback( async (job: SecurityJob, latestTimestampMs: number, enable: boolean) => { - const result = await enableDatafeed(job, latestTimestampMs, enable); + if (enable) { + await enableDatafeed(job, latestTimestampMs); + } else { + await disableDatafeed(job); + } + refreshJobs(); - return result; }, - [refreshJobs, enableDatafeed] + [refreshJobs, enableDatafeed, disableDatafeed] ); const filteredJobs = filterJobs({ diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_start_ml_jobs.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_start_ml_jobs.tsx index a0afcad5901e4..cf12b8b0f1bf6 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_start_ml_jobs.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_start_ml_jobs.tsx @@ -43,7 +43,7 @@ export const useStartMlJobs = (): ReturnUseStartMlJobs => { } const latestTimestampMs = job.latestTimestampMs ?? 0; - await enableDatafeed(job, latestTimestampMs, true); + await enableDatafeed(job, latestTimestampMs); }) ); refetchJobs(); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/ml_jobs_description/admin/ml_admin_job_description.integration.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/ml_jobs_description/admin/ml_admin_job_description.integration.test.tsx index ad9b3c751f59f..104ea748f106c 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/ml_jobs_description/admin/ml_admin_job_description.integration.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/ml_jobs_description/admin/ml_admin_job_description.integration.test.tsx @@ -56,8 +56,7 @@ describe('MlAdminJobDescription', () => { userEvent.click(screen.getByTestId('job-switch')); expect(enableDatafeedSpy).toHaveBeenCalledWith( securityJobNotStarted, - securityJobNotStarted.latestTimestampMs, - true + securityJobNotStarted.latestTimestampMs ); await waitFor(() => { diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/ml_jobs_description/admin/ml_admin_job_description.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/ml_jobs_description/admin/ml_admin_job_description.tsx index 7d4616a004364..7f1236aef08cd 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/ml_jobs_description/admin/ml_admin_job_description.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/ml_jobs_description/admin/ml_admin_job_description.tsx @@ -25,14 +25,23 @@ const MlAdminJobDescriptionComponent: FC = ({ loading, refreshJob, }) => { - const { enableDatafeed, isLoading: isLoadingEnableDataFeed } = useEnableDataFeed(); + const { + enableDatafeed, + disableDatafeed, + isLoading: isLoadingEnableDataFeed, + } = useEnableDataFeed(); const handleJobStateChange = useCallback( async (_, latestTimestampMs: number, enable: boolean) => { - await enableDatafeed(job, latestTimestampMs, enable); + if (enable) { + await enableDatafeed(job, latestTimestampMs); + } else { + await disableDatafeed(job); + } + refreshJob(job); }, - [enableDatafeed, job, refreshJob] + [enableDatafeed, disableDatafeed, job, refreshJob] ); const switchComponent = useMemo( diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/columns.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/columns.tsx index 1f8556dd27a98..4ef5d2811b5c2 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/columns.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/columns.tsx @@ -4,20 +4,15 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React, { useCallback, useMemo } from 'react'; +import React, { useMemo } from 'react'; import styled from 'styled-components'; import type { EuiBasicTableColumn } from '@elastic/eui'; -import { EuiIcon, EuiLoadingSpinner } from '@elastic/eui'; import * as i18n from './translations'; -import type { AnomaliesCount } from '../../../../common/components/ml/anomaly/use_anomalies_search'; -import { LinkAnchor } from '../../../../common/components/links'; import type { SecurityJob } from '../../../../common/components/ml_popover/types'; -import { - isJobFailed, - isJobStarted, - isJobLoading, -} from '../../../../../common/machine_learning/helpers'; -import { AnomaliesCountLink } from './anomalies_count_link'; +import { isJobStarted } from '../../../../../common/machine_learning/helpers'; + +import { TotalAnomalies } from './components/total_anomalies'; +import type { AnomaliesCount } from '../../../../common/components/ml/anomaly/use_anomalies_search'; type AnomaliesColumns = Array>; @@ -27,7 +22,8 @@ const MediumShadeText = styled.span` export const useAnomaliesColumns = ( loading: boolean, - onJobStateChange: (job: SecurityJob) => Promise + onJobEnabled: (job: SecurityJob) => void, + recentlyEnabledJobIds: string[] ): AnomaliesColumns => { const columns: AnomaliesColumns = useMemo( () => [ @@ -66,40 +62,20 @@ export const useAnomaliesColumns = ( 'data-test-subj': 'anomalies-table-column-count', render: (count, { entity, job }) => { if (!job) return ''; - - if (count > 0 || isJobStarted(job.jobState, job.datafeedState)) { - return ; - } else if (isJobFailed(job.jobState, job.datafeedState)) { - return i18n.JOB_STATUS_FAILED; - } else if (job.isCompatible) { - return ; - } else { - return ; - } + return ( + + ); }, }, ], - [loading, onJobStateChange] + [loading, onJobEnabled, recentlyEnabledJobIds] ); return columns; }; - -const EnableJob = ({ - job, - isLoading, - onJobStateChange, -}: { - job: SecurityJob; - isLoading: boolean; - onJobStateChange: (job: SecurityJob) => Promise; -}) => { - const handleChange = useCallback(() => onJobStateChange(job), [job, onJobStateChange]); - - return isLoading || isJobLoading(job.jobState, job.datafeedState) ? ( - - ) : ( - - {i18n.RUN_JOB} - - ); -}; diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/components/anomalies_tab_link.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/components/anomalies_tab_link.tsx new file mode 100644 index 0000000000000..80f78ee50331e --- /dev/null +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/components/anomalies_tab_link.tsx @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useCallback } from 'react'; +import { useDispatch } from 'react-redux'; +import { SecuritySolutionLinkAnchor } from '../../../../../common/components/links'; +import { SecurityPageName } from '../../../../../app/types'; +import { usersActions } from '../../../../../explore/users/store'; +import { hostsActions } from '../../../../../explore/hosts/store'; +import { HostsType } from '../../../../../explore/hosts/store/model'; +import { UsersType } from '../../../../../explore/users/store/model'; +import { AnomalyEntity } from '../../../../../common/components/ml/anomaly/use_anomalies_search'; + +export const AnomaliesTabLink = ({ + count, + jobId, + entity, +}: { + count: number; + jobId?: string; + entity: AnomalyEntity; +}) => { + const dispatch = useDispatch(); + + const deepLinkId = + entity === AnomalyEntity.User + ? SecurityPageName.usersAnomalies + : SecurityPageName.hostsAnomalies; + + const onClick = useCallback(() => { + if (!jobId) return; + + if (entity === AnomalyEntity.User) { + dispatch( + usersActions.updateUsersAnomaliesJobIdFilter({ + jobIds: [jobId], + usersType: UsersType.page, + }) + ); + + dispatch( + usersActions.updateUsersAnomaliesInterval({ + interval: 'second', + usersType: UsersType.page, + }) + ); + } else { + dispatch( + hostsActions.updateHostsAnomaliesJobIdFilter({ + jobIds: [jobId], + hostsType: HostsType.page, + }) + ); + + dispatch( + hostsActions.updateHostsAnomaliesInterval({ + interval: 'second', + hostsType: HostsType.page, + }) + ); + } + }, [jobId, dispatch, entity]); + + return ( + + {count} + + ); +}; diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/components/enable_job.test.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/components/enable_job.test.tsx new file mode 100644 index 0000000000000..c5c199b79df09 --- /dev/null +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/components/enable_job.test.tsx @@ -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 React from 'react'; +import { render, fireEvent, waitFor } from '@testing-library/react'; +import { useEnableDataFeed } from '../../../../../common/components/ml_popover/hooks/use_enable_data_feed'; +import type { SecurityJob } from '../../../../../common/components/ml_popover/types'; +import { EnableJob } from './enable_job'; + +jest.mock('../../../../../common/components/ml_popover/hooks/use_enable_data_feed', () => ({ + useEnableDataFeed: jest.fn(() => ({ enableDatafeed: jest.fn(), isLoading: false })), +})); + +describe('EnableJob', () => { + const job = { id: 'job-1', latestTimestampMs: 123456789 } as SecurityJob; + + it('renders loading spinner when isLoading is true', () => { + const { queryByTestId } = render( + + ); + expect(queryByTestId('job-switch-loader')).toBeInTheDocument(); + }); + + it('renders enable job when isLoading is false', () => { + const { queryByTestId } = render( + + ); + expect(queryByTestId('job-switch-loader')).not.toBeInTheDocument(); + }); + + it('calls enableDatafeed and onJobEnabled when enable job is clicked', async () => { + const enableDatafeedMock = jest.fn(() => ({ enabled: true })); + const onJobEnabledMock = jest.fn(); + (useEnableDataFeed as jest.Mock).mockReturnValueOnce({ + enableDatafeed: enableDatafeedMock, + isLoading: false, + }); + const { getByText } = render( + + ); + fireEvent.click(getByText('Run job')); + + await waitFor(() => { + expect(enableDatafeedMock).toHaveBeenCalledWith(job, job.latestTimestampMs); + expect(onJobEnabledMock).toHaveBeenCalledWith(job); + }); + }); + + it('renders loading spinner when enabling data feed', async () => { + const enableDatafeedMock = jest.fn(() => ({ enabled: true })); + const onJobEnabledMock = jest.fn(); + (useEnableDataFeed as jest.Mock).mockReturnValueOnce({ + enableDatafeed: enableDatafeedMock, + isLoading: true, + }); + const { queryByTestId } = render( + + ); + + expect(queryByTestId('job-switch-loader')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/components/enable_job.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/components/enable_job.tsx new file mode 100644 index 0000000000000..533a0eddcbc1a --- /dev/null +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/components/enable_job.tsx @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useCallback } from 'react'; +import { EuiLoadingSpinner } from '@elastic/eui'; +import type { SecurityJob } from '../../../../../common/components/ml_popover/types'; +import { LinkAnchor } from '../../../../../common/components/links'; +import * as i18n from '../translations'; +import { useEnableDataFeed } from '../../../../../common/components/ml_popover/hooks/use_enable_data_feed'; + +export const EnableJob = ({ + job, + isLoading, + onJobEnabled, +}: { + job: SecurityJob; + isLoading: boolean; + onJobEnabled: (job: SecurityJob) => void; +}) => { + const { enableDatafeed, isLoading: isEnabling } = useEnableDataFeed(); + + const handleChange = useCallback(async () => { + const result = await enableDatafeed(job, job.latestTimestampMs || 0); + + if (result.enabled) { + onJobEnabled(job); + } + }, [enableDatafeed, job, onJobEnabled]); + + return isLoading || isEnabling ? ( + + ) : ( + + {i18n.RUN_JOB} + + ); +}; diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/components/total_anomalies.test.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/components/total_anomalies.test.tsx new file mode 100644 index 0000000000000..3cd8e25869763 --- /dev/null +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/components/total_anomalies.test.tsx @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { AnomalyEntity } from '../../../../../common/components/ml/anomaly/use_anomalies_search'; +import type { SecurityJob } from '../../../../../common/components/ml_popover/types'; +import { render } from '@testing-library/react'; +import { TotalAnomalies } from './total_anomalies'; +import { TestProviders } from '../../../../../common/mock'; + +const defaultProps = { + count: 0, + job: { isInstalled: true, datafeedState: 'started', jobState: 'opened' } as SecurityJob, + entity: AnomalyEntity.User, + recentlyEnabledJobIds: [], + loading: false, + onJobEnabled: () => {}, +}; + +describe('TotalAnomalies', () => { + it('shows a waiting status when the job is loading', () => { + const loadingJob = { + isInstalled: false, + datafeedState: 'starting', + jobState: 'opening', + } as SecurityJob; + + const { container } = render(, { + wrapper: TestProviders, + }); + + expect(container).toHaveTextContent('Waiting'); + }); +}); diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/components/total_anomalies.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/components/total_anomalies.tsx new file mode 100644 index 0000000000000..8311b28177a08 --- /dev/null +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/components/total_anomalies.tsx @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiIcon } from '@elastic/eui'; +import React from 'react'; +import { + isJobFailed, + isJobLoading, + isJobStarted, +} from '../../../../../../common/machine_learning/helpers'; +import type { AnomalyEntity } from '../../../../../common/components/ml/anomaly/use_anomalies_search'; +import type { SecurityJob } from '../../../../../common/components/ml_popover/types'; +import * as i18n from '../translations'; +import { AnomaliesTabLink } from './anomalies_tab_link'; +import { EnableJob } from './enable_job'; + +export const TotalAnomalies = ({ + count, + job, + entity, + recentlyEnabledJobIds, + loading, + onJobEnabled, +}: { + count: number; + job: SecurityJob; + entity: AnomalyEntity; + recentlyEnabledJobIds: string[]; + loading: boolean; + onJobEnabled: (job: SecurityJob) => void; +}) => { + if (isJobLoading(job.jobState, job.datafeedState)) { + return <>{i18n.JOB_STATUS_WAITING}; + } else if (isJobFailed(job.jobState, job.datafeedState)) { + return <>{i18n.JOB_STATUS_FAILED}; + } else if ( + count > 0 || + isJobStarted(job.jobState, job.datafeedState) || + recentlyEnabledJobIds.includes(job.id) + ) { + return ; + } else if (job.isCompatible) { + return ; + } else { + return ; + } +}; diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/config.ts b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/config.ts index 29f227e800f7a..1396568384b17 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/config.ts +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/config.ts @@ -5,18 +5,49 @@ * 2.0. */ -export const NOTABLE_ANOMALIES_IDS: NotableAnomaliesJobId[] = [ +export const NOTABLE_ANOMALIES_IDS = [ 'auth_rare_source_ip_for_a_user', 'packetbeat_dns_tunneling', 'packetbeat_rare_server_domain', 'packetbeat_rare_dns_question', 'suspicious_login_activity', 'v3_windows_anomalous_script', -]; -export type NotableAnomaliesJobId = - | 'auth_rare_source_ip_for_a_user' - | 'packetbeat_dns_tunneling' - | 'packetbeat_rare_server_domain' - | 'packetbeat_rare_dns_question' - | 'suspicious_login_activity' - | 'v3_windows_anomalous_script'; + 'high_count_network_denies', + 'v3_windows_anomalous_process_all_hosts', + 'v3_linux_rare_metadata_process', + 'packetbeat_rare_user_agent', + 'v3_linux_anomalous_process_all_hosts', + 'packetbeat_rare_urls', + 'v3_windows_anomalous_path_activity', + 'v3_windows_anomalous_process_creation', + 'v3_linux_system_process_discovery', + 'v3_linux_system_user_discovery', + 'high_count_by_destination_country', + 'auth_high_count_logon_events', + 'v3_linux_anomalous_user_name', + 'v3_rare_process_by_host_windows', + 'v3_linux_anomalous_network_activity', + 'auth_high_count_logon_fails', + 'auth_high_count_logon_events_for_a_source_ip', + 'v3_linux_rare_metadata_user', + 'rare_destination_country', + 'v3_linux_system_information_discovery', + 'v3_linux_rare_user_compiler', + 'v3_windows_anomalous_user_name', + 'v3_rare_process_by_host_linux', + 'v3_windows_anomalous_network_activity', + 'auth_rare_hour_for_a_user', + 'v3_windows_rare_metadata_user', + 'v3_windows_rare_user_type10_remote_login', + 'v3_linux_anomalous_network_port_activity', + 'v3_linux_rare_sudo_user', + 'v3_windows_anomalous_service', + 'v3_windows_rare_metadata_process', + 'v3_windows_rare_user_runas_event', + 'v3_linux_network_connection_discovery', + 'v3_linux_network_configuration_discovery', + 'auth_rare_user', + 'high_count_network_events', +] as const; + +export type NotableAnomaliesJobId = typeof NOTABLE_ANOMALIES_IDS[number]; diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/index.test.tsx index 12ef348fcb903..58f075ff76b21 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/index.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/index.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { fireEvent, render } from '@testing-library/react'; +import { act, fireEvent, render, waitFor } from '@testing-library/react'; import React from 'react'; import { EntityAnalyticsAnomalies } from '.'; import type { AnomaliesCount } from '../../../../common/components/ml/anomaly/use_anomalies_search'; @@ -14,6 +14,13 @@ import { AnomalyEntity } from '../../../../common/components/ml/anomaly/use_anom import { TestProviders } from '../../../../common/mock'; import type { SecurityJob } from '../../../../common/components/ml_popover/types'; +jest.mock('../../../../common/components/ml_popover/hooks/use_enable_data_feed', () => ({ + useEnableDataFeed: () => ({ + loading: false, + enableDatafeed: jest.fn().mockResolvedValue({ enabled: true }), + }), +})); + // Query toggle only works if pageName.lenght > 0 jest.mock('../../../../common/utils/route/use_route_spy', () => ({ useRouteSpy: jest.fn().mockReturnValue([ @@ -162,6 +169,32 @@ describe('EntityAnalyticsAnomalies', () => { expect(getByTestId('enable-job')).toBeInTheDocument(); }); + it('renders recently installed jobs', async () => { + const jobCount: AnomaliesCount = { + job: { isInstalled: false, isCompatible: true } as SecurityJob, + name: 'v3_windows_anomalous_script', + count: 0, + + entity: AnomalyEntity.User, + }; + + mockUseNotableAnomaliesSearch.mockReturnValue({ + isLoading: false, + data: [jobCount], + refetch: jest.fn(), + }); + + const { getByTestId } = render(, { wrapper: TestProviders }); + + act(() => { + fireEvent.click(getByTestId('enable-job')); + }); + + await waitFor(() => { + expect(getByTestId('anomalies-table-column-count')).toHaveTextContent('0'); + }); + }); + it('renders failed jobs', () => { const jobCount: AnomaliesCount = { job: { diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/index.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/index.tsx index 25c97c1ababe8..44213690721ea 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/index.tsx @@ -35,7 +35,6 @@ import { SecurityPageName } from '../../../../app/types'; import { getTabsOnUsersUrl } from '../../../../common/components/link_to/redirect_to_users'; import { UsersTableType } from '../../../../explore/users/store/model'; import { useKibana } from '../../../../common/lib/kibana'; -import { useEnableDataFeed } from '../../../../common/components/ml_popover/hooks/use_enable_data_feed'; import type { SecurityJob } from '../../../../common/components/ml_popover/types'; const TABLE_QUERY_ID = 'entityAnalyticsDashboardAnomaliesTable'; @@ -50,6 +49,8 @@ const TABLE_SORTING = { export const ENTITY_ANALYTICS_ANOMALIES_PANEL = 'entity_analytics_anomalies'; export const EntityAnalyticsAnomalies = () => { + const [recentlyEnabledJobIds, setRecentlyEnabledJobIds] = useState([]); + const { services: { ml, http, docLinks }, } = useKibana(); @@ -70,21 +71,12 @@ export const EntityAnalyticsAnomalies = () => { from, to, }); - const { isLoading: isEnableDataFeedLoading, enableDatafeed } = useEnableDataFeed(); - - const handleJobStateChange = useCallback( - async (job: SecurityJob) => { - const result = await enableDatafeed(job, job.latestTimestampMs || 0, true); - refetch(); - return result; - }, - [refetch, enableDatafeed] - ); - const columns = useAnomaliesColumns( - isSearchLoading || isEnableDataFeedLoading, - handleJobStateChange - ); + const onJobEnabled = useCallback(async (job: SecurityJob) => { + setRecentlyEnabledJobIds((current) => [...current, job.id]); + }, []); + + const columns = useAnomaliesColumns(isSearchLoading, onJobEnabled, recentlyEnabledJobIds); const getSecuritySolutionLinkProps = useGetSecuritySolutionLinkProps(); useEffect(() => { @@ -116,8 +108,12 @@ export const EntityAnalyticsAnomalies = () => { }, [getSecuritySolutionLinkProps]); const installedJobsIds = useMemo( - () => data.filter(({ job }) => !!job && job.isInstalled).map(({ job }) => job?.id ?? ''), - [data] + () => + data + .filter(({ job }) => !!job && job.isInstalled) + .map(({ job }) => job?.id ?? '') + .concat(recentlyEnabledJobIds), + [data, recentlyEnabledJobIds] ); const incompatibleJobCount = useMemo( @@ -192,7 +188,6 @@ export const EntityAnalyticsAnomalies = () => { />

- )} @@ -201,6 +196,9 @@ export const EntityAnalyticsAnomalies = () => { responsive={false} items={data} columns={columns} + pagination={{ + showPerPageOptions: true, + }} loading={isSearchLoading} id={TABLE_QUERY_ID} sorting={TABLE_SORTING} diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/translations.ts b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/translations.ts index fdef8b65baddf..d8dbcd8664c95 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/translations.ts +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/translations.ts @@ -77,6 +77,13 @@ export const JOB_STATUS_FAILED = i18n.translate( } ); +export const JOB_STATUS_WAITING = i18n.translate( + 'xpack.securitySolution.entityAnalytics.anomalies.jobStatusLoading', + { + defaultMessage: 'Waiting', + } +); + export const MODULE_NOT_COMPATIBLE_TITLE = (incompatibleJobCount: number) => i18n.translate('xpack.securitySolution.entityAnalytics.anomalies.moduleNotCompatibleTitle', { values: { incompatibleJobCount }, From dfea483ef21cc8c03ab118de3716d3fb7a4fbe23 Mon Sep 17 00:00:00 2001 From: Maryam Saeidi Date: Tue, 25 Apr 2023 11:14:47 +0200 Subject: [PATCH 31/52] [AO] Add threshold information to the metric threshold alert details page (#155493) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #153740, closes #153833, closes #155593 This PR adds threshold information and rule name to the metric threshold alert details page. ![image](https://user-images.githubusercontent.com/12370520/233968325-8a66166b-2534-4b9b-9054-9085270db5f6.png) ## 🧪 How to test - Add xpack.observability.unsafe.alertDetails.metrics.enabled: true to the Kibana config - Create a metric threshold rule with multiple conditions that generates an alert - Go to the alert details page and check threshold information - Click on the rule link; it should send you to the rule page --- .../metrics/metric_value_formatter.test.ts | 27 ++++ .../metrics/metric_value_formatter.ts | 21 ++++ .../alert_details_app_section.test.tsx.snap | 1 + .../alert_details_app_section.test.tsx | 36 +++++- .../components/alert_details_app_section.tsx | 115 +++++++++++++++--- .../components/expression_chart.tsx | 54 ++++---- .../mocks/metric_threshold_rule.ts | 19 ++- .../pages/alert_details/alert_details.tsx | 1 + 8 files changed, 225 insertions(+), 49 deletions(-) create mode 100644 x-pack/plugins/infra/common/alerting/metrics/metric_value_formatter.test.ts create mode 100644 x-pack/plugins/infra/common/alerting/metrics/metric_value_formatter.ts diff --git a/x-pack/plugins/infra/common/alerting/metrics/metric_value_formatter.test.ts b/x-pack/plugins/infra/common/alerting/metrics/metric_value_formatter.test.ts new file mode 100644 index 0000000000000..b6413b37b0380 --- /dev/null +++ b/x-pack/plugins/infra/common/alerting/metrics/metric_value_formatter.test.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { metricValueFormatter } from './metric_value_formatter'; + +describe('metricValueFormatter', () => { + const testData = [ + { value: null, metric: undefined, result: '[NO DATA]' }, + { value: null, metric: 'system.cpu.user.pct', result: '[NO DATA]' }, + { value: 50, metric: undefined, result: '50' }, + { value: 0.7, metric: 'system.cpu.user.pct', result: '70%' }, + { value: 0.7012345, metric: 'system.cpu.user.pct', result: '70.1%' }, + { value: 208, metric: 'system.cpu.user.ticks', result: '208' }, + { value: 0.8, metric: 'system.cpu.user.ticks', result: '0.8' }, + ]; + + it.each(testData)( + 'metricValueFormatter($value, $metric) = $result', + ({ value, metric, result }) => { + expect(metricValueFormatter(value, metric)).toBe(result); + } + ); +}); diff --git a/x-pack/plugins/infra/common/alerting/metrics/metric_value_formatter.ts b/x-pack/plugins/infra/common/alerting/metrics/metric_value_formatter.ts new file mode 100644 index 0000000000000..2049a3667d0e5 --- /dev/null +++ b/x-pack/plugins/infra/common/alerting/metrics/metric_value_formatter.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { createFormatter } from '../../formatters'; + +export const metricValueFormatter = (value: number | null, metric: string = '') => { + const noDataValue = i18n.translate('xpack.infra.metrics.alerting.noDataFormattedValue', { + defaultMessage: '[NO DATA]', + }); + + const formatter = metric.endsWith('.pct') + ? createFormatter('percent') + : createFormatter('highPrecision'); + + return value == null ? noDataValue : formatter(value); +}; diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/__snapshots__/alert_details_app_section.test.tsx.snap b/x-pack/plugins/infra/public/alerting/metric_threshold/components/__snapshots__/alert_details_app_section.test.tsx.snap index 9994945cd3290..5ee10d2d3381e 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/__snapshots__/alert_details_app_section.test.tsx.snap +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/__snapshots__/alert_details_app_section.test.tsx.snap @@ -34,6 +34,7 @@ Array [ "groupBy": Array [ "host.hostname", ], + "hideTitle": true, "source": Object { "id": "default", }, diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_details_app_section.test.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_details_app_section.test.tsx index 20f134633e04b..d73aec96da4d1 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_details_app_section.test.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_details_app_section.test.tsx @@ -6,6 +6,8 @@ */ import React from 'react'; +import { EuiLink } from '@elastic/eui'; +import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; import { coreMock as mockCoreMock } from '@kbn/core/public/mocks'; import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { render } from '@testing-library/react'; @@ -17,6 +19,8 @@ import { import { AlertDetailsAppSection } from './alert_details_app_section'; import { ExpressionChart } from './expression_chart'; +const mockedChartStartContract = chartPluginMock.createStartContract(); + jest.mock('@kbn/observability-alert-details', () => ({ AlertAnnotation: () => {}, AlertActiveTimeRangeAnnotation: () => {}, @@ -32,7 +36,10 @@ jest.mock('./expression_chart', () => ({ jest.mock('../../../hooks/use_kibana', () => ({ useKibanaContextForPlugin: () => ({ - services: mockCoreMock.createStart(), + services: { + ...mockCoreMock.createStart(), + charts: mockedChartStartContract, + }, }), })); @@ -46,6 +53,8 @@ jest.mock('../../../containers/metrics_source/source', () => ({ describe('AlertDetailsAppSection', () => { const queryClient = new QueryClient(); + const mockedSetAlertSummaryFields = jest.fn(); + const ruleLink = 'ruleLink'; const renderComponent = () => { return render( @@ -53,16 +62,39 @@ describe('AlertDetailsAppSection', () => {
); }; - it('should render rule data', async () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should render rule and alert data', async () => { const result = renderComponent(); expect((await result.findByTestId('metricThresholdAppSection')).children.length).toBe(3); + expect(result.getByTestId('threshold-2000-2500')).toBeTruthy(); + }); + + it('should render rule link', async () => { + renderComponent(); + + expect(mockedSetAlertSummaryFields).toBeCalledTimes(1); + expect(mockedSetAlertSummaryFields).toBeCalledWith([ + { + label: 'Rule', + value: ( + + Monitoring hosts + + ), + }, + ]); }); it('should render annotations', async () => { diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_details_app_section.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_details_app_section.tsx index 06602c9638865..466d032b5c01f 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_details_app_section.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_details_app_section.tsx @@ -5,17 +5,31 @@ * 2.0. */ -import React, { useMemo } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import React, { useEffect, useMemo } from 'react'; import moment from 'moment'; -import { EuiFlexGroup, EuiFlexItem, EuiPanel, useEuiTheme } from '@elastic/eui'; -import { TopAlert } from '@kbn/observability-plugin/public'; -import { ALERT_END, ALERT_START } from '@kbn/rule-data-utils'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiLink, + EuiPanel, + EuiSpacer, + EuiText, + EuiTitle, + useEuiTheme, +} from '@elastic/eui'; +import { AlertSummaryField, TopAlert } from '@kbn/observability-plugin/public'; +import { ALERT_END, ALERT_START, ALERT_EVALUATION_VALUES } from '@kbn/rule-data-utils'; import { Rule } from '@kbn/alerting-plugin/common'; import { AlertAnnotation, getPaddedAlertTimeRange, AlertActiveTimeRangeAnnotation, } from '@kbn/observability-alert-details'; +import { metricValueFormatter } from '../../../../common/alerting/metrics/metric_value_formatter'; +import { TIME_LABELS } from '../../common/criterion_preview_chart/criterion_preview_chart'; +import { Threshold } from '../../common/components/threshold'; import { useSourceContext, withSourceProvider } from '../../../containers/metrics_source'; import { generateUniqueKey } from '../lib/generate_unique_key'; import { MetricsExplorerChartType } from '../../../pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options'; @@ -37,12 +51,19 @@ const ALERT_START_ANNOTATION_ID = 'alert_start_annotation'; const ALERT_TIME_RANGE_ANNOTATION_ID = 'alert_time_range_annotation'; interface AppSectionProps { - rule: MetricThresholdRule; alert: MetricThresholdAlert; + rule: MetricThresholdRule; + ruleLink: string; + setAlertSummaryFields: React.Dispatch>; } -export function AlertDetailsAppSection({ alert, rule }: AppSectionProps) { - const { uiSettings } = useKibanaContextForPlugin().services; +export function AlertDetailsAppSection({ + alert, + rule, + ruleLink, + setAlertSummaryFields, +}: AppSectionProps) { + const { uiSettings, charts } = useKibanaContextForPlugin().services; const { source, createDerivedIndexPattern } = useSourceContext(); const { euiTheme } = useEuiTheme(); @@ -50,6 +71,10 @@ export function AlertDetailsAppSection({ alert, rule }: AppSectionProps) { () => createDerivedIndexPattern(), [createDerivedIndexPattern] ); + const chartProps = { + theme: charts.theme.useChartsTheme(), + baseTheme: charts.theme.useChartsBaseTheme(), + }; const timeRange = getPaddedAlertTimeRange(alert.fields[ALERT_START]!, alert.fields[ALERT_END]); const alertEnd = alert.fields[ALERT_END] ? moment(alert.fields[ALERT_END]).valueOf() : undefined; const annotations = [ @@ -68,22 +93,76 @@ export function AlertDetailsAppSection({ alert, rule }: AppSectionProps) { key={ALERT_TIME_RANGE_ANNOTATION_ID} />, ]; + useEffect(() => { + setAlertSummaryFields([ + { + label: i18n.translate('xpack.infra.metrics.alertDetailsAppSection.summaryField.rule', { + defaultMessage: 'Rule', + }), + value: ( + + {rule.name} + + ), + }, + ]); + }, [alert, rule, ruleLink, setAlertSummaryFields]); return !!rule.params.criteria ? ( - {rule.params.criteria.map((criterion) => ( + {rule.params.criteria.map((criterion, index) => ( - + +

+ {criterion.aggType.toUpperCase()}{' '} + {'metric' in criterion ? criterion.metric : undefined} +

+
+ + + + + + + + metricValueFormatter(d, 'metric' in criterion ? criterion.metric : undefined) + } + title={i18n.translate( + 'xpack.infra.metrics.alertDetailsAppSection.thresholdTitle', + { + defaultMessage: 'Threshold breached', + } + )} + comparator={criterion.comparator} + /> + + + + +
))} diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.tsx index 8b453579b5e67..41a313adda8fa 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.tsx @@ -49,23 +49,25 @@ import { CUSTOM_EQUATION } from '../i18n_strings'; interface Props { expression: MetricExpression; derivedIndexPattern: DataViewBase; - source?: MetricsSourceConfiguration; + annotations?: Array>; + chartType?: MetricsExplorerChartType; filterQuery?: string; groupBy?: string | string[]; - chartType?: MetricsExplorerChartType; + hideTitle?: boolean; + source?: MetricsSourceConfiguration; timeRange?: TimeRange; - annotations?: Array>; } export const ExpressionChart: React.FC = ({ expression, derivedIndexPattern, - source, + annotations, + chartType = MetricsExplorerChartType.bar, filterQuery, groupBy, - chartType = MetricsExplorerChartType.bar, + hideTitle = false, + source, timeRange, - annotations, }) => { const { uiSettings, charts } = useKibanaContextForPlugin().services; @@ -200,25 +202,27 @@ export const ExpressionChart: React.FC = ({ /> -
- {series.id !== 'ALL' ? ( - - - - ) : ( - - - - )} -
+ {!hideTitle && ( +
+ {series.id !== 'ALL' ? ( + + + + ) : ( + + + + )} +
+ )} ); }; diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/mocks/metric_threshold_rule.ts b/x-pack/plugins/infra/public/alerting/metric_threshold/mocks/metric_threshold_rule.ts index 3f579a56ce7a3..812a8864cffd7 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/mocks/metric_threshold_rule.ts +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/mocks/metric_threshold_rule.ts @@ -130,18 +130,29 @@ export const buildMetricThresholdAlert = ( 'kibana.alert.rule.parameters': { criteria: [ { - aggType: 'avg', - comparator: '>', - threshold: [0.1], - timeSize: 1, + aggType: Aggregators.AVERAGE, + comparator: Comparator.GT, + threshold: [2000], + timeSize: 15, timeUnit: 'm', metric: 'system.cpu.user.pct', }, + { + aggType: Aggregators.MAX, + comparator: Comparator.GT, + threshold: [4], + timeSize: 15, + timeUnit: 'm', + metric: 'system.cpu.user.pct', + warningComparator: Comparator.GT, + warningThreshold: [2.2], + }, ], sourceId: 'default', alertOnNoData: true, alertOnGroupDisappear: true, }, + 'kibana.alert.evaluation.values': [2500, 5], 'kibana.alert.rule.category': 'Metric threshold', 'kibana.alert.rule.consumer': 'alerts', 'kibana.alert.rule.execution.uuid': '62dd07ef-ead9-4b1f-a415-7c83d03925f7', diff --git a/x-pack/plugins/observability/public/pages/alert_details/alert_details.tsx b/x-pack/plugins/observability/public/pages/alert_details/alert_details.tsx index d994669fbc6b0..9572c287257de 100644 --- a/x-pack/plugins/observability/public/pages/alert_details/alert_details.tsx +++ b/x-pack/plugins/observability/public/pages/alert_details/alert_details.tsx @@ -130,6 +130,7 @@ export function AlertDetails() { rule={rule} timeZone={timeZone} setAlertSummaryFields={setSummaryFields} + ruleLink={http.basePath.prepend(paths.observability.ruleDetails(rule.id))} /> )} From f1fe2c49afe241ade3dc55ef6be8cbbbf10c7a06 Mon Sep 17 00:00:00 2001 From: jennypavlova Date: Tue, 25 Apr 2023 11:41:05 +0200 Subject: [PATCH 32/52] [Infra UI] Only show 'Hosts' tour when in 'hosts' view (#155619) Closes #155590 ## Summary This PR shows hosts tour only when "hosts" is selected in the `Show` menu ## Testing - Go to Inventory page - Select hosts (default if not changed) - You should see the hosts tour image - Select anything else (for example Kubernetes Pods, Docker Containers) - You shouldn't see the hosts tour image Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../public/pages/metrics/inventory_view/components/layout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx index 6fca2e5ced98a..373a6a563fee9 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx @@ -181,7 +181,7 @@ export const Layout = React.memo(
- {!hostsLinkClickedRef.current && ( + {!hostsLinkClickedRef.current && nodeType === 'host' && ( Date: Tue, 25 Apr 2023 12:04:12 +0200 Subject: [PATCH 33/52] [SLO] Small quality of life improvements (#155485) --- .../hooks/slo/use_fetch_rules_for_slo.ts | 14 ++++++++----- .../slo_details/components/header_control.tsx | 21 ++++++++++++++++++- .../slo_edit/components/slo_edit_form.tsx | 14 ++++++++++++- .../pages/slos/components/slo_list_item.tsx | 21 ++++++++++++++++++- .../rules_list/components/rules_list.tsx | 8 +++---- 5 files changed, 66 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/observability/public/hooks/slo/use_fetch_rules_for_slo.ts b/x-pack/plugins/observability/public/hooks/slo/use_fetch_rules_for_slo.ts index 0c0b5a7108952..153e2fb95c966 100644 --- a/x-pack/plugins/observability/public/hooks/slo/use_fetch_rules_for_slo.ts +++ b/x-pack/plugins/observability/public/hooks/slo/use_fetch_rules_for_slo.ts @@ -11,13 +11,13 @@ import { RefetchQueryFilters, useQuery, } from '@tanstack/react-query'; -import { Rule } from '@kbn/triggers-actions-ui-plugin/public'; +import type { Rule } from '@kbn/triggers-actions-ui-plugin/public'; import { useKibana } from '../../utils/kibana_react'; type SloId = string; interface Params { - sloIds: SloId[]; + sloIds?: SloId[]; } // eslint-disable-next-line @typescript-eslint/consistent-type-definitions @@ -42,7 +42,7 @@ export interface UseFetchRulesForSloResponse { ) => Promise>> | undefined, unknown>>; } -export function useFetchRulesForSlo({ sloIds = [] }: Params): UseFetchRulesForSloResponse { +export function useFetchRulesForSlo({ sloIds }: Params): UseFetchRulesForSloResponse { const { http } = useKibana().services; const { isInitialLoading, isLoading, isError, isSuccess, isRefetching, data, refetch } = useQuery( @@ -51,7 +51,11 @@ export function useFetchRulesForSlo({ sloIds = [] }: Params): UseFetchRulesForSl queryFn: async () => { try { const body = JSON.stringify({ - filter: `alert.attributes.params.sloId:(${sloIds.join(' or ')})`, + filter: `${sloIds?.reduce((acc, sloId, index, array) => { + return `${acc}alert.attributes.params.sloId:${sloId}${ + index < array.length - 1 ? ' or ' : '' + }`; + }, '')}`, fields: ['params.sloId', 'name'], per_page: 1000, }); @@ -60,7 +64,7 @@ export function useFetchRulesForSlo({ sloIds = [] }: Params): UseFetchRulesForSl body, }); - const init = sloIds.reduce((acc, sloId) => ({ ...acc, [sloId]: [] }), {}); + const init = sloIds?.reduce((acc, sloId) => ({ ...acc, [sloId]: [] }), {}); return response.data.reduce( (acc, rule) => ({ diff --git a/x-pack/plugins/observability/public/pages/slo_details/components/header_control.tsx b/x-pack/plugins/observability/public/pages/slo_details/components/header_control.tsx index 79fe2d7d862ea..99ce1ba03f9b6 100644 --- a/x-pack/plugins/observability/public/pages/slo_details/components/header_control.tsx +++ b/x-pack/plugins/observability/public/pages/slo_details/components/header_control.tsx @@ -52,6 +52,14 @@ export function HeaderControl({ isLoading, slo }: Props) { setRuleFlyoutVisibility(true); }; + const handleNavigateToRules = () => { + navigateToUrl( + basePath.prepend( + `${paths.observability.rules}?_a=(lastResponse:!(),search:%27%27,params:(sloId:%27${slo?.id}%27),status:!(),type:!())` + ) + ); + }; + const handleNavigateToApm = () => { if ( slo?.indicator.type === 'sli.apm.transactionDuration' || @@ -121,10 +129,20 @@ export function HeaderControl({ isLoading, slo }: Props) { {i18n.translate( 'xpack.observability.slo.sloDetails.headerControl.createBurnRateRule', { - defaultMessage: 'Create alert rule', + defaultMessage: 'Create new alert rule', } )} , + + {i18n.translate('xpack.observability.slo.sloDetails.headerControl.manageRules', { + defaultMessage: 'Manage rules', + })} + , ].concat( !!slo && isApmIndicatorType(slo.indicator.type) ? [ @@ -145,6 +163,7 @@ export function HeaderControl({ isLoading, slo }: Props) { )} /> + {!!slo && isRuleFlyoutVisible ? ( { + if (isEditMode && rules && rules[slo.id].length && isCreateRuleCheckboxChecked) { + setIsCreateRuleCheckboxChecked(false); + } + }, [isCreateRuleCheckboxChecked, isEditMode, rules, slo]); + const methods = useForm({ defaultValues: { ...SLO_EDIT_FORM_DEFAULT_VALUES, ...urlParams }, values: transformSloResponseToCreateSloInput(slo), @@ -211,6 +222,7 @@ export function SloEditForm({ slo }: Props) { diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.tsx index ffe07cbb9a902..049481486eb9e 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.tsx @@ -92,6 +92,14 @@ export function SloListItem({ queryClient.invalidateQueries(['fetchRulesForSlo']); }; + const handleNavigateToRules = () => { + navigateToUrl( + basePath.prepend( + `${paths.observability.rules}?_a=(lastResponse:!(),search:%27%27,params:(sloId:%27${slo?.id}%27),status:!(),type:!())` + ) + ); + }; + const handleClone = () => { const newSlo = transformValuesToCreateSLOInput( transformSloResponseToCreateSloInput({ ...slo, name: `[Copy] ${slo.name}` })! @@ -203,7 +211,18 @@ export function SloListItem({ data-test-subj="sloActionsCreateRule" > {i18n.translate('xpack.observability.slo.slo.item.actions.createRule', { - defaultMessage: 'Create Alert rule', + defaultMessage: 'Create new Alert rule', + })} + , + + {i18n.translate('xpack.observability.slo.slo.item.actions.manageRules', { + defaultMessage: 'Manage rules', })} , + {showSearchBar && !isEmpty(filters.ruleParams) ? ( + + ) : null} + - {showSearchBar && !isEmpty(filters.ruleParams) ? ( - - ) : null} - {showRulesList && ( <> {showSearchBar ? ( From ab978f5291c5c4e479827757168abbbaa4bffe0f Mon Sep 17 00:00:00 2001 From: Abdul Wahab Zahid Date: Tue, 25 Apr 2023 12:09:13 +0200 Subject: [PATCH 34/52] Show error message on Getting Started in case of failure. (#155622) Fixes https://github.com/elastic/kibana/issues/153707 ## Summary Shows error toast if monitor creation errors out on Getting Started. Screenshot 2023-04-24 at 15 12 22 Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../getting_started/simple_monitor_form.tsx | 7 +++++++ .../getting_started/use_simple_monitor.ts | 19 ++++++++++++++----- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/simple_monitor_form.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/simple_monitor_form.tsx index 049d213249cb9..a4d3460225462 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/simple_monitor_form.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/simple_monitor_form.tsx @@ -142,6 +142,13 @@ export const MONITOR_SUCCESS_LABEL = i18n.translate( } ); +export const MONITOR_FAILURE_LABEL = i18n.translate( + 'xpack.synthetics.monitorManagement.monitorFailureMessage', + { + defaultMessage: 'Monitor was unable to be saved. Please try again later.', + } +); + export const URL_REQUIRED_LABEL = i18n.translate( 'xpack.synthetics.monitorManagement.urlRequiredLabel', { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/use_simple_monitor.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/use_simple_monitor.ts index e8a9e51dad844..9e344af3fcfbc 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/use_simple_monitor.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/use_simple_monitor.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { useFetcher } from '@kbn/observability-plugin/public'; +import { FETCH_STATUS, useFetcher } from '@kbn/observability-plugin/public'; import { useEffect } from 'react'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { useDispatch, useSelector } from 'react-redux'; @@ -20,7 +20,11 @@ import { ServiceLocationErrors, SyntheticsMonitorWithId, } from '../../../../../common/runtime_types'; -import { MONITOR_SUCCESS_LABEL, SimpleFormData } from './simple_monitor_form'; +import { + MONITOR_SUCCESS_LABEL, + MONITOR_FAILURE_LABEL, + SimpleFormData, +} from './simple_monitor_form'; import { kibanaService } from '../../../../utils/kibana_service'; export const useSimpleMonitor = ({ monitorData }: { monitorData?: SimpleFormData }) => { @@ -31,7 +35,7 @@ export const useSimpleMonitor = ({ monitorData }: { monitorData?: SimpleFormData const { refreshApp } = useSyntheticsRefreshContext(); - const { data, loading } = useFetcher(() => { + const { data, loading, status } = useFetcher(() => { if (!monitorData) { return new Promise((resolve) => resolve(undefined)); } @@ -62,7 +66,12 @@ export const useSimpleMonitor = ({ monitorData }: { monitorData?: SimpleFormData ); } - if (!loading && newMonitor?.id) { + if (!loading && status === FETCH_STATUS.FAILURE) { + kibanaService.toasts.addDanger({ + title: MONITOR_FAILURE_LABEL, + toastLifeTimeMs: 3000, + }); + } else if (!loading && newMonitor?.id) { kibanaService.toasts.addSuccess({ title: MONITOR_SUCCESS_LABEL, toastLifeTimeMs: 3000, @@ -71,7 +80,7 @@ export const useSimpleMonitor = ({ monitorData }: { monitorData?: SimpleFormData dispatch(cleanMonitorListState()); application?.navigateToApp('synthetics', { path: 'monitors' }); } - }, [application, data, dispatch, loading, refreshApp, serviceLocations]); + }, [application, data, status, dispatch, loading, refreshApp, serviceLocations]); return { data: data as SyntheticsMonitorWithId, loading }; }; From b6b602a689541ceb44d1d96026ac4bf059041dce Mon Sep 17 00:00:00 2001 From: Maryam Saeidi Date: Tue, 25 Apr 2023 12:31:20 +0200 Subject: [PATCH 35/52] [AO] Add the last step of the alert details page breadcrumb (#155623) Closes #153435 ## Summary This PR adds the last step of the alert details page Breadcrumb ![image](https://user-images.githubusercontent.com/12370520/234010388-6fbee7ec-fd25-4a4c-89bc-ab3469622a41.png) --- .../alert_details/alert_details.test.tsx | 17 ++++++++++++---- .../pages/alert_details/alert_details.tsx | 10 ++++++++-- .../alert_details/components/page_title.tsx | 20 ++++++++++++------- 3 files changed, 34 insertions(+), 13 deletions(-) diff --git a/x-pack/plugins/observability/public/pages/alert_details/alert_details.test.tsx b/x-pack/plugins/observability/public/pages/alert_details/alert_details.test.tsx index dce255a6d244b..f8d1b4b43af74 100644 --- a/x-pack/plugins/observability/public/pages/alert_details/alert_details.test.tsx +++ b/x-pack/plugins/observability/public/pages/alert_details/alert_details.test.tsx @@ -6,11 +6,12 @@ */ import React, { Fragment } from 'react'; -import * as useUiSettingHook from '@kbn/kibana-react-plugin/public/ui_settings/use_ui_setting'; import { useParams } from 'react-router-dom'; import { Chance } from 'chance'; import { waitFor } from '@testing-library/react'; import { casesPluginMock } from '@kbn/cases-plugin/public/mocks'; +import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; +import * as useUiSettingHook from '@kbn/kibana-react-plugin/public/ui_settings/use_ui_setting'; import { Subset } from '../../typings'; import { render } from '../../utils/test_helper'; @@ -120,10 +121,18 @@ describe('Alert details', () => { mockKibana(); }); + const renderComponent = () => + render( + + + , + config + ); + it('should show the alert detail page with all necessary components', async () => { useFetchAlertDetailMock.mockReturnValue([false, alert]); - const alertDetails = render(, config); + const alertDetails = renderComponent(); await waitFor(() => expect(alertDetails.queryByTestId('centerJustifiedSpinner')).toBeFalsy()); @@ -136,7 +145,7 @@ describe('Alert details', () => { it('should show error loading the alert details', async () => { useFetchAlertDetailMock.mockReturnValue([false, alertWithNoData]); - const alertDetails = render(, config); + const alertDetails = renderComponent(); expect(alertDetails.queryByTestId('alertDetailsError')).toBeTruthy(); expect(alertDetails.queryByTestId('centerJustifiedSpinner')).toBeFalsy(); @@ -146,7 +155,7 @@ describe('Alert details', () => { it('should show loading spinner', async () => { useFetchAlertDetailMock.mockReturnValue([true, alertWithNoData]); - const alertDetails = render(, config); + const alertDetails = renderComponent(); expect(alertDetails.queryByTestId('centerJustifiedSpinner')).toBeTruthy(); expect(alertDetails.queryByTestId('alertDetailsError')).toBeFalsy(); diff --git a/x-pack/plugins/observability/public/pages/alert_details/alert_details.tsx b/x-pack/plugins/observability/public/pages/alert_details/alert_details.tsx index 9572c287257de..7bdb4d1054640 100644 --- a/x-pack/plugins/observability/public/pages/alert_details/alert_details.tsx +++ b/x-pack/plugins/observability/public/pages/alert_details/alert_details.tsx @@ -9,7 +9,7 @@ import React, { useEffect, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { useParams } from 'react-router-dom'; import { EuiEmptyPrompt, EuiPanel, EuiSpacer } from '@elastic/eui'; -import { ALERT_RULE_TYPE_ID, ALERT_RULE_UUID } from '@kbn/rule-data-utils'; +import { ALERT_RULE_CATEGORY, ALERT_RULE_TYPE_ID, ALERT_RULE_UUID } from '@kbn/rule-data-utils'; import { RuleTypeModel } from '@kbn/triggers-actions-ui-plugin/public'; import { useKibana } from '../../utils/kibana_react'; @@ -17,7 +17,7 @@ import { useFetchRule } from '../../hooks/use_fetch_rule'; import { usePluginContext } from '../../hooks/use_plugin_context'; import { useBreadcrumbs } from '../../hooks/use_breadcrumbs'; import { useFetchAlertDetail } from '../../hooks/use_fetch_alert_detail'; -import { PageTitle } from './components/page_title'; +import { PageTitle, pageTitleContent } from './components/page_title'; import { HeaderActions } from './components/header_actions'; import { AlertSummary, AlertSummaryField } from './components/alert_summary'; import { CenterJustifiedSpinner } from '../../components/center_justified_spinner'; @@ -33,6 +33,9 @@ interface AlertDetailsPathParams { } export const ALERT_DETAILS_PAGE_ID = 'alert-details-o11y'; +const defaultBreadcrumb = i18n.translate('xpack.observability.breadcrumbs.alertDetails', { + defaultMessage: 'Alert details', +}); export function AlertDetails() { const { @@ -69,6 +72,9 @@ export function AlertDetails() { defaultMessage: 'Alerts', }), }, + { + text: alert ? pageTitleContent(alert.fields[ALERT_RULE_CATEGORY]) : defaultBreadcrumb, + }, ]); if (isLoading) { diff --git a/x-pack/plugins/observability/public/pages/alert_details/components/page_title.tsx b/x-pack/plugins/observability/public/pages/alert_details/components/page_title.tsx index 41172644b9cf7..201e19a5c94a6 100644 --- a/x-pack/plugins/observability/public/pages/alert_details/components/page_title.tsx +++ b/x-pack/plugins/observability/public/pages/alert_details/components/page_title.tsx @@ -34,6 +34,18 @@ export interface PageTitleProps { alert: TopAlert | null; } +export function pageTitleContent(ruleCategory: string) { + return ( + + ); +} + export function PageTitle({ alert }: PageTitleProps) { const { euiTheme } = useEuiTheme(); @@ -41,13 +53,7 @@ export function PageTitle({ alert }: PageTitleProps) { return (
- + {pageTitleContent(alert.fields[ALERT_RULE_CATEGORY])} From bd6ae3e36ffcf39856f4834a245ca417961a557f Mon Sep 17 00:00:00 2001 From: Coen Warmer Date: Tue, 25 Apr 2023 12:49:56 +0200 Subject: [PATCH 36/52] Update CODEOWNERS (#155695) Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .github/CODEOWNERS | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 2da78ed653a0d..ac389064e2986 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -776,21 +776,20 @@ packages/kbn-yarn-lock-validator @elastic/kibana-operations ### Observability Plugins -# Observability Shared -/x-pack/plugins/observability/public/components/shared/date_picker/ @elastic/uptime +# Observability Shared App +x-pack/plugins/observability_shared @elastic/observability-ui -# Unified Observability - on hold due to team capacity shortage -# For now, if you're changing these pages, get a review from someone who understand the changes -# /x-pack/plugins/observability/public/context @elastic/unified-observability -# /x-pack/test/observability_functional @elastic/unified-observability +# Observability App +x-pack/plugins/observability @elastic/actionable-observability + +# Observability App > Overview page +x-pack/plugins/observability/public/pages/overview @elastic/observability-ui -# Home/Overview/Landing Pages -/x-pack/plugins/observability/public/pages/home @elastic/observability-ui -/x-pack/plugins/observability/public/pages/landing @elastic/observability-ui -/x-pack/plugins/observability/public/pages/overview @elastic/observability-ui +# Observability App > Alert Details +x-pack/packages/observability/alert_details @elastic/actionable-observability -# Actionable Observability -/x-pack/test/observability_functional @elastic/actionable-observability +# Observability App > Functional Tests +x-pack/test/observability_functional @elastic/actionable-observability # Observability robots /.github/workflows/deploy-my-kibana.yml @elastic/observablt-robots From 65a4ae6a7f22019087740ce369bccc9c38ef26be Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Tue, 25 Apr 2023 12:52:50 +0200 Subject: [PATCH 37/52] [Security Solution] Add rule snoozing on the rule editing page (#155612) **Addresses:** https://github.com/elastic/kibana/issues/147737 ## Summary This PR adds rule snooze feature on the Rule editing page. https://user-images.githubusercontent.com/3775283/234186169-72db1d91-ad34-4cea-922d-b0c96752c3d3.mov ### Checklist - [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) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [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 --- .../pages/rule_editing/index.tsx | 2 + .../rule_details_snooze_settings/index.tsx | 35 ---------------- .../pages/rule_details/index.test.tsx | 4 +- .../pages/rule_details/index.tsx | 4 +- .../rule_management/api/api.test.ts | 36 ++++++++++++++++ .../rule_management/api/api.ts | 18 ++++++-- .../hooks/use_fetch_rules_snooze_settings.ts | 6 +-- .../components/rule_snooze_badge/index.ts | 8 ++++ .../rule_snooze_badge}/rule_snooze_badge.tsx | 42 +++++++++---------- .../rule_snooze_badge}/translations.ts | 4 +- .../use_rule_snooze_settings.ts | 40 ++++++++++++++++++ .../rule_management/logic/types.ts | 12 +++++- .../rules_table/rules_table_context.test.tsx | 16 +++---- .../components/rules_table/translations.ts | 7 ---- .../components/rules_table/use_columns.tsx | 25 ++--------- .../rules/step_rule_actions/index.tsx | 7 +++- .../step_rule_actions/rule_snooze_section.tsx | 42 +++++++++++++++++++ .../rules/step_rule_actions/translations.tsx | 15 +++++++ 18 files changed, 212 insertions(+), 111 deletions(-) delete mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/components/rule_details_snooze_settings/index.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_snooze_badge/index.ts rename x-pack/plugins/security_solution/public/detection_engine/{components => rule_management/components/rule_snooze_badge}/rule_snooze_badge.tsx (55%) rename x-pack/plugins/security_solution/public/detection_engine/{rule_details_ui/pages/rule_details/components/rule_details_snooze_settings => rule_management/components/rule_snooze_badge}/translations.ts (68%) create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_snooze_badge/use_rule_snooze_settings.ts create mode 100644 x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/rule_snooze_section.tsx diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_editing/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_editing/index.tsx index 829a6688f4b60..e07848c145d08 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_editing/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_editing/index.tsx @@ -303,6 +303,7 @@ const EditRulePageComponent: FC = () => { {actionsStep.data != null && ( { }, ], [ + rule?.id, rule?.immutable, rule?.type, loading, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/components/rule_details_snooze_settings/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/components/rule_details_snooze_settings/index.tsx deleted file mode 100644 index e610715d676ce..0000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/components/rule_details_snooze_settings/index.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 React from 'react'; -import { useFetchRulesSnoozeSettings } from '../../../../../rule_management/api/hooks/use_fetch_rules_snooze_settings'; -import { RuleSnoozeBadge } from '../../../../../components/rule_snooze_badge'; -import * as i18n from './translations'; - -interface RuleDetailsSnoozeBadge { - /** - * Rule's SO id (not ruleId) - */ - id: string; -} - -export function RuleDetailsSnoozeSettings({ id }: RuleDetailsSnoozeBadge): JSX.Element { - const { data: rulesSnoozeSettings, isFetching, isError } = useFetchRulesSnoozeSettings([id]); - const snoozeSettings = rulesSnoozeSettings?.[0]; - - return ( - - ); -} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.test.tsx index 07cbd4294cb22..67a156e31edf2 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.test.tsx @@ -87,8 +87,8 @@ jest.mock('react-router-dom', () => { }); // RuleDetailsSnoozeSettings is an isolated component and not essential for existing tests -jest.mock('./components/rule_details_snooze_settings', () => ({ - RuleDetailsSnoozeSettings: () => <>, +jest.mock('../../../rule_management/components/rule_snooze_badge', () => ({ + RuleSnoozeBadge: () => <>, })); const mockRedirectLegacyUrl = jest.fn(); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx index 90f1d38f69774..211321618068f 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx @@ -140,7 +140,7 @@ import { EditRuleSettingButtonLink } from '../../../../detections/pages/detectio import { useStartMlJobs } from '../../../rule_management/logic/use_start_ml_jobs'; import { useBulkDuplicateExceptionsConfirmation } from '../../../rule_management_ui/components/rules_table/bulk_actions/use_bulk_duplicate_confirmation'; import { BulkActionDuplicateExceptionsConfirmation } from '../../../rule_management_ui/components/rules_table/bulk_actions/bulk_duplicate_exceptions_confirmation'; -import { RuleDetailsSnoozeSettings } from './components/rule_details_snooze_settings'; +import { RuleSnoozeBadge } from '../../../rule_management/components/rule_snooze_badge'; /** * Need a 100% height here to account for the graph/analyze tool, which sets no explicit height parameters, but fills the available space. @@ -559,7 +559,7 @@ const RuleDetailsPageComponent: React.FC = ({ )} - + ); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.test.ts index b1a2d0f95417a..5e3248818bf4e 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.test.ts @@ -791,6 +791,9 @@ describe('Detections Rules API', () => { describe('fetchRulesSnoozeSettings', () => { beforeEach(() => { fetchMock.mockClear(); + fetchMock.mockResolvedValue({ + data: [], + }); }); test('requests snooze settings of multiple rules by their IDs', () => { @@ -836,5 +839,38 @@ describe('Detections Rules API', () => { }) ); }); + + test('returns mapped data', async () => { + fetchMock.mockResolvedValue({ + data: [ + { + id: '1', + mute_all: false, + }, + { + id: '1', + mute_all: false, + active_snoozes: [], + is_snoozed_until: '2023-04-24T19:31:46.765Z', + }, + ], + }); + + const result = await fetchRulesSnoozeSettings({ ids: ['id1'] }); + + expect(result).toEqual([ + { + id: '1', + muteAll: false, + activeSnoozes: [], + }, + { + id: '1', + muteAll: false, + activeSnoozes: [], + isSnoozedUntil: new Date('2023-04-24T19:31:46.765Z'), + }, + ]); + }); }); }); 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 b8078421ce683..24b66cada346c 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 @@ -57,7 +57,8 @@ import type { PrePackagedRulesStatusResponse, PreviewRulesProps, Rule, - RulesSnoozeSettingsResponse, + RuleSnoozeSettings, + RulesSnoozeSettingsBatchResponse, UpdateRulesProps, } from '../logic/types'; import { convertRulesFilterToKQL } from '../logic/utils'; @@ -197,8 +198,8 @@ export const fetchRuleById = async ({ id, signal }: FetchRuleProps): Promise => - KibanaServices.get().http.fetch( +}: FetchRuleSnoozingProps): Promise => { + const response = await KibanaServices.get().http.fetch( INTERNAL_ALERTING_API_FIND_RULES_PATH, { method: 'GET', @@ -211,6 +212,17 @@ export const fetchRulesSnoozeSettings = async ({ } ); + return response.data?.map((snoozeSettings) => ({ + id: snoozeSettings?.id ?? '', + muteAll: snoozeSettings?.mute_all ?? false, + activeSnoozes: snoozeSettings?.active_snoozes ?? [], + isSnoozedUntil: snoozeSettings?.is_snoozed_until + ? new Date(snoozeSettings.is_snoozed_until) + : undefined, + snoozeSchedule: snoozeSettings?.snooze_schedule, + })); +}; + export interface BulkActionSummary { failed: number; skipped: number; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_fetch_rules_snooze_settings.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_fetch_rules_snooze_settings.ts index bdc101fe18644..8e0ef31871826 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_fetch_rules_snooze_settings.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_fetch_rules_snooze_settings.ts @@ -29,11 +29,7 @@ export const useFetchRulesSnoozeSettings = ( ) => { return useQuery( [...FETCH_RULE_SNOOZE_SETTINGS_QUERY_KEY, ...ids], - async ({ signal }) => { - const response = await fetchRulesSnoozeSettings({ ids, signal }); - - return response.data; - }, + ({ signal }) => fetchRulesSnoozeSettings({ ids, signal }), { ...DEFAULT_QUERY_OPTIONS, ...queryOptions, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_snooze_badge/index.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_snooze_badge/index.ts new file mode 100644 index 0000000000000..8e231398688f0 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_snooze_badge/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 './rule_snooze_badge'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/components/rule_snooze_badge.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_snooze_badge/rule_snooze_badge.tsx similarity index 55% rename from x-pack/plugins/security_solution/public/detection_engine/components/rule_snooze_badge.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_snooze_badge/rule_snooze_badge.tsx index 7fa16826eec60..e488127c25691 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/components/rule_snooze_badge.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_snooze_badge/rule_snooze_badge.tsx @@ -7,46 +7,44 @@ import { EuiButtonIcon, EuiToolTip } from '@elastic/eui'; import React, { useMemo } from 'react'; -import { useUserData } from '../../detections/components/user_info'; -import { hasUserCRUDPermission } from '../../common/utils/privileges'; -import { useKibana } from '../../common/lib/kibana'; -import type { RuleSnoozeSettings } from '../rule_management/logic'; -import { useInvalidateFetchRulesSnoozeSettingsQuery } from '../rule_management/api/hooks/use_fetch_rules_snooze_settings'; +import type { RuleObjectId } from '../../../../../common/detection_engine/rule_schema'; +import { useUserData } from '../../../../detections/components/user_info'; +import { hasUserCRUDPermission } from '../../../../common/utils/privileges'; +import { useKibana } from '../../../../common/lib/kibana'; +import { useInvalidateFetchRulesSnoozeSettingsQuery } from '../../api/hooks/use_fetch_rules_snooze_settings'; +import { useRuleSnoozeSettings } from './use_rule_snooze_settings'; interface RuleSnoozeBadgeProps { /** - * Rule's snooze settings, when set to `undefined` considered as a loading state + * Rule's SO id (not ruleId) */ - snoozeSettings: RuleSnoozeSettings | undefined; - /** - * It should represent a user readable error message happened during data snooze settings fetching - */ - error?: string; + ruleId: RuleObjectId; showTooltipInline?: boolean; } export function RuleSnoozeBadge({ - snoozeSettings, - error, + ruleId, showTooltipInline = false, }: RuleSnoozeBadgeProps): JSX.Element { const RulesListNotifyBadge = useKibana().services.triggersActionsUi.getRulesListNotifyBadge; + const { snoozeSettings, error } = useRuleSnoozeSettings(ruleId); const [{ canUserCRUD }] = useUserData(); const hasCRUDPermissions = hasUserCRUDPermission(canUserCRUD); const invalidateFetchRuleSnoozeSettings = useInvalidateFetchRulesSnoozeSettingsQuery(); const isLoading = !snoozeSettings; - const rule = useMemo(() => { - return { + const rule = useMemo( + () => ({ id: snoozeSettings?.id ?? '', - muteAll: snoozeSettings?.mute_all ?? false, - activeSnoozes: snoozeSettings?.active_snoozes ?? [], - isSnoozedUntil: snoozeSettings?.is_snoozed_until - ? new Date(snoozeSettings.is_snoozed_until) + muteAll: snoozeSettings?.muteAll ?? false, + activeSnoozes: snoozeSettings?.activeSnoozes ?? [], + isSnoozedUntil: snoozeSettings?.isSnoozedUntil + ? new Date(snoozeSettings.isSnoozedUntil) : undefined, - snoozeSchedule: snoozeSettings?.snooze_schedule, + snoozeSchedule: snoozeSettings?.snoozeSchedule, isEditable: hasCRUDPermissions, - }; - }, [snoozeSettings, hasCRUDPermissions]); + }), + [snoozeSettings, hasCRUDPermissions] + ); if (error) { return ( diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/components/rule_details_snooze_settings/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_snooze_badge/translations.ts similarity index 68% rename from x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/components/rule_details_snooze_settings/translations.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_snooze_badge/translations.ts index 37b3b6c75ba6e..2c67bdab2744f 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/components/rule_details_snooze_settings/translations.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_snooze_badge/translations.ts @@ -7,8 +7,8 @@ import { i18n } from '@kbn/i18n'; -export const UNABLE_TO_FETCH_RULE_SNOOZE_SETTINGS = i18n.translate( - 'xpack.securitySolution.detectionEngine.ruleDetails.rulesSnoozeSettings.error.unableToFetch', +export const UNABLE_TO_FETCH_RULES_SNOOZE_SETTINGS = i18n.translate( + 'xpack.securitySolution.detectionEngine.rulesSnoozeBadge.error.unableToFetch', { defaultMessage: 'Unable to fetch snooze settings', } diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_snooze_badge/use_rule_snooze_settings.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_snooze_badge/use_rule_snooze_settings.ts new file mode 100644 index 0000000000000..94a857b1e9842 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_snooze_badge/use_rule_snooze_settings.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { RuleSnoozeSettings } from '../../logic'; +import { useFetchRulesSnoozeSettings } from '../../api/hooks/use_fetch_rules_snooze_settings'; +import { useRulesTableContextOptional } from '../../../rule_management_ui/components/rules_table/rules_table/rules_table_context'; +import * as i18n from './translations'; + +interface UseRuleSnoozeSettingsResult { + snoozeSettings?: RuleSnoozeSettings; + error?: string; +} + +export function useRuleSnoozeSettings(id: string): UseRuleSnoozeSettingsResult { + const { + state: { rulesSnoozeSettings: rulesTableSnoozeSettings }, + } = useRulesTableContextOptional() ?? { state: {} }; + const { + data: rulesSnoozeSettings, + isFetching: isSingleSnoozeSettingsFetching, + isError: isSingleSnoozeSettingsError, + } = useFetchRulesSnoozeSettings([id], { + enabled: !rulesTableSnoozeSettings?.data[id] && !rulesTableSnoozeSettings?.isFetching, + }); + const snoozeSettings = rulesTableSnoozeSettings?.data[id] ?? rulesSnoozeSettings?.[0]; + const isFetching = rulesTableSnoozeSettings?.isFetching || isSingleSnoozeSettingsFetching; + const isError = rulesTableSnoozeSettings?.isError || isSingleSnoozeSettingsError; + + return { + snoozeSettings, + error: + isError || (!snoozeSettings && !isFetching) + ? i18n.UNABLE_TO_FETCH_RULES_SNOOZE_SETTINGS + : undefined, + }; +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts index ca71fa2680f17..e22be9467c6a1 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts @@ -219,6 +219,14 @@ export interface FetchRulesProps { } export interface RuleSnoozeSettings { + id: string; + muteAll: boolean; + snoozeSchedule?: RuleSnooze; + activeSnoozes?: string[]; + isSnoozedUntil?: Date; +} + +interface RuleSnoozeSettingsResponse { id: string; mute_all: boolean; snooze_schedule?: RuleSnooze; @@ -226,8 +234,8 @@ export interface RuleSnoozeSettings { is_snoozed_until?: string; } -export interface RulesSnoozeSettingsResponse { - data: RuleSnoozeSettings[]; +export interface RulesSnoozeSettingsBatchResponse { + data: RuleSnoozeSettingsResponse[]; } export type SortingOptions = t.TypeOf; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/rules_table_context.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/rules_table_context.test.tsx index 22a3af8ff0814..abc384cea3bfb 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/rules_table_context.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/rules_table_context.test.tsx @@ -190,8 +190,8 @@ describe('RulesTableContextProvider', () => { { id: '2', name: 'rule 2' }, ] as Rule[], rulesSnoozeSettings: [ - { id: '1', mute_all: true, snooze_schedule: [] }, - { id: '2', mute_all: false, snooze_schedule: [] }, + { id: '1', muteAll: true, snoozeSchedule: [] }, + { id: '2', muteAll: false, snoozeSchedule: [] }, ], }); @@ -216,21 +216,21 @@ describe('RulesTableContextProvider', () => { { id: '2', name: 'rule 2' }, ] as Rule[], rulesSnoozeSettings: [ - { id: '1', mute_all: true, snooze_schedule: [] }, - { id: '2', mute_all: false, snooze_schedule: [] }, + { id: '1', muteAll: true, snoozeSchedule: [] }, + { id: '2', muteAll: false, snoozeSchedule: [] }, ], }); expect(state.rulesSnoozeSettings.data).toEqual({ '1': { id: '1', - mute_all: true, - snooze_schedule: [], + muteAll: true, + snoozeSchedule: [], }, '2': { id: '2', - mute_all: false, - snooze_schedule: [], + muteAll: false, + snoozeSchedule: [], }, }); }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/translations.ts index ad3cd89604030..52b4a5d4ba622 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/translations.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/translations.ts @@ -21,10 +21,3 @@ export const ML_RULE_JOBS_WARNING_BUTTON_LABEL = i18n.translate( defaultMessage: 'Visit rule details page to investigate', } ); - -export const UNABLE_TO_FETCH_RULES_SNOOZE_SETTINGS = i18n.translate( - 'xpack.securitySolution.detectionEngine.ruleManagement.rulesSnoozeSettings.error.unableToFetch', - { - defaultMessage: 'Unable to fetch snooze settings', - } -); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx index 0ffb0ac7574a6..cccd9f394d65e 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx @@ -22,7 +22,7 @@ import type { } from '../../../../../common/detection_engine/rule_monitoring'; import { isMlRule } from '../../../../../common/machine_learning/helpers'; import { getEmptyTagValue } from '../../../../common/components/empty_value'; -import { RuleSnoozeBadge } from '../../../components/rule_snooze_badge'; +import { RuleSnoozeBadge } from '../../../rule_management/components/rule_snooze_badge'; import { FormattedRelativePreferenceDate } from '../../../../common/components/formatted_date'; import { SecuritySolutionLinkAnchor } from '../../../../common/components/links'; import { getRuleDetailsTabUrl } from '../../../../common/components/link_to/redirect_to_detection_engine'; @@ -46,7 +46,6 @@ import { useHasActionsPrivileges } from './use_has_actions_privileges'; import { useHasMlPermissions } from './use_has_ml_permissions'; import { useRulesTableActions } from './use_rules_table_actions'; import { MlRuleWarningPopover } from './ml_rule_warning_popover'; -import * as rulesTableI18n from './translations'; export type TableColumn = EuiBasicTableColumn | EuiTableActionsColumnType; @@ -109,33 +108,15 @@ const useEnabledColumn = ({ hasCRUDPermissions, startMlJobs }: ColumnsProps): Ta }; const useRuleSnoozeColumn = (): TableColumn => { - const { - state: { rulesSnoozeSettings }, - } = useRulesTableContext(); - return useMemo( () => ({ field: 'snooze', name: i18n.COLUMN_SNOOZE, - render: (_, rule: Rule) => { - const snoozeSettings = rulesSnoozeSettings.data[rule.id]; - const { isFetching, isError } = rulesSnoozeSettings; - - return ( - - ); - }, + render: (_, rule: Rule) => , width: '100px', sortable: false, }), - [rulesSnoozeSettings] + [] ); }; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/index.tsx index 86bbb7604add2..47c33f9282572 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/index.tsx @@ -22,6 +22,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import type { ActionVariables } from '@kbn/triggers-actions-ui-plugin/public'; import { UseArray } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; import type { Type } from '@kbn/securitysolution-io-ts-alerting-types'; +import type { RuleObjectId } from '../../../../../common/detection_engine/rule_schema'; import { isQueryRule } from '../../../../../common/detection_engine/utils'; import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; import { ResponseActionsForm } from '../../../../detection_engine/rule_response_actions/response_actions_form'; @@ -35,8 +36,10 @@ import { useKibana } from '../../../../common/lib/kibana'; import { getSchema } from './get_schema'; import * as I18n from './translations'; import { APP_UI_ID } from '../../../../../common/constants'; +import { RuleSnoozeSection } from './rule_snooze_section'; interface StepRuleActionsProps extends RuleStepProps { + ruleId?: RuleObjectId; // Rule SO's id (not ruleId) defaultValues?: ActionsStepRule | null; actionMessageParams: ActionVariables; ruleType?: Type; @@ -68,6 +71,7 @@ const DisplayActionsHeader = () => { }; const StepRuleActionsComponent: FC = ({ + ruleId, addPadding = false, defaultValues, isReadOnlyView, @@ -166,9 +170,9 @@ const StepRuleActionsComponent: FC = ({ return application.capabilities.actions.show ? ( <> + {ruleId && } {displayActionsOptions} {responseActionsEnabled && displayResponseActionsOptions} - @@ -178,6 +182,7 @@ const StepRuleActionsComponent: FC = ({ ); }, [ + ruleId, application.capabilities.actions.show, displayActionsOptions, displayResponseActionsOptions, diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/rule_snooze_section.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/rule_snooze_section.tsx new file mode 100644 index 0000000000000..d9586f80f3e93 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/rule_snooze_section.tsx @@ -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 React from 'react'; +import { css } from '@emotion/react'; +import { EuiFlexGroup, EuiFlexItem, EuiText, useEuiTheme } from '@elastic/eui'; +import type { RuleObjectId } from '../../../../../common/detection_engine/rule_schema'; +import { RuleSnoozeBadge } from '../../../../detection_engine/rule_management/components/rule_snooze_badge'; +import * as i18n from './translations'; + +interface RuleSnoozeSectionProps { + ruleId: RuleObjectId; // Rule SO's id (not ruleId) +} + +export function RuleSnoozeSection({ ruleId }: RuleSnoozeSectionProps): JSX.Element { + const { euiTheme } = useEuiTheme(); + + return ( +
+ {i18n.RULE_SNOOZE_DESCRIPTION} + + + + + + + {i18n.SNOOZED_ACTIONS_WARNING} + + + +
+ ); +} diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/translations.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/translations.tsx index d467c3af05f8f..06368eadc30df 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/translations.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/translations.tsx @@ -28,3 +28,18 @@ export const NO_ACTIONS_READ_PERMISSIONS = i18n.translate( 'Cannot create rule actions. You do not have "Read" permissions for the "Actions" plugin.', } ); + +export const RULE_SNOOZE_DESCRIPTION = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepRuleActions.snoozeDescription', + { + defaultMessage: + 'Select when automated actions should be performed if a rule evaluates as true.', + } +); + +export const SNOOZED_ACTIONS_WARNING = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepRuleActions.snoozedActionsWarning', + { + defaultMessage: 'Actions will not be preformed until it is unsnoozed.', + } +); From 460e9a707cfe1db8e818ef8ba553636996eed985 Mon Sep 17 00:00:00 2001 From: jennypavlova Date: Tue, 25 Apr 2023 12:56:43 +0200 Subject: [PATCH 38/52] [Infrastructure UI] Telemetry: Host view flyout - Add / Remove filter (#155511) Closes [#155415](https://github.com/elastic/kibana/issues/155415) ## Summary This PR adds telemetry for the flyout add/remove filter events # Testing - Open the hosts view flyout, select the metadata tab and add/remove a filter from there - Check in the network tab - there should be a request to the telemetry cluster including the events: image - It should be also visible in [FS](https://app.fullstory.com/ui/1397FY/home) under API events (for some reason I don't see the events made from my machine in FS at all so I couldn't check it there) --- .../cloud_full_story/server/config.ts | 2 + .../metadata/add_metadata_filter_button.tsx | 11 ++++- .../telemetry/telemetry_client.mock.ts | 2 + .../services/telemetry/telemetry_client.ts | 17 +++++++ .../services/telemetry/telemetry_events.ts | 32 +++++++++++++- .../telemetry/telemetry_service.test.ts | 44 ++++++++++++++++++- .../infra/public/services/telemetry/types.ts | 21 ++++++++- 7 files changed, 125 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/cloud_integrations/cloud_full_story/server/config.ts b/x-pack/plugins/cloud_integrations/cloud_full_story/server/config.ts index 15bcfdc53512f..85822bae819eb 100644 --- a/x-pack/plugins/cloud_integrations/cloud_full_story/server/config.ts +++ b/x-pack/plugins/cloud_integrations/cloud_full_story/server/config.ts @@ -22,6 +22,8 @@ const configSchema = schema.object({ 'Loaded Kibana', // Sent once per page refresh (potentially, once per session) 'Hosts View Query Submitted', // Worst-case scenario 1 every 2 seconds 'Host Entry Clicked', // Worst-case scenario once per second - AT RISK, + 'Host Flyout Filter Removed', // Worst-case scenario once per second - AT RISK, + 'Host Flyout Filter Added', // Worst-case scenario once per second - AT RISK, ], }), }); diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/metadata/add_metadata_filter_button.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/metadata/add_metadata_filter_button.tsx index 51277427b6352..acefcbea5e304 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/metadata/add_metadata_filter_button.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/metadata/add_metadata_filter_button.tsx @@ -37,6 +37,7 @@ export const AddMetadataFilterButton = ({ item }: AddMetadataFilterButtonProps) query: { filterManager: filterManagerService }, }, notifications: { toasts: toastsService }, + telemetry, }, } = useKibanaContextForPlugin(); @@ -53,6 +54,9 @@ export const AddMetadataFilterButton = ({ item }: AddMetadataFilterButtonProps) negate: false, }); if (newFilter) { + telemetry.reportHostFlyoutFilterAdded({ + field_name: item.name, + }); filterManagerService.addFilters(newFilter); toastsService.addSuccess({ title: filterAddedToastTitle, @@ -84,7 +88,12 @@ export const AddMetadataFilterButton = ({ item }: AddMetadataFilterButtonProps) defaultMessage: 'Filter', } )} - onClick={() => filterManagerService.removeFilter(existingFilter)} + onClick={() => { + telemetry.reportHostFlyoutFilterRemoved({ + field_name: existingFilter.meta.key!, + }); + filterManagerService.removeFilter(existingFilter); + }} /> diff --git a/x-pack/plugins/infra/public/services/telemetry/telemetry_client.mock.ts b/x-pack/plugins/infra/public/services/telemetry/telemetry_client.mock.ts index 83e8fc2420440..298424f5c9db6 100644 --- a/x-pack/plugins/infra/public/services/telemetry/telemetry_client.mock.ts +++ b/x-pack/plugins/infra/public/services/telemetry/telemetry_client.mock.ts @@ -10,4 +10,6 @@ import { ITelemetryClient } from './types'; export const createTelemetryClientMock = (): jest.Mocked => ({ reportHostEntryClicked: jest.fn(), reportHostsViewQuerySubmitted: jest.fn(), + reportHostFlyoutFilterRemoved: jest.fn(), + reportHostFlyoutFilterAdded: jest.fn(), }); diff --git a/x-pack/plugins/infra/public/services/telemetry/telemetry_client.ts b/x-pack/plugins/infra/public/services/telemetry/telemetry_client.ts index 66ee3220c2935..f53a6b298de1d 100644 --- a/x-pack/plugins/infra/public/services/telemetry/telemetry_client.ts +++ b/x-pack/plugins/infra/public/services/telemetry/telemetry_client.ts @@ -8,6 +8,7 @@ import { AnalyticsServiceSetup } from '@kbn/core-analytics-server'; import { HostEntryClickedParams, + HostFlyoutFilterActionParams, HostsViewQuerySubmittedParams, InfraTelemetryEventTypes, ITelemetryClient, @@ -30,6 +31,22 @@ export class TelemetryClient implements ITelemetryClient { }); }; + public reportHostFlyoutFilterRemoved = ({ + field_name: fieldName, + }: HostFlyoutFilterActionParams) => { + this.analytics.reportEvent(InfraTelemetryEventTypes.HOST_FLYOUT_FILTER_REMOVED, { + field_name: fieldName, + }); + }; + + public reportHostFlyoutFilterAdded = ({ + field_name: fieldName, + }: HostFlyoutFilterActionParams) => { + this.analytics.reportEvent(InfraTelemetryEventTypes.HOST_FLYOUT_FILTER_ADDED, { + field_name: fieldName, + }); + }; + public reportHostsViewQuerySubmitted = (params: HostsViewQuerySubmittedParams) => { this.analytics.reportEvent(InfraTelemetryEventTypes.HOSTS_VIEW_QUERY_SUBMITTED, params); }; diff --git a/x-pack/plugins/infra/public/services/telemetry/telemetry_events.ts b/x-pack/plugins/infra/public/services/telemetry/telemetry_events.ts index ee7545022d9ee..597c9c56eacfd 100644 --- a/x-pack/plugins/infra/public/services/telemetry/telemetry_events.ts +++ b/x-pack/plugins/infra/public/services/telemetry/telemetry_events.ts @@ -66,4 +66,34 @@ const hostsEntryClickedEvent: InfraTelemetryEvent = { }, }; -export const infraTelemetryEvents = [hostsViewQuerySubmittedEvent, hostsEntryClickedEvent]; +const hostFlyoutRemoveFilter: InfraTelemetryEvent = { + eventType: InfraTelemetryEventTypes.HOST_FLYOUT_FILTER_REMOVED, + schema: { + field_name: { + type: 'keyword', + _meta: { + description: 'Removed filter field name for the selected host.', + optional: false, + }, + }, + }, +}; +const hostFlyoutAddFilter: InfraTelemetryEvent = { + eventType: InfraTelemetryEventTypes.HOST_FLYOUT_FILTER_ADDED, + schema: { + field_name: { + type: 'keyword', + _meta: { + description: 'Added filter field name for the selected host.', + optional: false, + }, + }, + }, +}; + +export const infraTelemetryEvents = [ + hostsViewQuerySubmittedEvent, + hostsEntryClickedEvent, + hostFlyoutRemoveFilter, + hostFlyoutAddFilter, +]; diff --git a/x-pack/plugins/infra/public/services/telemetry/telemetry_service.test.ts b/x-pack/plugins/infra/public/services/telemetry/telemetry_service.test.ts index d3516fc84600b..6fdd2105ec2df 100644 --- a/x-pack/plugins/infra/public/services/telemetry/telemetry_service.test.ts +++ b/x-pack/plugins/infra/public/services/telemetry/telemetry_service.test.ts @@ -49,6 +49,8 @@ describe('TelemetryService', () => { const telemetry = service.start(); expect(telemetry).toHaveProperty('reportHostEntryClicked'); + expect(telemetry).toHaveProperty('reportHostFlyoutFilterRemoved'); + expect(telemetry).toHaveProperty('reportHostFlyoutFilterAdded'); expect(telemetry).toHaveProperty('reportHostsViewQuerySubmitted'); }); }); @@ -74,7 +76,7 @@ describe('TelemetryService', () => { ); }); - it('should report hosts entry click with cloud provider equal to "unknow" if not exist', async () => { + it('should report hosts entry click with cloud provider equal to "unknown" if not exist', async () => { const setupParams = getSetupParams(); service.setup(setupParams); const telemetry = service.start(); @@ -119,4 +121,44 @@ describe('TelemetryService', () => { ); }); }); + + describe('#reportHostFlyoutFilterRemoved', () => { + it('should report Host Flyout Filter Removed click with field name', async () => { + const setupParams = getSetupParams(); + service.setup(setupParams); + const telemetry = service.start(); + + telemetry.reportHostFlyoutFilterRemoved({ + field_name: 'agent.version', + }); + + expect(setupParams.analytics.reportEvent).toHaveBeenCalledTimes(1); + expect(setupParams.analytics.reportEvent).toHaveBeenCalledWith( + InfraTelemetryEventTypes.HOST_FLYOUT_FILTER_REMOVED, + { + field_name: 'agent.version', + } + ); + }); + }); + + describe('#reportHostFlyoutFilterAdded', () => { + it('should report Host Flyout Filter Added click with field name', async () => { + const setupParams = getSetupParams(); + service.setup(setupParams); + const telemetry = service.start(); + + telemetry.reportHostFlyoutFilterAdded({ + field_name: 'agent.version', + }); + + expect(setupParams.analytics.reportEvent).toHaveBeenCalledTimes(1); + expect(setupParams.analytics.reportEvent).toHaveBeenCalledWith( + InfraTelemetryEventTypes.HOST_FLYOUT_FILTER_ADDED, + { + field_name: 'agent.version', + } + ); + }); + }); }); diff --git a/x-pack/plugins/infra/public/services/telemetry/types.ts b/x-pack/plugins/infra/public/services/telemetry/types.ts index a24f64e4c5f4a..f0f64ff00c918 100644 --- a/x-pack/plugins/infra/public/services/telemetry/types.ts +++ b/x-pack/plugins/infra/public/services/telemetry/types.ts @@ -15,6 +15,8 @@ export interface TelemetryServiceSetupParams { export enum InfraTelemetryEventTypes { HOSTS_VIEW_QUERY_SUBMITTED = 'Hosts View Query Submitted', HOSTS_ENTRY_CLICKED = 'Host Entry Clicked', + HOST_FLYOUT_FILTER_REMOVED = 'Host Flyout Filter Removed', + HOST_FLYOUT_FILTER_ADDED = 'Host Flyout Filter Added', } export interface HostsViewQuerySubmittedParams { @@ -29,10 +31,19 @@ export interface HostEntryClickedParams { cloud_provider?: string | null; } -export type InfraTelemetryEventParams = HostsViewQuerySubmittedParams | HostEntryClickedParams; +export interface HostFlyoutFilterActionParams { + field_name: string; +} + +export type InfraTelemetryEventParams = + | HostsViewQuerySubmittedParams + | HostEntryClickedParams + | HostFlyoutFilterActionParams; export interface ITelemetryClient { reportHostEntryClicked(params: HostEntryClickedParams): void; + reportHostFlyoutFilterRemoved(params: HostFlyoutFilterActionParams): void; + reportHostFlyoutFilterAdded(params: HostFlyoutFilterActionParams): void; reportHostsViewQuerySubmitted(params: HostsViewQuerySubmittedParams): void; } @@ -41,6 +52,14 @@ export type InfraTelemetryEvent = eventType: InfraTelemetryEventTypes.HOSTS_VIEW_QUERY_SUBMITTED; schema: RootSchema; } + | { + eventType: InfraTelemetryEventTypes.HOST_FLYOUT_FILTER_ADDED; + schema: RootSchema; + } + | { + eventType: InfraTelemetryEventTypes.HOST_FLYOUT_FILTER_REMOVED; + schema: RootSchema; + } | { eventType: InfraTelemetryEventTypes.HOSTS_ENTRY_CLICKED; schema: RootSchema; From b0c7c2d2899dc8aaa94ba3221eadf36b881f1645 Mon Sep 17 00:00:00 2001 From: Ignacio Rivas Date: Tue, 25 Apr 2023 13:06:32 +0200 Subject: [PATCH 39/52] [Ingest Pipelines] Allow inference processor for all licenses (#155689) --- .../components/shared/map_processor_type_to_form.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/shared/map_processor_type_to_form.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/shared/map_processor_type_to_form.tsx index 60f66dbb415f3..6d232ba70557d 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/shared/map_processor_type_to_form.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/shared/map_processor_type_to_form.tsx @@ -496,7 +496,6 @@ export const mapProcessorTypeToDescriptor: MapProcessorTypeToDescriptor = { }, inference: { FieldsComponent: Inference, - forLicenseAtLeast: 'platinum', docLinkPath: '/inference-processor.html', label: i18n.translate('xpack.ingestPipelines.processors.label.inference', { defaultMessage: 'Inference', From 880d1f275c644faab1336e90182d47e221cc6ba0 Mon Sep 17 00:00:00 2001 From: Florian Lehner Date: Tue, 25 Apr 2023 13:43:13 +0200 Subject: [PATCH 40/52] [FLEET] Add profiler_symbolizer (#155585) ## Summary Add the package profiler_symbolizer to the list of packages that are bundled with kibana. Bundling the profiler_symbolizer package with kibana is required to install the package in the Integration Server environment. ### For maintainers - [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) Signed-off-by: Florian Lehner Co-authored-by: Julia Bardi <90178898+juliaElastic@users.noreply.github.com> --- fleet_packages.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/fleet_packages.json b/fleet_packages.json index f9734cdd91380..eef982351e455 100644 --- a/fleet_packages.json +++ b/fleet_packages.json @@ -40,6 +40,12 @@ "name": "fleet_server", "version": "1.3.0" }, + { + "name": "profiler_symbolizer", + "version": "8.8.0-preview", + "forceAlignStackVersion": true, + "allowSyncToPrerelease": true + }, { "name": "synthetics", "version": "0.12.1" From 7761e2716be2b1c3a30d533e25b3ff24acac6bb3 Mon Sep 17 00:00:00 2001 From: Kevin Delemme Date: Tue, 25 Apr 2023 07:43:54 -0400 Subject: [PATCH 41/52] chore(slo): unset ecs usage for burn rate rule (#155466) --- .../observability/server/lib/rules/slo_burn_rate/register.ts | 2 +- x-pack/test/api_integration/apis/maps/maps_telemetry.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/register.ts b/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/register.ts index 91c0b9f8c8ffd..6f61c5f6277f2 100644 --- a/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/register.ts +++ b/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/register.ts @@ -65,7 +65,7 @@ export function sloBurnRateRuleType( alerts: { context: SLO_RULE_REGISTRATION_CONTEXT, mappings: { fieldMap: { ...legacyExperimentalFieldMap, ...sloRuleFieldMap } }, - useEcs: true, + useEcs: false, useLegacyAlerts: true, }, }; diff --git a/x-pack/test/api_integration/apis/maps/maps_telemetry.ts b/x-pack/test/api_integration/apis/maps/maps_telemetry.ts index c953aff7000c5..2a055ce007fee 100644 --- a/x-pack/test/api_integration/apis/maps/maps_telemetry.ts +++ b/x-pack/test/api_integration/apis/maps/maps_telemetry.ts @@ -30,8 +30,8 @@ export default function ({ getService }: FtrProviderContext) { return fieldStat.name === 'geo_point'; } ); - expect(geoPointFieldStats.count).to.be(39); - expect(geoPointFieldStats.index_count).to.be(10); + expect(geoPointFieldStats.count).to.be(31); + expect(geoPointFieldStats.index_count).to.be(9); const geoShapeFieldStats = apiResponse.cluster_stats.indices.mappings.field_types.find( (fieldStat: estypes.ClusterStatsFieldTypes) => { From f1c18d940d1edf372baa86b3950ff9656ff1c424 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Tue, 25 Apr 2023 13:01:03 +0100 Subject: [PATCH 42/52] [Lens] Test field formatters for keyword fields (#155491) ## Summary Part of https://github.com/elastic/kibana/issues/147428 Adds field formatters test for keyword fields [Flaky runner 50 times https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/2155 ](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/2157) ### 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 --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Marco Liberati --- test/functional/services/field_editor.ts | 42 ++++++ .../apps/lens/group1/field_formatters.ts | 122 ++++++++++++++++++ .../test/functional/apps/lens/group1/index.ts | 1 + .../test/functional/page_objects/lens_page.ts | 12 ++ x-pack/test/tsconfig.json | 1 + 5 files changed, 178 insertions(+) create mode 100644 x-pack/test/functional/apps/lens/group1/field_formatters.ts diff --git a/test/functional/services/field_editor.ts b/test/functional/services/field_editor.ts index 68fc3707ca3a9..439eb289ec95f 100644 --- a/test/functional/services/field_editor.ts +++ b/test/functional/services/field_editor.ts @@ -12,6 +12,7 @@ export class FieldEditorService extends FtrService { private readonly browser = this.ctx.getService('browser'); private readonly testSubjects = this.ctx.getService('testSubjects'); private readonly retry = this.ctx.getService('retry'); + private readonly find = this.ctx.getService('find'); public async setName(name: string, clearFirst = false, typeCharByChar = false) { await this.testSubjects.setValue('nameField > input', name, { @@ -50,6 +51,47 @@ export class FieldEditorService extends FtrService { await this.testSubjects.click('fieldSaveButton'); } + async setUrlFieldFormat(template: string) { + const urlTemplateField = await this.find.byCssSelector( + 'input[data-test-subj="urlEditorUrlTemplate"]' + ); + await urlTemplateField.type(template); + } + + public async setStaticLookupFormat(oldValue: string, newValue: string) { + await this.testSubjects.click('staticLookupEditorAddEntry'); + await this.testSubjects.setValue('~staticLookupEditorKey', oldValue); + await this.testSubjects.setValue('~staticLookupEditorValue', newValue); + } + + public async setColorFormat(value: string, color: string, backgroundColor?: string) { + await this.testSubjects.click('colorEditorAddColor'); + await this.testSubjects.setValue('~colorEditorKeyPattern', value); + await this.testSubjects.setValue('~colorEditorColorPicker', color); + if (backgroundColor) { + await this.testSubjects.setValue('~colorEditorBackgroundPicker', backgroundColor); + } + } + + public async setStringFormat(transform: string) { + await this.testSubjects.selectValue('stringEditorTransform', transform); + } + + public async setTruncateFormatLength(length: string) { + await this.testSubjects.setValue('truncateEditorLength', length); + } + + public async setFieldFormat(format: string) { + await this.find.clickByCssSelector( + 'select[data-test-subj="editorSelectedFormatId"] > option[value="' + format + '"]' + ); + } + + public async setFormat(format: string) { + await this.testSubjects.setEuiSwitch('formatRow > toggle', 'check'); + await this.setFieldFormat(format); + } + public async confirmSave() { await this.retry.try(async () => { await this.testSubjects.setValue('saveModalConfirmText', 'change'); diff --git a/x-pack/test/functional/apps/lens/group1/field_formatters.ts b/x-pack/test/functional/apps/lens/group1/field_formatters.ts new file mode 100644 index 0000000000000..855515bd3d2ce --- /dev/null +++ b/x-pack/test/functional/apps/lens/group1/field_formatters.ts @@ -0,0 +1,122 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { FIELD_FORMAT_IDS } from '@kbn/field-formats-plugin/common'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects([ + 'visualize', + 'lens', + 'header', + 'dashboard', + 'common', + 'settings', + ]); + const retry = getService('retry'); + const fieldEditor = getService('fieldEditor'); + + describe('lens fields formatters tests', () => { + before(async () => { + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickVisType('lens'); + await PageObjects.lens.goToTimeRange(); + await PageObjects.lens.switchToVisualization('lnsDatatable'); + }); + + afterEach(async () => { + await PageObjects.lens.clickField('runtimefield'); + await PageObjects.lens.removeField('runtimefield'); + await fieldEditor.confirmDelete(); + await PageObjects.lens.waitForFieldMissing('runtimefield'); + }); + + it('should display url formatter correctly', async () => { + await retry.try(async () => { + await PageObjects.lens.clickAddField(); + await fieldEditor.setName('runtimefield'); + await fieldEditor.enableValue(); + await fieldEditor.typeScript("emit(doc['geo.dest'].value)"); + await fieldEditor.setFormat(FIELD_FORMAT_IDS.URL); + await fieldEditor.setUrlFieldFormat('https://www.elastic.co?{{value}}'); + await fieldEditor.save(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.lens.searchField('runtime'); + await PageObjects.lens.waitForField('runtimefield'); + await PageObjects.lens.dragFieldToWorkspace('runtimefield'); + }); + await PageObjects.lens.waitForVisualization(); + expect(await PageObjects.lens.getDatatableHeaderText(0)).to.equal( + 'Top 5 values of runtimefield' + ); + expect(await PageObjects.lens.getDatatableCellText(0, 0)).to.eql('https://www.elastic.co?CN'); + }); + + it('should display static lookup formatter correctly', async () => { + await retry.try(async () => { + await PageObjects.lens.clickAddField(); + await fieldEditor.setName('runtimefield'); + await fieldEditor.enableValue(); + await fieldEditor.typeScript("emit(doc['geo.dest'].value)"); + await fieldEditor.setFormat(FIELD_FORMAT_IDS.STATIC_LOOKUP); + await fieldEditor.setStaticLookupFormat('CN', 'China'); + await fieldEditor.save(); + await PageObjects.header.waitUntilLoadingHasFinished(); + }); + await PageObjects.lens.waitForVisualization(); + expect(await PageObjects.lens.getDatatableCellText(0, 0)).to.eql('China'); + }); + + it('should display color formatter correctly', async () => { + await retry.try(async () => { + await PageObjects.lens.clickAddField(); + await fieldEditor.setName('runtimefield'); + await fieldEditor.enableValue(); + await fieldEditor.typeScript("emit(doc['geo.dest'].value)"); + await fieldEditor.setFormat(FIELD_FORMAT_IDS.COLOR); + await fieldEditor.setColorFormat('CN', '#ffffff', '#ff0000'); + await fieldEditor.save(); + await PageObjects.header.waitUntilLoadingHasFinished(); + }); + await PageObjects.lens.waitForVisualization(); + const styleObj = await PageObjects.lens.getDatatableCellSpanStyle(0, 0); + expect(styleObj['background-color']).to.be('rgb(255, 0, 0)'); + expect(styleObj.color).to.be('rgb(255, 255, 255)'); + }); + + it('should display string formatter correctly', async () => { + await retry.try(async () => { + await PageObjects.lens.clickAddField(); + await fieldEditor.setName('runtimefield'); + await fieldEditor.enableValue(); + await fieldEditor.typeScript("emit(doc['geo.dest'].value)"); + await fieldEditor.setFormat(FIELD_FORMAT_IDS.STRING); + await fieldEditor.setStringFormat('lower'); + await fieldEditor.save(); + await PageObjects.header.waitUntilLoadingHasFinished(); + }); + await PageObjects.lens.waitForVisualization(); + expect(await PageObjects.lens.getDatatableCellText(0, 0)).to.eql('cn'); + }); + + it('should display truncate string formatter correctly', async () => { + await retry.try(async () => { + await PageObjects.lens.clickAddField(); + await fieldEditor.setName('runtimefield'); + await fieldEditor.enableValue(); + await fieldEditor.typeScript("emit(doc['links.raw'].value)"); + await fieldEditor.setFormat(FIELD_FORMAT_IDS.TRUNCATE); + await fieldEditor.setTruncateFormatLength('3'); + await fieldEditor.save(); + await PageObjects.header.waitUntilLoadingHasFinished(); + }); + await PageObjects.lens.waitForVisualization(); + expect(await PageObjects.lens.getDatatableCellText(0, 0)).to.eql('dal...'); + }); + }); +} diff --git a/x-pack/test/functional/apps/lens/group1/index.ts b/x-pack/test/functional/apps/lens/group1/index.ts index 03f4a4032154e..a129a66c519d9 100644 --- a/x-pack/test/functional/apps/lens/group1/index.ts +++ b/x-pack/test/functional/apps/lens/group1/index.ts @@ -83,6 +83,7 @@ export default ({ getService, loadTestFile, getPageObjects }: FtrProviderContext loadTestFile(require.resolve('./text_based_languages')); loadTestFile(require.resolve('./fields_list')); loadTestFile(require.resolve('./layer_actions')); + loadTestFile(require.resolve('./field_formatters')); } }); }; diff --git a/x-pack/test/functional/page_objects/lens_page.ts b/x-pack/test/functional/page_objects/lens_page.ts index acd78cb0da0ea..6db18af0abeaa 100644 --- a/x-pack/test/functional/page_objects/lens_page.ts +++ b/x-pack/test/functional/page_objects/lens_page.ts @@ -1045,6 +1045,18 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont }, {}); }, + async getDatatableCellSpanStyle(rowIndex = 0, colIndex = 0) { + const el = await (await this.getDatatableCell(rowIndex, colIndex)).findByCssSelector('span'); + const styleString = await el.getAttribute('style'); + return styleString.split(';').reduce>((memo, cssLine) => { + const [prop, value] = cssLine.split(':'); + if (prop && value) { + memo[prop.trim()] = value.trim(); + } + return memo; + }, {}); + }, + async getCountOfDatatableColumns() { const table = await find.byCssSelector('.euiDataGrid'); const $ = await table.parseDomContent(); diff --git a/x-pack/test/tsconfig.json b/x-pack/test/tsconfig.json index 2e92bd5f38c3d..a11346427456f 100644 --- a/x-pack/test/tsconfig.json +++ b/x-pack/test/tsconfig.json @@ -122,5 +122,6 @@ "@kbn/securitysolution-io-ts-alerting-types", "@kbn/alerting-state-types", "@kbn/assetManager-plugin", + "@kbn/field-formats-plugin", ] } From c3ccede36ede1d514c1b99a433d9a3f0e3e128a9 Mon Sep 17 00:00:00 2001 From: Dominique Clarke Date: Tue, 25 Apr 2023 08:03:10 -0400 Subject: [PATCH 43/52] [Synthetics] Adds additional heartbeat configuration options to the Synthetics app (#154390) ## Summary Closes https://github.com/elastic/kibana/issues/140864 Relates to https://github.com/elastic/synthetics-dev/issues/159 Relates to https://github.com/elastic/synthetics/issues/655 Adds missing heartbeat configs to the Synthetics app. The additional configs are specified below: HTTP (proxy_headers, mode, ipv4, ipv6, response.include_body_max_bytes, check.response.json) ![image](https://user-images.githubusercontent.com/11356435/231040397-4f80d3af-ff08-4ef1-bbd9-c49f63d29bd0.png) ![image](https://user-images.githubusercontent.com/11356435/231040371-b7baa693-d573-46e1-b0f1-6d21c32522b8.png) TCP (mode, ipv4, ivp6) ![image](https://user-images.githubusercontent.com/11356435/231040065-ad865160-7f6c-4450-ab1c-98c17aedd3f0.png) ICMP (mode, ipv4, ipv6) ![image](https://user-images.githubusercontent.com/11356435/231039925-d2a2e9ab-69aa-4d74-8c3e-91223dd963d1.png) ### Testing 1. Create a private location 2. Create an http monitor selecting both a public and private location, adjusting the settings for mode, ipv4, ipv6, response.include_body_max_bytes, check.response.json and proxy headers. 3. Navigate to the edit page for that monitor, ensure the configuration above was saved to the monitor settings 4. Create an icmp monitor selecting both a public and private location, adjusting the settings for mode, ipv6, and ipv4. 5. Navigate to the dit page for that monitor, ensure the configuration above was saved to the monitor settings 6. Create an tcp monitor selecting both a public and private location, adjusting the settings for mode, ipv6, and ipv4. 7. Navigate to the dit page for that monitor, ensure the configuration above was saved to the monitor settings 8. Navigate to the agent policy for the private location selected. Ensure that the configuration options are represented on the the individual integration policies --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: florent-leborgne Co-authored-by: Shahzad --- .../common/constants/monitor_defaults.ts | 19 +- .../common/constants/monitor_management.ts | 8 + .../common/formatters/http/formatters.ts | 6 + .../common/formatters/icmp/formatters.ts | 3 + .../common/formatters/tcp/formatters.ts | 3 + .../monitor_management/monitor_configs.ts | 19 +- .../monitor_management/monitor_types.ts | 76 ++++-- .../fields/header_field.test.tsx | 6 +- .../monitor_add_edit/fields/header_field.tsx | 14 +- .../fields/key_value_field.tsx | 16 +- .../fields/request_body_field.test.tsx | 6 +- .../fields/request_body_field.tsx | 65 +++--- .../monitor_add_edit/form/field.tsx | 10 +- .../monitor_add_edit/form/field_config.tsx | 221 ++++++++++++++++-- .../monitor_add_edit/form/field_wrappers.tsx | 14 ++ .../monitor_add_edit/form/form_config.tsx | 26 ++- .../components/monitor_add_edit/types.ts | 13 +- .../migrations/monitors/8.8.0.ts | 2 +- .../monitor_cruds/monitor_validation.test.ts | 4 +- .../formatters/format_configs.test.ts | 32 ++- .../synthetics_service/formatters/http.ts | 2 + .../normalizers/http_monitor.ts | 13 +- .../add_monitor_private_location.ts | 6 +- .../apis/synthetics/add_monitor_project.ts | 39 +++- .../synthetics/add_monitor_project_legacy.ts | 34 ++- .../apis/synthetics/delete_monitor.ts | 2 +- .../apis/synthetics/delete_monitor_project.ts | 2 +- .../apis/synthetics/get_monitor_project.ts | 2 +- .../sample_data/test_browser_policy.ts | 24 +- .../synthetics/sample_data/test_policy.ts | 27 ++- .../test_project_monitor_policy.ts | 24 +- .../apis/synthetics/sync_global_params.ts | 2 +- .../uptime/rest/fixtures/http_monitor.json | 8 +- .../uptime/rest/fixtures/icmp_monitor.json | 5 +- .../rest/fixtures/project_http_monitor.json | 6 +- .../uptime/rest/fixtures/tcp_monitor.json | 5 +- 36 files changed, 613 insertions(+), 151 deletions(-) diff --git a/x-pack/plugins/synthetics/common/constants/monitor_defaults.ts b/x-pack/plugins/synthetics/common/constants/monitor_defaults.ts index adfa002e8e20a..23ceb20ad75d6 100644 --- a/x-pack/plugins/synthetics/common/constants/monitor_defaults.ts +++ b/x-pack/plugins/synthetics/common/constants/monitor_defaults.ts @@ -6,6 +6,7 @@ */ import { i18n } from '@kbn/i18n'; import { + CodeEditorMode, BrowserAdvancedFields, BrowserSimpleFields, CommonFields, @@ -198,19 +199,25 @@ export const DEFAULT_HTTP_SIMPLE_FIELDS: HTTPSimpleFields = { export const DEFAULT_HTTP_ADVANCED_FIELDS: HTTPAdvancedFields = { [ConfigKey.PASSWORD]: '', [ConfigKey.PROXY_URL]: '', + [ConfigKey.PROXY_HEADERS]: {}, [ConfigKey.RESPONSE_BODY_CHECK_NEGATIVE]: [], [ConfigKey.RESPONSE_BODY_CHECK_POSITIVE]: [], + [ConfigKey.RESPONSE_JSON_CHECK]: [], [ConfigKey.RESPONSE_BODY_INDEX]: ResponseBodyIndexPolicy.ON_ERROR, [ConfigKey.RESPONSE_HEADERS_CHECK]: {}, [ConfigKey.RESPONSE_HEADERS_INDEX]: true, [ConfigKey.RESPONSE_STATUS_CHECK]: [], [ConfigKey.REQUEST_BODY_CHECK]: { value: '', - type: Mode.PLAINTEXT, + type: CodeEditorMode.PLAINTEXT, }, [ConfigKey.REQUEST_HEADERS_CHECK]: {}, [ConfigKey.REQUEST_METHOD_CHECK]: HTTPMethod.GET, [ConfigKey.USERNAME]: '', + [ConfigKey.MODE]: Mode.ANY, + [ConfigKey.RESPONSE_BODY_MAX_BYTES]: '1024', + [ConfigKey.IPV4]: true, + [ConfigKey.IPV6]: true, }; export const DEFAULT_ICMP_SIMPLE_FIELDS: ICMPSimpleFields = { @@ -238,6 +245,15 @@ export const DEFAULT_TCP_ADVANCED_FIELDS: TCPAdvancedFields = { [ConfigKey.PROXY_USE_LOCAL_RESOLVER]: false, [ConfigKey.RESPONSE_RECEIVE_CHECK]: '', [ConfigKey.REQUEST_SEND_CHECK]: '', + [ConfigKey.MODE]: Mode.ANY, + [ConfigKey.IPV4]: true, + [ConfigKey.IPV6]: true, +}; + +export const DEFAULT_ICMP_ADVANCED_FIELDS = { + [ConfigKey.MODE]: Mode.ANY, + [ConfigKey.IPV4]: true, + [ConfigKey.IPV6]: true, }; export const DEFAULT_TLS_FIELDS: TLSFields = { @@ -262,6 +278,7 @@ export const DEFAULT_FIELDS: MonitorDefaults = { }, [DataStream.ICMP]: { ...DEFAULT_ICMP_SIMPLE_FIELDS, + ...DEFAULT_ICMP_ADVANCED_FIELDS, }, [DataStream.BROWSER]: { ...DEFAULT_BROWSER_SIMPLE_FIELDS, diff --git a/x-pack/plugins/synthetics/common/constants/monitor_management.ts b/x-pack/plugins/synthetics/common/constants/monitor_management.ts index 01da8ebce5d43..be3802f132a58 100644 --- a/x-pack/plugins/synthetics/common/constants/monitor_management.ts +++ b/x-pack/plugins/synthetics/common/constants/monitor_management.ts @@ -27,6 +27,7 @@ export enum ConfigKey { JOURNEY_ID = 'journey_id', MAX_REDIRECTS = 'max_redirects', METADATA = '__ui', + MODE = 'mode', MONITOR_TYPE = 'type', NAME = 'name', NAMESPACE = 'namespace', @@ -37,12 +38,15 @@ export enum ConfigKey { ORIGINAL_SPACE = 'original_space', // the original space the montior was saved in. Used by push monitors to ensure uniqueness of monitor id sent to heartbeat and prevent data collisions PORT = 'url.port', PROXY_URL = 'proxy_url', + PROXY_HEADERS = 'proxy_headers', PROXY_USE_LOCAL_RESOLVER = 'proxy_use_local_resolver', RESPONSE_BODY_CHECK_NEGATIVE = 'check.response.body.negative', RESPONSE_BODY_CHECK_POSITIVE = 'check.response.body.positive', + RESPONSE_JSON_CHECK = 'check.response.json', RESPONSE_BODY_INDEX = 'response.include_body', RESPONSE_HEADERS_CHECK = 'check.response.headers', RESPONSE_HEADERS_INDEX = 'response.include_headers', + RESPONSE_BODY_MAX_BYTES = 'response.include_body_max_bytes', RESPONSE_RECEIVE_CHECK = 'check.receive', RESPONSE_STATUS_CHECK = 'check.response.status', REQUEST_BODY_CHECK = 'check.request.body', @@ -54,6 +58,8 @@ export enum ConfigKey { SCREENSHOTS = 'screenshots', SOURCE_PROJECT_CONTENT = 'source.project.content', SOURCE_INLINE = 'source.inline.script', + IPV4 = 'ipv4', + IPV6 = 'ipv6', PROJECT_ID = 'project_id', SYNTHETICS_ARGS = 'synthetics_args', TEXT_ASSERTION = 'playwright_text_assertion', @@ -73,6 +79,7 @@ export enum ConfigKey { } export const secretKeys = [ + ConfigKey.PROXY_HEADERS, ConfigKey.PARAMS, ConfigKey.PASSWORD, ConfigKey.REQUEST_BODY_CHECK, @@ -80,6 +87,7 @@ export const secretKeys = [ ConfigKey.REQUEST_SEND_CHECK, ConfigKey.RESPONSE_BODY_CHECK_NEGATIVE, ConfigKey.RESPONSE_BODY_CHECK_POSITIVE, + ConfigKey.RESPONSE_JSON_CHECK, ConfigKey.RESPONSE_HEADERS_CHECK, ConfigKey.RESPONSE_RECEIVE_CHECK, ConfigKey.SOURCE_INLINE, diff --git a/x-pack/plugins/synthetics/common/formatters/http/formatters.ts b/x-pack/plugins/synthetics/common/formatters/http/formatters.ts index 300e4f9fdb9ff..437112939f283 100644 --- a/x-pack/plugins/synthetics/common/formatters/http/formatters.ts +++ b/x-pack/plugins/synthetics/common/formatters/http/formatters.ts @@ -23,9 +23,11 @@ export const httpFormatters: HTTPFormatMap = { [ConfigKey.USERNAME]: null, [ConfigKey.PASSWORD]: null, [ConfigKey.PROXY_URL]: null, + [ConfigKey.PROXY_HEADERS]: objectToJsonFormatter, [ConfigKey.PORT]: null, [ConfigKey.RESPONSE_BODY_CHECK_NEGATIVE]: arrayToJsonFormatter, [ConfigKey.RESPONSE_BODY_CHECK_POSITIVE]: arrayToJsonFormatter, + [ConfigKey.RESPONSE_JSON_CHECK]: arrayToJsonFormatter, [ConfigKey.RESPONSE_HEADERS_CHECK]: objectToJsonFormatter, [ConfigKey.RESPONSE_STATUS_CHECK]: arrayToJsonFormatter, [ConfigKey.REQUEST_HEADERS_CHECK]: objectToJsonFormatter, @@ -33,6 +35,10 @@ export const httpFormatters: HTTPFormatMap = { fields[ConfigKey.REQUEST_BODY_CHECK]?.value ? JSON.stringify(fields[ConfigKey.REQUEST_BODY_CHECK]?.value) : null, + [ConfigKey.RESPONSE_BODY_MAX_BYTES]: null, + [ConfigKey.MODE]: null, + [ConfigKey.IPV4]: null, + [ConfigKey.IPV6]: null, ...tlsFormatters, ...commonFormatters, }; diff --git a/x-pack/plugins/synthetics/common/formatters/icmp/formatters.ts b/x-pack/plugins/synthetics/common/formatters/icmp/formatters.ts index 0ebf69db5b408..f58e15c86b3ad 100644 --- a/x-pack/plugins/synthetics/common/formatters/icmp/formatters.ts +++ b/x-pack/plugins/synthetics/common/formatters/icmp/formatters.ts @@ -15,5 +15,8 @@ export type ICMPFormatMap = Record; export const icmpFormatters: ICMPFormatMap = { [ConfigKey.HOSTS]: null, [ConfigKey.WAIT]: secondsToCronFormatter, + [ConfigKey.MODE]: null, + [ConfigKey.IPV4]: null, + [ConfigKey.IPV6]: null, ...commonFormatters, }; diff --git a/x-pack/plugins/synthetics/common/formatters/tcp/formatters.ts b/x-pack/plugins/synthetics/common/formatters/tcp/formatters.ts index 6acb9abe21877..2d850e95ceaf1 100644 --- a/x-pack/plugins/synthetics/common/formatters/tcp/formatters.ts +++ b/x-pack/plugins/synthetics/common/formatters/tcp/formatters.ts @@ -23,6 +23,9 @@ export const tcpFormatters: TCPFormatMap = { [ConfigKey.PROXY_URL]: null, [ConfigKey.PORT]: null, [ConfigKey.URLS]: null, + [ConfigKey.MODE]: null, + [ConfigKey.IPV4]: null, + [ConfigKey.IPV6]: null, ...tlsFormatters, ...commonFormatters, }; diff --git a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_configs.ts b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_configs.ts index e616d887df4f7..dcd6b18974da4 100644 --- a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_configs.ts +++ b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_configs.ts @@ -54,15 +54,15 @@ export const MonacoEditorLangIdCodec = tEnum( ); export type MonacoEditorLangIdType = t.TypeOf; -export enum Mode { +export enum CodeEditorMode { FORM = 'form', JSON = 'json', PLAINTEXT = 'text', XML = 'xml', } -export const ModeCodec = tEnum('Mode', Mode); -export type ModeType = t.TypeOf; +export const CodeEditorModeCodec = tEnum('CodeEditorMode', CodeEditorMode); +export type CodeEditorModeType = t.TypeOf; export enum ContentType { JSON = 'application/json', @@ -127,3 +127,16 @@ export enum FormMonitorType { } export const FormMonitorTypeCodec = tEnum('FormMonitorType', FormMonitorType); + +export enum Mode { + ANY = 'any', + ALL = 'all', +} +export const ModeCodec = tEnum('Mode', Mode); +export type ModeType = t.TypeOf; + +export const ResponseCheckJSONCodec = t.interface({ + description: t.string, + expression: t.string, +}); +export type ResponseCheckJSON = t.TypeOf; diff --git a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_types.ts b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_types.ts index 97363987afcc4..d5a8d26568633 100644 --- a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_types.ts +++ b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_types.ts @@ -11,11 +11,13 @@ import { secretKeys } from '../../constants/monitor_management'; import { ConfigKey } from './config_key'; import { MonitorServiceLocationCodec, ServiceLocationErrors } from './locations'; import { + CodeEditorModeCodec, DataStream, DataStreamCodec, FormMonitorTypeCodec, ModeCodec, ResponseBodyIndexPolicyCodec, + ResponseCheckJSONCodec, ScheduleUnitCodec, SourceTypeCodec, TLSVersionCodec, @@ -94,10 +96,17 @@ export const TCPSimpleFieldsCodec = t.intersection([ export type TCPSimpleFields = t.TypeOf; // TCPAdvancedFields -export const TCPAdvancedFieldsCodec = t.interface({ - [ConfigKey.PROXY_URL]: t.string, - [ConfigKey.PROXY_USE_LOCAL_RESOLVER]: t.boolean, -}); +export const TCPAdvancedFieldsCodec = t.intersection([ + t.interface({ + [ConfigKey.PROXY_URL]: t.string, + [ConfigKey.PROXY_USE_LOCAL_RESOLVER]: t.boolean, + }), + t.partial({ + [ConfigKey.MODE]: ModeCodec, + [ConfigKey.IPV4]: t.boolean, + [ConfigKey.IPV6]: t.boolean, + }), +]); export const TCPSensitiveAdvancedFieldsCodec = t.interface({ [ConfigKey.RESPONSE_RECEIVE_CHECK]: t.string, @@ -136,7 +145,18 @@ export const ICMPSimpleFieldsCodec = t.intersection([ ]); export type ICMPSimpleFields = t.TypeOf; -export type ICMPFields = t.TypeOf; + +// ICMPAdvancedFields +export const ICMPAdvancedFieldsCodec = t.partial({ + [ConfigKey.MODE]: ModeCodec, + [ConfigKey.IPV4]: t.boolean, + [ConfigKey.IPV6]: t.boolean, +}); + +// ICMPFields +export const ICMPFieldsCodec = t.intersection([ICMPSimpleFieldsCodec, ICMPAdvancedFieldsCodec]); + +export type ICMPFields = t.TypeOf; // HTTPSimpleFields export const HTTPSimpleFieldsCodec = t.intersection([ @@ -152,23 +172,37 @@ export const HTTPSimpleFieldsCodec = t.intersection([ export type HTTPSimpleFields = t.TypeOf; // HTTPAdvancedFields -export const HTTPAdvancedFieldsCodec = t.interface({ - [ConfigKey.PROXY_URL]: t.string, - [ConfigKey.RESPONSE_BODY_INDEX]: ResponseBodyIndexPolicyCodec, - [ConfigKey.RESPONSE_HEADERS_INDEX]: t.boolean, - [ConfigKey.RESPONSE_STATUS_CHECK]: t.array(t.string), - [ConfigKey.REQUEST_METHOD_CHECK]: t.string, -}); +export const HTTPAdvancedFieldsCodec = t.intersection([ + t.interface({ + [ConfigKey.PROXY_URL]: t.string, + [ConfigKey.RESPONSE_BODY_INDEX]: ResponseBodyIndexPolicyCodec, + [ConfigKey.RESPONSE_HEADERS_INDEX]: t.boolean, + [ConfigKey.RESPONSE_STATUS_CHECK]: t.array(t.string), + [ConfigKey.REQUEST_METHOD_CHECK]: t.string, + }), + t.partial({ + [ConfigKey.MODE]: ModeCodec, + [ConfigKey.RESPONSE_BODY_MAX_BYTES]: t.string, + [ConfigKey.IPV4]: t.boolean, + [ConfigKey.IPV6]: t.boolean, + }), +]); -export const HTTPSensitiveAdvancedFieldsCodec = t.interface({ - [ConfigKey.PASSWORD]: t.string, - [ConfigKey.RESPONSE_BODY_CHECK_NEGATIVE]: t.array(t.string), - [ConfigKey.RESPONSE_BODY_CHECK_POSITIVE]: t.array(t.string), - [ConfigKey.RESPONSE_HEADERS_CHECK]: t.record(t.string, t.string), - [ConfigKey.REQUEST_BODY_CHECK]: t.interface({ value: t.string, type: ModeCodec }), - [ConfigKey.REQUEST_HEADERS_CHECK]: t.record(t.string, t.string), - [ConfigKey.USERNAME]: t.string, -}); +export const HTTPSensitiveAdvancedFieldsCodec = t.intersection([ + t.interface({ + [ConfigKey.PASSWORD]: t.string, + [ConfigKey.RESPONSE_BODY_CHECK_NEGATIVE]: t.array(t.string), + [ConfigKey.RESPONSE_BODY_CHECK_POSITIVE]: t.array(t.string), + [ConfigKey.RESPONSE_HEADERS_CHECK]: t.record(t.string, t.string), + [ConfigKey.REQUEST_BODY_CHECK]: t.interface({ value: t.string, type: CodeEditorModeCodec }), + [ConfigKey.REQUEST_HEADERS_CHECK]: t.record(t.string, t.string), + [ConfigKey.USERNAME]: t.string, + }), + t.partial({ + [ConfigKey.PROXY_HEADERS]: t.record(t.string, t.string), + [ConfigKey.RESPONSE_JSON_CHECK]: t.array(ResponseCheckJSONCodec), + }), +]); export const HTTPAdvancedCodec = t.intersection([ HTTPAdvancedFieldsCodec, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/header_field.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/header_field.test.tsx index 6f920bf10d84a..c08434460aab5 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/header_field.test.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/header_field.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { fireEvent, waitFor } from '@testing-library/react'; import { render } from '../../../utils/testing/rtl_helpers'; import { HeaderField, contentTypes } from './header_field'; -import { Mode } from '../types'; +import { CodeEditorMode } from '../types'; describe('', () => { const onChange = jest.fn(); @@ -95,14 +95,14 @@ describe('', () => { }); it('handles content mode', async () => { - const contentMode: Mode = Mode.PLAINTEXT; + const contentMode: CodeEditorMode = CodeEditorMode.PLAINTEXT; render( ); await waitFor(() => { expect(onChange).toBeCalledWith({ - 'Content-Type': contentTypes[Mode.PLAINTEXT], + 'Content-Type': contentTypes[CodeEditorMode.PLAINTEXT], }); }); }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/header_field.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/header_field.tsx index a26fe8616d90b..c3159043b9958 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/header_field.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/header_field.tsx @@ -7,12 +7,12 @@ import React, { useEffect, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; -import { ContentType, Mode } from '../types'; +import { ContentType, CodeEditorMode } from '../types'; import { KeyValuePairsField, Pair } from './key_value_field'; export interface HeaderFieldProps { - contentMode?: Mode; + contentMode?: CodeEditorMode; defaultValue: Record; onChange: (value: Record) => void; onBlur?: () => void; @@ -72,9 +72,9 @@ export const HeaderField = ({ ); }; -export const contentTypes: Record = { - [Mode.JSON]: ContentType.JSON, - [Mode.PLAINTEXT]: ContentType.TEXT, - [Mode.XML]: ContentType.XML, - [Mode.FORM]: ContentType.FORM, +export const contentTypes: Record = { + [CodeEditorMode.JSON]: ContentType.JSON, + [CodeEditorMode.PLAINTEXT]: ContentType.TEXT, + [CodeEditorMode.XML]: ContentType.XML, + [CodeEditorMode.FORM]: ContentType.FORM, }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/key_value_field.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/key_value_field.tsx index 95b2348fd0219..7d062fbde7548 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/key_value_field.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/key_value_field.tsx @@ -46,13 +46,15 @@ export type Pair = [ string // value ]; -interface Props { +export interface KeyValuePairsFieldProps { addPairControlLabel: string | React.ReactElement; defaultPairs: Pair[]; onChange: (pairs: Pair[]) => void; onBlur?: () => void; 'data-test-subj'?: string; readOnly?: boolean; + keyLabel?: string | React.ReactElement; + valueLabel?: string | React.ReactElement; } export const KeyValuePairsField = ({ @@ -62,7 +64,9 @@ export const KeyValuePairsField = ({ onBlur, 'data-test-subj': dataTestSubj, readOnly, -}: Props) => { + keyLabel, + valueLabel, +}: KeyValuePairsFieldProps) => { const [pairs, setPairs] = useState(defaultPairs); const handleOnChange = useCallback( @@ -121,20 +125,20 @@ export const KeyValuePairsField = ({ children: ( - { + {keyLabel || ( - } + )} - { + {valueLabel || ( - } + )} ), diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/request_body_field.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/request_body_field.test.tsx index a472c3231053b..d3a8bd8076fe1 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/request_body_field.test.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/request_body_field.test.tsx @@ -13,7 +13,7 @@ import { fireEvent, waitFor } from '@testing-library/react'; import { mockGlobals } from '../../../utils/testing'; import { render } from '../../../utils/testing/rtl_helpers'; import { RequestBodyField } from './request_body_field'; -import { Mode } from '../types'; +import { CodeEditorMode } from '../types'; mockGlobals(); @@ -40,7 +40,7 @@ jest.mock('@kbn/kibana-react-plugin/public', () => { }); describe('', () => { - const defaultMode = Mode.PLAINTEXT; + const defaultMode = CodeEditorMode.PLAINTEXT; const defaultValue = 'sample value'; const WrappedComponent = ({ readOnly }: { readOnly?: boolean }) => { const [config, setConfig] = useState({ @@ -55,7 +55,7 @@ describe('', () => { type: config.type, }} onChange={useCallback( - (code) => setConfig({ type: code.type as Mode, value: code.value }), + (code) => setConfig({ type: code.type as CodeEditorMode, value: code.value }), [setConfig] )} readOnly={readOnly} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/request_body_field.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/request_body_field.tsx index fe117a4703ffb..e6877942f4f14 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/request_body_field.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/request_body_field.tsx @@ -9,15 +9,15 @@ import { stringify, parse } from 'query-string'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { EuiTabbedContent } from '@elastic/eui'; -import { Mode, MonacoEditorLangId } from '../types'; +import { CodeEditorMode, MonacoEditorLangId } from '../types'; import { KeyValuePairsField, Pair } from './key_value_field'; import { CodeEditor } from './code_editor'; export interface RequestBodyFieldProps { - onChange: (requestBody: { type: Mode; value: string }) => void; + onChange: (requestBody: { type: CodeEditorMode; value: string }) => void; onBlur?: () => void; value: { - type: Mode; + type: CodeEditorMode; value: string; }; readOnly?: boolean; @@ -36,22 +36,27 @@ export const RequestBodyField = ({ readOnly, }: RequestBodyFieldProps) => { const [values, setValues] = useState>({ - [ResponseBodyType.FORM]: type === Mode.FORM ? value : '', - [ResponseBodyType.CODE]: type !== Mode.FORM ? value : '', + [ResponseBodyType.FORM]: type === CodeEditorMode.FORM ? value : '', + [ResponseBodyType.CODE]: type !== CodeEditorMode.FORM ? value : '', }); useEffect(() => { onChange({ type, - value: type === Mode.FORM ? values[ResponseBodyType.FORM] : values[ResponseBodyType.CODE], + value: + type === CodeEditorMode.FORM + ? values[ResponseBodyType.FORM] + : values[ResponseBodyType.CODE], }); }, [onChange, type, values]); const handleSetMode = useCallback( - (currentMode: Mode) => { + (currentMode: CodeEditorMode) => { onChange({ type: currentMode, value: - currentMode === Mode.FORM ? values[ResponseBodyType.FORM] : values[ResponseBodyType.CODE], + currentMode === CodeEditorMode.FORM + ? values[ResponseBodyType.FORM] + : values[ResponseBodyType.CODE], }); }, [onChange, values] @@ -71,14 +76,14 @@ export const RequestBodyField = ({ }, {}); return setValues((prevValues) => ({ ...prevValues, - [Mode.FORM]: stringify(formattedPairs), + [CodeEditorMode.FORM]: stringify(formattedPairs), })); }, [setValues] ); const defaultFormPairs: Pair[] = useMemo(() => { - const pairs = parse(values[Mode.FORM]); + const pairs = parse(values[CodeEditorMode.FORM]); const keys = Object.keys(pairs); const formattedPairs: Pair[] = keys.map((key: string) => { // key, value, checked; @@ -89,9 +94,9 @@ export const RequestBodyField = ({ const tabs = [ { - id: Mode.PLAINTEXT, - name: modeLabels[Mode.PLAINTEXT], - 'data-test-subj': `syntheticsRequestBodyTab__${Mode.PLAINTEXT}`, + id: CodeEditorMode.PLAINTEXT, + name: modeLabels[CodeEditorMode.PLAINTEXT], + 'data-test-subj': `syntheticsRequestBodyTab__${CodeEditorMode.PLAINTEXT}`, content: ( { setValues((prevValues) => ({ ...prevValues, [ResponseBodyType.CODE]: code })); @@ -112,9 +117,9 @@ export const RequestBodyField = ({ ), }, { - id: Mode.JSON, - name: modeLabels[Mode.JSON], - 'data-test-subj': `syntheticsRequestBodyTab__${Mode.JSON}`, + id: CodeEditorMode.JSON, + name: modeLabels[CodeEditorMode.JSON], + 'data-test-subj': `syntheticsRequestBodyTab__${CodeEditorMode.JSON}`, content: ( { setValues((prevValues) => ({ ...prevValues, [ResponseBodyType.CODE]: code })); @@ -135,9 +140,9 @@ export const RequestBodyField = ({ ), }, { - id: Mode.XML, - name: modeLabels[Mode.XML], - 'data-test-subj': `syntheticsRequestBodyTab__${Mode.XML}`, + id: CodeEditorMode.XML, + name: modeLabels[CodeEditorMode.XML], + 'data-test-subj': `syntheticsRequestBodyTab__${CodeEditorMode.XML}`, content: ( { setValues((prevValues) => ({ ...prevValues, [ResponseBodyType.CODE]: code })); @@ -158,9 +163,9 @@ export const RequestBodyField = ({ ), }, { - id: Mode.FORM, - name: modeLabels[Mode.FORM], - 'data-test-subj': `syntheticsRequestBodyTab__${Mode.FORM}`, + id: CodeEditorMode.FORM, + name: modeLabels[CodeEditorMode.FORM], + 'data-test-subj': `syntheticsRequestBodyTab__${CodeEditorMode.FORM}`, content: ( tab.id === type)} autoFocus="selected" onTabClick={(tab) => { - handleSetMode(tab.id as Mode); + handleSetMode(tab.id as CodeEditorMode); }} />
@@ -195,25 +200,25 @@ export const RequestBodyField = ({ }; const modeLabels = { - [Mode.FORM]: i18n.translate( + [CodeEditorMode.FORM]: i18n.translate( 'xpack.synthetics.createPackagePolicy.stepConfigure.requestBodyType.form', { defaultMessage: 'Form', } ), - [Mode.PLAINTEXT]: i18n.translate( + [CodeEditorMode.PLAINTEXT]: i18n.translate( 'xpack.synthetics.createPackagePolicy.stepConfigure.requestBodyType.text', { defaultMessage: 'Text', } ), - [Mode.JSON]: i18n.translate( + [CodeEditorMode.JSON]: i18n.translate( 'xpack.synthetics.createPackagePolicy.stepConfigure.requestBodyType.JSON', { defaultMessage: 'JSON', } ), - [Mode.XML]: i18n.translate( + [CodeEditorMode.XML]: i18n.translate( 'xpack.synthetics.createPackagePolicy.stepConfigure.requestBodyType.XML', { defaultMessage: 'XML', diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field.tsx index 7aed077680d4b..cd39245c1c7ec 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field.tsx @@ -24,7 +24,6 @@ export const Field = memo( props, fieldKey, controlled, - showWhen, shouldUseSetValue, required, validation, @@ -32,6 +31,7 @@ export const Field = memo( fieldError, dependencies, customHook, + hidden, }: Props) => { const { register, watch, control, setValue, reset, getFieldState, formState } = useFormContext(); @@ -41,13 +41,7 @@ export const Field = memo( const [dependenciesFieldMeta, setDependenciesFieldMeta] = useState< Record >({}); - let show = true; let dependenciesValues: unknown[] = []; - if (showWhen) { - const [showKey, expectedValue] = showWhen; - const [actualValue] = watch([showKey]); - show = actualValue === expectedValue; - } if (dependencies) { dependenciesValues = watch(dependencies); } @@ -64,7 +58,7 @@ export const Field = memo( // eslint-disable-next-line react-hooks/exhaustive-deps }, [JSON.stringify(dependenciesValues || []), dependencies, getFieldState]); - if (!show) { + if (hidden && hidden(dependenciesValues)) { return null; } diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field_config.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field_config.tsx index b2839da207ff8..9653c415e171a 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field_config.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field_config.tsx @@ -6,6 +6,7 @@ */ import React from 'react'; +import { isEqual } from 'lodash'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { isValidNamespace } from '@kbn/fleet-plugin/common'; @@ -15,7 +16,6 @@ import { EuiFlexGroup, EuiFlexItem, EuiLink, - EuiTextArea, EuiSelectProps, EuiFieldTextProps, EuiSwitchProps, @@ -54,6 +54,8 @@ import { ResponseBodyIndexField, ResponseBodyIndexFieldProps, ControlledFieldProp, + KeyValuePairsField, + TextArea, ThrottlingWrapper, } from './field_wrappers'; import { getDocLinks } from '../../../../../kibana_services'; @@ -64,16 +66,20 @@ import { FormMonitorType, HTTPMethod, ScreenshotOption, + Mode, MonitorFields, TLSVersion, VerificationMode, FieldMap, FormLocation, + ResponseBodyIndexPolicy, + ResponseCheckJSON, ThrottlingConfig, } from '../types'; import { AlertConfigKey, ALLOWED_SCHEDULES_IN_MINUTES } from '../constants'; import { getDefaultFormFields } from './defaults'; import { validate, validateHeaders, WHOLE_NUMBERS_ONLY, FLOATS_ONLY } from './validation'; +import { KeyValuePairsFieldProps } from '../fields/key_value_field'; const getScheduleContent = (value: number) => { if (value > 60) { @@ -765,7 +771,7 @@ export const FIELD = (readOnly?: boolean): FieldMap => ({ fieldKey: ConfigKey.RESPONSE_STATUS_CHECK, component: FormattedComboBox, label: i18n.translate('xpack.synthetics.monitorConfig.responseStatusCheck.label', { - defaultMessage: 'Check response status equals', + defaultMessage: 'Response status equals', }), helpText: i18n.translate('xpack.synthetics.monitorConfig.responseStatusCheck.helpText', { defaultMessage: @@ -794,7 +800,7 @@ export const FIELD = (readOnly?: boolean): FieldMap => ({ fieldKey: ConfigKey.RESPONSE_HEADERS_CHECK, component: HeaderField, label: i18n.translate('xpack.synthetics.monitorConfig.responseHeadersCheck.label', { - defaultMessage: 'Check response headers contain', + defaultMessage: 'Response headers contain', }), helpText: i18n.translate('xpack.synthetics.monitorConfig.responseHeadersCheck.helpText', { defaultMessage: 'A list of expected response headers.', @@ -814,7 +820,7 @@ export const FIELD = (readOnly?: boolean): FieldMap => ({ fieldKey: ConfigKey.RESPONSE_BODY_CHECK_POSITIVE, component: FormattedComboBox, label: i18n.translate('xpack.synthetics.monitorConfig.responseBodyCheck.label', { - defaultMessage: 'Check response body contains', + defaultMessage: 'Response body contains', }), helpText: i18n.translate('xpack.synthetics.monitorConfig.responseBodyCheck.helpText', { defaultMessage: @@ -830,7 +836,7 @@ export const FIELD = (readOnly?: boolean): FieldMap => ({ fieldKey: ConfigKey.RESPONSE_BODY_CHECK_NEGATIVE, component: FormattedComboBox, label: i18n.translate('xpack.synthetics.monitorConfig.responseBodyCheckNegative.label', { - defaultMessage: 'Check response body does not contain', + defaultMessage: 'Response body does not contain', }), helpText: i18n.translate('xpack.synthetics.monitorConfig.responseBodyCheckNegative.helpText', { defaultMessage: @@ -846,7 +852,7 @@ export const FIELD = (readOnly?: boolean): FieldMap => ({ fieldKey: ConfigKey.RESPONSE_RECEIVE_CHECK, component: FieldText, label: i18n.translate('xpack.synthetics.monitorConfig.responseReceiveCheck.label', { - defaultMessage: 'Check response contains', + defaultMessage: 'Response contains', }), helpText: i18n.translate('xpack.synthetics.monitorConfig.responseReceiveCheck.helpText', { defaultMessage: 'The expected remote host response.', @@ -986,7 +992,11 @@ export const FIELD = (readOnly?: boolean): FieldMap => ({ defaultMessage: 'Verifies that the provided certificate is signed by a trusted authority (CA) and also verifies that the server’s hostname (or IP address) matches the names identified within the certificate. If the Subject Alternative Name is empty, it returns an error.', }), - showWhen: ['isTLSEnabled', true], + hidden: (dependencies) => { + const [isTLSEnabled] = dependencies; + return !Boolean(isTLSEnabled); + }, + dependencies: ['isTLSEnabled'], props: (): EuiSelectProps => ({ options: Object.values(VerificationMode).map((method) => ({ value: method, @@ -1002,7 +1012,11 @@ export const FIELD = (readOnly?: boolean): FieldMap => ({ defaultMessage: 'Supported TLS protocols', }), controlled: true, - showWhen: ['isTLSEnabled', true], + hidden: (dependencies) => { + const [isTLSEnabled] = dependencies; + return !Boolean(isTLSEnabled); + }, + dependencies: ['isTLSEnabled'], props: ({ field, setValue }): EuiComboBoxProps => { return { options: Object.values(TLSVersion).map((version) => ({ @@ -1023,42 +1037,54 @@ export const FIELD = (readOnly?: boolean): FieldMap => ({ }, [ConfigKey.TLS_CERTIFICATE_AUTHORITIES]: { fieldKey: ConfigKey.TLS_CERTIFICATE_AUTHORITIES, - component: EuiTextArea, + component: TextArea, label: i18n.translate('xpack.synthetics.monitorConfig.certificateAuthorities.label', { defaultMessage: 'Certificate authorities', }), helpText: i18n.translate('xpack.synthetics.monitorConfig.certificateAuthorities.helpText', { defaultMessage: 'PEM-formatted custom certificate authorities.', }), - showWhen: ['isTLSEnabled', true], + hidden: (dependencies) => { + const [isTLSEnabled] = dependencies; + return !Boolean(isTLSEnabled); + }, + dependencies: ['isTLSEnabled'], props: (): EuiTextAreaProps => ({ readOnly, }), }, [ConfigKey.TLS_CERTIFICATE]: { fieldKey: ConfigKey.TLS_CERTIFICATE, - component: EuiTextArea, + component: TextArea, label: i18n.translate('xpack.synthetics.monitorConfig.clientCertificate.label', { defaultMessage: 'Client certificate', }), helpText: i18n.translate('xpack.synthetics.monitorConfig.clientCertificate.helpText', { defaultMessage: 'PEM-formatted certificate for TLS client authentication.', }), - showWhen: ['isTLSEnabled', true], + hidden: (dependencies) => { + const [isTLSEnabled] = dependencies; + return !Boolean(isTLSEnabled); + }, + dependencies: ['isTLSEnabled'], props: (): EuiTextAreaProps => ({ readOnly, }), }, [ConfigKey.TLS_KEY]: { fieldKey: ConfigKey.TLS_KEY, - component: EuiTextArea, + component: TextArea, label: i18n.translate('xpack.synthetics.monitorConfig.clientKey.label', { defaultMessage: 'Client key', }), helpText: i18n.translate('xpack.synthetics.monitorConfig.clientKey.helpText', { defaultMessage: 'PEM-formatted certificate key for TLS client authentication.', }), - showWhen: ['isTLSEnabled', true], + hidden: (dependencies) => { + const [isTLSEnabled] = dependencies; + return !Boolean(isTLSEnabled); + }, + dependencies: ['isTLSEnabled'], props: (): EuiTextAreaProps => ({ readOnly, }), @@ -1072,7 +1098,11 @@ export const FIELD = (readOnly?: boolean): FieldMap => ({ helpText: i18n.translate('xpack.synthetics.monitorConfig.clientKeyPassphrase.helpText', { defaultMessage: 'Certificate key passphrase for TLS client authentication.', }), - showWhen: ['isTLSEnabled', true], + hidden: (dependencies) => { + const [isTLSEnabled] = dependencies; + return !Boolean(isTLSEnabled); + }, + dependencies: ['isTLSEnabled'], props: (): EuiFieldPasswordProps => ({ readOnly, }), @@ -1252,4 +1282,165 @@ export const FIELD = (readOnly?: boolean): FieldMap => ({ isDisabled: readOnly, }), }, + [ConfigKey.MODE]: { + fieldKey: ConfigKey.MODE, + component: Select, + label: i18n.translate('xpack.synthetics.monitorConfig.mode.label', { + defaultMessage: 'Mode', + }), + helpText: ( + all, + any: any, + }} + /> + ), + props: (): EuiSelectProps => ({ + options: Object.values(Mode).map((value) => ({ + value, + text: value, + })), + disabled: readOnly, + }), + }, + [ConfigKey.RESPONSE_BODY_MAX_BYTES]: { + fieldKey: ConfigKey.RESPONSE_BODY_MAX_BYTES, + component: FieldNumber, + label: i18n.translate('xpack.synthetics.monitorConfig.responseBodyMaxBytes.label', { + defaultMessage: 'Response body max bytes', + }), + helpText: i18n.translate('xpack.synthetics.monitorConfig.responseBodyMaxBytes.helpText', { + defaultMessage: 'Controls the maximum size of the stored body contents.', + }), + hidden: (dependencies) => { + const [responseBodyIndex] = dependencies || []; + return responseBodyIndex === ResponseBodyIndexPolicy.NEVER; + }, + props: (): EuiFieldNumberProps => ({ min: 1, step: 'any', readOnly }), + dependencies: [ConfigKey.RESPONSE_BODY_INDEX], + }, + [ConfigKey.IPV4]: { + fieldKey: ConfigKey.IPV4, // also controls ipv6 + component: ComboBox, + label: i18n.translate('xpack.synthetics.monitorConfig.ipv4.label', { + defaultMessage: 'IP protocols', + }), + helpText: i18n.translate('xpack.synthetics.monitorConfig.ipv4.helpText', { + defaultMessage: 'IP protocols to use when pinging the remote host.', + }), + controlled: true, + dependencies: [ConfigKey.IPV6], + props: ({ field, setValue, dependencies }): EuiComboBoxProps => { + const [ipv6] = dependencies; + const ipv4 = field?.value; + const values: string[] = []; + if (ipv4) { + values.push('IPv4'); + } + if (ipv6) { + values.push('IPv6'); + } + return { + options: [ + { + label: 'IPv4', + }, + { + label: 'IPv6', + }, + ], + selectedOptions: values.map((version) => ({ + label: version, + })), + onChange: (updatedValues: Array>) => { + setValue( + ConfigKey.IPV4, + updatedValues.some((value) => value.label === 'IPv4') + ); + setValue( + ConfigKey.IPV6, + updatedValues.some((value) => value.label === 'IPv6') + ); + }, + isDisabled: readOnly, + }; + }, + }, + [ConfigKey.PROXY_HEADERS]: { + fieldKey: ConfigKey.PROXY_HEADERS, + component: HeaderField, + label: i18n.translate('xpack.synthetics.monitorConfig.proxyHeaders.label', { + defaultMessage: 'Proxy headers', + }), + helpText: i18n.translate('xpack.synthetics.monitorConfig.proxyHeaders.helpText', { + defaultMessage: 'Additional headers to send to proxies for CONNECT requests.', + }), + controlled: true, + validation: () => ({ + validate: (headers) => !validateHeaders(headers), + }), + error: i18n.translate('xpack.synthetics.monitorConfig.proxyHeaders.error', { + defaultMessage: 'The header key must be a valid HTTP token.', + }), + props: (): HeaderFieldProps => ({ + readOnly, + }), + }, + ['check.response.json']: { + fieldKey: ConfigKey.RESPONSE_JSON_CHECK, + component: KeyValuePairsField, + label: i18n.translate('xpack.synthetics.monitorConfig.responseJSON.label', { + defaultMessage: 'Response body contains JSON', + }), + helpText: i18n.translate('xpack.synthetics.monitorConfig.responseJSON.helpText', { + defaultMessage: + 'A list of expressions executed against the body when parsed as JSON. The body size must be less than or equal to 100 MiB.', + }), + controlled: true, + props: ({ field, setValue }): KeyValuePairsFieldProps => ({ + readOnly, + keyLabel: i18n.translate('xpack.synthetics.monitorConfig.responseJSON.key.label', { + defaultMessage: 'Description', + }), + valueLabel: i18n.translate('xpack.synthetics.monitorConfig.responseJSON.value.label', { + defaultMessage: 'Expression', + }), + addPairControlLabel: i18n.translate( + 'xpack.synthetics.monitorConfig.responseJSON.addPair.label', + { + defaultMessage: 'Add expression', + } + ), + onChange: (pairs) => { + const value: ResponseCheckJSON[] = pairs + .map((pair) => { + const [description, expression] = pair; + return { + description, + expression, + }; + }) + .filter((pair) => pair.description || pair.expression); + if (!isEqual(value, field?.value)) { + setValue(ConfigKey.RESPONSE_JSON_CHECK, value); + } + }, + defaultPairs: field?.value.map((check) => [check.description, check.expression]) || [], + }), + validation: () => { + return { + validate: (value: ResponseCheckJSON[]) => { + if (value.some((check) => !check.expression || !check.description)) { + return i18n.translate('xpack.synthetics.monitorConfig.responseJSON.error', { + defaultMessage: + "This JSON expression isn't valid. Make sure that both the label and expression are defined.", + }); + } + }, + }; + }, + }, }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field_wrappers.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field_wrappers.tsx index 27a8a3ee5ab4d..b2ae5b290aecc 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field_wrappers.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field_wrappers.tsx @@ -25,6 +25,8 @@ import { EuiButtonGroupProps, EuiComboBox, EuiComboBoxProps, + EuiTextArea, + EuiTextAreaProps, } from '@elastic/eui'; import { ThrottlingConfigField, @@ -47,6 +49,10 @@ import { HeaderField as DefaultHeaderField, HeaderFieldProps as DefaultHeaderFieldProps, } from '../fields/header_field'; +import { + KeyValuePairsField as DefaultKeyValuePairsField, + KeyValuePairsFieldProps as DefaultKeyValuePairsFieldProps, +} from '../fields/key_value_field'; import { RequestBodyField as DefaultRequestBodyField, RequestBodyFieldProps as DefaultRequestBodyFieldProps, @@ -81,6 +87,10 @@ export const FieldText = React.forwardRef( ) ); +export const TextArea = React.forwardRef((props, ref) => ( + +)); + export const FieldNumber = React.forwardRef((props, ref) => ( )); @@ -129,6 +139,10 @@ export const HeaderField = React.forwardRef((p )); +export const KeyValuePairsField = React.forwardRef( + (props, _ref) => +); + export const RequestBodyField = React.forwardRef( (props, _ref) => ); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/form_config.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/form_config.tsx index 46a482512413a..8e74162ecef77 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/form_config.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/form_config.tsx @@ -39,10 +39,13 @@ const HTTP_ADVANCED = (readOnly: boolean) => ({ components: [ FIELD(readOnly)[ConfigKey.USERNAME], FIELD(readOnly)[ConfigKey.PASSWORD], - FIELD(readOnly)[ConfigKey.PROXY_URL], FIELD(readOnly)[ConfigKey.REQUEST_METHOD_CHECK], FIELD(readOnly)[ConfigKey.REQUEST_HEADERS_CHECK], FIELD(readOnly)[ConfigKey.REQUEST_BODY_CHECK], + FIELD(readOnly)[ConfigKey.PROXY_URL], + FIELD(readOnly)[ConfigKey.PROXY_HEADERS], + FIELD(readOnly)[ConfigKey.MODE], + FIELD(readOnly)[ConfigKey.IPV4], ], }, responseConfig: { @@ -58,6 +61,7 @@ const HTTP_ADVANCED = (readOnly: boolean) => ({ components: [ FIELD(readOnly)[ConfigKey.RESPONSE_HEADERS_INDEX], FIELD(readOnly)[ConfigKey.RESPONSE_BODY_INDEX], + FIELD(readOnly)[ConfigKey.RESPONSE_BODY_MAX_BYTES], ], }, responseChecks: { @@ -75,6 +79,7 @@ const HTTP_ADVANCED = (readOnly: boolean) => ({ FIELD(readOnly)[ConfigKey.RESPONSE_HEADERS_CHECK], FIELD(readOnly)[ConfigKey.RESPONSE_BODY_CHECK_POSITIVE], FIELD(readOnly)[ConfigKey.RESPONSE_BODY_CHECK_NEGATIVE], + FIELD(readOnly)[ConfigKey.RESPONSE_JSON_CHECK], ], }, }); @@ -93,6 +98,8 @@ export const TCP_ADVANCED = (readOnly: boolean) => ({ components: [ FIELD(readOnly)[`${ConfigKey.PROXY_URL}__tcp`], FIELD(readOnly)[ConfigKey.REQUEST_SEND_CHECK], + FIELD(readOnly)[ConfigKey.MODE], + FIELD(readOnly)[ConfigKey.IPV4], ], }, responseChecks: { @@ -109,6 +116,21 @@ export const TCP_ADVANCED = (readOnly: boolean) => ({ }, }); +export const ICMP_ADVANCED = (readOnly: boolean) => ({ + requestConfig: { + title: i18n.translate('xpack.synthetics.monitorConfig.section.requestConfigICMP.title', { + defaultMessage: 'Request configuration', + }), + description: i18n.translate( + 'xpack.synthetics.monitorConfig.section.requestConfigICMP.description', + { + defaultMessage: 'Configure the payload sent to the remote host.', + } + ), + components: [FIELD(readOnly)[ConfigKey.MODE], FIELD(readOnly)[ConfigKey.IPV4]], + }, +}); + export const BROWSER_ADVANCED = (readOnly: boolean) => [ { title: i18n.translate('xpack.synthetics.monitorConfig.section.syntAgentOptions.title', { @@ -264,6 +286,6 @@ export const FORM_CONFIG = (readOnly: boolean): FieldConfig => ({ FIELD(readOnly)[ConfigKey.ENABLED], FIELD(readOnly)[AlertConfigKey.STATUS_ENABLED], ], - advanced: [DEFAULT_DATA_OPTIONS(readOnly)], + advanced: [DEFAULT_DATA_OPTIONS(readOnly), ICMP_ADVANCED(readOnly).requestConfig], }, }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/types.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/types.ts index b3b86eef542fe..6abe63786563e 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/types.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/types.ts @@ -17,6 +17,7 @@ import { ServiceLocation, FormMonitorType, MonitorFields, + ResponseCheckJSON, } from '../../../../../common/runtime_types/monitor_management'; import { AlertConfigKey } from './constants'; @@ -55,6 +56,11 @@ export type FormConfig = MonitorFields & { ssl: { supported_protocols: MonitorFields[ConfigKey.TLS_VERSION]; }; + check: { + response: { + json: ResponseCheckJSON[]; + }; + }; }; export interface FieldMeta { @@ -63,6 +69,7 @@ export interface FieldMeta { label?: string; ariaLabel?: string; helpText?: string | React.ReactNode; + hidden?: (depenencies: unknown[]) => boolean; props?: (params: { field?: ControllerRenderProps; formState: FormState; @@ -88,7 +95,6 @@ export interface FieldMeta { event: React.ChangeEvent, formOnChange: (event: React.ChangeEvent) => void ) => void; - showWhen?: [keyof FormConfig, any]; // show field when another field equals an arbitrary value validation?: (dependencies: unknown[]) => Parameters[1]; error?: React.ReactNode; dependencies?: Array; // fields that another field may depend for or validation. Values are passed to the validation function @@ -123,16 +129,19 @@ export interface FieldMap { [ConfigKey.USERNAME]: FieldMeta; [ConfigKey.PASSWORD]: FieldMeta; [ConfigKey.PROXY_URL]: FieldMeta; + [ConfigKey.PROXY_HEADERS]: FieldMeta; ['proxy_url__tcp']: FieldMeta; [ConfigKey.REQUEST_METHOD_CHECK]: FieldMeta; [ConfigKey.REQUEST_HEADERS_CHECK]: FieldMeta; [ConfigKey.REQUEST_BODY_CHECK]: FieldMeta; [ConfigKey.RESPONSE_HEADERS_INDEX]: FieldMeta; [ConfigKey.RESPONSE_BODY_INDEX]: FieldMeta; + [ConfigKey.RESPONSE_BODY_MAX_BYTES]: FieldMeta; [ConfigKey.RESPONSE_STATUS_CHECK]: FieldMeta; [ConfigKey.RESPONSE_HEADERS_CHECK]: FieldMeta; [ConfigKey.RESPONSE_BODY_CHECK_POSITIVE]: FieldMeta; [ConfigKey.RESPONSE_BODY_CHECK_NEGATIVE]: FieldMeta; + [ConfigKey.RESPONSE_JSON_CHECK]: FieldMeta; [ConfigKey.RESPONSE_RECEIVE_CHECK]: FieldMeta; [ConfigKey.REQUEST_SEND_CHECK]: FieldMeta; ['source.inline']: FieldMeta; @@ -142,4 +151,6 @@ export interface FieldMap { [ConfigKey.PLAYWRIGHT_OPTIONS]: FieldMeta; [ConfigKey.SYNTHETICS_ARGS]: FieldMeta; [ConfigKey.IGNORE_HTTPS_ERRORS]: FieldMeta; + [ConfigKey.MODE]: FieldMeta; + [ConfigKey.IPV4]: FieldMeta; } diff --git a/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/migrations/monitors/8.8.0.ts b/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/migrations/monitors/8.8.0.ts index 426984969c191..9aec3104fc2bf 100644 --- a/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/migrations/monitors/8.8.0.ts +++ b/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/migrations/monitors/8.8.0.ts @@ -105,7 +105,7 @@ const getNearestSupportedSchedule = (currentSchedule: string): string => { return closest; } catch { - return ALLOWED_SCHEDULES_IN_MINUTES[0]; + return '10'; } }; diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/monitor_validation.test.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/monitor_validation.test.ts index 12fe8306d1731..7c2498242b35c 100644 --- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/monitor_validation.test.ts +++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/monitor_validation.test.ts @@ -9,6 +9,7 @@ import { BrowserAdvancedFields, BrowserFields, BrowserSimpleFields, + CodeEditorMode, CommonFields, ConfigKey, DataStream, @@ -18,7 +19,6 @@ import { HTTPSimpleFields, ICMPSimpleFields, Metadata, - Mode, MonitorFields, ResponseBodyIndexPolicy, ScheduleUnit, @@ -142,7 +142,7 @@ describe('validateMonitor', () => { [ConfigKey.RESPONSE_HEADERS_CHECK]: {}, [ConfigKey.RESPONSE_HEADERS_INDEX]: true, [ConfigKey.RESPONSE_STATUS_CHECK]: ['200', '201'], - [ConfigKey.REQUEST_BODY_CHECK]: { value: 'testValue', type: Mode.JSON }, + [ConfigKey.REQUEST_BODY_CHECK]: { value: 'testValue', type: CodeEditorMode.JSON }, [ConfigKey.REQUEST_HEADERS_CHECK]: {}, [ConfigKey.REQUEST_METHOD_CHECK]: '', [ConfigKey.USERNAME]: 'test-username', diff --git a/x-pack/plugins/synthetics/server/synthetics_service/formatters/format_configs.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/formatters/format_configs.test.ts index e5ff19ddaf1c0..921fd737e5d3e 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/formatters/format_configs.test.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/formatters/format_configs.test.ts @@ -14,7 +14,7 @@ import { import { ConfigKey, DataStream, - Mode, + CodeEditorMode, MonitorFields, ResponseBodyIndexPolicy, ScheduleUnit, @@ -39,11 +39,19 @@ const testHTTPConfig: Partial = { proxy_url: '${proxyUrl}', 'check.response.body.negative': [], 'check.response.body.positive': [], + 'check.response.json': [ + { + description: 'test description', + expression: 'foo.bar == "myValue"', + }, + ], 'response.include_body': 'on_error' as ResponseBodyIndexPolicy, - 'check.response.headers': {}, + 'check.response.headers': { + 'test-header': 'test-value', + }, 'response.include_headers': true, 'check.response.status': [], - 'check.request.body': { type: 'text' as Mode, value: '' }, + 'check.request.body': { type: 'text' as CodeEditorMode, value: '' }, 'check.request.headers': {}, 'check.request.method': 'GET', 'ssl.verification_mode': VerificationMode.NONE, @@ -99,6 +107,15 @@ describe('formatMonitorConfig', () => { expect(yamlConfig).toEqual({ 'check.request.method': 'GET', + 'check.response.headers': { + 'test-header': 'test-value', + }, + 'check.response.json': [ + { + description: 'test description', + expression: 'foo.bar == "myValue"', + }, + ], enabled: true, locations: [], max_redirects: '0', @@ -129,6 +146,15 @@ describe('formatMonitorConfig', () => { expect(yamlConfig).toEqual({ 'check.request.method': 'GET', + 'check.response.headers': { + 'test-header': 'test-value', + }, + 'check.response.json': [ + { + description: 'test description', + expression: 'foo.bar == "myValue"', + }, + ], enabled: true, locations: [], max_redirects: '0', diff --git a/x-pack/plugins/synthetics/server/synthetics_service/formatters/http.ts b/x-pack/plugins/synthetics/server/synthetics_service/formatters/http.ts index 43c1a5e76ea70..4f65e7546d966 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/formatters/http.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/formatters/http.ts @@ -17,10 +17,12 @@ export const httpFormatters: HTTPFormatMap = { [ConfigKey.METADATA]: objectFormatter, [ConfigKey.RESPONSE_BODY_CHECK_NEGATIVE]: arrayFormatter, [ConfigKey.RESPONSE_BODY_CHECK_POSITIVE]: arrayFormatter, + [ConfigKey.RESPONSE_JSON_CHECK]: arrayFormatter, [ConfigKey.RESPONSE_HEADERS_CHECK]: objectFormatter, [ConfigKey.RESPONSE_STATUS_CHECK]: arrayFormatter, [ConfigKey.REQUEST_HEADERS_CHECK]: objectFormatter, [ConfigKey.REQUEST_BODY_CHECK]: (fields) => fields[ConfigKey.REQUEST_BODY_CHECK]?.value || null, + [ConfigKey.PROXY_HEADERS]: objectFormatter, ...tlsFormatters, ...commonFormatters, }; diff --git a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/http_monitor.ts b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/http_monitor.ts index cd7ac7a26f6dd..0045cc711f9bc 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/http_monitor.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/http_monitor.ts @@ -7,11 +7,11 @@ import { get } from 'lodash'; import { DEFAULT_FIELDS } from '../../../../common/constants/monitor_defaults'; import { + CodeEditorMode, ConfigKey, DataStream, FormMonitorType, HTTPFields, - Mode, TLSVersion, } from '../../../../common/runtime_types/monitor_management'; import { @@ -70,6 +70,11 @@ export const getNormalizeHTTPFields = ({ (yamlConfig as Record)[ConfigKey.REQUEST_BODY_CHECK] as string, defaultFields[ConfigKey.REQUEST_BODY_CHECK] ), + [ConfigKey.RESPONSE_BODY_MAX_BYTES]: `${get( + yamlConfig, + ConfigKey.RESPONSE_BODY_MAX_BYTES, + defaultFields[ConfigKey.RESPONSE_BODY_MAX_BYTES] + )}`, [ConfigKey.TLS_VERSION]: get(monitor, ConfigKey.TLS_VERSION) ? (getOptionalListField(get(monitor, ConfigKey.TLS_VERSION)) as TLSVersion[]) : defaultFields[ConfigKey.TLS_VERSION], @@ -94,14 +99,14 @@ export const getRequestBodyField = ( defaultValue: HTTPFields[ConfigKey.REQUEST_BODY_CHECK] ): HTTPFields[ConfigKey.REQUEST_BODY_CHECK] => { let parsedValue: string; - let type: Mode; + let type: CodeEditorMode; if (typeof value === 'object') { parsedValue = JSON.stringify(value); - type = Mode.JSON; + type = CodeEditorMode.JSON; } else { parsedValue = value; - type = Mode.PLAINTEXT; + type = CodeEditorMode.PLAINTEXT; } return { type, diff --git a/x-pack/test/api_integration/apis/synthetics/add_monitor_private_location.ts b/x-pack/test/api_integration/apis/synthetics/add_monitor_private_location.ts index d680e99d13405..b48cf2c9a56ca 100644 --- a/x-pack/test/api_integration/apis/synthetics/add_monitor_private_location.ts +++ b/x-pack/test/api_integration/apis/synthetics/add_monitor_private_location.ts @@ -36,7 +36,7 @@ export default function ({ getService }: FtrProviderContext) { before(async () => { await supertestAPI.post('/api/fleet/setup').set('kbn-xsrf', 'true').send().expect(200); await supertestAPI - .post('/api/fleet/epm/packages/synthetics/0.11.4') + .post('/api/fleet/epm/packages/synthetics/0.12.0') .set('kbn-xsrf', 'true') .send({ force: true }) .expect(200); @@ -455,7 +455,7 @@ export default function ({ getService }: FtrProviderContext) { pkgPolicy.id === monitorId + '-' + testFleetPolicyID + `-default` ); - expect(packagePolicy.package.version).eql('0.11.4'); + expect(packagePolicy.package.version).eql('0.12.0'); await supertestAPI.post('/api/fleet/setup').set('kbn-xsrf', 'true').send().expect(200); const policyResponseAfterUpgrade = await supertestAPI.get( @@ -465,7 +465,7 @@ export default function ({ getService }: FtrProviderContext) { (pkgPolicy: PackagePolicy) => pkgPolicy.id === monitorId + '-' + testFleetPolicyID + `-default` ); - expect(semver.gte(packagePolicyAfterUpgrade.package.version, '0.11.4')).eql(true); + expect(semver.gte(packagePolicyAfterUpgrade.package.version, '0.12.0')).eql(true); } finally { await supertestAPI .delete(API_URLS.SYNTHETICS_MONITORS + '/' + monitorId) diff --git a/x-pack/test/api_integration/apis/synthetics/add_monitor_project.ts b/x-pack/test/api_integration/apis/synthetics/add_monitor_project.ts index e6a8ae14cda1a..7674d2fbc534f 100644 --- a/x-pack/test/api_integration/apis/synthetics/add_monitor_project.ts +++ b/x-pack/test/api_integration/apis/synthetics/add_monitor_project.ts @@ -77,7 +77,7 @@ export default function ({ getService }: FtrProviderContext) { await supertest.put(API_URLS.SYNTHETICS_ENABLEMENT).set('kbn-xsrf', 'true').expect(200); await supertest.post('/api/fleet/setup').set('kbn-xsrf', 'true').send().expect(200); await supertest - .post('/api/fleet/epm/packages/synthetics/0.11.4') + .post('/api/fleet/epm/packages/synthetics/0.12.0') .set('kbn-xsrf', 'true') .send({ force: true }) .expect(200); @@ -321,6 +321,9 @@ export default function ({ getService }: FtrProviderContext) { custom_heartbeat_id: `${journeyId}-${project}-default`, 'check.response.body.negative': [], 'check.response.body.positive': ['Saved', 'saved'], + 'check.response.json': [ + { description: 'check status', expression: 'foo.bar == "myValue"' }, + ], 'check.response.headers': {}, 'check.request.body': { type: 'text', @@ -357,8 +360,10 @@ export default function ({ getService }: FtrProviderContext) { username: '', password: '', proxy_url: '', + proxy_headers: {}, 'response.include_body': 'always', 'response.include_headers': false, + 'response.include_body_max_bytes': '900', revision: 1, schedule: { number: '60', @@ -378,6 +383,9 @@ export default function ({ getService }: FtrProviderContext) { 'url.port': null, id: `${journeyId}-${project}-default`, hash: 'ekrjelkjrelkjre', + mode: 'any', + ipv6: true, + ipv4: true, }); } } finally { @@ -486,6 +494,9 @@ export default function ({ getService }: FtrProviderContext) { urls: '', id: `${journeyId}-${project}-default`, hash: 'ekrjelkjrelkjre', + mode: 'any', + ipv6: true, + ipv4: true, }); } } finally { @@ -590,6 +601,9 @@ export default function ({ getService }: FtrProviderContext) { : `${parseInt(monitor.wait?.slice(0, -1) || '1', 10) * 60}`, id: `${journeyId}-${project}-default`, hash: 'ekrjelkjrelkjre', + mode: 'any', + ipv4: true, + ipv6: true, }); } } finally { @@ -1784,6 +1798,7 @@ export default function ({ getService }: FtrProviderContext) { positive: ['Saved', 'saved'], }, status: [200], + json: [{ description: 'check status', expression: 'foo.bar == "myValue"' }], }, enabled: false, hash: 'ekrjelkjrelkjre', @@ -1792,6 +1807,7 @@ export default function ({ getService }: FtrProviderContext) { name: 'My Monitor 3', response: { include_body: 'always', + include_body_max_bytes: 900, }, 'response.include_headers': false, schedule: 60, @@ -1885,6 +1901,12 @@ export default function ({ getService }: FtrProviderContext) { positive: ['Saved', 'saved'], }, status: [200], + json: [ + { + description: 'check status', + expression: 'foo.bar == "myValue"', + }, + ], }, enabled: false, hash: 'ekrjelkjrelkjre', @@ -1893,6 +1915,7 @@ export default function ({ getService }: FtrProviderContext) { name: 'My Monitor 3', response: { include_body: 'always', + include_body_max_bytes: 900, }, 'response.include_headers': false, schedule: 60, @@ -1946,6 +1969,12 @@ export default function ({ getService }: FtrProviderContext) { positive: ['Saved', 'saved'], }, status: [200], + json: [ + { + description: 'check status', + expression: 'foo.bar == "myValue"', + }, + ], }, enabled: false, hash: 'ekrjelkjrelkjre', @@ -1954,6 +1983,7 @@ export default function ({ getService }: FtrProviderContext) { name: 'My Monitor 3', response: { include_body: 'always', + include_body_max_bytes: 900, }, 'response.include_headers': false, schedule: 60, @@ -2008,6 +2038,12 @@ export default function ({ getService }: FtrProviderContext) { positive: ['Saved', 'saved'], }, status: [200], + json: [ + { + description: 'check status', + expression: 'foo.bar == "myValue"', + }, + ], }, enabled: false, hash: 'ekrjelkjrelkjre', @@ -2016,6 +2052,7 @@ export default function ({ getService }: FtrProviderContext) { name: 'My Monitor 3', response: { include_body: 'always', + include_body_max_bytes: 900, }, 'response.include_headers': false, schedule: 60, diff --git a/x-pack/test/api_integration/apis/synthetics/add_monitor_project_legacy.ts b/x-pack/test/api_integration/apis/synthetics/add_monitor_project_legacy.ts index 65768d80a53f3..0e2cec6c3a5c3 100644 --- a/x-pack/test/api_integration/apis/synthetics/add_monitor_project_legacy.ts +++ b/x-pack/test/api_integration/apis/synthetics/add_monitor_project_legacy.ts @@ -86,7 +86,7 @@ export default function ({ getService }: FtrProviderContext) { before(async () => { await supertest.post('/api/fleet/setup').set('kbn-xsrf', 'true').send().expect(200); await supertest - .post('/api/fleet/epm/packages/synthetics/0.11.4') + .post('/api/fleet/epm/packages/synthetics/0.12.0') .set('kbn-xsrf', 'true') .send({ force: true }) .expect(200); @@ -256,6 +256,9 @@ export default function ({ getService }: FtrProviderContext) { custom_heartbeat_id: `${journeyId}-test-suite-default`, 'check.response.body.negative': [], 'check.response.body.positive': ['Saved', 'saved'], + 'check.response.json': [ + { description: 'check status', expression: 'foo.bar == "myValue"' }, + ], 'check.response.headers': {}, 'check.request.body': { type: 'text', @@ -292,8 +295,10 @@ export default function ({ getService }: FtrProviderContext) { username: '', password: '', proxy_url: '', + proxy_headers: {}, 'response.include_body': 'always', 'response.include_headers': false, + 'response.include_body_max_bytes': '900', revision: 1, schedule: { number: '60', @@ -313,6 +318,9 @@ export default function ({ getService }: FtrProviderContext) { 'url.port': null, id: `${journeyId}-test-suite-default`, hash: 'ekrjelkjrelkjre', + ipv6: true, + ipv4: true, + mode: 'any', }); } } finally { @@ -418,6 +426,9 @@ export default function ({ getService }: FtrProviderContext) { urls: '', id: `${journeyId}-test-suite-default`, hash: 'ekrjelkjrelkjre', + ipv6: true, + ipv4: true, + mode: 'any', }); } } finally { @@ -520,6 +531,9 @@ export default function ({ getService }: FtrProviderContext) { : `${parseInt(monitor.wait?.slice(0, -1) || '1', 10) * 60}`, id: `${journeyId}-test-suite-default`, hash: 'ekrjelkjrelkjre', + ipv6: true, + ipv4: true, + mode: 'any', }); } } finally { @@ -1802,11 +1816,13 @@ export default function ({ getService }: FtrProviderContext) { timeout: { value: '80s', type: 'text' }, max_redirects: { value: '0', type: 'integer' }, proxy_url: { value: '', type: 'text' }, + proxy_headers: { value: null, type: 'yaml' }, tags: { value: '["tag2","tag2"]', type: 'yaml' }, username: { value: '', type: 'text' }, password: { value: '', type: 'password' }, 'response.include_headers': { value: false, type: 'bool' }, 'response.include_body': { value: 'always', type: 'text' }, + 'response.include_body_max_bytes': { value: '900', type: 'text' }, 'check.request.method': { value: 'POST', type: 'text' }, 'check.request.headers': { value: '{"Content-Type":"application/x-www-form-urlencoded"}', @@ -1817,6 +1833,10 @@ export default function ({ getService }: FtrProviderContext) { 'check.response.headers': { value: null, type: 'yaml' }, 'check.response.body.positive': { value: '["Saved","saved"]', type: 'yaml' }, 'check.response.body.negative': { value: null, type: 'yaml' }, + 'check.response.json': { + value: '[{"description":"check status","expression":"foo.bar == \\"myValue\\""}]', + type: 'yaml', + }, 'ssl.certificate_authorities': { value: null, type: 'yaml' }, 'ssl.certificate': { value: null, type: 'yaml' }, 'ssl.key': { value: null, type: 'yaml' }, @@ -1842,6 +1862,9 @@ export default function ({ getService }: FtrProviderContext) { type: 'text', value: 'test-suite', }, + ipv4: { type: 'bool', value: true }, + ipv6: { type: 'bool', value: true }, + mode: { type: 'text', value: 'any' }, }, id: `synthetics/http-http-${id}-${testPolicyId}`, compiled_stream: { @@ -1866,6 +1889,15 @@ export default function ({ getService }: FtrProviderContext) { 'ssl.supported_protocols': ['TLSv1.1', 'TLSv1.2', 'TLSv1.3'], 'run_from.geo.name': 'Test private location 0', 'run_from.id': 'Test private location 0', + 'check.response.json': [ + { + description: 'check status', + expression: 'foo.bar == "myValue"', + }, + ], + ipv4: true, + ipv6: true, + mode: 'any', processors: [ { add_fields: { diff --git a/x-pack/test/api_integration/apis/synthetics/delete_monitor.ts b/x-pack/test/api_integration/apis/synthetics/delete_monitor.ts index 87d033be74743..9ab6063775844 100644 --- a/x-pack/test/api_integration/apis/synthetics/delete_monitor.ts +++ b/x-pack/test/api_integration/apis/synthetics/delete_monitor.ts @@ -41,7 +41,7 @@ export default function ({ getService }: FtrProviderContext) { _httpMonitorJson = getFixtureJson('http_monitor'); await supertest.post('/api/fleet/setup').set('kbn-xsrf', 'true').send().expect(200); await supertest - .post('/api/fleet/epm/packages/synthetics/0.11.4') + .post('/api/fleet/epm/packages/synthetics/0.12.0') .set('kbn-xsrf', 'true') .send({ force: true }) .expect(200); diff --git a/x-pack/test/api_integration/apis/synthetics/delete_monitor_project.ts b/x-pack/test/api_integration/apis/synthetics/delete_monitor_project.ts index 8ba507beb7e4c..13bcab980d248 100644 --- a/x-pack/test/api_integration/apis/synthetics/delete_monitor_project.ts +++ b/x-pack/test/api_integration/apis/synthetics/delete_monitor_project.ts @@ -45,7 +45,7 @@ export default function ({ getService }: FtrProviderContext) { before(async () => { await supertest.post('/api/fleet/setup').set('kbn-xsrf', 'true').send().expect(200); await supertest - .post('/api/fleet/epm/packages/synthetics/0.11.4') + .post('/api/fleet/epm/packages/synthetics/0.12.0') .set('kbn-xsrf', 'true') .send({ force: true }) .expect(200); diff --git a/x-pack/test/api_integration/apis/synthetics/get_monitor_project.ts b/x-pack/test/api_integration/apis/synthetics/get_monitor_project.ts index d24a3775bbf44..beefd6c561829 100644 --- a/x-pack/test/api_integration/apis/synthetics/get_monitor_project.ts +++ b/x-pack/test/api_integration/apis/synthetics/get_monitor_project.ts @@ -46,7 +46,7 @@ export default function ({ getService }: FtrProviderContext) { before(async () => { await supertest.post('/api/fleet/setup').set('kbn-xsrf', 'true').send().expect(200); await supertest - .post('/api/fleet/epm/packages/synthetics/0.11.4') + .post('/api/fleet/epm/packages/synthetics/0.12.0') .set('kbn-xsrf', 'true') .send({ force: true }) .expect(200); diff --git a/x-pack/test/api_integration/apis/synthetics/sample_data/test_browser_policy.ts b/x-pack/test/api_integration/apis/synthetics/sample_data/test_browser_policy.ts index eff1026cb4486..5eec726cdb328 100644 --- a/x-pack/test/api_integration/apis/synthetics/sample_data/test_browser_policy.ts +++ b/x-pack/test/api_integration/apis/synthetics/sample_data/test_browser_policy.ts @@ -21,7 +21,7 @@ export const getTestBrowserSyntheticsPolicy = ({ version: 'WzEzNzYsMV0=', name: 'Test HTTP Monitor 03-Test private location 0-default', namespace: 'testnamespace', - package: { name: 'synthetics', title: 'Elastic Synthetics', version: '0.11.4' }, + package: { name: 'synthetics', title: 'Elastic Synthetics', version: '0.12.0' }, enabled: true, policy_id: 'fe621d20-7b01-11ed-803f-475d82e1f9ca', inputs: [ @@ -44,11 +44,13 @@ export const getTestBrowserSyntheticsPolicy = ({ timeout: { type: 'text' }, max_redirects: { type: 'integer' }, proxy_url: { type: 'text' }, + proxy_headers: { type: 'yaml' }, tags: { type: 'yaml' }, username: { type: 'text' }, password: { type: 'password' }, 'response.include_headers': { type: 'bool' }, 'response.include_body': { type: 'text' }, + 'response.include_body_max_bytes': { type: 'text' }, 'check.request.method': { type: 'text' }, 'check.request.headers': { type: 'yaml' }, 'check.request.body': { type: 'yaml' }, @@ -56,6 +58,7 @@ export const getTestBrowserSyntheticsPolicy = ({ 'check.response.headers': { type: 'yaml' }, 'check.response.body.positive': { type: 'yaml' }, 'check.response.body.negative': { type: 'yaml' }, + 'check.response.json': { type: 'yaml' }, 'ssl.certificate_authorities': { type: 'yaml' }, 'ssl.certificate': { type: 'yaml' }, 'ssl.key': { type: 'yaml' }, @@ -69,6 +72,9 @@ export const getTestBrowserSyntheticsPolicy = ({ origin: { type: 'text' }, 'monitor.project.id': { type: 'text' }, 'monitor.project.name': { type: 'text' }, + ipv4: { type: 'bool', value: true }, + ipv6: { type: 'bool', value: true }, + mode: { type: 'text' }, }, id: 'synthetics/http-http-abf904a4-cb9a-4b29-8c11-4d183cca289b-fe621d20-7b01-11ed-803f-475d82e1f9ca-default', }, @@ -109,6 +115,9 @@ export const getTestBrowserSyntheticsPolicy = ({ origin: { type: 'text' }, 'monitor.project.id': { type: 'text' }, 'monitor.project.name': { type: 'text' }, + ipv4: { type: 'bool', value: true }, + ipv6: { type: 'bool', value: true }, + mode: { type: 'text' }, }, id: 'synthetics/tcp-tcp-abf904a4-cb9a-4b29-8c11-4d183cca289b-fe621d20-7b01-11ed-803f-475d82e1f9ca-default', }, @@ -140,6 +149,9 @@ export const getTestBrowserSyntheticsPolicy = ({ origin: { type: 'text' }, 'monitor.project.id': { type: 'text' }, 'monitor.project.name': { type: 'text' }, + ipv4: { type: 'bool', value: true }, + ipv6: { type: 'bool', value: true }, + mode: { type: 'text' }, }, id: 'synthetics/icmp-icmp-abf904a4-cb9a-4b29-8c11-4d183cca289b-fe621d20-7b01-11ed-803f-475d82e1f9ca-default', }, @@ -249,10 +261,7 @@ export const getTestBrowserSyntheticsPolicy = ({ }, id: 'synthetics/browser-browser.network-abf904a4-cb9a-4b29-8c11-4d183cca289b-fe621d20-7b01-11ed-803f-475d82e1f9ca-default', compiled_stream: { - processors: [ - { add_observer_metadata: { geo: { name: 'Fleet managed' } } }, - { add_fields: { target: '', fields: { 'monitor.fleet_managed': true } } }, - ], + processors: [{ add_fields: { target: '', fields: { 'monitor.fleet_managed': true } } }], }, }, { @@ -264,10 +273,7 @@ export const getTestBrowserSyntheticsPolicy = ({ }, id: 'synthetics/browser-browser.screenshot-abf904a4-cb9a-4b29-8c11-4d183cca289b-fe621d20-7b01-11ed-803f-475d82e1f9ca-default', compiled_stream: { - processors: [ - { add_observer_metadata: { geo: { name: 'Fleet managed' } } }, - { add_fields: { target: '', fields: { 'monitor.fleet_managed': true } } }, - ], + processors: [{ add_fields: { target: '', fields: { 'monitor.fleet_managed': true } } }], }, }, ], diff --git a/x-pack/test/api_integration/apis/synthetics/sample_data/test_policy.ts b/x-pack/test/api_integration/apis/synthetics/sample_data/test_policy.ts index 82e19948779bb..b0962e4d285e6 100644 --- a/x-pack/test/api_integration/apis/synthetics/sample_data/test_policy.ts +++ b/x-pack/test/api_integration/apis/synthetics/sample_data/test_policy.ts @@ -21,7 +21,7 @@ export const getTestSyntheticsPolicy = ( version: 'WzE2MjYsMV0=', name: 'test-monitor-name-Test private location 0-default', namespace: namespace || 'testnamespace', - package: { name: 'synthetics', title: 'Elastic Synthetics', version: '0.11.4' }, + package: { name: 'synthetics', title: 'Elastic Synthetics', version: '0.12.0' }, enabled: true, policy_id: '5347cd10-0368-11ed-8df7-a7424c6f5167', inputs: [ @@ -55,11 +55,13 @@ export const getTestSyntheticsPolicy = ( timeout: { value: '3ms', type: 'text' }, max_redirects: { value: '3', type: 'integer' }, proxy_url: { value: proxyUrl ?? 'http://proxy.com', type: 'text' }, + proxy_headers: { value: null, type: 'yaml' }, tags: { value: '["tag1","tag2"]', type: 'yaml' }, username: { value: 'test-username', type: 'text' }, password: { value: 'test', type: 'password' }, 'response.include_headers': { value: true, type: 'bool' }, 'response.include_body': { value: 'never', type: 'text' }, + 'response.include_body_max_bytes': { value: '1024', type: 'text' }, 'check.request.method': { value: '', type: 'text' }, 'check.request.headers': { value: '{"sampleHeader":"sampleHeaderValue"}', @@ -70,6 +72,7 @@ export const getTestSyntheticsPolicy = ( 'check.response.headers': { value: null, type: 'yaml' }, 'check.response.body.positive': { value: null, type: 'yaml' }, 'check.response.body.negative': { value: null, type: 'yaml' }, + 'check.response.json': { value: null, type: 'yaml' }, 'ssl.certificate_authorities': { value: isTLSEnabled ? '"t.string"' : null, type: 'yaml', @@ -89,6 +92,9 @@ export const getTestSyntheticsPolicy = ( origin: { value: 'ui', type: 'text' }, 'monitor.project.id': { type: 'text', value: null }, 'monitor.project.name': { type: 'text', value: null }, + ipv4: { type: 'bool', value: true }, + ipv6: { type: 'bool', value: true }, + mode: { type: 'text', value: 'any' }, }, id: 'synthetics/http-http-2bfd7da0-22ed-11ed-8c6b-09a2d21dfbc3-27337270-22ed-11ed-8c6b-09a2d21dfbc3-default', compiled_stream: { @@ -116,6 +122,9 @@ export const getTestSyntheticsPolicy = ( 'check.request.headers': { sampleHeader: 'sampleHeaderValue' }, 'check.request.body': 'testValue', 'check.response.status': ['200', '201'], + ipv4: true, + ipv6: true, + mode: 'any', ...(isTLSEnabled ? { 'ssl.certificate': 't.string', @@ -179,6 +188,9 @@ export const getTestSyntheticsPolicy = ( origin: { type: 'text' }, 'monitor.project.id': { type: 'text' }, 'monitor.project.name': { type: 'text' }, + ipv4: { type: 'bool', value: true }, + ipv6: { type: 'bool', value: true }, + mode: { type: 'text' }, }, id: 'synthetics/tcp-tcp-2bfd7da0-22ed-11ed-8c6b-09a2d21dfbc3-27337270-22ed-11ed-8c6b-09a2d21dfbc3-default', }, @@ -213,6 +225,9 @@ export const getTestSyntheticsPolicy = ( origin: { type: 'text' }, 'monitor.project.id': { type: 'text' }, 'monitor.project.name': { type: 'text' }, + ipv4: { type: 'bool', value: true }, + ipv6: { type: 'bool', value: true }, + mode: { type: 'text' }, }, id: 'synthetics/icmp-icmp-2bfd7da0-22ed-11ed-8c6b-09a2d21dfbc3-27337270-22ed-11ed-8c6b-09a2d21dfbc3-default', }, @@ -299,10 +314,7 @@ export const getTestSyntheticsPolicy = ( }, id: 'synthetics/browser-browser.network-2bfd7da0-22ed-11ed-8c6b-09a2d21dfbc3-27337270-22ed-11ed-8c6b-09a2d21dfbc3-default', compiled_stream: { - processors: [ - { add_observer_metadata: { geo: { name: 'Fleet managed' } } }, - { add_fields: { target: '', fields: { 'monitor.fleet_managed': true } } }, - ], + processors: [{ add_fields: { target: '', fields: { 'monitor.fleet_managed': true } } }], }, }, { @@ -318,10 +330,7 @@ export const getTestSyntheticsPolicy = ( }, id: 'synthetics/browser-browser.screenshot-2bfd7da0-22ed-11ed-8c6b-09a2d21dfbc3-27337270-22ed-11ed-8c6b-09a2d21dfbc3-default', compiled_stream: { - processors: [ - { add_observer_metadata: { geo: { name: 'Fleet managed' } } }, - { add_fields: { target: '', fields: { 'monitor.fleet_managed': true } } }, - ], + processors: [{ add_fields: { target: '', fields: { 'monitor.fleet_managed': true } } }], }, }, ], diff --git a/x-pack/test/api_integration/apis/synthetics/sample_data/test_project_monitor_policy.ts b/x-pack/test/api_integration/apis/synthetics/sample_data/test_project_monitor_policy.ts index ba48a26ff50fe..bc015cb4bd77c 100644 --- a/x-pack/test/api_integration/apis/synthetics/sample_data/test_project_monitor_policy.ts +++ b/x-pack/test/api_integration/apis/synthetics/sample_data/test_project_monitor_policy.ts @@ -34,7 +34,7 @@ export const getTestProjectSyntheticsPolicy = ( version: 'WzEzMDksMV0=', name: `4b6abc6c-118b-4d93-a489-1135500d09f1-${projectId}-default-Test private location 0`, namespace: 'default', - package: { name: 'synthetics', title: 'Elastic Synthetics', version: '0.11.4' }, + package: { name: 'synthetics', title: 'Elastic Synthetics', version: '0.12.0' }, enabled: true, policy_id: '46034710-0ba6-11ed-ba04-5f123b9faa8b', inputs: [ @@ -60,11 +60,13 @@ export const getTestProjectSyntheticsPolicy = ( timeout: { type: 'text' }, max_redirects: { type: 'integer' }, proxy_url: { type: 'text' }, + proxy_headers: { type: 'yaml' }, tags: { type: 'yaml' }, username: { type: 'text' }, password: { type: 'password' }, 'response.include_headers': { type: 'bool' }, 'response.include_body': { type: 'text' }, + 'response.include_body_max_bytes': { type: 'text' }, 'check.request.method': { type: 'text' }, 'check.request.headers': { type: 'yaml' }, 'check.request.body': { type: 'yaml' }, @@ -72,6 +74,7 @@ export const getTestProjectSyntheticsPolicy = ( 'check.response.headers': { type: 'yaml' }, 'check.response.body.positive': { type: 'yaml' }, 'check.response.body.negative': { type: 'yaml' }, + 'check.response.json': { type: 'yaml' }, 'ssl.certificate_authorities': { type: 'yaml' }, 'ssl.certificate': { type: 'yaml' }, 'ssl.key': { type: 'yaml' }, @@ -85,6 +88,9 @@ export const getTestProjectSyntheticsPolicy = ( origin: { type: 'text' }, 'monitor.project.id': { type: 'text' }, 'monitor.project.name': { type: 'text' }, + ipv4: { type: 'bool', value: true }, + ipv6: { type: 'bool', value: true }, + mode: { type: 'text' }, }, id: `synthetics/http-http-4b6abc6c-118b-4d93-a489-1135500d09f1-${projectId}-default-d70a46e0-22ea-11ed-8c6b-09a2d21dfbc3`, }, @@ -128,6 +134,9 @@ export const getTestProjectSyntheticsPolicy = ( origin: { type: 'text' }, 'monitor.project.id': { type: 'text' }, 'monitor.project.name': { type: 'text' }, + ipv4: { type: 'bool', value: true }, + ipv6: { type: 'bool', value: true }, + mode: { type: 'text' }, }, id: `synthetics/tcp-tcp-4b6abc6c-118b-4d93-a489-1135500d09f1-${projectId}-default-d70a46e0-22ea-11ed-8c6b-09a2d21dfbc3`, }, @@ -162,6 +171,9 @@ export const getTestProjectSyntheticsPolicy = ( origin: { type: 'text' }, 'monitor.project.id': { type: 'text' }, 'monitor.project.name': { type: 'text' }, + ipv4: { type: 'bool', value: true }, + ipv6: { type: 'bool', value: true }, + mode: { type: 'text' }, }, id: `synthetics/icmp-icmp-4b6abc6c-118b-4d93-a489-1135500d09f1-${projectId}-default-d70a46e0-22ea-11ed-8c6b-09a2d21dfbc3`, }, @@ -287,10 +299,7 @@ export const getTestProjectSyntheticsPolicy = ( }, id: `synthetics/browser-browser.network-4b6abc6c-118b-4d93-a489-1135500d09f1-${projectId}-default-d70a46e0-22ea-11ed-8c6b-09a2d21dfbc3`, compiled_stream: { - processors: [ - { add_observer_metadata: { geo: { name: 'Fleet managed' } } }, - { add_fields: { target: '', fields: { 'monitor.fleet_managed': true } } }, - ], + processors: [{ add_fields: { target: '', fields: { 'monitor.fleet_managed': true } } }], }, }, { @@ -306,10 +315,7 @@ export const getTestProjectSyntheticsPolicy = ( }, id: `synthetics/browser-browser.screenshot-4b6abc6c-118b-4d93-a489-1135500d09f1-${projectId}-default-d70a46e0-22ea-11ed-8c6b-09a2d21dfbc3`, compiled_stream: { - processors: [ - { add_observer_metadata: { geo: { name: 'Fleet managed' } } }, - { add_fields: { target: '', fields: { 'monitor.fleet_managed': true } } }, - ], + processors: [{ add_fields: { target: '', fields: { 'monitor.fleet_managed': true } } }], }, }, ], diff --git a/x-pack/test/api_integration/apis/synthetics/sync_global_params.ts b/x-pack/test/api_integration/apis/synthetics/sync_global_params.ts index acb6b4250628d..f5ff56d2ee506 100644 --- a/x-pack/test/api_integration/apis/synthetics/sync_global_params.ts +++ b/x-pack/test/api_integration/apis/synthetics/sync_global_params.ts @@ -44,7 +44,7 @@ export default function ({ getService }: FtrProviderContext) { before(async () => { await supertestAPI.post('/api/fleet/setup').set('kbn-xsrf', 'true').send().expect(200); await supertestAPI - .post('/api/fleet/epm/packages/synthetics/0.11.4') + .post('/api/fleet/epm/packages/synthetics/0.12.0') .set('kbn-xsrf', 'true') .send({ force: true }) .expect(200); diff --git a/x-pack/test/api_integration/apis/uptime/rest/fixtures/http_monitor.json b/x-pack/test/api_integration/apis/uptime/rest/fixtures/http_monitor.json index 091f18e6afe57..b2961eb0660d6 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/fixtures/http_monitor.json +++ b/x-pack/test/api_integration/apis/uptime/rest/fixtures/http_monitor.json @@ -25,9 +25,12 @@ "urls": "https://nextjs-test-synthetics.vercel.app/api/users", "url.port": null, "proxy_url": "http://proxy.com", + "proxy_headers": {}, "check.response.body.negative": [], "check.response.body.positive": [], + "check.response.json": [], "response.include_body": "never", + "response.include_body_max_bytes": "1024", "check.request.headers": { "sampleHeader": "sampleHeaderValue" }, @@ -81,5 +84,8 @@ "form_monitor_type": "http", "journey_id": "", "id": "", - "hash": "" + "hash": "", + "mode": "any", + "ipv4": true, + "ipv6": true } diff --git a/x-pack/test/api_integration/apis/uptime/rest/fixtures/icmp_monitor.json b/x-pack/test/api_integration/apis/uptime/rest/fixtures/icmp_monitor.json index ab172c3fe8ca5..ef94fcc067a98 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/fixtures/icmp_monitor.json +++ b/x-pack/test/api_integration/apis/uptime/rest/fixtures/icmp_monitor.json @@ -26,5 +26,8 @@ "origin": "ui", "form_monitor_type": "icmp", "id": "", - "hash": "" + "hash": "", + "mode": "any", + "ipv4": true, + "ipv6": true } diff --git a/x-pack/test/api_integration/apis/uptime/rest/fixtures/project_http_monitor.json b/x-pack/test/api_integration/apis/uptime/rest/fixtures/project_http_monitor.json index c8c2c27ce0f17..dd6d6eefecfcd 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/fixtures/project_http_monitor.json +++ b/x-pack/test/api_integration/apis/uptime/rest/fixtures/project_http_monitor.json @@ -56,7 +56,8 @@ } }, "response": { - "include_body": "always" + "include_body": "always", + "include_body_max_bytes": 900 }, "tags": "tag2,tag2", "response.include_headers": false, @@ -69,7 +70,8 @@ "Saved", "saved" ] - } + }, + "json": [{"description":"check status","expression":"foo.bar == \"myValue\""}] }, "hash": "ekrjelkjrelkjre", "ssl.verification_mode": "strict" diff --git a/x-pack/test/api_integration/apis/uptime/rest/fixtures/tcp_monitor.json b/x-pack/test/api_integration/apis/uptime/rest/fixtures/tcp_monitor.json index c3664c5646b16..a0390c0bc173e 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/fixtures/tcp_monitor.json +++ b/x-pack/test/api_integration/apis/uptime/rest/fixtures/tcp_monitor.json @@ -34,5 +34,8 @@ "origin": "ui", "form_monitor_type": "tcp", "id": "", - "hash": "" + "hash": "", + "mode": "any", + "ipv4": true, + "ipv6": true } From a04ad04dd5db128daf34b7dc0f77428a46ebaab4 Mon Sep 17 00:00:00 2001 From: Ignacio Rivas Date: Tue, 25 Apr 2023 14:26:18 +0200 Subject: [PATCH 44/52] [Grok] Migrate code editors to monaco (#155492) --- x-pack/plugins/grokdebugger/kibana.jsonc | 3 +- .../custom_patterns_input.js | 24 +++++----- .../components/event_input/event_input.js | 27 ++++++----- .../components/event_output/event_output.js | 25 ++++------- .../components/grok_debugger/brace_imports.ts | 10 ----- .../components/grok_debugger/grok_debugger.js | 1 - .../components/pattern_input/pattern_input.js | 28 ++++++------ .../public/lib/ace/grok_highlight_rules.js | 45 ------------------- .../grokdebugger/public/lib/ace/grok_mode.js | 18 -------- .../grokdebugger/public/lib/ace/index.js | 8 ---- .../grokdebugger/public/shared_imports.ts | 2 - .../page_objects/grok_debugger_page.ts | 21 +++------ .../test/functional/services/grok_debugger.js | 14 +++--- 13 files changed, 58 insertions(+), 168 deletions(-) delete mode 100644 x-pack/plugins/grokdebugger/public/components/grok_debugger/brace_imports.ts delete mode 100644 x-pack/plugins/grokdebugger/public/lib/ace/grok_highlight_rules.js delete mode 100644 x-pack/plugins/grokdebugger/public/lib/ace/grok_mode.js delete mode 100644 x-pack/plugins/grokdebugger/public/lib/ace/index.js diff --git a/x-pack/plugins/grokdebugger/kibana.jsonc b/x-pack/plugins/grokdebugger/kibana.jsonc index 340088efc772f..aa0bdc864142f 100644 --- a/x-pack/plugins/grokdebugger/kibana.jsonc +++ b/x-pack/plugins/grokdebugger/kibana.jsonc @@ -16,8 +16,7 @@ "devTools" ], "requiredBundles": [ - "kibanaReact", - "esUiShared" + "kibanaReact" ] } } diff --git a/x-pack/plugins/grokdebugger/public/components/custom_patterns_input/custom_patterns_input.js b/x-pack/plugins/grokdebugger/public/components/custom_patterns_input/custom_patterns_input.js index cac942fa44694..def839c3f9b09 100644 --- a/x-pack/plugins/grokdebugger/public/components/custom_patterns_input/custom_patterns_input.js +++ b/x-pack/plugins/grokdebugger/public/components/custom_patterns_input/custom_patterns_input.js @@ -6,11 +6,11 @@ */ import React from 'react'; +import { i18n } from '@kbn/i18n'; import { EuiAccordion, EuiCallOut, EuiCodeBlock, EuiFormRow, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import { EDITOR } from '../../../common/constants'; -import { EuiCodeEditor } from '../../shared_imports'; +import { CodeEditor } from '@kbn/kibana-react-plugin/public'; export function CustomPatternsInput({ value, onChange }) { const sampleCustomPatterns = `POSTFIX_QUEUEID [0-9A-F]{10,11} @@ -43,18 +43,18 @@ MSG message-id=<%{GREEDYDATA}>`; - diff --git a/x-pack/plugins/grokdebugger/public/components/event_input/event_input.js b/x-pack/plugins/grokdebugger/public/components/event_input/event_input.js index 35b3be399fdce..2bfdce4f0a893 100644 --- a/x-pack/plugins/grokdebugger/public/components/event_input/event_input.js +++ b/x-pack/plugins/grokdebugger/public/components/event_input/event_input.js @@ -6,11 +6,10 @@ */ import React from 'react'; +import { i18n } from '@kbn/i18n'; import { EuiFormRow } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; - -import { EDITOR } from '../../../common/constants'; -import { EuiCodeEditor } from '../../shared_imports'; +import { CodeEditor } from '@kbn/kibana-react-plugin/public'; export function EventInput({ value, onChange }) { return ( @@ -19,20 +18,20 @@ export function EventInput({ value, onChange }) { } fullWidth - data-test-subj="aceEventInput" + data-test-subj="eventInput" > - ); diff --git a/x-pack/plugins/grokdebugger/public/components/event_output/event_output.js b/x-pack/plugins/grokdebugger/public/components/event_output/event_output.js index a2a02259c3fdf..e26672e467c3e 100644 --- a/x-pack/plugins/grokdebugger/public/components/event_output/event_output.js +++ b/x-pack/plugins/grokdebugger/public/components/event_output/event_output.js @@ -6,11 +6,9 @@ */ import React from 'react'; -import { EuiFormRow } from '@elastic/eui'; +import { EuiFormRow, EuiCodeBlock } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiCodeEditor } from '../../shared_imports'; - export function EventOutput({ value }) { return ( } fullWidth - data-test-subj="aceEventOutput" > - + + {JSON.stringify(value, null, 2)} + ); } diff --git a/x-pack/plugins/grokdebugger/public/components/grok_debugger/brace_imports.ts b/x-pack/plugins/grokdebugger/public/components/grok_debugger/brace_imports.ts deleted file mode 100644 index 4f81fd1744795..0000000000000 --- a/x-pack/plugins/grokdebugger/public/components/grok_debugger/brace_imports.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. - */ - -import 'brace/mode/json'; -import 'brace/mode/text'; -import 'brace/theme/textmate'; diff --git a/x-pack/plugins/grokdebugger/public/components/grok_debugger/grok_debugger.js b/x-pack/plugins/grokdebugger/public/components/grok_debugger/grok_debugger.js index f40948730872b..7eb4e9412edaa 100644 --- a/x-pack/plugins/grokdebugger/public/components/grok_debugger/grok_debugger.js +++ b/x-pack/plugins/grokdebugger/public/components/grok_debugger/grok_debugger.js @@ -11,7 +11,6 @@ import { i18n } from '@kbn/i18n'; // eslint-disable-next-line no-restricted-imports import isEmpty from 'lodash/isEmpty'; -import './brace_imports'; import { EuiForm, EuiButton, diff --git a/x-pack/plugins/grokdebugger/public/components/pattern_input/pattern_input.js b/x-pack/plugins/grokdebugger/public/components/pattern_input/pattern_input.js index 75af1453c6b40..096537bd8a6bf 100644 --- a/x-pack/plugins/grokdebugger/public/components/pattern_input/pattern_input.js +++ b/x-pack/plugins/grokdebugger/public/components/pattern_input/pattern_input.js @@ -6,12 +6,10 @@ */ import React from 'react'; +import { i18n } from '@kbn/i18n'; import { EuiFormRow } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; - -import { EDITOR } from '../../../common/constants'; -import { EuiCodeEditor } from '../../shared_imports'; -import { GrokMode } from '../../lib/ace'; +import { CodeEditor } from '@kbn/kibana-react-plugin/public'; export function PatternInput({ value, onChange }) { return ( @@ -20,20 +18,20 @@ export function PatternInput({ value, onChange }) { } fullWidth - data-test-subj="acePatternInput" + data-test-subj="patternInput" > - ); diff --git a/x-pack/plugins/grokdebugger/public/lib/ace/grok_highlight_rules.js b/x-pack/plugins/grokdebugger/public/lib/ace/grok_highlight_rules.js deleted file mode 100644 index 5987cd672ec05..0000000000000 --- a/x-pack/plugins/grokdebugger/public/lib/ace/grok_highlight_rules.js +++ /dev/null @@ -1,45 +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 ace from 'brace'; - -const { TextHighlightRules } = ace.acequire('ace/mode/text_highlight_rules'); - -export class GrokHighlightRules extends TextHighlightRules { - constructor() { - super(); - this.$rules = { - start: [ - { - token: ['grokStart', 'grokPatternName', 'grokSeparator', 'grokFieldName', 'grokEnd'], - regex: '(%{)([^:]+)(:)([^:]+)(})', - }, - { - token: [ - 'grokStart', - 'grokPatternName', - 'grokSeparator', - 'grokFieldName', - 'grokSeparator', - 'grokFieldType', - 'grokEnd', - ], - regex: '(%{)([^:]+)(:)([^:]+)(:)([^:]+)(})', - }, - { - token: (escapeToken /* regexToken */) => { - if (escapeToken) { - return ['grokEscape', 'grokEscaped']; - } - return 'grokRegex'; - }, - regex: '(\\\\)?([\\[\\]\\(\\)\\?\\:\\|])', - }, - ], - }; - } -} diff --git a/x-pack/plugins/grokdebugger/public/lib/ace/grok_mode.js b/x-pack/plugins/grokdebugger/public/lib/ace/grok_mode.js deleted file mode 100644 index a3f97de6dd934..0000000000000 --- a/x-pack/plugins/grokdebugger/public/lib/ace/grok_mode.js +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import ace from 'brace'; -import { GrokHighlightRules } from './grok_highlight_rules'; - -const TextMode = ace.acequire('ace/mode/text').Mode; - -export class GrokMode extends TextMode { - constructor() { - super(); - this.HighlightRules = GrokHighlightRules; - } -} diff --git a/x-pack/plugins/grokdebugger/public/lib/ace/index.js b/x-pack/plugins/grokdebugger/public/lib/ace/index.js deleted file mode 100644 index 3152baa94f0ec..0000000000000 --- a/x-pack/plugins/grokdebugger/public/lib/ace/index.js +++ /dev/null @@ -1,8 +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 { GrokMode } from './grok_mode'; diff --git a/x-pack/plugins/grokdebugger/public/shared_imports.ts b/x-pack/plugins/grokdebugger/public/shared_imports.ts index b2e82bce637f1..0bfbb3e05f933 100644 --- a/x-pack/plugins/grokdebugger/public/shared_imports.ts +++ b/x-pack/plugins/grokdebugger/public/shared_imports.ts @@ -5,6 +5,4 @@ * 2.0. */ -export { EuiCodeEditor } from '@kbn/es-ui-shared-plugin/public'; - export { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; diff --git a/x-pack/test/functional/page_objects/grok_debugger_page.ts b/x-pack/test/functional/page_objects/grok_debugger_page.ts index 06848c6b9ed3b..b7bad6ed57b9d 100644 --- a/x-pack/test/functional/page_objects/grok_debugger_page.ts +++ b/x-pack/test/functional/page_objects/grok_debugger_page.ts @@ -9,7 +9,7 @@ import { FtrService } from '../ftr_provider_context'; export class GrokDebuggerPageObject extends FtrService { private readonly testSubjects = this.ctx.getService('testSubjects'); - private readonly aceEditor = this.ctx.getService('aceEditor'); + private readonly monacoEditor = this.ctx.getService('monacoEditor'); private readonly retry = this.ctx.getService('retry'); async simulateButton() { @@ -17,30 +17,19 @@ export class GrokDebuggerPageObject extends FtrService { } async getEventOutput() { - return await this.aceEditor.getValue( - 'grokDebuggerContainer > aceEventOutput > codeEditorContainer' - ); + return await this.testSubjects.getVisibleText('eventOutputCodeBlock'); } async setEventInput(value: string) { - await this.aceEditor.setValue( - 'grokDebuggerContainer > aceEventInput > codeEditorContainer', - value - ); + await this.monacoEditor.setCodeEditorValue(value, 0); } async setPatternInput(pattern: string) { - await this.aceEditor.setValue( - 'grokDebuggerContainer > acePatternInput > codeEditorContainer', - pattern - ); + await this.monacoEditor.setCodeEditorValue(pattern, 1); } async setCustomPatternInput(customPattern: string) { - await this.aceEditor.setValue( - 'grokDebuggerContainer > aceCustomPatternsInput > codeEditorContainer', - customPattern - ); + await this.monacoEditor.setCodeEditorValue(customPattern, 2); } async toggleSetCustomPattern() { diff --git a/x-pack/test/functional/services/grok_debugger.js b/x-pack/test/functional/services/grok_debugger.js index 42a80edd70c85..618353130c20e 100644 --- a/x-pack/test/functional/services/grok_debugger.js +++ b/x-pack/test/functional/services/grok_debugger.js @@ -8,18 +8,14 @@ import expect from '@kbn/expect'; export function GrokDebuggerProvider({ getService }) { - const aceEditor = getService('aceEditor'); const testSubjects = getService('testSubjects'); const retry = getService('retry'); + const monacoEditor = getService('monacoEditor'); // test subject selectors const SUBJ_CONTAINER = 'grokDebuggerContainer'; - const SUBJ_UI_ACE_EVENT_INPUT = `${SUBJ_CONTAINER} > aceEventInput > codeEditorContainer`; const SUBJ_UI_ACE_PATTERN_INPUT = `${SUBJ_CONTAINER} > acePatternInput > codeEditorContainer`; - const SUBJ_UI_ACE_CUSTOM_PATTERNS_INPUT = `${SUBJ_CONTAINER} > aceCustomPatternsInput > codeEditorContainer`; - const SUBJ_UI_ACE_EVENT_OUTPUT = `${SUBJ_CONTAINER} > aceEventOutput > codeEditorContainer`; - const SUBJ_BTN_TOGGLE_CUSTOM_PATTERNS_INPUT = `${SUBJ_CONTAINER} > btnToggleCustomPatternsInput`; const SUBJ_BTN_SIMULATE = `${SUBJ_CONTAINER} > btnSimulate`; @@ -29,11 +25,11 @@ export function GrokDebuggerProvider({ getService }) { } async setEventInput(value) { - await aceEditor.setValue(SUBJ_UI_ACE_EVENT_INPUT, value); + await monacoEditor.setCodeEditorValue(value, 0); } async setPatternInput(value) { - await aceEditor.setValue(SUBJ_UI_ACE_PATTERN_INPUT, value); + await monacoEditor.setCodeEditorValue(value, 1); } async toggleCustomPatternsInput() { @@ -41,11 +37,11 @@ export function GrokDebuggerProvider({ getService }) { } async setCustomPatternsInput(value) { - await aceEditor.setValue(SUBJ_UI_ACE_CUSTOM_PATTERNS_INPUT, value); + await monacoEditor.setCodeEditorValue(value, 2); } async getEventOutput() { - return await aceEditor.getValue(SUBJ_UI_ACE_EVENT_OUTPUT); + return await testSubjects.getVisibleText('eventOutputCodeBlock'); } async assertExists() { From 8de71325a3016bdf138954ebdefe4f99b83f9990 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Tue, 25 Apr 2023 08:40:35 -0400 Subject: [PATCH 45/52] [Fleet] Add more tests for preconfigured outputs (#155656) --- .../output_preconfiguration.test.ts | 144 ++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 x-pack/plugins/fleet/server/integration_tests/output_preconfiguration.test.ts diff --git a/x-pack/plugins/fleet/server/integration_tests/output_preconfiguration.test.ts b/x-pack/plugins/fleet/server/integration_tests/output_preconfiguration.test.ts new file mode 100644 index 0000000000000..81a14f6ddec40 --- /dev/null +++ b/x-pack/plugins/fleet/server/integration_tests/output_preconfiguration.test.ts @@ -0,0 +1,144 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 Path from 'path'; + +import { + type TestElasticsearchUtils, + type TestKibanaUtils, + createRootWithCorePlugins, + createTestServers, +} from '@kbn/core-test-helpers-kbn-server'; + +import type { OutputSOAttributes } from '../types'; + +import { useDockerRegistry, waitForFleetSetup } from './helpers'; + +const logFilePath = Path.join(__dirname, 'logs.log'); + +describe('Fleet preconfigured outputs', () => { + let esServer: TestElasticsearchUtils; + let kbnServer: TestKibanaUtils; + + const registryUrl = useDockerRegistry(); + + const startServers = async (outputs: any) => { + const { startES } = createTestServers({ + adjustTimeout: (t) => jest.setTimeout(t), + settings: { + es: { + license: 'trial', + }, + kbn: {}, + }, + }); + + esServer = await startES(); + if (kbnServer) { + await kbnServer.stop(); + } + + const root = createRootWithCorePlugins( + { + xpack: { + fleet: { + outputs, + registryUrl, + }, + }, + logging: { + appenders: { + file: { + type: 'file', + fileName: logFilePath, + layout: { + type: 'json', + }, + }, + }, + loggers: [ + { + name: 'root', + appenders: ['file'], + }, + { + name: 'plugins.fleet', + level: 'all', + }, + ], + }, + }, + { oss: false } + ); + + await root.preboot(); + const coreSetup = await root.setup(); + const coreStart = await root.start(); + + kbnServer = { + root, + coreSetup, + coreStart, + stop: async () => await root.shutdown(), + }; + await waitForFleetSetup(kbnServer.root); + }; + + const stopServers = async () => { + if (kbnServer) { + await kbnServer.stop(); + } + + if (esServer) { + await esServer.stop(); + } + + await new Promise((res) => setTimeout(res, 10000)); + }; + + describe('Preconfigured outputs', () => { + describe('With a preconfigured monitoring output', () => { + beforeAll(async () => { + await startServers([ + { + name: 'Test output', + is_default_monitoring: true, + type: 'elasticsearch', + id: 'output-default-monitoring', + hosts: ['http://elasticsearch-alternative-url:9200'], + }, + ]); + }); + + afterAll(async () => { + await stopServers(); + }); + + it('Should create a default output and the default preconfigured output', async () => { + const outputs = await kbnServer.coreStart.savedObjects + .createInternalRepository() + .find({ + type: 'ingest-outputs', + perPage: 10000, + }); + + expect(outputs.total).toBe(2); + expect(outputs.saved_objects.filter((so) => so.attributes.is_default)).toHaveLength(1); + expect( + outputs.saved_objects.filter((so) => so.attributes.is_default_monitoring) + ).toHaveLength(1); + + const defaultDataOutput = outputs.saved_objects.find((so) => so.attributes.is_default); + const defaultMonitoringOutput = outputs.saved_objects.find( + (so) => so.attributes.is_default_monitoring + ); + expect(defaultDataOutput!.id).not.toBe(defaultMonitoringOutput!.id); + expect(defaultDataOutput!.attributes.is_default_monitoring).toBeFalsy(); + }); + }); + }); +}); From ab039e63f386a77bc9ab06da8820838496350788 Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Tue, 25 Apr 2023 13:56:19 +0100 Subject: [PATCH 46/52] [ML] Display info when no datafeed preview results are found (#155650) When an empty list is returned from the datafeed preview endpoint, it now displays a callout informing the user that there were no results. Closes https://github.com/elastic/kibana/issues/153306 ![image](https://user-images.githubusercontent.com/22172091/234065738-2c4f3d52-978b-4dec-9129-d755397eaa6f.png) Also rewrites the datafeed preview component in typescript. --- .../job_details/datafeed_preview_tab.js | 106 ------------------ .../job_details/datafeed_preview_tab.tsx | 102 +++++++++++++++++ .../services/ml_api_service/jobs.ts | 8 +- 3 files changed, 104 insertions(+), 112 deletions(-) delete mode 100644 x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/datafeed_preview_tab.js create mode 100644 x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/datafeed_preview_tab.tsx diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/datafeed_preview_tab.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/datafeed_preview_tab.js deleted file mode 100644 index f63511d857277..0000000000000 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/datafeed_preview_tab.js +++ /dev/null @@ -1,106 +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 PropTypes from 'prop-types'; -import React, { Component } from 'react'; - -import { EuiSpacer, EuiCallOut, EuiLoadingSpinner } from '@elastic/eui'; - -import { ml } from '../../../../services/ml_api_service'; -import { checkPermission } from '../../../../capabilities/check_capabilities'; -import { ML_DATA_PREVIEW_COUNT } from '../../../../../../common/util/job_utils'; -import { MLJobEditor } from '../ml_job_editor'; -import { FormattedMessage } from '@kbn/i18n-react'; - -export class DatafeedPreviewPane extends Component { - constructor(props) { - super(props); - - this.state = { - previewJson: '', - loading: true, - canPreviewDatafeed: true, - }; - } - - renderContent() { - const { previewJson, loading, canPreviewDatafeed } = this.state; - - if (canPreviewDatafeed === false) { - return ( - - } - color="warning" - iconType="warning" - > -

- -

-
- ); - } else if (loading === true) { - return ; - } else { - return ; - } - } - - componentDidMount() { - const canPreviewDatafeed = - checkPermission('canPreviewDatafeed') && this.props.job.datafeed_config !== undefined; - this.setState({ canPreviewDatafeed }); - - updateDatafeedPreview(this.props.job, canPreviewDatafeed) - .then((previewJson) => { - this.setState({ previewJson, loading: false }); - }) - .catch((error) => { - console.log('Datafeed preview could not be loaded', error); - this.setState({ loading: false }); - }); - } - - render() { - return ( - - - {this.renderContent()} - - ); - } -} -DatafeedPreviewPane.propTypes = { - job: PropTypes.object.isRequired, -}; - -function updateDatafeedPreview(job, canPreviewDatafeed) { - return new Promise((resolve, reject) => { - if (canPreviewDatafeed) { - ml.jobs - .datafeedPreview(job.datafeed_config.datafeed_id) - .then((resp) => { - if (Array.isArray(resp)) { - resolve(JSON.stringify(resp.slice(0, ML_DATA_PREVIEW_COUNT), null, 2)); - } else { - resolve(''); - console.log('Datafeed preview could not be loaded', resp); - } - }) - .catch((error) => { - reject(error); - }); - } - }); -} diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/datafeed_preview_tab.tsx b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/datafeed_preview_tab.tsx new file mode 100644 index 0000000000000..1ad46ce7bfce5 --- /dev/null +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/datafeed_preview_tab.tsx @@ -0,0 +1,102 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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, { FC, useEffect, useState } from 'react'; +import { EuiCallOut, EuiLoadingSpinner } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { ML_DATA_PREVIEW_COUNT } from '../../../../../../common/util/job_utils'; +import { useMlApiContext } from '../../../../contexts/kibana'; +import { usePermissionCheck } from '../../../../capabilities/check_capabilities'; +import { CombinedJob } from '../../../../../shared'; +import { MLJobEditor } from '../ml_job_editor'; + +interface Props { + job: CombinedJob; +} + +export const DatafeedPreviewPane: FC = ({ job }) => { + const { + jobs: { datafeedPreview }, + } = useMlApiContext(); + + const canPreviewDatafeed = usePermissionCheck('canPreviewDatafeed'); + const [loading, setLoading] = useState(false); + const [previewJson, setPreviewJson] = useState(''); + + useEffect(() => { + setLoading(true); + datafeedPreview(job.datafeed_config.datafeed_id).then((resp) => { + if (Array.isArray(resp)) { + if (resp.length === 0) { + setPreviewJson(null); + } else { + setPreviewJson(JSON.stringify(resp.slice(0, ML_DATA_PREVIEW_COUNT), null, 2)); + } + } else { + setPreviewJson(''); + } + + setLoading(false); + }); + }, [datafeedPreview, job]); + + if (canPreviewDatafeed === false) { + return ; + } + + return loading ? ( + + ) : ( + <> + {previewJson === null ? ( + + ) : ( + + )} + + ); +}; + +const InsufficientPermissions: FC = () => ( + + } + color="warning" + iconType="warning" + > +

+ +

+
+); + +const EmptyResults: FC = () => ( + + } + color="warning" + iconType="warning" + > +

+ +

+
+); diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/jobs.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/jobs.ts index 0e209c01ddb6a..1ab3a659442b1 100644 --- a/x-pack/plugins/ml/public/application/services/ml_api_service/jobs.ts +++ b/x-pack/plugins/ml/public/application/services/ml_api_service/jobs.ts @@ -368,8 +368,7 @@ export const jobsApiProvider = (httpService: HttpService) => ({ ) { const body = JSON.stringify({ jobId, snapshotId, replay, end, calendarEvents }); return httpService.http<{ - total: number; - categories: Array<{ count?: number; category: Category }>; + success: boolean; }>({ path: `${ML_BASE_PATH}/jobs/revert_model_snapshot`, method: 'POST', @@ -379,10 +378,7 @@ export const jobsApiProvider = (httpService: HttpService) => ({ datafeedPreview(datafeedId?: string, job?: Job, datafeed?: Datafeed) { const body = JSON.stringify({ datafeedId, job, datafeed }); - return httpService.http<{ - total: number; - categories: Array<{ count?: number; category: Category }>; - }>({ + return httpService.http({ path: `${ML_BASE_PATH}/jobs/datafeed_preview`, method: 'POST', body, From e495581618571569cc6d20e91f5952e6b1894edd Mon Sep 17 00:00:00 2001 From: Shahzad Date: Tue, 25 Apr 2023 15:13:54 +0200 Subject: [PATCH 47/52] [Synthetics] Fixes exp view no data state (#155591) --- .../observability_data_views.test.ts | 1 + .../observability_data_views/observability_data_views.ts | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/x-pack/plugins/exploratory_view/public/utils/observability_data_views/observability_data_views.test.ts b/x-pack/plugins/exploratory_view/public/utils/observability_data_views/observability_data_views.test.ts index 4b9b904b73fc4..4bd40762a98fb 100644 --- a/x-pack/plugins/exploratory_view/public/utils/observability_data_views/observability_data_views.test.ts +++ b/x-pack/plugins/exploratory_view/public/utils/observability_data_views/observability_data_views.test.ts @@ -101,6 +101,7 @@ describe('ObservabilityDataViews', function () { expect(indexP).toEqual({ id: dataViewList.ux }); expect(dataViews?.createAndSave).toHaveBeenCalledWith({ + allowNoIndex: true, fieldFormats, id: 'rum_static_index_pattern_id_trace_apm_', timeFieldName: '@timestamp', diff --git a/x-pack/plugins/exploratory_view/public/utils/observability_data_views/observability_data_views.ts b/x-pack/plugins/exploratory_view/public/utils/observability_data_views/observability_data_views.ts index c22a2ae6d1712..ae3a60e7fe65b 100644 --- a/x-pack/plugins/exploratory_view/public/utils/observability_data_views/observability_data_views.ts +++ b/x-pack/plugins/exploratory_view/public/utils/observability_data_views/observability_data_views.ts @@ -140,11 +140,16 @@ export class ObservabilityDataViews { timeFieldName: '@timestamp', fieldFormats: this.getFieldFormats(app), name: DataTypesLabels[app], + allowNoIndex: true, }, false, false ); + if (dataView.matchedIndices.length === 0) { + throw new DataViewMissingIndices('No indices match pattern'); + } + if (runtimeFields !== null) { runtimeFields.forEach(({ name, field }) => { dataView.addRuntimeField(name, field); @@ -170,6 +175,7 @@ export class ObservabilityDataViews { timeFieldName: '@timestamp', fieldFormats: this.getFieldFormats(app), name: DataTypesLabels[app], + allowNoIndex: true, }); } // we want to make sure field formats remain same From 9dec953894ec928ccbd653329759da11ba46d84a Mon Sep 17 00:00:00 2001 From: Navarone Feekery <13634519+navarone-feekery@users.noreply.github.com> Date: Tue, 25 Apr 2023 15:14:24 +0200 Subject: [PATCH 48/52] [Enterprise Search] Update native connector config fields (#155606) ## Summary For native connectors: - Update configurable field labels - Add missing fields and field properties --- .../common/connectors/native_connectors.ts | 80 +++++++++++++++++-- 1 file changed, 74 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/enterprise_search/common/connectors/native_connectors.ts b/x-pack/plugins/enterprise_search/common/connectors/native_connectors.ts index 78a720304d996..bccfe15835a63 100644 --- a/x-pack/plugins/enterprise_search/common/connectors/native_connectors.ts +++ b/x-pack/plugins/enterprise_search/common/connectors/native_connectors.ts @@ -19,7 +19,7 @@ export const NATIVE_CONNECTOR_DEFINITIONS: Record Date: Tue, 25 Apr 2023 09:16:09 -0400 Subject: [PATCH 49/52] [Security Solution][Endpoint] Cypress test to validate that Endpoint can stream alerts to ES/Kibana (#155455) ## Summary - Adds cypress test that stands up a real endpoint and validates that it can trigger alerts and send those to ES/Kbn and that they show up on the Alerts list --- .../endpoint_agent_status.test.tsx | 2 +- .../endpoint_agent_status.tsx | 8 +- .../public/management/cypress/cypress.d.ts | 21 ++ .../e2e/endpoint/endpoint_alerts.cy.ts | 107 +++++++++ .../management/cypress/screens/alerts.ts | 41 ++++ .../management/cypress/screens/endpoints.ts | 6 +- .../cypress/support/data_loaders.ts | 53 +++++ .../public/management/cypress/support/e2e.ts | 38 ++++ .../public/management/cypress/tasks/alerts.ts | 170 +++++++++++++++ .../cypress/tasks/delete_all_endpoint_data.ts | 14 ++ .../cypress/tasks/endpoint_policy.ts | 63 ++++++ .../cypress/tasks/response_actions.ts | 41 ++++ .../management/cypress_endpoint.config.ts | 6 +- .../common/delete_all_endpoint_data.ts | 77 +++++++ .../endpoint/common/endpoint_host_services.ts | 205 ++++++++++++++++++ .../common/endpoint_metadata_services.ts | 43 ++++ .../scripts/endpoint/common/fleet_services.ts | 142 +++++++++++- .../endpoint/common/security_user_services.ts | 31 ++- .../scripts/endpoint/common/stack_services.ts | 6 +- .../endpoint_config.ts | 4 + 20 files changed, 1067 insertions(+), 11 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/endpoint_alerts.cy.ts create mode 100644 x-pack/plugins/security_solution/public/management/cypress/screens/alerts.ts create mode 100644 x-pack/plugins/security_solution/public/management/cypress/tasks/alerts.ts create mode 100644 x-pack/plugins/security_solution/public/management/cypress/tasks/delete_all_endpoint_data.ts create mode 100644 x-pack/plugins/security_solution/public/management/cypress/tasks/endpoint_policy.ts create mode 100644 x-pack/plugins/security_solution/scripts/endpoint/common/delete_all_endpoint_data.ts create mode 100644 x-pack/plugins/security_solution/scripts/endpoint/common/endpoint_host_services.ts diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/endpoint_agent_status/endpoint_agent_status.test.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/endpoint_agent_status/endpoint_agent_status.test.tsx index bf6f85712b6c7..7fa169b32d348 100644 --- a/x-pack/plugins/security_solution/public/common/components/endpoint/endpoint_agent_status/endpoint_agent_status.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/endpoint_agent_status/endpoint_agent_status.test.tsx @@ -344,7 +344,7 @@ describe('When showing Endpoint Agent Status', () => { }); it('should keep agent status up to date when autoRefresh is true', async () => { - renderProps.autoFresh = true; + renderProps.autoRefresh = true; apiMocks.responseProvider.metadataDetails.mockReturnValueOnce(endpointDetails); const { getByTestId } = render(); diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/endpoint_agent_status/endpoint_agent_status.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/endpoint_agent_status/endpoint_agent_status.tsx index e74cdcf41fd57..1b2d021634a2f 100644 --- a/x-pack/plugins/security_solution/public/common/components/endpoint/endpoint_agent_status/endpoint_agent_status.tsx +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/endpoint_agent_status/endpoint_agent_status.tsx @@ -138,7 +138,7 @@ export interface EndpointAgentStatusByIdProps { * If set to `true` (Default), then the endpoint status and isolation/action counts will * be kept up to date by querying the API periodically */ - autoFresh?: boolean; + autoRefresh?: boolean; 'data-test-subj'?: string; } @@ -150,9 +150,9 @@ export interface EndpointAgentStatusByIdProps { * instead in order to avoid duplicate API calls. */ export const EndpointAgentStatusById = memo( - ({ endpointAgentId, autoFresh, 'data-test-subj': dataTestSubj }) => { + ({ endpointAgentId, autoRefresh, 'data-test-subj': dataTestSubj }) => { const { data } = useGetEndpointDetails(endpointAgentId, { - refetchInterval: autoFresh ? DEFAULT_POLL_INTERVAL : false, + refetchInterval: autoRefresh ? DEFAULT_POLL_INTERVAL : false, }); const emptyValue = ( @@ -169,7 +169,7 @@ export const EndpointAgentStatusById = memo( ); } diff --git a/x-pack/plugins/security_solution/public/management/cypress/cypress.d.ts b/x-pack/plugins/security_solution/public/management/cypress/cypress.d.ts index c461f712b75b5..51cd74de62f67 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/cypress.d.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/cypress.d.ts @@ -10,6 +10,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import type { CasePostRequest } from '@kbn/cases-plugin/common/api'; +import type { DeleteAllEndpointDataResponse } from '../../../scripts/endpoint/common/delete_all_endpoint_data'; import type { IndexedEndpointPolicyResponse } from '../../../common/endpoint/data_loaders/index_endpoint_policy_response'; import type { HostPolicyResponse, @@ -56,6 +57,20 @@ declare global { ...args: Parameters['find']> ): Chainable>; + /** + * Continuously call provided callback function until it either return `true` + * or fail if `timeout` is reached. + * @param fn + * @param options + */ + waitUntil( + fn: (subject?: any) => boolean | Promise | Chainable, + options?: Partial<{ + interval: number; + timeout: number; + }> + ): Chainable; + task( name: 'indexFleetEndpointPolicy', arg: { @@ -124,6 +139,12 @@ declare global { arg: HostActionResponse, options?: Partial ): Chainable; + + task( + name: 'deleteAllEndpointData', + arg: { endpointAgentIds: string[] }, + options?: Partial + ): Chainable; } } } diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/endpoint_alerts.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/endpoint_alerts.cy.ts new file mode 100644 index 0000000000000..8163e74db17b1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/endpoint_alerts.cy.ts @@ -0,0 +1,107 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { deleteAllLoadedEndpointData } from '../../tasks/delete_all_endpoint_data'; +import { getAlertsTableRows, navigateToAlertsList } from '../../screens/alerts'; +import { waitForEndpointAlerts } from '../../tasks/alerts'; +import { request } from '../../tasks/common'; +import { getEndpointIntegrationVersion } from '../../tasks/fleet'; +import type { IndexedFleetEndpointPolicyResponse } from '../../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy'; +import { enableAllPolicyProtections } from '../../tasks/endpoint_policy'; +import type { PolicyData, ResponseActionApiResponse } from '../../../../../common/endpoint/types'; +import type { CreateAndEnrollEndpointHostResponse } from '../../../../../scripts/endpoint/common/endpoint_host_services'; +import { login } from '../../tasks/login'; +import { EXECUTE_ROUTE } from '../../../../../common/endpoint/constants'; +import { waitForActionToComplete } from '../../tasks/response_actions'; + +describe('Endpoint generated alerts', () => { + let indexedPolicy: IndexedFleetEndpointPolicyResponse; + let policy: PolicyData; + let createdHost: CreateAndEnrollEndpointHostResponse; + + before(() => { + getEndpointIntegrationVersion().then((version) => { + const policyName = `alerts test ${Math.random().toString(36).substring(2, 7)}`; + + cy.task('indexFleetEndpointPolicy', { + policyName, + endpointPackageVersion: version, + agentPolicyName: policyName, + }).then((data) => { + indexedPolicy = data; + policy = indexedPolicy.integrationPolicies[0]; + + return enableAllPolicyProtections(policy.id).then(() => { + // Create and enroll a new Endpoint host + return cy + .task( + 'createEndpointHost', + { + agentPolicyId: policy.policy_id, + }, + { timeout: 180000 } + ) + .then((host) => { + createdHost = host as CreateAndEnrollEndpointHostResponse; + }); + }); + }); + }); + }); + + after(() => { + if (createdHost) { + cy.task('destroyEndpointHost', createdHost).then(() => {}); + } + + if (indexedPolicy) { + cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy); + } + + if (createdHost) { + deleteAllLoadedEndpointData({ endpointAgentIds: [createdHost.agentId] }); + } + }); + + beforeEach(() => { + login(); + }); + + it('should create a Detection Engine alert from an endpoint alert', () => { + // Triggers a Malicious Behaviour alert on Linux system (`grep *` was added only to identify this specific alert) + const executeMaliciousCommand = `bash -c cat /dev/tcp/foo | grep ${Math.random() + .toString(16) + .substring(2)}`; + + // Send `execute` command that triggers malicious behaviour using the `execute` response action + request({ + method: 'POST', + url: EXECUTE_ROUTE, + body: { + endpoint_ids: [createdHost.agentId], + parameters: { + command: executeMaliciousCommand, + }, + }, + }) + .then((response) => waitForActionToComplete(response.body.data.id)) + .then(() => { + return waitForEndpointAlerts(createdHost.agentId, [ + { + term: { 'process.group_leader.args': executeMaliciousCommand }, + }, + ]); + }) + .then(() => { + return navigateToAlertsList( + `query=(language:kuery,query:'agent.id: "${createdHost.agentId}" ')` + ); + }); + + getAlertsTableRows().should('have.length.greaterThan', 0); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/cypress/screens/alerts.ts b/x-pack/plugins/security_solution/public/management/cypress/screens/alerts.ts new file mode 100644 index 0000000000000..48f0747464bf8 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/cypress/screens/alerts.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { APP_ALERTS_PATH } from '../../../../common/constants'; + +export const navigateToAlertsList = (urlQueryParams: string = '') => { + cy.visit(`${APP_ALERTS_PATH}${urlQueryParams ? `?${urlQueryParams}` : ''}`); +}; + +export const clickAlertListRefreshButton = (): Cypress.Chainable => { + return cy.getByTestSubj('querySubmitButton').click().should('be.enabled'); +}; + +/** + * Waits until the Alerts list has alerts data and return the number of rows that are currently displayed + * @param timeout + */ +export const getAlertsTableRows = (timeout?: number): Cypress.Chainable> => { + let $rows: JQuery = Cypress.$(); + + return cy + .waitUntil( + () => { + clickAlertListRefreshButton(); + + return cy + .getByTestSubj('alertsTable') + .find('.euiDataGridRow') + .then(($rowsFound) => { + $rows = $rowsFound; + return Boolean($rows); + }); + }, + { timeout } + ) + .then(() => $rows); +}; diff --git a/x-pack/plugins/security_solution/public/management/cypress/screens/endpoints.ts b/x-pack/plugins/security_solution/public/management/cypress/screens/endpoints.ts index 32a12168aadb0..da2c278b8e30d 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/screens/endpoints.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/screens/endpoints.ts @@ -6,7 +6,7 @@ */ import { APP_PATH } from '../../../../common/constants'; -import { getEndpointDetailsPath } from '../../common/routing'; +import { getEndpointDetailsPath, getEndpointListPath } from '../../common/routing'; export const AGENT_HOSTNAME_CELL = 'hostnameCellLink'; export const AGENT_POLICY_CELL = 'policyNameCellLink'; @@ -21,3 +21,7 @@ export const navigateToEndpointPolicyResponse = ( getEndpointDetailsPath({ name: 'endpointPolicyResponse', selected_endpoint: endpointAgentId }) ); }; + +export const navigateToEndpointList = (): Cypress.Chainable => { + return cy.visit(APP_PATH + getEndpointListPath({ name: 'endpointList' })); +}; diff --git a/x-pack/plugins/security_solution/public/management/cypress/support/data_loaders.ts b/x-pack/plugins/security_solution/public/management/cypress/support/data_loaders.ts index 6dd4bedaa8937..ffcca01a6f1e9 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/support/data_loaders.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/support/data_loaders.ts @@ -9,6 +9,13 @@ import type { CasePostRequest } from '@kbn/cases-plugin/common/api'; import { sendEndpointActionResponse } from '../../../../scripts/endpoint/agent_emulator/services/endpoint_response_actions'; +import type { DeleteAllEndpointDataResponse } from '../../../../scripts/endpoint/common/delete_all_endpoint_data'; +import { deleteAllEndpointData } from '../../../../scripts/endpoint/common/delete_all_endpoint_data'; +import { waitForEndpointToStreamData } from '../../../../scripts/endpoint/common/endpoint_metadata_services'; +import type { + CreateAndEnrollEndpointHostOptions, + CreateAndEnrollEndpointHostResponse, +} from '../../../../scripts/endpoint/common/endpoint_host_services'; import type { IndexedEndpointPolicyResponse } from '../../../../common/endpoint/data_loaders/index_endpoint_policy_response'; import { deleteIndexedEndpointPolicyResponse, @@ -39,6 +46,10 @@ import { deleteIndexedEndpointRuleAlerts, indexEndpointRuleAlerts, } from '../../../../common/endpoint/data_loaders/index_endpoint_rule_alerts'; +import { + createAndEnrollEndpointHost, + destroyEndpointHost, +} from '../../../../scripts/endpoint/common/endpoint_host_services'; /** * Cypress plugin for adding data loading related `task`s @@ -155,5 +166,47 @@ export const dataLoaders = ( const { esClient } = await stackServicesPromise; return sendEndpointActionResponse(esClient, data.action, { state: data.state.state }); }, + + deleteAllEndpointData: async ({ + endpointAgentIds, + }: { + endpointAgentIds: string[]; + }): Promise => { + const { esClient } = await stackServicesPromise; + return deleteAllEndpointData(esClient, endpointAgentIds); + }, + }); +}; + +export const dataLoadersForRealEndpoints = ( + on: Cypress.PluginEvents, + config: Cypress.PluginConfigOptions +): void => { + const stackServicesPromise = createRuntimeServices({ + kibanaUrl: config.env.KIBANA_URL, + elasticsearchUrl: config.env.ELASTICSEARCH_URL, + username: config.env.ELASTICSEARCH_USERNAME, + password: config.env.ELASTICSEARCH_PASSWORD, + asSuperuser: true, + }); + + on('task', { + createEndpointHost: async ( + options: Omit + ): Promise => { + const { kbnClient, log } = await stackServicesPromise; + return createAndEnrollEndpointHost({ ...options, log, kbnClient }).then((newHost) => { + return waitForEndpointToStreamData(kbnClient, newHost.agentId, 120000).then(() => { + return newHost; + }); + }); + }, + + destroyEndpointHost: async ( + createdHost: CreateAndEnrollEndpointHostResponse + ): Promise => { + const { kbnClient } = await stackServicesPromise; + return destroyEndpointHost(kbnClient, createdHost).then(() => null); + }, }); }; diff --git a/x-pack/plugins/security_solution/public/management/cypress/support/e2e.ts b/x-pack/plugins/security_solution/public/management/cypress/support/e2e.ts index 0ccb00e8d5e63..12c236f481791 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/support/e2e.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/support/e2e.ts @@ -50,4 +50,42 @@ Cypress.Commands.addQuery<'findByTestSubj'>( } ); +Cypress.Commands.add( + 'waitUntil', + { prevSubject: 'optional' }, + (subject, fn, { interval = 500, timeout = 30000 } = {}) => { + let attempts = Math.floor(timeout / interval); + + const completeOrRetry = (result: boolean) => { + if (result) { + return result; + } + if (attempts < 1) { + throw new Error(`Timed out while retrying, last result was: {${result}}`); + } + cy.wait(interval, { log: false }).then(() => { + attempts--; + return evaluate(); + }); + }; + + const evaluate = () => { + const result = fn(subject); + + if (typeof result === 'boolean') { + return completeOrRetry(result); + } else if ('then' in result) { + // @ts-expect-error + return result.then(completeOrRetry); + } else { + throw new Error( + `Unknown return type from callback: ${Object.prototype.toString.call(result)}` + ); + } + }; + + return evaluate(); + } +); + Cypress.on('uncaught:exception', () => false); diff --git a/x-pack/plugins/security_solution/public/management/cypress/tasks/alerts.ts b/x-pack/plugins/security_solution/public/management/cypress/tasks/alerts.ts new file mode 100644 index 0000000000000..a78b1c6742afa --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/cypress/tasks/alerts.ts @@ -0,0 +1,170 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { estypes } from '@elastic/elasticsearch'; +import type { Rule } from '../../../detection_engine/rule_management/logic'; +import { + DETECTION_ENGINE_QUERY_SIGNALS_URL, + DETECTION_ENGINE_RULES_BULK_ACTION, + DETECTION_ENGINE_RULES_URL, +} from '../../../../common/constants'; +import { ELASTIC_SECURITY_RULE_ID } from '../../../../common'; +import { request } from './common'; +import { ENDPOINT_ALERTS_INDEX } from '../../../../scripts/endpoint/common/constants'; +const ES_URL = Cypress.env('ELASTICSEARCH_URL'); + +/** + * Continuously check for any alert to have been received by the given endpoint. + * + * NOTE: This is tno the same as the alerts that populate the Alerts list. To check for + * those types of alerts, use `waitForDetectionAlerts()` + */ +export const waitForEndpointAlerts = ( + endpointAgentId: string, + additionalFilters?: object[], + timeout = 120000 +): Cypress.Chainable => { + return cy + .waitUntil( + () => { + return request({ + method: 'GET', + url: `${ES_URL}/${ENDPOINT_ALERTS_INDEX}/_search`, + body: { + query: { + match: { + 'agent.id': endpointAgentId, + }, + }, + size: 1, + _source: false, + }, + }).then(({ body: streamedAlerts }) => { + return (streamedAlerts.hits.total as estypes.SearchTotalHits).value > 0; + }); + }, + { timeout } + ) + .then(() => { + // Stop/start Endpoint rule so that it can pickup and create Detection alerts + cy.log( + `Received endpoint alerts for agent [${endpointAgentId}] in index [${ENDPOINT_ALERTS_INDEX}]` + ); + + return stopStartEndpointDetectionsRule(); + }) + .then(() => { + // wait until the Detection alert shows up in the API + return waitForDetectionAlerts(getEndpointDetectionAlertsQueryForAgentId(endpointAgentId)); + }); +}; + +export const fetchEndpointSecurityDetectionRule = (): Cypress.Chainable => { + return request({ + method: 'GET', + url: DETECTION_ENGINE_RULES_URL, + qs: { + rule_id: ELASTIC_SECURITY_RULE_ID, + }, + }).then(({ body }) => { + return body; + }); +}; + +export const stopStartEndpointDetectionsRule = (): Cypress.Chainable => { + return fetchEndpointSecurityDetectionRule() + .then((endpointRule) => { + // Disabled it + return request({ + method: 'POST', + url: DETECTION_ENGINE_RULES_BULK_ACTION, + body: { + action: 'disable', + ids: [endpointRule.id], + }, + }).then(() => { + return endpointRule; + }); + }) + .then((endpointRule) => { + cy.log(`Endpoint rule id [${endpointRule.id}] has been disabled`); + + // Re-enable it + return request({ + method: 'POST', + url: DETECTION_ENGINE_RULES_BULK_ACTION, + body: { + action: 'enable', + ids: [endpointRule.id], + }, + }).then(() => endpointRule); + }) + .then((endpointRule) => { + cy.log(`Endpoint rule id [${endpointRule.id}] has been re-enabled`); + return cy.wrap(endpointRule); + }); +}; + +/** + * Waits for alerts to have been loaded by continuously calling the detections engine alerts + * api until data shows up + * @param query + * @param timeout + */ +export const waitForDetectionAlerts = ( + /** The ES query. Defaults to `{ match_all: {} }` */ + query: object = { match_all: {} }, + timeout?: number +): Cypress.Chainable => { + return cy.waitUntil( + () => { + return request({ + method: 'POST', + url: DETECTION_ENGINE_QUERY_SIGNALS_URL, + body: { + query, + size: 1, + }, + }).then(({ body: alertsResponse }) => { + return Boolean((alertsResponse.hits.total as estypes.SearchTotalHits)?.value ?? 0); + }); + }, + { timeout } + ); +}; + +/** + * Builds and returns the ES `query` object for use in querying for Endpoint Detection Engine + * alerts. Can be used in ES searches or with the Detection Engine query signals (alerts) url. + * @param endpointAgentId + */ +export const getEndpointDetectionAlertsQueryForAgentId = (endpointAgentId: string) => { + return { + bool: { + filter: [ + { + bool: { + should: [{ match_phrase: { 'agent.type': 'endpoint' } }], + minimum_should_match: 1, + }, + }, + { + bool: { + should: [{ match_phrase: { 'agent.id': endpointAgentId } }], + minimum_should_match: 1, + }, + }, + { + bool: { + should: [{ exists: { field: 'kibana.alert.rule.uuid' } }], + minimum_should_match: 1, + }, + }, + ], + }, + }; +}; diff --git a/x-pack/plugins/security_solution/public/management/cypress/tasks/delete_all_endpoint_data.ts b/x-pack/plugins/security_solution/public/management/cypress/tasks/delete_all_endpoint_data.ts new file mode 100644 index 0000000000000..761cde513ad52 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/cypress/tasks/delete_all_endpoint_data.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 type { DeleteAllEndpointDataResponse } from '../../../../scripts/endpoint/common/delete_all_endpoint_data'; + +export const deleteAllLoadedEndpointData = (options: { + endpointAgentIds: string[]; +}): Cypress.Chainable => { + return cy.task('deleteAllEndpointData', options); +}; diff --git a/x-pack/plugins/security_solution/public/management/cypress/tasks/endpoint_policy.ts b/x-pack/plugins/security_solution/public/management/cypress/tasks/endpoint_policy.ts new file mode 100644 index 0000000000000..134fc470b412b --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/cypress/tasks/endpoint_policy.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 { + GetOnePackagePolicyResponse, + UpdatePackagePolicy, + UpdatePackagePolicyResponse, +} from '@kbn/fleet-plugin/common'; +import { packagePolicyRouteService } from '@kbn/fleet-plugin/common'; +import { request } from './common'; +import { ProtectionModes } from '../../../../common/endpoint/types'; + +/** + * Updates the given Endpoint policy and enables all of the policy protections + * @param endpointPolicyId + */ +export const enableAllPolicyProtections = ( + endpointPolicyId: string +): Cypress.Chainable> => { + return request({ + method: 'GET', + url: packagePolicyRouteService.getInfoPath(endpointPolicyId), + }).then(({ body: { item: endpointPolicy } }) => { + const { + created_by: _createdBy, + created_at: _createdAt, + updated_at: _updatedAt, + updated_by: _updatedBy, + id, + version, + revision, + ...restOfPolicy + } = endpointPolicy; + + const updatedEndpointPolicy: UpdatePackagePolicy = restOfPolicy; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const policy = updatedEndpointPolicy!.inputs[0]!.config!.policy.value; + + policy.mac.malware.mode = ProtectionModes.prevent; + policy.windows.malware.mode = ProtectionModes.prevent; + policy.linux.malware.mode = ProtectionModes.prevent; + + policy.mac.memory_protection.mode = ProtectionModes.prevent; + policy.windows.memory_protection.mode = ProtectionModes.prevent; + policy.linux.memory_protection.mode = ProtectionModes.prevent; + + policy.mac.behavior_protection.mode = ProtectionModes.prevent; + policy.windows.behavior_protection.mode = ProtectionModes.prevent; + policy.linux.behavior_protection.mode = ProtectionModes.prevent; + + policy.windows.ransomware.mode = ProtectionModes.prevent; + + return request({ + method: 'PUT', + url: packagePolicyRouteService.getUpdatePath(endpointPolicyId), + body: updatedEndpointPolicy, + }); + }); +}; diff --git a/x-pack/plugins/security_solution/public/management/cypress/tasks/response_actions.ts b/x-pack/plugins/security_solution/public/management/cypress/tasks/response_actions.ts index c888e7dce1254..13829f8d3378c 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/tasks/response_actions.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/tasks/response_actions.ts @@ -5,6 +5,10 @@ * 2.0. */ +import { request } from './common'; +import { resolvePathVariables } from '../../../common/utils/resolve_path_variables'; +import { ACTION_DETAILS_ROUTE } from '../../../../common/endpoint/constants'; +import type { ActionDetails, ActionDetailsApiResponse } from '../../../../common/endpoint/types'; import { ENABLED_AUTOMATED_RESPONSE_ACTION_COMMANDS } from '../../../../common/endpoint/service/response_actions/constants'; export const validateAvailableCommands = () => { @@ -59,3 +63,40 @@ export const tryAddingDisabledResponseAction = (itemNumber = 0) => { }); cy.getByTestSubj(`response-actions-list-item-${itemNumber}`).should('not.exist'); }; + +/** + * Continuously checks an Response Action until it completes (or timeout is reached) + * @param actionId + * @param timeout + */ +export const waitForActionToComplete = ( + actionId: string, + timeout = 60000 +): Cypress.Chainable => { + let action: ActionDetails | undefined; + + return cy + .waitUntil( + () => { + return request({ + method: 'GET', + url: resolvePathVariables(ACTION_DETAILS_ROUTE, { action_id: actionId || 'undefined' }), + }).then((response) => { + if (response.body.data.isCompleted) { + action = response.body.data; + return true; + } + + return false; + }); + }, + { timeout } + ) + .then(() => { + if (!action) { + throw new Error(`Failed to retrieve completed action`); + } + + return action; + }); +}; diff --git a/x-pack/plugins/security_solution/public/management/cypress_endpoint.config.ts b/x-pack/plugins/security_solution/public/management/cypress_endpoint.config.ts index 8975599350fe2..50a9d8f1f5356 100644 --- a/x-pack/plugins/security_solution/public/management/cypress_endpoint.config.ts +++ b/x-pack/plugins/security_solution/public/management/cypress_endpoint.config.ts @@ -7,7 +7,7 @@ import { defineCypressConfig } from '@kbn/cypress-config'; // eslint-disable-next-line @kbn/imports/no_boundary_crossing -import { dataLoaders } from './cypress/support/data_loaders'; +import { dataLoaders, dataLoadersForRealEndpoints } from './cypress/support/data_loaders'; // eslint-disable-next-line import/no-default-export export default defineCypressConfig({ @@ -40,7 +40,9 @@ export default defineCypressConfig({ specPattern: 'public/management/cypress/e2e/endpoint/*.cy.{js,jsx,ts,tsx}', experimentalRunAllSpecs: true, setupNodeEvents: (on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions) => { - return dataLoaders(on, config); + dataLoaders(on, config); + // Data loaders specific to "real" Endpoint testing + dataLoadersForRealEndpoints(on, config); }, }, }); diff --git a/x-pack/plugins/security_solution/scripts/endpoint/common/delete_all_endpoint_data.ts b/x-pack/plugins/security_solution/scripts/endpoint/common/delete_all_endpoint_data.ts new file mode 100644 index 0000000000000..6382964fda643 --- /dev/null +++ b/x-pack/plugins/security_solution/scripts/endpoint/common/delete_all_endpoint_data.ts @@ -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 type { Client, estypes } from '@elastic/elasticsearch'; +import assert from 'assert'; +import { createEsClient } from './stack_services'; +import { createSecuritySuperuser } from './security_user_services'; + +export interface DeleteAllEndpointDataResponse { + count: number; + query: string; + response: estypes.DeleteByQueryResponse; +} + +/** + * Attempts to delete all data associated with the provided endpoint agent IDs. + * + * **NOTE:** This utility will create a new role and user that has elevated privileges and access to system indexes. + * + * @param esClient + * @param endpointAgentIds + */ +export const deleteAllEndpointData = async ( + esClient: Client, + endpointAgentIds: string[] +): Promise => { + assert(endpointAgentIds.length > 0, 'At least one endpoint agent id must be defined'); + + const unrestrictedUser = await createSecuritySuperuser(esClient, 'super_superuser'); + const esUrl = getEsUrlFromClient(esClient); + const esClientUnrestricted = createEsClient({ + url: esUrl, + username: unrestrictedUser.username, + password: unrestrictedUser.password, + }); + + const queryString = endpointAgentIds.map((id) => `(${id})`).join(' OR '); + + const deleteResponse = await esClientUnrestricted.deleteByQuery({ + index: '*,.*', + body: { + query: { + query_string: { + query: queryString, + }, + }, + }, + ignore_unavailable: true, + conflicts: 'proceed', + }); + + return { + count: deleteResponse.deleted ?? 0, + query: queryString, + response: deleteResponse, + }; +}; + +const getEsUrlFromClient = (esClient: Client) => { + const connection = esClient.connectionPool.connections.find((entry) => entry.status === 'alive'); + + if (!connection) { + throw new Error( + 'Unable to get esClient connection information. No connection found with status `alive`' + ); + } + + const url = new URL(connection.url.href); + url.username = ''; + url.password = ''; + + return url.href; +}; diff --git a/x-pack/plugins/security_solution/scripts/endpoint/common/endpoint_host_services.ts b/x-pack/plugins/security_solution/scripts/endpoint/common/endpoint_host_services.ts new file mode 100644 index 0000000000000..4bb03324f172e --- /dev/null +++ b/x-pack/plugins/security_solution/scripts/endpoint/common/endpoint_host_services.ts @@ -0,0 +1,205 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { kibanaPackageJson } from '@kbn/repo-info'; +import type { KbnClient } from '@kbn/test'; +import type { ToolingLog } from '@kbn/tooling-log'; +import execa from 'execa'; +import assert from 'assert'; +import { + fetchAgentPolicyEnrollmentKey, + fetchFleetServerUrl, + getAgentDownloadUrl, + unEnrollFleetAgent, + waitForHostToEnroll, +} from './fleet_services'; + +export interface CreateAndEnrollEndpointHostOptions + extends Pick { + kbnClient: KbnClient; + log: ToolingLog; + /** The fleet Agent Policy ID to use for enrolling the agent */ + agentPolicyId: string; + /** version of the Agent to install. Defaults to stack version */ + version?: string; + /** The name for the host. Will also be the name of the VM */ + hostname?: string; +} + +export interface CreateAndEnrollEndpointHostResponse { + hostname: string; + agentId: string; +} + +/** + * Creates a new virtual machine (host) and enrolls that with Fleet + */ +export const createAndEnrollEndpointHost = async ({ + kbnClient, + log, + agentPolicyId, + cpus, + disk, + memory, + hostname, + version = kibanaPackageJson.version, +}: CreateAndEnrollEndpointHostOptions): Promise => { + const [vm, agentDownloadUrl, fleetServerUrl, enrollmentToken] = await Promise.all([ + createMultipassVm({ + vmName: hostname ?? `test-host-${Math.random().toString().substring(2, 6)}`, + disk, + cpus, + memory, + }), + + getAgentDownloadUrl(version, true, log), + + fetchFleetServerUrl(kbnClient), + + fetchAgentPolicyEnrollmentKey(kbnClient, agentPolicyId), + ]); + + // Some validations before we proceed + assert(agentDownloadUrl, 'Missing agent download URL'); + assert(fleetServerUrl, 'Fleet server URL not set'); + assert(enrollmentToken, `No enrollment token for agent policy id [${agentPolicyId}]`); + + log.verbose(`Enrolling host [${vm.vmName}] + with fleet-server [${fleetServerUrl}] + using enrollment token [${enrollmentToken}]`); + + const { agentId } = await enrollHostWithFleet({ + kbnClient, + log, + fleetServerUrl, + agentDownloadUrl, + enrollmentToken, + vmName: vm.vmName, + }); + + return { + hostname: vm.vmName, + agentId, + }; +}; + +/** + * Destroys the Endpoint Host VM and un-enrolls the Fleet agent + * @param kbnClient + * @param createdHost + */ +export const destroyEndpointHost = async ( + kbnClient: KbnClient, + createdHost: CreateAndEnrollEndpointHostResponse +): Promise => { + await Promise.all([ + deleteMultipassVm(createdHost.hostname), + unEnrollFleetAgent(kbnClient, createdHost.agentId, true), + ]); +}; + +interface CreateMultipassVmOptions { + vmName: string; + /** Number of CPUs */ + cpus?: number; + /** Disk size */ + disk?: string; + /** Amount of memory */ + memory?: string; +} + +interface CreateMultipassVmResponse { + vmName: string; +} + +/** + * Creates a new VM using `multipass` + */ +const createMultipassVm = async ({ + vmName, + disk = '8G', + cpus = 1, + memory = '1G', +}: CreateMultipassVmOptions): Promise => { + await execa.command( + `multipass launch --name ${vmName} --disk ${disk} --cpus ${cpus} --memory ${memory}` + ); + + return { + vmName, + }; +}; + +const deleteMultipassVm = async (vmName: string): Promise => { + await execa.command(`multipass delete -p ${vmName}`); +}; + +interface EnrollHostWithFleetOptions { + kbnClient: KbnClient; + log: ToolingLog; + vmName: string; + agentDownloadUrl: string; + fleetServerUrl: string; + enrollmentToken: string; +} + +const enrollHostWithFleet = async ({ + kbnClient, + log, + vmName, + fleetServerUrl, + agentDownloadUrl, + enrollmentToken, +}: EnrollHostWithFleetOptions): Promise<{ agentId: string }> => { + const agentDownloadedFile = agentDownloadUrl.substring(agentDownloadUrl.lastIndexOf('/') + 1); + const vmDirName = agentDownloadedFile.replace(/\.tar\.gz$/, ''); + + await execa.command( + `multipass exec ${vmName} -- curl -L ${agentDownloadUrl} -o ${agentDownloadedFile}` + ); + await execa.command(`multipass exec ${vmName} -- tar -zxf ${agentDownloadedFile}`); + await execa.command(`multipass exec ${vmName} -- rm -f ${agentDownloadedFile}`); + + const agentInstallArguments = [ + 'exec', + + vmName, + + '--working-directory', + `/home/ubuntu/${vmDirName}`, + + '--', + + 'sudo', + + './elastic-agent', + + 'install', + + '--insecure', + + '--force', + + '--url', + fleetServerUrl, + + '--enrollment-token', + enrollmentToken, + ]; + + log.info(`Enrolling elastic agent with Fleet`); + log.verbose(`Command: multipass ${agentInstallArguments.join(' ')}`); + + await execa(`multipass`, agentInstallArguments); + + log.info(`Waiting for Agent to check-in with Fleet`); + const agent = await waitForHostToEnroll(kbnClient, vmName, 120000); + + return { + agentId: agent.id, + }; +}; diff --git a/x-pack/plugins/security_solution/scripts/endpoint/common/endpoint_metadata_services.ts b/x-pack/plugins/security_solution/scripts/endpoint/common/endpoint_metadata_services.ts index a1f21b80567d6..7823309aa0059 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/common/endpoint_metadata_services.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/common/endpoint_metadata_services.ts @@ -131,3 +131,46 @@ const fetchLastStreamedEndpointUpdate = async ( return queryResult.hits?.hits[0]?._source; }; + +/** + * Waits for an endpoint to have streamed data to ES and for that data to have made it to the + * Endpoint Details API (transform destination index) + * @param kbnClient + * @param endpointAgentId + * @param timeoutMs + */ +export const waitForEndpointToStreamData = async ( + kbnClient: KbnClient, + endpointAgentId: string, + timeoutMs: number = 60000 +): Promise => { + const started = new Date(); + const hasTimedOut = (): boolean => { + const elapsedTime = Date.now() - started.getTime(); + return elapsedTime > timeoutMs; + }; + let found: HostInfo | undefined; + + while (!found && !hasTimedOut()) { + found = await fetchEndpointMetadata(kbnClient, 'invalid-id-test').catch((error) => { + // Ignore `not found` (404) responses. Endpoint could be new and thus documents might not have + // been streamed yet. + if (error?.response?.status === 404) { + return undefined; + } + + throw error; + }); + + if (!found) { + // sleep and check again + await new Promise((r) => setTimeout(r, 2000)); + } + } + + if (!found) { + throw new Error(`Timed out waiting for Endpoint id [${endpointAgentId}] to stream data to ES`); + } + + return found; +}; diff --git a/x-pack/plugins/security_solution/scripts/endpoint/common/fleet_services.ts b/x-pack/plugins/security_solution/scripts/endpoint/common/fleet_services.ts index 1f4d28cecc569..fca93d1848f4a 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/common/fleet_services.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/common/fleet_services.ts @@ -14,7 +14,12 @@ import type { GetAgentPoliciesResponse, GetAgentsResponse, } from '@kbn/fleet-plugin/common'; -import { AGENT_API_ROUTES, agentPolicyRouteService, AGENTS_INDEX } from '@kbn/fleet-plugin/common'; +import { + AGENT_API_ROUTES, + agentPolicyRouteService, + agentRouteService, + AGENTS_INDEX, +} from '@kbn/fleet-plugin/common'; import { ToolingLog } from '@kbn/tooling-log'; import type { KbnClient } from '@kbn/test'; import type { GetFleetServerHostsResponse } from '@kbn/fleet-plugin/common/types/rest_spec/fleet_server_hosts'; @@ -26,7 +31,10 @@ import type { EnrollmentAPIKey, GetAgentsRequest, GetEnrollmentAPIKeysResponse, + PostAgentUnenrollResponse, } from '@kbn/fleet-plugin/common/types'; +import nodeFetch from 'node-fetch'; +import semver from 'semver'; import { FleetAgentGenerator } from '../../../common/endpoint/data_generators/fleet_agent_generator'; const fleetGenerator = new FleetAgentGenerator(); @@ -236,3 +244,135 @@ export const getAgentVersionMatchingCurrentStack = async ( return version; }; + +interface ElasticArtifactSearchResponse { + manifest: { + 'last-update-time': string; + 'seconds-since-last-update': number; + }; + packages: { + [packageFileName: string]: { + architecture: string; + os: string[]; + type: string; + asc_url: string; + sha_url: string; + url: string; + }; + }; +} + +/** + * Retrieves the download URL to the Linux installation package for a given version of the Elastic Agent + * @param version + * @param closestMatch + * @param log + */ +export const getAgentDownloadUrl = async ( + version: string, + /** + * When set to true a check will be done to determine the latest version of the agent that + * is less than or equal to the `version` provided + */ + closestMatch: boolean = false, + log?: ToolingLog +): Promise => { + const agentVersion = closestMatch ? await getLatestAgentDownloadVersion(version, log) : version; + const downloadArch = + { arm64: 'arm64', x64: 'x86_64' }[process.arch] ?? `UNSUPPORTED_ARCHITECTURE_${process.arch}`; + const agentFile = `elastic-agent-${agentVersion}-linux-${downloadArch}.tar.gz`; + const artifactSearchUrl = `https://artifacts-api.elastic.co/v1/search/${agentVersion}/${agentFile}`; + + log?.verbose(`Retrieving elastic agent download URL from:\n ${artifactSearchUrl}`); + + const searchResult: ElasticArtifactSearchResponse = await nodeFetch(artifactSearchUrl).then( + (response) => { + if (!response.ok) { + throw new Error( + `Failed to search elastic's artifact repository: ${response.statusText} (HTTP ${response.status}) {URL: ${artifactSearchUrl})` + ); + } + + return response.json(); + } + ); + + log?.verbose(searchResult); + + if (!searchResult.packages[agentFile]) { + throw new Error(`Unable to find an Agent download URL for version [${agentVersion}]`); + } + + return searchResult.packages[agentFile].url; +}; + +/** + * Given a stack version number, function will return the closest Agent download version available + * for download. THis could be the actual version passed in or lower. + * @param version + */ +export const getLatestAgentDownloadVersion = async ( + version: string, + log?: ToolingLog +): Promise => { + const artifactsUrl = 'https://artifacts-api.elastic.co/v1/versions'; + const semverMatch = `<=${version}`; + const artifactVersionsResponse: { versions: string[] } = await nodeFetch(artifactsUrl).then( + (response) => { + if (!response.ok) { + throw new Error( + `Failed to retrieve list of versions from elastic's artifact repository: ${response.statusText} (HTTP ${response.status}) {URL: ${artifactsUrl})` + ); + } + + return response.json(); + } + ); + + const stackVersionToArtifactVersion: Record = + artifactVersionsResponse.versions.reduce((acc, artifactVersion) => { + const stackVersion = artifactVersion.split('-SNAPSHOT')[0]; + acc[stackVersion] = artifactVersion; + return acc; + }, {} as Record); + + log?.verbose( + `Versions found from [${artifactsUrl}]:\n${JSON.stringify( + stackVersionToArtifactVersion, + null, + 2 + )}` + ); + + const matchedVersion = semver.maxSatisfying( + Object.keys(stackVersionToArtifactVersion), + semverMatch + ); + + if (!matchedVersion) { + throw new Error(`Unable to find a semver version that meets ${semverMatch}`); + } + + return stackVersionToArtifactVersion[matchedVersion]; +}; + +/** + * Un-enrolls a Fleet agent + * + * @param kbnClient + * @param agentId + * @param force + */ +export const unEnrollFleetAgent = async ( + kbnClient: KbnClient, + agentId: string, + force = false +): Promise => { + const { data } = await kbnClient.request({ + method: 'POST', + path: agentRouteService.getUnenrollPath(agentId), + body: { revoke: force }, + }); + + return data; +}; diff --git a/x-pack/plugins/security_solution/scripts/endpoint/common/security_user_services.ts b/x-pack/plugins/security_solution/scripts/endpoint/common/security_user_services.ts index dab9e2b6abd27..f17bf7b514f21 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/common/security_user_services.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/common/security_user_services.ts @@ -17,12 +17,41 @@ export const createSecuritySuperuser = async ( throw new Error(`username and password require values.`); } + // Create a role which has full access to restricted indexes + await esClient.transport.request({ + method: 'POST', + path: '_security/role/superuser_restricted_indices', + body: { + cluster: ['all'], + indices: [ + { + names: ['*'], + privileges: ['all'], + allow_restricted_indices: true, + }, + { + names: ['*'], + privileges: ['monitor', 'read', 'view_index_metadata', 'read_cross_cluster'], + allow_restricted_indices: true, + }, + ], + applications: [ + { + application: '*', + privileges: ['*'], + resources: ['*'], + }, + ], + run_as: ['*'], + }, + }); + const addedUser = await esClient.transport.request>({ method: 'POST', path: `_security/user/${username}`, body: { password, - roles: ['superuser', 'kibana_system'], + roles: ['superuser', 'kibana_system', 'superuser_restricted_indices'], full_name: username, }, }); diff --git a/x-pack/plugins/security_solution/scripts/endpoint/common/stack_services.ts b/x-pack/plugins/security_solution/scripts/endpoint/common/stack_services.ts index 424f451c3fdc6..f7ba4c1a5b514 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/common/stack_services.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/common/stack_services.ts @@ -99,7 +99,11 @@ export const createRuntimeServices = async ({ }; }; -const buildUrlWithCredentials = (url: string, username: string, password: string): string => { +export const buildUrlWithCredentials = ( + url: string, + username: string, + password: string +): string => { const newUrl = new URL(url); newUrl.username = username; diff --git a/x-pack/test/defend_workflows_cypress/endpoint_config.ts b/x-pack/test/defend_workflows_cypress/endpoint_config.ts index f1ea9a9c81a12..e6191cea7074b 100644 --- a/x-pack/test/defend_workflows_cypress/endpoint_config.ts +++ b/x-pack/test/defend_workflows_cypress/endpoint_config.ts @@ -8,6 +8,7 @@ import { getLocalhostRealIp } from '@kbn/security-solution-plugin/scripts/endpoint/common/localhost_services'; import { FtrConfigProviderContext } from '@kbn/test'; +import { ExperimentalFeatures } from '@kbn/security-solution-plugin/common/experimental_features'; import { DefendWorkflowsCypressEndpointTestRunner } from './runner'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { @@ -15,6 +16,8 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { const config = defendWorkflowsCypressConfig.getAll(); const hostIp = getLocalhostRealIp(); + const enabledFeatureFlags: Array = ['responseActionExecuteEnabled']; + return { ...config, kbnTestServer: { @@ -27,6 +30,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { )}`, // set the packagerTaskInterval to 5s in order to speed up test executions when checking fleet artifacts '--xpack.securitySolution.packagerTaskInterval=5s', + `--xpack.securitySolution.enableExperimental=${JSON.stringify(enabledFeatureFlags)}`, ], }, testRunner: DefendWorkflowsCypressEndpointTestRunner, From aa67e22b0ee7d94610db8464bec346751edda7b9 Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 25 Apr 2023 15:20:27 +0200 Subject: [PATCH 50/52] [RAM] Create slack web api connector (#154359) ## Summary Create separate Slack Web API connector ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: Xavier Mouligneau Co-authored-by: Julian Gernun <17549662+jcger@users.noreply.github.com> --- .../common/slack_api/constants.ts | 9 + .../stack_connectors/common/slack_api/lib.ts | 79 +++++ .../common/slack_api/schema.ts | 30 ++ .../common/slack_api/types.ts | 67 ++++ .../public/connector_types/index.ts | 6 +- .../public/connector_types/slack/index.ts | 2 +- .../public/connector_types/slack/slack.tsx | 30 ++ .../slack/slack_connectors.test.tsx | 2 +- .../connector_types/slack_api/index.tsx | 8 + .../slack_api/slack_api.test.tsx | 88 +++++ .../connector_types/slack_api/slack_api.tsx | 76 +++++ .../slack_api/slack_connectors.test.tsx | 77 +++++ .../slack_api/slack_connectors.tsx | 36 +++ .../slack_api/slack_params.test.tsx | 201 ++++++++++++ .../slack_api/slack_params.tsx | 209 ++++++++++++ .../connector_types/slack_api/translations.ts | 42 +++ .../server/connector_types/index.ts | 12 +- .../connector_types/slack_api/api.test.ts | 101 ++++++ .../server/connector_types/slack_api/api.ts | 24 ++ .../connector_types/slack_api/index.test.ts | 303 ++++++++++++++++++ .../server/connector_types/slack_api/index.ts | 124 +++++++ .../connector_types/slack_api/service.test.ts | 181 +++++++++++ .../connector_types/slack_api/service.ts | 162 ++++++++++ .../connector_types/slack_api/translations.ts | 12 + .../stack_connectors/server/plugin.test.ts | 20 +- .../plugins/stack_connectors/server/types.ts | 6 +- .../synthetics/common/rules/alert_actions.ts | 4 +- .../plugins/synthetics/common/rules/types.ts | 4 +- .../action_connector_form/action_form.tsx | 66 +++- .../action_type_form.tsx | 2 + .../action_type_menu.test.tsx | 20 +- .../action_type_menu.tsx | 18 +- .../connector_add_inline.tsx | 9 +- .../connector_add_modal.tsx | 147 ++++++++- .../action_connector_form/connector_form.tsx | 21 +- .../connectors_selection.tsx | 27 +- .../create_connector_flyout/index.tsx | 75 ++++- .../application/sections/common/connectors.ts | 6 +- .../triggers_actions_ui/public/types.ts | 4 + .../alerting_api_integration/common/config.ts | 2 + .../actions/connector_types/slack_api.ts | 76 +++++ .../{slack.ts => slack_webhook.ts} | 2 +- .../group2/tests/actions/index.ts | 5 +- .../check_registered_connector_types.ts | 1 + .../test/functional/services/actions/index.ts | 2 + .../test/functional/services/actions/slack.ts | 53 +++ .../triggers_actions_ui/connectors/index.ts | 1 + .../triggers_actions_ui/connectors/slack.ts | 208 ++++++++++++ .../functional_with_es_ssl/config.base.ts | 1 + .../check_registered_task_types.ts | 1 + 50 files changed, 2588 insertions(+), 74 deletions(-) create mode 100644 x-pack/plugins/stack_connectors/common/slack_api/constants.ts create mode 100644 x-pack/plugins/stack_connectors/common/slack_api/lib.ts create mode 100644 x-pack/plugins/stack_connectors/common/slack_api/schema.ts create mode 100644 x-pack/plugins/stack_connectors/common/slack_api/types.ts create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/slack_api/index.tsx create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/slack_api/slack_api.test.tsx create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/slack_api/slack_api.tsx create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/slack_api/slack_connectors.test.tsx create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/slack_api/slack_connectors.tsx create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/slack_api/slack_params.test.tsx create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/slack_api/slack_params.tsx create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/slack_api/translations.ts create mode 100644 x-pack/plugins/stack_connectors/server/connector_types/slack_api/api.test.ts create mode 100644 x-pack/plugins/stack_connectors/server/connector_types/slack_api/api.ts create mode 100644 x-pack/plugins/stack_connectors/server/connector_types/slack_api/index.test.ts create mode 100644 x-pack/plugins/stack_connectors/server/connector_types/slack_api/index.ts create mode 100644 x-pack/plugins/stack_connectors/server/connector_types/slack_api/service.test.ts create mode 100644 x-pack/plugins/stack_connectors/server/connector_types/slack_api/service.ts create mode 100644 x-pack/plugins/stack_connectors/server/connector_types/slack_api/translations.ts create mode 100644 x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/slack_api.ts rename x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/{slack.ts => slack_webhook.ts} (99%) create mode 100644 x-pack/test/functional/services/actions/slack.ts create mode 100644 x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors/slack.ts diff --git a/x-pack/plugins/stack_connectors/common/slack_api/constants.ts b/x-pack/plugins/stack_connectors/common/slack_api/constants.ts new file mode 100644 index 0000000000000..3c107e1c05342 --- /dev/null +++ b/x-pack/plugins/stack_connectors/common/slack_api/constants.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 const SLACK_API_CONNECTOR_ID = '.slack_api'; +export const SLACK_URL = 'https://slack.com/api/'; diff --git a/x-pack/plugins/stack_connectors/common/slack_api/lib.ts b/x-pack/plugins/stack_connectors/common/slack_api/lib.ts new file mode 100644 index 0000000000000..449b1aef56b14 --- /dev/null +++ b/x-pack/plugins/stack_connectors/common/slack_api/lib.ts @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { ActionTypeExecutorResult as ConnectorTypeExecutorResult } from '@kbn/actions-plugin/server/types'; +import { i18n } from '@kbn/i18n'; + +export function successResult( + actionId: string, + data: unknown +): ConnectorTypeExecutorResult { + return { status: 'ok', data, actionId }; +} + +export function errorResult(actionId: string, message: string): ConnectorTypeExecutorResult { + return { + status: 'error', + message, + actionId, + }; +} +export function serviceErrorResult( + actionId: string, + serviceMessage?: string +): ConnectorTypeExecutorResult { + const errMessage = i18n.translate('xpack.stackConnectors.slack.errorPostingErrorMessage', { + defaultMessage: 'error posting slack message', + }); + return { + status: 'error', + message: errMessage, + actionId, + serviceMessage, + }; +} + +export function retryResult(actionId: string, message: string): ConnectorTypeExecutorResult { + const errMessage = i18n.translate( + 'xpack.stackConnectors.slack.errorPostingRetryLaterErrorMessage', + { + defaultMessage: 'error posting a slack message, retry later', + } + ); + return { + status: 'error', + message: errMessage, + retry: true, + actionId, + }; +} + +export function retryResultSeconds( + actionId: string, + message: string, + retryAfter: number +): ConnectorTypeExecutorResult { + const retryEpoch = Date.now() + retryAfter * 1000; + const retry = new Date(retryEpoch); + const retryString = retry.toISOString(); + const errMessage = i18n.translate( + 'xpack.stackConnectors.slack.errorPostingRetryDateErrorMessage', + { + defaultMessage: 'error posting a slack message, retry at {retryString}', + values: { + retryString, + }, + } + ); + return { + status: 'error', + message: errMessage, + retry, + actionId, + serviceMessage: message, + }; +} diff --git a/x-pack/plugins/stack_connectors/common/slack_api/schema.ts b/x-pack/plugins/stack_connectors/common/slack_api/schema.ts new file mode 100644 index 0000000000000..a1060f3290b28 --- /dev/null +++ b/x-pack/plugins/stack_connectors/common/slack_api/schema.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 { schema } from '@kbn/config-schema'; + +export const SlackApiSecretsSchema = schema.object({ + token: schema.string({ minLength: 1 }), +}); + +export const GetChannelsParamsSchema = schema.object({ + subAction: schema.literal('getChannels'), +}); + +export const PostMessageSubActionParamsSchema = schema.object({ + channels: schema.arrayOf(schema.string()), + text: schema.string(), +}); +export const PostMessageParamsSchema = schema.object({ + subAction: schema.literal('postMessage'), + subActionParams: PostMessageSubActionParamsSchema, +}); + +export const SlackApiParamsSchema = schema.oneOf([ + GetChannelsParamsSchema, + PostMessageParamsSchema, +]); diff --git a/x-pack/plugins/stack_connectors/common/slack_api/types.ts b/x-pack/plugins/stack_connectors/common/slack_api/types.ts new file mode 100644 index 0000000000000..1098d40eded19 --- /dev/null +++ b/x-pack/plugins/stack_connectors/common/slack_api/types.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ActionType as ConnectorType } from '@kbn/actions-plugin/server/types'; +import { TypeOf } from '@kbn/config-schema'; +import type { ActionTypeExecutorOptions as ConnectorTypeExecutorOptions } from '@kbn/actions-plugin/server/types'; +import type { ActionTypeExecutorResult as ConnectorTypeExecutorResult } from '@kbn/actions-plugin/server/types'; +import { + PostMessageParamsSchema, + PostMessageSubActionParamsSchema, + SlackApiSecretsSchema, + SlackApiParamsSchema, +} from './schema'; + +export type SlackApiSecrets = TypeOf; + +export type PostMessageParams = TypeOf; +export type PostMessageSubActionParams = TypeOf; +export type SlackApiParams = TypeOf; +export type SlackApiConnectorType = ConnectorType<{}, SlackApiSecrets, SlackApiParams, unknown>; + +export type SlackApiExecutorOptions = ConnectorTypeExecutorOptions< + {}, + SlackApiSecrets, + SlackApiParams +>; + +export type SlackExecutorOptions = ConnectorTypeExecutorOptions< + {}, + SlackApiSecrets, + SlackApiParams +>; + +export type SlackApiActionParams = TypeOf; + +export interface GetChannelsResponse { + ok: true; + error?: string; + channels?: Array<{ + id: string; + name: string; + is_channel: boolean; + is_archived: boolean; + is_private: boolean; + }>; +} + +export interface PostMessageResponse { + ok: boolean; + channel?: string; + error?: string; + message?: { + text: string; + }; +} + +export interface SlackApiService { + getChannels: () => Promise>; + postMessage: ({ + channels, + text, + }: PostMessageSubActionParams) => Promise>; +} diff --git a/x-pack/plugins/stack_connectors/public/connector_types/index.ts b/x-pack/plugins/stack_connectors/public/connector_types/index.ts index 55b2a31d2ca80..2cedad5996a8b 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/index.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/index.ts @@ -18,7 +18,8 @@ import { getServerLogConnectorType } from './server_log'; import { getServiceNowITOMConnectorType } from './servicenow_itom'; import { getServiceNowITSMConnectorType } from './servicenow_itsm'; import { getServiceNowSIRConnectorType } from './servicenow_sir'; -import { getSlackConnectorType } from './slack'; +import { getSlackWebhookConnectorType } from './slack'; +import { getSlackApiConnectorType } from './slack_api'; import { getSwimlaneConnectorType } from './swimlane'; import { getTeamsConnectorType } from './teams'; import { getTinesConnectorType } from './tines'; @@ -41,7 +42,8 @@ export function registerConnectorTypes({ services: RegistrationServices; }) { connectorTypeRegistry.register(getServerLogConnectorType()); - connectorTypeRegistry.register(getSlackConnectorType()); + connectorTypeRegistry.register(getSlackWebhookConnectorType()); + connectorTypeRegistry.register(getSlackApiConnectorType()); connectorTypeRegistry.register(getEmailConnectorType(services)); connectorTypeRegistry.register(getIndexConnectorType()); connectorTypeRegistry.register(getPagerDutyConnectorType()); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/slack/index.ts b/x-pack/plugins/stack_connectors/public/connector_types/slack/index.ts index 05d27afff76fb..74a96853ab149 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/slack/index.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/slack/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { getConnectorType as getSlackConnectorType } from './slack'; +export { getConnectorType as getSlackWebhookConnectorType } from './slack'; diff --git a/x-pack/plugins/stack_connectors/public/connector_types/slack/slack.tsx b/x-pack/plugins/stack_connectors/public/connector_types/slack/slack.tsx index fabfe46a4db12..c1b4b72182fa9 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/slack/slack.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/slack/slack.tsx @@ -12,10 +12,28 @@ import type { GenericValidationResult, } from '@kbn/triggers-actions-ui-plugin/public/types'; import { SlackActionParams, SlackSecrets } from '../types'; +import { PostMessageParams } from '../../../common/slack_api/types'; + +export const subtype = [ + { + id: '.slack', + name: i18n.translate('xpack.stackConnectors.components.slack.webhook', { + defaultMessage: 'Webhook', + }), + }, + { + id: '.slack_api', + name: i18n.translate('xpack.stackConnectors.components.slack.webApi', { + defaultMessage: 'Web API', + }), + }, +]; export function getConnectorType(): ConnectorTypeModel { return { id: '.slack', + subtype, + modalWidth: 675, iconClass: 'logoSlack', selectMessage: i18n.translate('xpack.stackConnectors.components.slack.selectMessageText', { defaultMessage: 'Send a message to a Slack channel or user.', @@ -38,5 +56,17 @@ export function getConnectorType(): ConnectorTypeModel import('./slack_connectors')), actionParamsFields: lazy(() => import('./slack_params')), + convertParamsBetweenGroups: ( + params: PostMessageParams | SlackActionParams + ): PostMessageParams | SlackActionParams | {} => { + if ('message' in params) { + return params; + } else if ('subAction' in params) { + return { + message: (params as PostMessageParams).subActionParams.text, + }; + } + return {}; + }, }; } diff --git a/x-pack/plugins/stack_connectors/public/connector_types/slack/slack_connectors.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/slack/slack_connectors.test.tsx index 0910565276216..e81dec2d662c2 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/slack/slack_connectors.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/slack/slack_connectors.test.tsx @@ -91,7 +91,7 @@ describe('SlackActionFields renders', () => { }); }); - it('validates teh web hook url field correctly', async () => { + it('validates the web hook url field correctly', async () => { const actionConnector = { secrets: { webhookUrl: 'http://test.com', diff --git a/x-pack/plugins/stack_connectors/public/connector_types/slack_api/index.tsx b/x-pack/plugins/stack_connectors/public/connector_types/slack_api/index.tsx new file mode 100644 index 0000000000000..258473accdd68 --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/slack_api/index.tsx @@ -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 { getConnectorType as getSlackApiConnectorType } from './slack_api'; diff --git a/x-pack/plugins/stack_connectors/public/connector_types/slack_api/slack_api.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/slack_api/slack_api.test.tsx new file mode 100644 index 0000000000000..17ed4f9380e09 --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/slack_api/slack_api.test.tsx @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { TypeRegistry } from '@kbn/triggers-actions-ui-plugin/public/application/type_registry'; +import type { ActionTypeModel as ConnectorTypeModel } from '@kbn/triggers-actions-ui-plugin/public/types'; +import { registerConnectorTypes } from '..'; +import { registrationServicesMock } from '../../mocks'; +import { SLACK_API_CONNECTOR_ID } from '../../../common/slack_api/constants'; + +let connectorTypeModel: ConnectorTypeModel; + +beforeAll(async () => { + const connectorTypeRegistry = new TypeRegistry(); + registerConnectorTypes({ connectorTypeRegistry, services: registrationServicesMock }); + const getResult = connectorTypeRegistry.get(SLACK_API_CONNECTOR_ID); + if (getResult !== null) { + connectorTypeModel = getResult; + } +}); + +describe('connectorTypeRegistry.get works', () => { + test('connector type static data is as expected', () => { + expect(connectorTypeModel.id).toEqual(SLACK_API_CONNECTOR_ID); + expect(connectorTypeModel.iconClass).toEqual('logoSlack'); + }); +}); + +describe('Slack action params validation', () => { + test('should succeed when action params include valid message and channels list', async () => { + const actionParams = { + subAction: 'postMessage', + subActionParams: { channels: ['general'], text: 'some text' }, + }; + + expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ + errors: { + text: [], + channels: [], + }, + }); + }); + + test('should fail when channels field is missing in action params', async () => { + const actionParams = { + subAction: 'postMessage', + subActionParams: { text: 'some text' }, + }; + + expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ + errors: { + text: [], + channels: ['Selected channel is required.'], + }, + }); + }); + + test('should fail when field text does not exist', async () => { + const actionParams = { + subAction: 'postMessage', + subActionParams: { channels: ['general'] }, + }; + + expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ + errors: { + text: ['Message is required.'], + channels: [], + }, + }); + }); + + test('should fail when text is empty string', async () => { + const actionParams = { + subAction: 'postMessage', + subActionParams: { channels: ['general'], text: '' }, + }; + + expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ + errors: { + text: ['Message is required.'], + channels: [], + }, + }); + }); +}); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/slack_api/slack_api.tsx b/x-pack/plugins/stack_connectors/public/connector_types/slack_api/slack_api.tsx new file mode 100644 index 0000000000000..6b985dbb90e34 --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/slack_api/slack_api.tsx @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { lazy } from 'react'; +import type { + ActionTypeModel as ConnectorTypeModel, + GenericValidationResult, +} from '@kbn/triggers-actions-ui-plugin/public/types'; +import { + ACTION_TYPE_TITLE, + CHANNEL_REQUIRED, + MESSAGE_REQUIRED, + SELECT_MESSAGE, +} from './translations'; +import type { + SlackApiActionParams, + SlackApiSecrets, + PostMessageParams, +} from '../../../common/slack_api/types'; +import { SLACK_API_CONNECTOR_ID } from '../../../common/slack_api/constants'; +import { SlackActionParams } from '../types'; +import { subtype } from '../slack/slack'; + +export const getConnectorType = (): ConnectorTypeModel< + unknown, + SlackApiSecrets, + PostMessageParams +> => ({ + id: SLACK_API_CONNECTOR_ID, + subtype, + hideInUi: true, + modalWidth: 675, + iconClass: 'logoSlack', + selectMessage: SELECT_MESSAGE, + actionTypeTitle: ACTION_TYPE_TITLE, + validateParams: async ( + actionParams: SlackApiActionParams + ): Promise> => { + const errors = { + text: new Array(), + channels: new Array(), + }; + const validationResult = { errors }; + if (actionParams.subAction === 'postMessage') { + if (!actionParams.subActionParams.text) { + errors.text.push(MESSAGE_REQUIRED); + } + if (!actionParams.subActionParams.channels?.length) { + errors.channels.push(CHANNEL_REQUIRED); + } + } + return validationResult; + }, + actionConnectorFields: lazy(() => import('./slack_connectors')), + actionParamsFields: lazy(() => import('./slack_params')), + convertParamsBetweenGroups: ( + params: SlackActionParams | PostMessageParams + ): SlackActionParams | PostMessageParams | {} => { + if ('message' in params) { + return { + subAction: 'postMessage', + subActionParams: { + channels: [], + text: params.message, + }, + }; + } else if ('subAction' in params) { + return params; + } + return {}; + }, +}); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/slack_api/slack_connectors.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/slack_api/slack_connectors.test.tsx new file mode 100644 index 0000000000000..ef9877c5a8772 --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/slack_api/slack_connectors.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 { act, render, fireEvent, screen } from '@testing-library/react'; +import SlackActionFields from './slack_connectors'; +import { ConnectorFormTestProvider, waitForComponentToUpdate } from '../lib/test_utils'; + +jest.mock('@kbn/triggers-actions-ui-plugin/public/common/lib/kibana'); + +describe('SlackActionFields renders', () => { + const onSubmit = jest.fn(); + beforeEach(() => { + jest.clearAllMocks(); + }); + it('all connector fields is rendered for web_api type', async () => { + const actionConnector = { + secrets: { + token: 'some token', + }, + id: 'test', + actionTypeId: '.slack', + name: 'slack', + config: {}, + isDeprecated: false, + }; + + render( + + {}} /> + + ); + + expect(screen.getByTestId('secrets.token-input')).toBeInTheDocument(); + expect(screen.getByTestId('secrets.token-input')).toHaveValue('some token'); + }); + + it('connector validation succeeds when connector config is valid for Web API type', async () => { + const actionConnector = { + secrets: { + token: 'some token', + }, + id: 'test', + actionTypeId: '.slack', + name: 'slack', + config: {}, + isDeprecated: false, + }; + + render( + + {}} /> + + ); + await waitForComponentToUpdate(); + await act(async () => { + fireEvent.click(screen.getByTestId('form-test-provide-submit')); + }); + expect(onSubmit).toBeCalledTimes(1); + expect(onSubmit).toBeCalledWith({ + data: { + secrets: { + token: 'some token', + }, + id: 'test', + actionTypeId: '.slack', + name: 'slack', + isDeprecated: false, + }, + isValid: true, + }); + }); +}); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/slack_api/slack_connectors.tsx b/x-pack/plugins/stack_connectors/public/connector_types/slack_api/slack_connectors.tsx new file mode 100644 index 0000000000000..4d36cc851ce69 --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/slack_api/slack_connectors.tsx @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { + ActionConnectorFieldsProps, + SecretsFieldSchema, + SimpleConnectorForm, +} from '@kbn/triggers-actions-ui-plugin/public'; +import * as i18n from './translations'; + +const secretsFormSchema: SecretsFieldSchema[] = [ + { + id: 'token', + label: i18n.TOKEN_LABEL, + isPasswordField: true, + }, +]; + +const SlackActionFields: React.FC = ({ readOnly, isEdit }) => { + return ( + + ); +}; + +// eslint-disable-next-line import/no-default-export +export { SlackActionFields as default }; diff --git a/x-pack/plugins/stack_connectors/public/connector_types/slack_api/slack_params.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/slack_api/slack_params.test.tsx new file mode 100644 index 0000000000000..e8353a3bbabf3 --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/slack_api/slack_params.test.tsx @@ -0,0 +1,201 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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, screen, fireEvent } from '@testing-library/react'; +import SlackParamsFields from './slack_params'; +import type { UseSubActionParams } from '@kbn/triggers-actions-ui-plugin/public/application/hooks/use_sub_action'; +import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; + +interface Result { + isLoading: boolean; + response: Record; + error: null | Error; +} + +const triggersActionsPath = '@kbn/triggers-actions-ui-plugin/public'; + +const mockUseSubAction = jest.fn]>( + jest.fn]>(() => ({ + isLoading: false, + response: { + channels: [ + { + id: 'id', + name: 'general', + is_channel: true, + is_archived: false, + is_private: true, + }, + ], + }, + error: null, + })) +); + +const mockToasts = { danger: jest.fn(), warning: jest.fn() }; +jest.mock(triggersActionsPath, () => { + const original = jest.requireActual(triggersActionsPath); + return { + ...original, + useSubAction: (params: UseSubActionParams) => mockUseSubAction(params), + useKibana: () => ({ + ...original.useKibana(), + notifications: { toasts: mockToasts }, + }), + }; +}); + +describe('SlackParamsFields renders', () => { + test('when useDefaultMessage is set to true and the default message changes, the underlying message is replaced with the default message', () => { + const editAction = jest.fn(); + const { rerender } = render( + + + + ); + expect(screen.getByTestId('webApiTextArea')).toBeInTheDocument(); + expect(screen.getByTestId('webApiTextArea')).toHaveValue('some text'); + rerender( + + + + ); + expect(editAction).toHaveBeenCalledWith( + 'subActionParams', + { channels: ['general'], text: 'some different default message' }, + 0 + ); + }); + + test('when useDefaultMessage is set to false and the default message changes, the underlying message is not changed, Web API', () => { + const editAction = jest.fn(); + const { rerender } = render( + + + + ); + expect(screen.getByTestId('webApiTextArea')).toBeInTheDocument(); + expect(screen.getByTestId('webApiTextArea')).toHaveValue('some text'); + + rerender( + + + + ); + expect(editAction).not.toHaveBeenCalled(); + }); + + test('all params fields is rendered for postMessage call', async () => { + render( + + {}} + index={0} + defaultMessage="default message" + messageVariables={[]} + /> + + ); + + expect(screen.getByTestId('webApiTextArea')).toBeInTheDocument(); + expect(screen.getByTestId('webApiTextArea')).toHaveValue('some text'); + }); + + test('all params fields is rendered for getChannels call', async () => { + render( + + {}} + index={0} + defaultMessage="default message" + messageVariables={[]} + /> + + ); + + expect(screen.getByTestId('slackChannelsButton')).toHaveTextContent('Channels'); + fireEvent.click(screen.getByTestId('slackChannelsButton')); + expect(screen.getByTestId('slackChannelsSelectableList')).toBeInTheDocument(); + expect(screen.getByTestId('slackChannelsSelectableList')).toHaveTextContent('general'); + fireEvent.click(screen.getByText('general')); + expect(screen.getByTitle('general').getAttribute('aria-checked')).toEqual('true'); + }); + + test('show error message when no channel is selected', async () => { + render( + + {}} + index={0} + defaultMessage="default message" + messageVariables={[]} + /> + + ); + expect(screen.getByText('my error message')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/slack_api/slack_params.tsx b/x-pack/plugins/stack_connectors/public/connector_types/slack_api/slack_params.tsx new file mode 100644 index 0000000000000..6d5f284e764b5 --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/slack_api/slack_params.tsx @@ -0,0 +1,209 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState, useEffect, useMemo, useCallback } from 'react'; +import type { ActionParamsProps } from '@kbn/triggers-actions-ui-plugin/public'; +import { i18n } from '@kbn/i18n'; +import { TextAreaWithMessageVariables } from '@kbn/triggers-actions-ui-plugin/public'; +import { + EuiSpacer, + EuiFilterGroup, + EuiPopover, + EuiFilterButton, + EuiSelectable, + EuiSelectableOption, + EuiFormRow, +} from '@elastic/eui'; +import { useSubAction, useKibana } from '@kbn/triggers-actions-ui-plugin/public'; +import { FormattedMessage } from '@kbn/i18n-react'; +import type { GetChannelsResponse, PostMessageParams } from '../../../common/slack_api/types'; + +interface ChannelsStatus { + label: string; + checked?: 'on'; +} + +const SlackParamsFields: React.FunctionComponent> = ({ + actionConnector, + actionParams, + editAction, + index, + errors, + messageVariables, + defaultMessage, + useDefaultMessage, +}) => { + const { subAction, subActionParams } = actionParams; + const { channels = [], text } = subActionParams ?? {}; + const { toasts } = useKibana().notifications; + + useEffect(() => { + if (useDefaultMessage || !text) { + editAction('subActionParams', { channels, text: defaultMessage }, index); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [defaultMessage, useDefaultMessage]); + + if (!subAction) { + editAction('subAction', 'postMessage', index); + } + if (!subActionParams) { + editAction( + 'subActionParams', + { + channels, + text, + }, + index + ); + } + + const { + response: { channels: channelsInfo } = {}, + isLoading: isLoadingChannels, + error: channelsError, + } = useSubAction({ + connectorId: actionConnector?.id, + subAction: 'getChannels', + }); + + useEffect(() => { + if (channelsError) { + toasts.danger({ + title: i18n.translate( + 'xpack.stackConnectors.slack.params.componentError.getChannelsRequestFailed', + { + defaultMessage: 'Failed to retrieve Slack channels list', + } + ), + body: channelsError.message, + }); + } + }, [toasts, channelsError]); + + const slackChannels = useMemo( + () => + channelsInfo + ?.filter((slackChannel) => slackChannel.is_channel) + .map((slackChannel) => ({ label: slackChannel.name })) ?? [], + [channelsInfo] + ); + + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const [selectedChannels, setSelectedChannels] = useState(channels ?? []); + + const button = ( + setIsPopoverOpen(!isPopoverOpen)} + numFilters={selectedChannels.length} + hasActiveFilters={selectedChannels.length > 0} + numActiveFilters={selectedChannels.length} + data-test-subj="slackChannelsButton" + > + + + ); + + const options: ChannelsStatus[] = useMemo( + () => + slackChannels.map((slackChannel) => ({ + label: slackChannel.label, + ...(selectedChannels.includes(slackChannel.label) ? { checked: 'on' } : {}), + })), + [slackChannels, selectedChannels] + ); + + const onChange = useCallback( + (newOptions: EuiSelectableOption[]) => { + const newSelectedChannels = newOptions.reduce((result, option) => { + if (option.checked === 'on') { + result = [...result, option.label]; + } + return result; + }, []); + + setSelectedChannels(newSelectedChannels); + editAction('subActionParams', { channels: newSelectedChannels, text }, index); + }, + [editAction, index, text] + ); + + return ( + <> + 0 && channels.length === 0} + > + + setIsPopoverOpen(false)} + > + + {(list, search) => ( + <> + {search} + + {list} + + )} + + + + + + + editAction('subActionParams', { channels, text: value }, index) + } + messageVariables={messageVariables} + paramsProperty="webApi" + inputTargetValue={text} + label={i18n.translate('xpack.stackConnectors.components.slack.messageTextAreaFieldLabel', { + defaultMessage: 'Message', + })} + errors={(errors.text ?? []) as string[]} + /> + + ); +}; + +// eslint-disable-next-line import/no-default-export +export { SlackParamsFields as default }; diff --git a/x-pack/plugins/stack_connectors/public/connector_types/slack_api/translations.ts b/x-pack/plugins/stack_connectors/public/connector_types/slack_api/translations.ts new file mode 100644 index 0000000000000..2c3ea5276ab92 --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/slack_api/translations.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 { i18n } from '@kbn/i18n'; + +export const MESSAGE_REQUIRED = i18n.translate( + 'xpack.stackConnectors.components.slack.error.requiredSlackMessageText', + { + defaultMessage: 'Message is required.', + } +); +export const CHANNEL_REQUIRED = i18n.translate( + 'xpack.stackConnectors.components.slack.error.requiredSlackChannel', + { + defaultMessage: 'Selected channel is required.', + } +); +export const TOKEN_LABEL = i18n.translate( + 'xpack.stackConnectors.components.slack.tokenTextFieldLabel', + { + defaultMessage: 'API Token', + } +); +export const WEB_API = i18n.translate('xpack.stackConnectors.components.slack.webApi', { + defaultMessage: 'Web API', +}); +export const SELECT_MESSAGE = i18n.translate( + 'xpack.stackConnectors.components.slack.selectMessageText', + { + defaultMessage: 'Send a message to a Slack channel or user.', + } +); +export const ACTION_TYPE_TITLE = i18n.translate( + 'xpack.stackConnectors.components.slack.connectorTypeTitle', + { + defaultMessage: 'Send to Slack', + } +); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/index.ts b/x-pack/plugins/stack_connectors/server/connector_types/index.ts index 648695bc7cbbd..0cd9a3b5a7194 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/index.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/index.ts @@ -20,7 +20,8 @@ import { getConnectorType as getIndexConnectorType } from './es_index'; import { getConnectorType as getPagerDutyConnectorType } from './pagerduty'; import { getConnectorType as getSwimlaneConnectorType } from './swimlane'; import { getConnectorType as getServerLogConnectorType } from './server_log'; -import { getConnectorType as getSlackConnectorType } from './slack'; +import { getConnectorType as getSlackWebhookConnectorType } from './slack'; +import { getConnectorType as getSlackApiConnectorType } from './slack_api'; import { getConnectorType as getWebhookConnectorType } from './webhook'; import { getConnectorType as getXmattersConnectorType } from './xmatters'; import { getConnectorType as getTeamsConnectorType } from './teams'; @@ -45,8 +46,10 @@ export type { ActionParamsType as PagerDutyActionParams } from './pagerduty'; export { ConnectorTypeId as ServerLogConnectorTypeId } from './server_log'; export type { ActionParamsType as ServerLogActionParams } from './server_log'; export { ServiceNowITOMConnectorTypeId } from './servicenow_itom'; -export { ConnectorTypeId as SlackConnectorTypeId } from './slack'; -export type { ActionParamsType as SlackActionParams } from './slack'; +export { ConnectorTypeId as SlackWebhookConnectorTypeId } from './slack'; +export type { ActionParamsType as SlackWebhookActionParams } from './slack'; +export { SLACK_API_CONNECTOR_ID as SlackApiConnectorTypeId } from '../../common/slack_api/constants'; +export type { SlackApiActionParams as SlackApiActionParams } from '../../common/slack_api/types'; export { ConnectorTypeId as TeamsConnectorTypeId } from './teams'; export type { ActionParamsType as TeamsActionParams } from './teams'; export { ConnectorTypeId as WebhookConnectorTypeId } from './webhook'; @@ -80,7 +83,8 @@ export function registerConnectorTypes({ actions.registerType(getPagerDutyConnectorType()); actions.registerType(getSwimlaneConnectorType()); actions.registerType(getServerLogConnectorType()); - actions.registerType(getSlackConnectorType({})); + actions.registerType(getSlackWebhookConnectorType({})); + actions.registerType(getSlackApiConnectorType()); actions.registerType(getWebhookConnectorType()); actions.registerType(getCasesWebhookConnectorType()); actions.registerType(getXmattersConnectorType()); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/slack_api/api.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/slack_api/api.test.ts new file mode 100644 index 0000000000000..2ae4a998b261a --- /dev/null +++ b/x-pack/plugins/stack_connectors/server/connector_types/slack_api/api.test.ts @@ -0,0 +1,101 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SlackApiService } from '../../../common/slack_api/types'; +import { api } from './api'; + +const createMock = (): jest.Mocked => { + const service = { + postMessage: jest.fn().mockImplementation(() => ({ + ok: true, + channel: 'general', + message: { + text: 'a message', + type: 'message', + }, + })), + getChannels: jest.fn().mockImplementation(() => [ + { + ok: true, + channels: [ + { + id: 'channel_id_1', + name: 'general', + is_channel: true, + is_archived: false, + is_private: true, + }, + { + id: 'channel_id_2', + name: 'privat', + is_channel: true, + is_archived: false, + is_private: false, + }, + ], + }, + ]), + }; + + return service; +}; + +const slackServiceMock = { + create: createMock, +}; + +describe('api', () => { + let externalService: jest.Mocked; + + beforeEach(() => { + externalService = slackServiceMock.create(); + }); + + test('getChannels', async () => { + const res = await api.getChannels({ + externalService, + }); + + expect(res).toEqual([ + { + channels: [ + { + id: 'channel_id_1', + is_archived: false, + is_channel: true, + is_private: true, + name: 'general', + }, + { + id: 'channel_id_2', + is_archived: false, + is_channel: true, + is_private: false, + name: 'privat', + }, + ], + ok: true, + }, + ]); + }); + + test('postMessage', async () => { + const res = await api.postMessage({ + externalService, + params: { channels: ['general'], text: 'a message' }, + }); + + expect(res).toEqual({ + channel: 'general', + message: { + text: 'a message', + type: 'message', + }, + ok: true, + }); + }); +}); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/slack_api/api.ts b/x-pack/plugins/stack_connectors/server/connector_types/slack_api/api.ts new file mode 100644 index 0000000000000..b0445b7c26e41 --- /dev/null +++ b/x-pack/plugins/stack_connectors/server/connector_types/slack_api/api.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 type { PostMessageSubActionParams, SlackApiService } from '../../../common/slack_api/types'; + +const getChannelsHandler = async ({ externalService }: { externalService: SlackApiService }) => + await externalService.getChannels(); + +const postMessageHandler = async ({ + externalService, + params: { channels, text }, +}: { + externalService: SlackApiService; + params: PostMessageSubActionParams; +}) => await externalService.postMessage({ channels, text }); + +export const api = { + getChannels: getChannelsHandler, + postMessage: postMessageHandler, +}; diff --git a/x-pack/plugins/stack_connectors/server/connector_types/slack_api/index.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/slack_api/index.test.ts new file mode 100644 index 0000000000000..66bc3fba1219c --- /dev/null +++ b/x-pack/plugins/stack_connectors/server/connector_types/slack_api/index.test.ts @@ -0,0 +1,303 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import axios from 'axios'; +import { Logger } from '@kbn/core/server'; +import { Services } from '@kbn/actions-plugin/server/types'; +import { validateParams, validateSecrets } from '@kbn/actions-plugin/server/lib'; +import { getConnectorType } from '.'; +import { actionsConfigMock } from '@kbn/actions-plugin/server/actions_config.mock'; +import { actionsMock } from '@kbn/actions-plugin/server/mocks'; +import { ActionsConfigurationUtilities } from '@kbn/actions-plugin/server/actions_config'; +import { loggerMock } from '@kbn/logging-mocks'; +import * as utils from '@kbn/actions-plugin/server/lib/axios_utils'; +import type { PostMessageParams, SlackApiConnectorType } from '../../../common/slack_api/types'; +import { SLACK_API_CONNECTOR_ID } from '../../../common/slack_api/constants'; +import { SLACK_CONNECTOR_NAME } from './translations'; + +jest.mock('axios'); +jest.mock('@kbn/actions-plugin/server/lib/axios_utils', () => { + const originalUtils = jest.requireActual('@kbn/actions-plugin/server/lib/axios_utils'); + return { + ...originalUtils, + request: jest.fn(), + }; +}); + +const requestMock = utils.request as jest.Mock; + +const services: Services = actionsMock.createServices(); +const mockedLogger: jest.Mocked = loggerMock.create(); + +let connectorType: SlackApiConnectorType; +let configurationUtilities: jest.Mocked; + +beforeEach(() => { + configurationUtilities = actionsConfigMock.create(); + connectorType = getConnectorType(); +}); + +describe('connector registration', () => { + test('returns connector type', () => { + expect(connectorType.id).toEqual(SLACK_API_CONNECTOR_ID); + expect(connectorType.name).toEqual(SLACK_CONNECTOR_NAME); + }); +}); + +describe('validate params', () => { + test('should validate and throw error when params are invalid', () => { + expect(() => { + validateParams(connectorType, {}, { configurationUtilities }); + }).toThrowErrorMatchingInlineSnapshot( + `"error validating action params: Cannot destructure property 'Symbol(Symbol.iterator)' of 'undefined' as it is undefined."` + ); + + expect(() => { + validateParams(connectorType, { message: 1 }, { configurationUtilities }); + }).toThrowErrorMatchingInlineSnapshot( + `"error validating action params: Cannot destructure property 'Symbol(Symbol.iterator)' of 'undefined' as it is undefined."` + ); + }); + + test('should validate and pass when params are valid for post message', () => { + expect( + validateParams( + connectorType, + { subAction: 'postMessage', subActionParams: { channels: ['general'], text: 'a text' } }, + { configurationUtilities } + ) + ).toEqual({ + subAction: 'postMessage', + subActionParams: { channels: ['general'], text: 'a text' }, + }); + }); + + test('should validate and pass when params are valid for get channels', () => { + expect( + validateParams(connectorType, { subAction: 'getChannels' }, { configurationUtilities }) + ).toEqual({ + subAction: 'getChannels', + }); + }); +}); + +describe('validate secrets', () => { + test('should validate and throw error when secrets is empty', () => { + expect(() => { + validateSecrets(connectorType, {}, { configurationUtilities }); + }).toThrowErrorMatchingInlineSnapshot( + `"error validating action type secrets: [token]: expected value of type [string] but got [undefined]"` + ); + }); + + test('should validate and pass when secrets is valid', () => { + validateSecrets( + connectorType, + { + token: 'token', + }, + { configurationUtilities } + ); + }); + + test('should validate and throw error when secrets is invalid', () => { + expect(() => { + validateSecrets(connectorType, { token: 1 }, { configurationUtilities }); + }).toThrowErrorMatchingInlineSnapshot( + `"error validating action type secrets: [token]: expected value of type [string] but got [number]"` + ); + }); + + test('config validation returns an error if the specified URL isnt added to allowedHosts', () => { + const configUtils = { + ...actionsConfigMock.create(), + ensureUriAllowed: () => { + throw new Error(`target hostname is not added to allowedHosts`); + }, + }; + + expect(() => { + validateSecrets( + connectorType, + { token: 'fake token' }, + { configurationUtilities: configUtils } + ); + }).toThrowErrorMatchingInlineSnapshot( + `"error validating action type secrets: error configuring slack action: target hostname is not added to allowedHosts"` + ); + }); +}); + +describe('execute', () => { + beforeEach(() => { + jest.resetAllMocks(); + axios.create = jest.fn().mockImplementation(() => axios); + connectorType = getConnectorType(); + }); + + test('should fail if params does not include subAction', async () => { + requestMock.mockImplementation(() => ({ + data: { + ok: true, + message: { text: 'some text' }, + channel: 'general', + }, + })); + + await expect( + connectorType.executor({ + actionId: SLACK_API_CONNECTOR_ID, + config: {}, + services, + secrets: { token: 'some token' }, + params: {} as PostMessageParams, + configurationUtilities, + logger: mockedLogger, + }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"[Action][ExternalService] -> [Slack API] Unsupported subAction type undefined."` + ); + }); + + test('should fail if subAction is not postMessage/getChannels', async () => { + requestMock.mockImplementation(() => ({ + data: { + ok: true, + message: { text: 'some text' }, + channel: 'general', + }, + })); + + await expect( + connectorType.executor({ + actionId: SLACK_API_CONNECTOR_ID, + services, + config: {}, + secrets: { token: 'some token' }, + params: { + subAction: 'getMessage' as 'getChannels', + }, + configurationUtilities, + logger: mockedLogger, + }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"[Action][ExternalService] -> [Slack API] Unsupported subAction type getMessage."` + ); + }); + + test('renders parameter templates as expected', async () => { + expect(connectorType.renderParameterTemplates).toBeTruthy(); + const paramsWithTemplates = { + subAction: 'postMessage' as const, + subActionParams: { text: 'some text', channels: ['general'] }, + }; + const variables = { rogue: '*bold*' }; + const params = connectorType.renderParameterTemplates!( + paramsWithTemplates, + variables + ) as PostMessageParams; + expect(params.subActionParams.text).toBe('some text'); + }); + + test('should execute with success for post message', async () => { + requestMock.mockImplementation(() => ({ + data: { + ok: true, + message: { text: 'some text' }, + channel: 'general', + }, + })); + + const response = await connectorType.executor({ + actionId: SLACK_API_CONNECTOR_ID, + services, + config: {}, + secrets: { token: 'some token' }, + params: { + subAction: 'postMessage', + subActionParams: { channels: ['general'], text: 'some text' }, + }, + configurationUtilities, + logger: mockedLogger, + }); + + expect(requestMock).toHaveBeenCalledWith({ + axios, + configurationUtilities, + logger: mockedLogger, + method: 'post', + url: 'chat.postMessage', + data: { channel: 'general', text: 'some text' }, + }); + + expect(response).toEqual({ + actionId: SLACK_API_CONNECTOR_ID, + data: { + channel: 'general', + message: { + text: 'some text', + }, + ok: true, + }, + + status: 'ok', + }); + }); + + test('should execute with success for get channels', async () => { + requestMock.mockImplementation(() => ({ + data: { + ok: true, + channels: [ + { + id: 'id', + name: 'general', + is_channel: true, + is_archived: false, + is_private: true, + }, + ], + }, + })); + const response = await connectorType.executor({ + actionId: SLACK_API_CONNECTOR_ID, + services, + config: {}, + secrets: { token: 'some token' }, + params: { + subAction: 'getChannels', + }, + configurationUtilities, + logger: mockedLogger, + }); + + expect(requestMock).toHaveBeenCalledWith({ + axios, + configurationUtilities, + logger: mockedLogger, + method: 'get', + url: 'conversations.list?types=public_channel,private_channel', + }); + + expect(response).toEqual({ + actionId: SLACK_API_CONNECTOR_ID, + data: { + channels: [ + { + id: 'id', + is_archived: false, + is_channel: true, + is_private: true, + name: 'general', + }, + ], + ok: true, + }, + status: 'ok', + }); + }); +}); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/slack_api/index.ts b/x-pack/plugins/stack_connectors/server/connector_types/slack_api/index.ts new file mode 100644 index 0000000000000..ee467dad3d8a2 --- /dev/null +++ b/x-pack/plugins/stack_connectors/server/connector_types/slack_api/index.ts @@ -0,0 +1,124 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { ActionTypeExecutorResult } from '@kbn/actions-plugin/server/types'; +import { + AlertingConnectorFeatureId, + SecurityConnectorFeatureId, +} from '@kbn/actions-plugin/common/types'; +import { renderMustacheString } from '@kbn/actions-plugin/server/lib/mustache_renderer'; +import type { ValidatorServices } from '@kbn/actions-plugin/server/types'; +import { i18n } from '@kbn/i18n'; +import { schema } from '@kbn/config-schema'; +import type { + SlackApiExecutorOptions, + SlackApiConnectorType, + SlackApiParams, + SlackApiSecrets, +} from '../../../common/slack_api/types'; +import { SlackApiSecretsSchema, SlackApiParamsSchema } from '../../../common/slack_api/schema'; +import { SLACK_API_CONNECTOR_ID, SLACK_URL } from '../../../common/slack_api/constants'; +import { SLACK_CONNECTOR_NAME } from './translations'; +import { api } from './api'; +import { createExternalService } from './service'; + +const supportedSubActions = ['getChannels', 'postMessage']; + +export const getConnectorType = (): SlackApiConnectorType => { + return { + id: SLACK_API_CONNECTOR_ID, + minimumLicenseRequired: 'gold', + name: SLACK_CONNECTOR_NAME, + supportedFeatureIds: [AlertingConnectorFeatureId, SecurityConnectorFeatureId], + validate: { + config: { schema: schema.object({}, { defaultValue: {} }) }, + secrets: { + schema: SlackApiSecretsSchema, + customValidator: validateSlackUrl, + }, + params: { + schema: SlackApiParamsSchema, + }, + }, + renderParameterTemplates, + executor: async (execOptions: SlackApiExecutorOptions) => await slackApiExecutor(execOptions), + }; +}; + +const validateSlackUrl = (secretsObject: SlackApiSecrets, validatorServices: ValidatorServices) => { + const { configurationUtilities } = validatorServices; + + try { + configurationUtilities.ensureUriAllowed(SLACK_URL); + } catch (allowedListError) { + throw new Error( + i18n.translate('xpack.stackConnectors.slack_api.configurationError', { + defaultMessage: 'error configuring slack action: {message}', + values: { + message: allowedListError.message, + }, + }) + ); + } +}; + +const renderParameterTemplates = (params: SlackApiParams, variables: Record) => { + if (params.subAction === 'postMessage') + return { + subAction: params.subAction, + subActionParams: { + ...params.subActionParams, + text: renderMustacheString(params.subActionParams.text, variables, 'slack'), + }, + }; + return params; +}; + +const slackApiExecutor = async ({ + actionId, + params, + secrets, + configurationUtilities, + logger, +}: SlackApiExecutorOptions): Promise> => { + const subAction = params.subAction; + + if (!api[subAction]) { + const errorMessage = `[Action][ExternalService] -> [Slack API] Unsupported subAction type ${subAction}.`; + logger.error(errorMessage); + throw new Error(errorMessage); + } + + if (!supportedSubActions.includes(subAction)) { + const errorMessage = `[Action][ExternalService] -> [Slack API] subAction ${subAction} not implemented.`; + logger.error(errorMessage); + throw new Error(errorMessage); + } + + const externalService = createExternalService( + { + secrets, + }, + logger, + configurationUtilities + ); + + if (subAction === 'getChannels') { + return await api.getChannels({ + externalService, + }); + } + + if (subAction === 'postMessage') { + return await api.postMessage({ + externalService, + params: params.subActionParams, + }); + } + + return { status: 'ok', data: {}, actionId }; +}; diff --git a/x-pack/plugins/stack_connectors/server/connector_types/slack_api/service.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/slack_api/service.test.ts new file mode 100644 index 0000000000000..350c6fc103fb4 --- /dev/null +++ b/x-pack/plugins/stack_connectors/server/connector_types/slack_api/service.test.ts @@ -0,0 +1,181 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import axios from 'axios'; +import { request, createAxiosResponse } from '@kbn/actions-plugin/server/lib/axios_utils'; +import { Logger } from '@kbn/core/server'; +import { loggingSystemMock } from '@kbn/core/server/mocks'; +import { actionsConfigMock } from '@kbn/actions-plugin/server/actions_config.mock'; +import { createExternalService } from './service'; +import { SlackApiService } from '../../../common/slack_api/types'; +import { SLACK_API_CONNECTOR_ID } from '../../../common/slack_api/constants'; + +const logger = loggingSystemMock.create().get() as jest.Mocked; + +jest.mock('axios'); +jest.mock('@kbn/actions-plugin/server/lib/axios_utils', () => { + const originalUtils = jest.requireActual('@kbn/actions-plugin/server/lib/axios_utils'); + return { + ...originalUtils, + request: jest.fn(), + }; +}); + +axios.create = jest.fn(() => axios); +const requestMock = request as jest.Mock; +const configurationUtilities = actionsConfigMock.create(); + +const channels = [ + { + id: 'channel_id_1', + name: 'general', + is_channel: true, + is_archived: false, + is_private: true, + }, + { + id: 'channel_id_2', + name: 'privat', + is_channel: true, + is_archived: false, + is_private: false, + }, +]; + +const getChannelsResponse = createAxiosResponse({ + data: { + ok: true, + channels, + }, +}); + +const postMessageResponse = createAxiosResponse({ + data: [ + { + ok: true, + channel: 'general', + message: { + text: 'a message', + type: 'message', + }, + }, + { + ok: true, + channel: 'privat', + message: { + text: 'a message', + type: 'message', + }, + }, + ], +}); + +describe('Slack API service', () => { + let service: SlackApiService; + + beforeAll(() => { + service = createExternalService( + { + secrets: { token: 'token' }, + }, + logger, + configurationUtilities + ); + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('Secrets validation', () => { + test('throws without token', () => { + expect(() => + createExternalService( + { + secrets: { token: '' }, + }, + logger, + configurationUtilities + ) + ).toThrowErrorMatchingInlineSnapshot(`"[Action][Slack API]: Wrong configuration."`); + }); + }); + + describe('getChannels', () => { + test('should get slack channels', async () => { + requestMock.mockImplementation(() => getChannelsResponse); + const res = await service.getChannels(); + expect(res).toEqual({ + actionId: SLACK_API_CONNECTOR_ID, + data: { + ok: true, + channels, + }, + status: 'ok', + }); + }); + + test('should call request with correct arguments', async () => { + requestMock.mockImplementation(() => getChannelsResponse); + + await service.getChannels(); + expect(requestMock).toHaveBeenCalledWith({ + axios, + logger, + configurationUtilities, + method: 'get', + url: 'conversations.list?types=public_channel,private_channel', + }); + }); + + test('should throw an error if request to slack fail', async () => { + requestMock.mockImplementation(() => { + throw new Error('request fail'); + }); + + expect(await service.getChannels()).toEqual({ + actionId: SLACK_API_CONNECTOR_ID, + message: 'error posting slack message', + serviceMessage: 'request fail', + status: 'error', + }); + }); + }); + + describe('postMessage', () => { + test('should call request with correct arguments', async () => { + requestMock.mockImplementation(() => postMessageResponse); + + await service.postMessage({ channels: ['general', 'privat'], text: 'a message' }); + + expect(requestMock).toHaveBeenCalledTimes(1); + expect(requestMock).toHaveBeenNthCalledWith(1, { + axios, + logger, + configurationUtilities, + method: 'post', + url: 'chat.postMessage', + data: { channel: 'general', text: 'a message' }, + }); + }); + + test('should throw an error if request to slack fail', async () => { + requestMock.mockImplementation(() => { + throw new Error('request fail'); + }); + + expect( + await service.postMessage({ channels: ['general', 'privat'], text: 'a message' }) + ).toEqual({ + actionId: SLACK_API_CONNECTOR_ID, + message: 'error posting slack message', + serviceMessage: 'request fail', + status: 'error', + }); + }); + }); +}); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/slack_api/service.ts b/x-pack/plugins/stack_connectors/server/connector_types/slack_api/service.ts new file mode 100644 index 0000000000000..723d629a74418 --- /dev/null +++ b/x-pack/plugins/stack_connectors/server/connector_types/slack_api/service.ts @@ -0,0 +1,162 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import axios, { AxiosResponse } from 'axios'; +import { Logger } from '@kbn/core/server'; +import { i18n } from '@kbn/i18n'; +import { ActionsConfigurationUtilities } from '@kbn/actions-plugin/server/actions_config'; +import { request } from '@kbn/actions-plugin/server/lib/axios_utils'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { map, getOrElse } from 'fp-ts/lib/Option'; +import type { ActionTypeExecutorResult as ConnectorTypeExecutorResult } from '@kbn/actions-plugin/server/types'; +import { SLACK_CONNECTOR_NAME } from './translations'; +import type { + PostMessageSubActionParams, + SlackApiService, + PostMessageResponse, +} from '../../../common/slack_api/types'; +import { + retryResultSeconds, + retryResult, + serviceErrorResult, + errorResult, + successResult, +} from '../../../common/slack_api/lib'; +import { SLACK_API_CONNECTOR_ID, SLACK_URL } from '../../../common/slack_api/constants'; +import { getRetryAfterIntervalFromHeaders } from '../lib/http_response_retry_header'; + +const buildSlackExecutorErrorResponse = ({ + slackApiError, + logger, +}: { + slackApiError: { + message: string; + response: { + status: number; + statusText: string; + headers: Record; + }; + }; + logger: Logger; +}) => { + if (!slackApiError.response) { + return serviceErrorResult(SLACK_API_CONNECTOR_ID, slackApiError.message); + } + + const { status, statusText, headers } = slackApiError.response; + + // special handling for 5xx + if (status >= 500) { + return retryResult(SLACK_API_CONNECTOR_ID, slackApiError.message); + } + + // special handling for rate limiting + if (status === 429) { + return pipe( + getRetryAfterIntervalFromHeaders(headers), + map((retry) => retryResultSeconds(SLACK_API_CONNECTOR_ID, slackApiError.message, retry)), + getOrElse(() => retryResult(SLACK_API_CONNECTOR_ID, slackApiError.message)) + ); + } + + const errorMessage = i18n.translate( + 'xpack.stackConnectors.slack.unexpectedHttpResponseErrorMessage', + { + defaultMessage: 'unexpected http response from slack: {httpStatus} {httpStatusText}', + values: { + httpStatus: status, + httpStatusText: statusText, + }, + } + ); + logger.error(`error on ${SLACK_API_CONNECTOR_ID} slack action: ${errorMessage}`); + + return errorResult(SLACK_API_CONNECTOR_ID, errorMessage); +}; + +const buildSlackExecutorSuccessResponse = ({ + slackApiResponseData, +}: { + slackApiResponseData: PostMessageResponse; +}) => { + if (!slackApiResponseData) { + const errMessage = i18n.translate( + 'xpack.stackConnectors.slack.unexpectedNullResponseErrorMessage', + { + defaultMessage: 'unexpected null response from slack', + } + ); + return errorResult(SLACK_API_CONNECTOR_ID, errMessage); + } + + if (!slackApiResponseData.ok) { + return serviceErrorResult(SLACK_API_CONNECTOR_ID, slackApiResponseData.error); + } + + return successResult(SLACK_API_CONNECTOR_ID, slackApiResponseData); +}; + +export const createExternalService = ( + { secrets }: { secrets: { token: string } }, + logger: Logger, + configurationUtilities: ActionsConfigurationUtilities +): SlackApiService => { + const { token } = secrets; + + if (!token) { + throw Error(`[Action][${SLACK_CONNECTOR_NAME}]: Wrong configuration.`); + } + + const axiosInstance = axios.create({ + baseURL: SLACK_URL, + headers: { + Authorization: `Bearer ${token}`, + 'Content-type': 'application/json; charset=UTF-8', + }, + }); + + const getChannels = async (): Promise> => { + try { + const result = await request({ + axios: axiosInstance, + configurationUtilities, + logger, + method: 'get', + url: 'conversations.list?types=public_channel,private_channel', + }); + + return buildSlackExecutorSuccessResponse({ slackApiResponseData: result.data }); + } catch (error) { + return buildSlackExecutorErrorResponse({ slackApiError: error, logger }); + } + }; + + const postMessage = async ({ + channels, + text, + }: PostMessageSubActionParams): Promise> => { + try { + const result: AxiosResponse = await request({ + axios: axiosInstance, + method: 'post', + url: 'chat.postMessage', + logger, + data: { channel: channels[0], text }, + configurationUtilities, + }); + + return buildSlackExecutorSuccessResponse({ slackApiResponseData: result.data }); + } catch (error) { + return buildSlackExecutorErrorResponse({ slackApiError: error, logger }); + } + }; + + return { + getChannels, + postMessage, + }; +}; diff --git a/x-pack/plugins/stack_connectors/server/connector_types/slack_api/translations.ts b/x-pack/plugins/stack_connectors/server/connector_types/slack_api/translations.ts new file mode 100644 index 0000000000000..03157b6c6d53f --- /dev/null +++ b/x-pack/plugins/stack_connectors/server/connector_types/slack_api/translations.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 { i18n } from '@kbn/i18n'; + +export const SLACK_CONNECTOR_NAME = i18n.translate('xpack.stackConnectors.slackApi.title', { + defaultMessage: 'Slack API', +}); diff --git a/x-pack/plugins/stack_connectors/server/plugin.test.ts b/x-pack/plugins/stack_connectors/server/plugin.test.ts index bfc2bb9fd4197..a572970e0be15 100644 --- a/x-pack/plugins/stack_connectors/server/plugin.test.ts +++ b/x-pack/plugins/stack_connectors/server/plugin.test.ts @@ -25,7 +25,7 @@ describe('Stack Connectors Plugin', () => { it('should register built in connector types', () => { const actionsSetup = actionsMock.createSetup(); plugin.setup(coreSetup, { actions: actionsSetup }); - expect(actionsSetup.registerType).toHaveBeenCalledTimes(16); + expect(actionsSetup.registerType).toHaveBeenCalledTimes(17); expect(actionsSetup.registerType).toHaveBeenNthCalledWith( 1, expect.objectContaining({ @@ -69,63 +69,63 @@ describe('Stack Connectors Plugin', () => { }) ); expect(actionsSetup.registerType).toHaveBeenNthCalledWith( - 7, + 8, expect.objectContaining({ id: '.webhook', name: 'Webhook', }) ); expect(actionsSetup.registerType).toHaveBeenNthCalledWith( - 8, + 9, expect.objectContaining({ id: '.cases-webhook', name: 'Webhook - Case Management', }) ); expect(actionsSetup.registerType).toHaveBeenNthCalledWith( - 9, + 10, expect.objectContaining({ id: '.xmatters', name: 'xMatters', }) ); expect(actionsSetup.registerType).toHaveBeenNthCalledWith( - 10, + 11, expect.objectContaining({ id: '.servicenow', name: 'ServiceNow ITSM', }) ); expect(actionsSetup.registerType).toHaveBeenNthCalledWith( - 11, + 12, expect.objectContaining({ id: '.servicenow-sir', name: 'ServiceNow SecOps', }) ); expect(actionsSetup.registerType).toHaveBeenNthCalledWith( - 12, + 13, expect.objectContaining({ id: '.servicenow-itom', name: 'ServiceNow ITOM', }) ); expect(actionsSetup.registerType).toHaveBeenNthCalledWith( - 13, + 14, expect.objectContaining({ id: '.jira', name: 'Jira', }) ); expect(actionsSetup.registerType).toHaveBeenNthCalledWith( - 14, + 15, expect.objectContaining({ id: '.resilient', name: 'IBM Resilient', }) ); expect(actionsSetup.registerType).toHaveBeenNthCalledWith( - 15, + 16, expect.objectContaining({ id: '.teams', name: 'Microsoft Teams', diff --git a/x-pack/plugins/stack_connectors/server/types.ts b/x-pack/plugins/stack_connectors/server/types.ts index 697c6a358fbe0..d9cd9f9b99cad 100644 --- a/x-pack/plugins/stack_connectors/server/types.ts +++ b/x-pack/plugins/stack_connectors/server/types.ts @@ -21,8 +21,10 @@ export type { PagerDutyActionParams, ServerLogConnectorTypeId, ServerLogActionParams, - SlackConnectorTypeId, - SlackActionParams, + SlackApiConnectorTypeId, + SlackApiActionParams, + SlackWebhookConnectorTypeId, + SlackWebhookActionParams, WebhookConnectorTypeId, WebhookActionParams, ServiceNowITSMConnectorTypeId, diff --git a/x-pack/plugins/synthetics/common/rules/alert_actions.ts b/x-pack/plugins/synthetics/common/rules/alert_actions.ts index 0c9782108743f..2dc1990e26b45 100644 --- a/x-pack/plugins/synthetics/common/rules/alert_actions.ts +++ b/x-pack/plugins/synthetics/common/rules/alert_actions.ts @@ -20,7 +20,7 @@ import { v4 as uuidv4 } from 'uuid'; import { ActionConnector, ActionTypeId } from './types'; import { DefaultEmail } from '../runtime_types'; -export const SLACK_ACTION_ID: ActionTypeId = '.slack'; +export const SLACK_WEBHOOK_ACTION_ID: ActionTypeId = '.slack'; export const PAGER_DUTY_ACTION_ID: ActionTypeId = '.pagerduty'; export const SERVER_LOG_ACTION_ID: ActionTypeId = '.server-log'; export const INDEX_ACTION_ID: ActionTypeId = '.index'; @@ -98,7 +98,7 @@ export function populateAlertActions({ recoveredAction.params = getWebhookActionParams(translations, true); actions.push(recoveredAction); break; - case SLACK_ACTION_ID: + case SLACK_WEBHOOK_ACTION_ID: case TEAMS_ACTION_ID: action.params = { message: translations.defaultActionMessage, diff --git a/x-pack/plugins/synthetics/common/rules/types.ts b/x-pack/plugins/synthetics/common/rules/types.ts index 101ce9c1418c6..c398d66e376a2 100644 --- a/x-pack/plugins/synthetics/common/rules/types.ts +++ b/x-pack/plugins/synthetics/common/rules/types.ts @@ -11,7 +11,7 @@ import type { PagerDutyConnectorTypeId, ServerLogConnectorTypeId, ServiceNowITSMConnectorTypeId as ServiceNowConnectorTypeId, - SlackConnectorTypeId, + SlackWebhookConnectorTypeId, TeamsConnectorTypeId, WebhookConnectorTypeId, EmailConnectorTypeId, @@ -20,7 +20,7 @@ import type { import type { ActionConnector as RawActionConnector } from '@kbn/triggers-actions-ui-plugin/public'; export type ActionTypeId = - | typeof SlackConnectorTypeId + | typeof SlackWebhookConnectorTypeId | typeof PagerDutyConnectorTypeId | typeof ServerLogConnectorTypeId | typeof IndexConnectorTypeId diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx index 6b7ede2f01747..25feead9c518e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx @@ -228,7 +228,8 @@ export const ActionForm = ({ return; } setIsAddActionPanelOpen(false); - const actionTypeConnectors = connectors.filter( + const allowGroupConnector = (actionTypeModel?.subtype ?? []).map((atm) => atm.id); + let actionTypeConnectors = connectors.filter( (field) => field.actionTypeId === actionTypeModel.id ); @@ -241,7 +242,22 @@ export const ActionForm = ({ frequency: defaultRuleFrequency, }); setActionIdByIndex(actionTypeConnectors[0].id, actions.length - 1); + } else { + actionTypeConnectors = connectors.filter((field) => + allowGroupConnector.includes(field.actionTypeId) + ); + if (actionTypeConnectors.length > 0) { + actions.push({ + id: '', + actionTypeId: actionTypeConnectors[0].actionTypeId, + group: defaultActionGroupId, + params: {}, + frequency: DEFAULT_FREQUENCY, + }); + setActionIdByIndex(actionTypeConnectors[0].id, actions.length - 1); + } } + if (actionTypeConnectors.length === 0) { // if no connectors exists or all connectors is already assigned an action under current alert // set actionType as id to be able to create new connector within the alert form @@ -263,7 +279,7 @@ export const ActionForm = ({ const preconfiguredConnectors = connectors.filter((connector) => connector.isPreconfigured); actionTypeNodes = actionTypeRegistry .list() - .filter((item) => actionTypesIndex[item.id]) + .filter((item) => actionTypesIndex[item.id] && !item.hideInUi) .filter((item) => !!item.actionParamsFields) .sort((a, b) => actionTypeCompare(actionTypesIndex[a.id], actionTypesIndex[b.id], preconfiguredConnectors) @@ -378,6 +394,27 @@ export const ActionForm = ({ }} onSelectConnector={(connectorId: string) => { setActionIdByIndex(connectorId, index); + const newConnector = connectors.find((connector) => connector.id === connectorId); + if (newConnector && newConnector.actionTypeId) { + const actionTypeRegistered = actionTypeRegistry.get(newConnector.actionTypeId); + if (actionTypeRegistered.convertParamsBetweenGroups) { + const updatedActions = actions.map((_item: RuleAction, i: number) => { + if (i === index) { + return { + ..._item, + actionTypeId: newConnector.actionTypeId, + id: connectorId, + params: + actionTypeRegistered.convertParamsBetweenGroups != null + ? actionTypeRegistered.convertParamsBetweenGroups(_item.params) + : {}, + }; + } + return _item; + }); + setActions(updatedActions); + } + } }} /> ); @@ -407,6 +444,31 @@ export const ActionForm = ({ }} onConnectorSelected={(id: string) => { setActionIdByIndex(id, index); + const newConnector = connectors.find((connector) => connector.id === id); + if ( + newConnector && + actionConnector && + newConnector.actionTypeId !== actionConnector.actionTypeId + ) { + const actionTypeRegistered = actionTypeRegistry.get(newConnector.actionTypeId); + if (actionTypeRegistered.convertParamsBetweenGroups) { + const updatedActions = actions.map((_item: RuleAction, i: number) => { + if (i === index) { + return { + ..._item, + actionTypeId: newConnector.actionTypeId, + id, + params: + actionTypeRegistered.convertParamsBetweenGroups != null + ? actionTypeRegistered.convertParamsBetweenGroups(_item.params) + : {}, + }; + } + return _item; + }); + setActions(updatedActions); + } + } }} actionTypeRegistry={actionTypeRegistry} onDeleteAction={() => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx index 1743d435b722f..a5222ac091800 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx @@ -308,6 +308,7 @@ export const ActionTypeForm = ({ const actionTypeRegistered = actionTypeRegistry.get(actionConnector.actionTypeId); if (!actionTypeRegistered) return null; + const allowGroupConnector = (actionTypeRegistered?.subtype ?? []).map((atr) => atr.id); const showActionGroupErrorIcon = (): boolean => { return !isOpen && some(actionParamsErrors.errors, (error) => !isEmpty(error)); @@ -362,6 +363,7 @@ export const ActionTypeForm = ({ } > { }, actionConnectorFields: null, }); - actionTypeRegistry.get.mockReturnValueOnce(actionType); - loadActionTypes.mockResolvedValueOnce([ + actionTypeRegistry.get.mockReturnValue(actionType); + loadActionTypes.mockResolvedValue([ { id: actionType.id, enabled: true, @@ -137,8 +137,8 @@ describe('connector_add_flyout', () => { }, actionConnectorFields: null, }); - actionTypeRegistry.get.mockReturnValueOnce(actionType); - loadActionTypes.mockResolvedValueOnce([ + actionTypeRegistry.get.mockReturnValue(actionType); + loadActionTypes.mockResolvedValue([ { id: actionType.id, enabled: false, @@ -180,8 +180,8 @@ describe('connector_add_flyout', () => { }, actionConnectorFields: null, }); - actionTypeRegistry.get.mockReturnValueOnce(actionType); - loadActionTypes.mockResolvedValueOnce([ + actionTypeRegistry.get.mockReturnValue(actionType); + loadActionTypes.mockResolvedValue([ { id: actionType.id, enabled: false, @@ -221,8 +221,8 @@ describe('connector_add_flyout', () => { actionConnectorFields: null, isExperimental: false, }); - actionTypeRegistry.get.mockReturnValueOnce(actionType); - loadActionTypes.mockResolvedValueOnce([ + actionTypeRegistry.get.mockReturnValue(actionType); + loadActionTypes.mockResolvedValue([ { id: actionType.id, enabled: false, @@ -263,8 +263,8 @@ describe('connector_add_flyout', () => { actionConnectorFields: null, isExperimental: true, }); - actionTypeRegistry.get.mockReturnValueOnce(actionType); - loadActionTypes.mockResolvedValueOnce([ + actionTypeRegistry.get.mockReturnValue(actionType); + loadActionTypes.mockResolvedValue([ { id: actionType.id, enabled: false, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_menu.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_menu.tsx index 60b153badffe6..f4717bb512a0c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_menu.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_menu.tsx @@ -22,6 +22,7 @@ interface Props { onActionTypeChange: (actionType: ActionType) => void; featureId?: string; setHasActionsUpgradeableByTrial?: (value: boolean) => void; + setAllActionTypes?: (actionsType: ActionTypeIndex) => void; actionTypeRegistry: ActionTypeRegistryContract; } @@ -29,6 +30,7 @@ export const ActionTypeMenu = ({ onActionTypeChange, featureId, setHasActionsUpgradeableByTrial, + setAllActionTypes, actionTypeRegistry, }: Props) => { const { @@ -37,7 +39,6 @@ export const ActionTypeMenu = ({ } = useKibana().services; const [loadingActionTypes, setLoadingActionTypes] = useState(false); const [actionTypesIndex, setActionTypesIndex] = useState(undefined); - useEffect(() => { (async () => { try { @@ -50,6 +51,9 @@ export const ActionTypeMenu = ({ index[actionTypeItem.id] = actionTypeItem; } setActionTypesIndex(index); + if (setAllActionTypes) { + setAllActionTypes(index); + } // determine if there are actions disabled by license that that // would be enabled by upgrading to gold or trial if (setHasActionsUpgradeableByTrial) { @@ -73,9 +77,13 @@ export const ActionTypeMenu = ({ })(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - const registeredActionTypes = Object.entries(actionTypesIndex ?? []) - .filter(([id, details]) => actionTypeRegistry.has(id) && details.enabledInConfig === true) + .filter( + ([id, details]) => + actionTypeRegistry.has(id) && + details.enabledInConfig === true && + !actionTypeRegistry.get(id).hideInUi + ) .map(([id, actionType]) => { const actionTypeModel = actionTypeRegistry.get(id); return { @@ -100,7 +108,9 @@ export const ActionTypeMenu = ({ title={item.name} description={item.selectMessage} isDisabled={!checkEnabledResult.isEnabled} - onClick={() => onActionTypeChange(item.actionType)} + onClick={() => { + onActionTypeChange(item.actionType); + }} /> ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_inline.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_inline.tsx index 530d41a4c27d1..ec5e4ef01ceec 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_inline.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_inline.tsx @@ -65,6 +65,7 @@ export const AddConnectorInline = ({ ? actionTypesIndex[actionItem.actionTypeId].name : actionItem.actionTypeId; const actionTypeRegistered = actionTypeRegistry.get(actionItem.actionTypeId); + const allowGroupConnector = (actionTypeRegistered?.subtype ?? []).map((subtype) => subtype.id); const connectorDropdownErrors = useMemo( () => [`Unable to load ${actionTypeRegistered.actionTypeTitle} connector`], [actionTypeRegistered.actionTypeTitle] @@ -90,7 +91,12 @@ export const AddConnectorInline = ({ ); useEffect(() => { - const filteredConnectors = getValidConnectors(connectors, actionItem, actionTypesIndex); + const filteredConnectors = getValidConnectors( + connectors, + actionItem, + actionTypesIndex, + allowGroupConnector + ); if (filteredConnectors.length > 0) { setHasConnectors(true); @@ -134,6 +140,7 @@ export const AddConnectorInline = ({ actionTypeRegistered={actionTypeRegistered} connectors={connectors} onConnectorSelected={onSelectConnector} + allowGroupConnector={allowGroupConnector} /> ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx index 4f704af65827d..1c2e1ae1e7318 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx @@ -19,16 +19,26 @@ import { EuiIcon, EuiFlexGroup, EuiBetaBadge, + EuiButtonGroup, + EuiSpacer, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import './connector_add_modal.scss'; import { betaBadgeProps } from './beta_badge_props'; import { hasSaveActionsCapability } from '../../lib/capabilities'; -import { ActionType, ActionConnector, ActionTypeRegistryContract } from '../../../types'; +import { + ActionType, + ActionConnector, + ActionTypeRegistryContract, + ActionTypeModel, + ActionTypeIndex, +} from '../../../types'; import { useKibana } from '../../../common/lib/kibana'; import { useCreateConnector } from '../../hooks/use_create_connector'; -import { ConnectorForm, ConnectorFormState } from './connector_form'; +import { ConnectorForm, ConnectorFormState, ResetForm } from './connector_form'; import { ConnectorFormSchema } from './types'; +import { loadActionTypes } from '../../lib/action_connector_api'; +import { SectionLoading } from '../../components'; export interface ConnectorAddModalProps { actionType: ActionType; @@ -38,27 +48,74 @@ export interface ConnectorAddModalProps { } const ConnectorAddModal = ({ - actionType, + actionType: tempActionType, onClose, postSaveEventHandler, actionTypeRegistry, }: ConnectorAddModalProps) => { const { application: { capabilities }, + http, + notifications: { toasts }, } = useKibana().services; - + const [actionType, setActionType] = useState(tempActionType); + const [loadingActionTypes, setLoadingActionTypes] = useState(false); + const [allActionTypes, setAllActionTypes] = useState(undefined); const { isLoading: isSavingConnector, createConnector } = useCreateConnector(); const isMounted = useRef(false); - const initialConnector = { - actionTypeId: actionType.id, + const [initialConnector, setInitialConnector] = useState({ + actionTypeId: actionType?.id ?? '', isDeprecated: false, - isMissingSecrets: false, config: {}, secrets: {}, - }; + isMissingSecrets: false, + }); const canSave = hasSaveActionsCapability(capabilities); const actionTypeModel = actionTypeRegistry.get(actionType.id); + const groupActionTypeModel: Array = + actionTypeModel && actionTypeModel.subtype + ? (actionTypeModel?.subtype ?? []).map((subtypeAction) => ({ + ...actionTypeRegistry.get(subtypeAction.id), + name: subtypeAction.name, + })) + : []; + + const groupActionButtons = groupActionTypeModel.map((gAction) => ({ + id: gAction.id, + label: gAction.name, + 'data-test-subj': `${gAction.id}Button`, + })); + + const resetConnectorForm = useRef(); + + const setResetForm = (reset: ResetForm) => { + resetConnectorForm.current = reset; + }; + + const onChangeGroupAction = (id: string) => { + if (allActionTypes && allActionTypes[id]) { + setActionType(allActionTypes[id]); + setInitialConnector({ + actionTypeId: id, + isDeprecated: false, + config: {}, + secrets: {}, + isMissingSecrets: false, + }); + if (resetConnectorForm.current) { + resetConnectorForm.current({ + resetValues: true, + defaultValue: { + actionTypeId: id, + isDeprecated: false, + config: {}, + secrets: {}, + }, + }); + } + } + }; const [preSubmitValidationErrorMessage, setPreSubmitValidationErrorMessage] = useState(null); @@ -131,8 +188,39 @@ const ConnectorAddModal = ({ }; }, []); + useEffect(() => { + (async () => { + try { + setLoadingActionTypes(true); + const availableActionTypes = await loadActionTypes({ http }); + setLoadingActionTypes(false); + + const index: ActionTypeIndex = {}; + for (const actionTypeItem of availableActionTypes) { + index[actionTypeItem.id] = actionTypeItem; + } + setAllActionTypes(index); + } catch (e) { + if (toasts) { + toasts.addDanger({ + title: i18n.translate( + 'xpack.triggersActionsUI.sections.actionsConnectorsList.unableToLoadConnectorTypesMessage', + { defaultMessage: 'Unable to load connector types' } + ), + }); + } + } + })(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + return ( - + {actionTypeModel && actionTypeModel.iconClass ? ( @@ -167,13 +255,40 @@ const ConnectorAddModal = ({ - - {preSubmitValidationErrorMessage} + {loadingActionTypes ? ( + + + + ) : ( + <> + {groupActionTypeModel && ( + <> + + + + )} + + {preSubmitValidationErrorMessage} + + )} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_form.tsx index b0d8072dae652..d9e59ac19db94 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_form.tsx @@ -27,6 +27,16 @@ export interface ConnectorFormState { preSubmitValidator: ConnectorValidationFunc | null; } +export type ResetForm = ( + options?: + | { + resetValues?: boolean | undefined; + defaultValue?: + | Partial, Record>> + | undefined; + } + | undefined +) => void; interface Props { actionTypeModel: ActionTypeModel | null; connector: ConnectorFormSchema & { isMissingSecrets: boolean }; @@ -35,6 +45,7 @@ interface Props { onChange?: (state: ConnectorFormState) => void; /** Handler to receive update on the form "isModified" state */ onFormModifiedChange?: (isModified: boolean) => void; + setResetForm?: (value: ResetForm) => void; } /** * The serializer and deserializer are needed to transform the headers of @@ -101,13 +112,14 @@ const ConnectorFormComponent: React.FC = ({ isEdit, onChange, onFormModifiedChange, + setResetForm, }) => { const { form } = useForm({ defaultValue: connector, serializer: formSerializer, deserializer: formDeserializer, }); - const { submit, isValid: isFormValid, isSubmitted, isSubmitting } = form; + const { submit, isValid: isFormValid, isSubmitted, isSubmitting, reset } = form; const [preSubmitValidator, setPreSubmitValidator] = useState( null ); @@ -133,6 +145,13 @@ const ConnectorFormComponent: React.FC = ({ } }, [isFormModified, onFormModifiedChange]); + useEffect(() => { + if (setResetForm) { + setResetForm(reset); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [reset]); + return (
getValidConnectors(connectors, actionItem, actionTypesIndex), - [actionItem, actionTypesIndex, connectors] + () => getValidConnectors(connectors, actionItem, actionTypesIndex, allowGroupConnector), + [actionItem, actionTypesIndex, allowGroupConnector, connectors] ); const selectedConnectors = useMemo( - () => getValueOfSelectedConnector(actionItem.id, validConnectors, actionTypeRegistered), - [actionItem.id, validConnectors, actionTypeRegistered] + () => + getValueOfSelectedConnector( + actionItem.id, + validConnectors, + actionTypeRegistered, + allowGroupConnector + ), + [actionItem.id, validConnectors, actionTypeRegistered, allowGroupConnector] ); const options = useMemo( @@ -83,10 +91,15 @@ function ConnectorsSelectionComponent({ const getValueOfSelectedConnector = ( actionItemId: string, connectors: ActionConnector[], - actionTypeRegistered: ActionTypeModel + actionTypeRegistered: ActionTypeModel, + allowGroupConnector: string[] = [] ): Array> => { - const selectedConnector = connectors.find((connector) => connector.id === actionItemId); - + let selectedConnector = connectors.find((connector) => connector.id === actionItemId); + if (allowGroupConnector.length > 0 && !selectedConnector) { + selectedConnector = connectors.find((connector) => + allowGroupConnector.includes(connector.actionTypeId) + ); + } if (!selectedConnector) { return []; } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/create_connector_flyout/index.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/create_connector_flyout/index.tsx index 32bd7931aecc8..5cf6f6f8de69b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/create_connector_flyout/index.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/create_connector_flyout/index.tsx @@ -6,21 +6,30 @@ */ import React, { memo, ReactNode, useCallback, useEffect, useRef, useState } from 'react'; -import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiFlyout, EuiFlyoutBody } from '@elastic/eui'; +import { + EuiButton, + EuiButtonGroup, + EuiFlexGroup, + EuiFlexItem, + EuiFlyout, + EuiFlyoutBody, + EuiSpacer, +} from '@elastic/eui'; -import { getConnectorCompatibility } from '@kbn/actions-plugin/common'; +import { getConnectorCompatibility, UptimeConnectorFeatureId } from '@kbn/actions-plugin/common'; import { FormattedMessage } from '@kbn/i18n-react'; import { ActionConnector, ActionType, ActionTypeModel, + ActionTypeIndex, ActionTypeRegistryContract, } from '../../../../types'; import { hasSaveActionsCapability } from '../../../lib/capabilities'; import { useKibana } from '../../../../common/lib/kibana'; import { ActionTypeMenu } from '../action_type_menu'; import { useCreateConnector } from '../../../hooks/use_create_connector'; -import { ConnectorForm, ConnectorFormState } from '../connector_form'; +import { ConnectorForm, ConnectorFormState, ResetForm } from '../connector_form'; import { ConnectorFormSchema } from '../types'; import { FlyoutHeader } from './header'; import { FlyoutFooter } from './footer'; @@ -47,6 +56,7 @@ const CreateConnectorFlyoutComponent: React.FC = ({ const { isLoading: isSavingConnector, createConnector } = useCreateConnector(); const isMounted = useRef(false); + const [allActionTypes, setAllActionTypes] = useState(undefined); const [actionType, setActionType] = useState(null); const [hasActionsUpgradeableByTrial, setHasActionsUpgradeableByTrial] = useState(false); const canSave = hasSaveActionsCapability(capabilities); @@ -78,6 +88,47 @@ const CreateConnectorFlyoutComponent: React.FC = ({ const actionTypeModel: ActionTypeModel | null = actionType != null ? actionTypeRegistry.get(actionType.id) : null; + /* Future Developer + * We are excluding `UptimeConnectorFeatureId` because as this time Synthetics won't work + * with slack API on their UI, We need to add an ISSUE here so they can fix it + */ + const groupActionTypeModel: Array = + actionTypeModel && actionTypeModel.subtype && featureId !== UptimeConnectorFeatureId + ? (actionTypeModel?.subtype ?? []).map((subtypeAction) => ({ + ...actionTypeRegistry.get(subtypeAction.id), + name: subtypeAction.name, + })) + : []; + + const groupActionButtons = groupActionTypeModel.map((gAction) => ({ + id: gAction.id, + label: gAction.name, + 'data-test-subj': `${gAction.id}Button`, + })); + + const resetConnectorForm = useRef(); + + const setResetForm = (reset: ResetForm) => { + resetConnectorForm.current = reset; + }; + + const onChangeGroupAction = (id: string) => { + if (allActionTypes && allActionTypes[id]) { + setActionType(allActionTypes[id]); + if (resetConnectorForm.current) { + resetConnectorForm.current({ + resetValues: true, + defaultValue: { + actionTypeId: id, + isDeprecated: false, + config: {}, + secrets: {}, + }, + }); + } + } + }; + const validateAndCreateConnector = useCallback(async () => { setPreSubmitValidationErrorMessage(null); @@ -166,14 +217,29 @@ const CreateConnectorFlyoutComponent: React.FC = ({ > {hasConnectorTypeSelected ? ( <> + {groupActionTypeModel && ( + <> + + + + )} {!!preSubmitValidationErrorMessage &&

{preSubmitValidationErrorMessage}

} - <> @@ -220,6 +286,7 @@ const CreateConnectorFlyoutComponent: React.FC = ({ featureId={featureId} onActionTypeChange={setActionType} setHasActionsUpgradeableByTrial={setHasActionsUpgradeableByTrial} + setAllActionTypes={setAllActionTypes} actionTypeRegistry={actionTypeRegistry} /> )} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/connectors.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/connectors.ts index 6543d74ecd7a2..af09d984f9417 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/connectors.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/connectors.ts @@ -10,13 +10,15 @@ import { ActionConnector, ActionTypeIndex, RuleAction } from '../../../types'; export const getValidConnectors = ( connectors: ActionConnector[], actionItem: RuleAction, - actionTypesIndex: ActionTypeIndex + actionTypesIndex: ActionTypeIndex, + allowGroupConnector: string[] = [] ): ActionConnector[] => { const actionType = actionTypesIndex[actionItem.actionTypeId]; return connectors.filter( (connector) => - connector.actionTypeId === actionItem.actionTypeId && + (allowGroupConnector.includes(connector.actionTypeId) || + connector.actionTypeId === actionItem.actionTypeId) && // include only enabled by config connectors or preconfigured (actionType?.enabledInConfig || connector.isPreconfigured) ); diff --git a/x-pack/plugins/triggers_actions_ui/public/types.ts b/x-pack/plugins/triggers_actions_ui/public/types.ts index b5278bde4e175..756ea14488fad 100644 --- a/x-pack/plugins/triggers_actions_ui/public/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/types.ts @@ -254,6 +254,10 @@ export interface ActionTypeModel; customConnectorSelectItem?: CustomConnectorSelectionItem; isExperimental?: boolean; + subtype?: Array<{ id: string; name: string }>; + convertParamsBetweenGroups?: (params: ActionParams) => ActionParams | {}; + hideInUi?: boolean; + modalWidth?: number; } export interface GenericValidationResult { diff --git a/x-pack/test/alerting_api_integration/common/config.ts b/x-pack/test/alerting_api_integration/common/config.ts index 4e8f8c45abb5b..d418b268f69ce 100644 --- a/x-pack/test/alerting_api_integration/common/config.ts +++ b/x-pack/test/alerting_api_integration/common/config.ts @@ -45,6 +45,7 @@ const enabledActionTypes = [ '.jira', '.resilient', '.slack', + '.slack_api', '.tines', '.webhook', '.xmatters', @@ -174,6 +175,7 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) 'localhost', 'some.non.existent.com', 'smtp.live.com', + 'slack.com', ])}`, `--xpack.actions.enableFooterInEmail=${enableFooterInEmail}`, '--xpack.encryptedSavedObjects.encryptionKey="wuGNaIhoMpk5sO4UBxgr3NyW1sFcLgIf"', diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/slack_api.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/slack_api.ts new file mode 100644 index 0000000000000..12c17d2a7a4f9 --- /dev/null +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/slack_api.ts @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default function slackTest({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + describe('Slack API action', () => { + it('should return 200 when creating a slack action successfully', async () => { + const { body: createdAction } = await supertest + .post('/api/actions/connector') + .set('kbn-xsrf', 'foo') + .send({ + name: 'A slack api action', + connector_type_id: '.slack_api', + secrets: { + token: 'some token', + }, + }) + .expect(200); + + expect(createdAction).to.eql({ + id: createdAction.id, + is_preconfigured: false, + is_deprecated: false, + is_missing_secrets: false, + name: 'A slack api action', + connector_type_id: '.slack_api', + config: {}, + }); + + expect(typeof createdAction.id).to.be('string'); + + const { body: fetchedAction } = await supertest + .get(`/api/actions/connector/${createdAction.id}`) + .expect(200); + + expect(fetchedAction).to.eql({ + id: fetchedAction.id, + is_preconfigured: false, + is_deprecated: false, + is_missing_secrets: false, + name: 'A slack api action', + connector_type_id: '.slack_api', + config: {}, + }); + }); + + it('should respond with a 400 Bad Request when creating a slack action with no token', async () => { + await supertest + .post('/api/actions/connector') + .set('kbn-xsrf', 'foo') + .send({ + name: 'A slack api action', + connector_type_id: '.slack_api', + secrets: {}, + }) + .expect(400) + .then((resp: any) => { + expect(resp.body).to.eql({ + statusCode: 400, + error: 'Bad Request', + message: + 'error validating action type secrets: [token]: expected value of type [string] but got [undefined]', + }); + }); + }); + }); +} diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/slack.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/slack_webhook.ts similarity index 99% rename from x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/slack.ts rename to x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/slack_webhook.ts index 305afa0fdcaf9..d9c32ccd643b0 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/slack.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/slack_webhook.ts @@ -18,7 +18,7 @@ export default function slackTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const configService = getService('config'); - describe('slack action', () => { + describe('Slack webhook action', () => { let simulatedActionId = ''; let slackSimulatorURL: string = ''; let slackServer: http.Server; diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/index.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/index.ts index 66f83daff0429..05bd4da72c19c 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/index.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/index.ts @@ -32,7 +32,8 @@ export default function connectorsTests({ loadTestFile, getService }: FtrProvide loadTestFile(require.resolve('./connector_types/opsgenie')); loadTestFile(require.resolve('./connector_types/pagerduty')); loadTestFile(require.resolve('./connector_types/server_log')); - loadTestFile(require.resolve('./connector_types/slack')); + loadTestFile(require.resolve('./connector_types/slack_webhook')); + loadTestFile(require.resolve('./connector_types/slack_api')); loadTestFile(require.resolve('./connector_types/webhook')); loadTestFile(require.resolve('./connector_types/xmatters')); loadTestFile(require.resolve('./connector_types/tines')); @@ -48,6 +49,6 @@ export default function connectorsTests({ loadTestFile, getService }: FtrProvide /** * Sub action framework */ - loadTestFile(require.resolve('./sub_action_framework')); + // loadTestFile(require.resolve('./sub_action_framework')); }); } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/check_registered_connector_types.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/check_registered_connector_types.ts index 830f5e6f8d96d..f0578f6dbd7ce 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/check_registered_connector_types.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/check_registered_connector_types.ts @@ -34,6 +34,7 @@ export default function createRegisteredConnectorTypeTests({ getService }: FtrPr '.swimlane', '.server-log', '.slack', + '.slack_api', '.webhook', '.cases-webhook', '.xmatters', diff --git a/x-pack/test/functional/services/actions/index.ts b/x-pack/test/functional/services/actions/index.ts index c7539c37f2c32..7218f3079aafb 100644 --- a/x-pack/test/functional/services/actions/index.ts +++ b/x-pack/test/functional/services/actions/index.ts @@ -10,6 +10,7 @@ import { ActionsCommonServiceProvider } from './common'; import { ActionsOpsgenieServiceProvider } from './opsgenie'; import { ActionsTinesServiceProvider } from './tines'; import { ActionsAPIServiceProvider } from './api'; +import { ActionsSlackServiceProvider } from './slack'; export function ActionsServiceProvider(context: FtrProviderContext) { const common = ActionsCommonServiceProvider(context); @@ -19,5 +20,6 @@ export function ActionsServiceProvider(context: FtrProviderContext) { common: ActionsCommonServiceProvider(context), opsgenie: ActionsOpsgenieServiceProvider(context, common), tines: ActionsTinesServiceProvider(context, common), + slack: ActionsSlackServiceProvider(context, common), }; } diff --git a/x-pack/test/functional/services/actions/slack.ts b/x-pack/test/functional/services/actions/slack.ts new file mode 100644 index 0000000000000..b4297644c7c93 --- /dev/null +++ b/x-pack/test/functional/services/actions/slack.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 expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; +import type { ActionsCommon } from './common'; + +export interface WebhookConnectorFormFields { + name: string; + url: string; +} + +export interface WebApiConnectorFormFields { + name: string; + token: string; +} + +export function ActionsSlackServiceProvider( + { getService }: FtrProviderContext, + common: ActionsCommon +) { + const testSubjects = getService('testSubjects'); + + return { + async createNewWebhook({ name, url }: WebhookConnectorFormFields) { + await common.openNewConnectorForm('slack'); + + await testSubjects.setValue('nameInput', name); + await testSubjects.setValue('slackWebhookUrlInput', url); + + const flyOutSaveButton = await testSubjects.find('create-connector-flyout-save-btn'); + expect(await flyOutSaveButton.isEnabled()).to.be(true); + await flyOutSaveButton.click(); + }, + async createNewWebAPI({ name, token }: WebApiConnectorFormFields) { + await common.openNewConnectorForm('slack'); + + const webApiTab = await testSubjects.find('.slack_apiButton'); + await webApiTab.click(); + + await testSubjects.setValue('nameInput', name); + await testSubjects.setValue('secrets.token-input', token); + + const flyOutSaveButton = await testSubjects.find('create-connector-flyout-save-btn'); + expect(await flyOutSaveButton.isEnabled()).to.be(true); + await flyOutSaveButton.click(); + }, + }; +} diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors/index.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors/index.ts index 1d6420004c0cd..c246f92309d2d 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors/index.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors/index.ts @@ -12,5 +12,6 @@ export default ({ loadTestFile }: FtrProviderContext) => { loadTestFile(require.resolve('./general')); loadTestFile(require.resolve('./opsgenie')); loadTestFile(require.resolve('./tines')); + loadTestFile(require.resolve('./slack')); }); }; diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors/slack.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors/slack.ts new file mode 100644 index 0000000000000..f975bed9f965e --- /dev/null +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors/slack.ts @@ -0,0 +1,208 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { ObjectRemover } from '../../../lib/object_remover'; +import { generateUniqueKey } from '../../../lib/get_test_data'; +import { createSlackConnectorAndObjectRemover, getConnectorByName } from './utils'; + +export default ({ getPageObjects, getService }: FtrProviderContext) => { + const testSubjects = getService('testSubjects'); + const pageObjects = getPageObjects(['common', 'triggersActionsUI', 'header']); + const retry = getService('retry'); + const supertest = getService('supertest'); + const actions = getService('actions'); + const rules = getService('rules'); + let objectRemover: ObjectRemover; + + describe('Slack', () => { + before(async () => { + objectRemover = await createSlackConnectorAndObjectRemover({ getService }); + }); + + after(async () => { + await objectRemover.removeAll(); + }); + + describe('connector page', () => { + beforeEach(async () => { + await pageObjects.common.navigateToApp('triggersActionsConnectors'); + }); + + it('should only show one slack connector', async () => { + if (await testSubjects.exists('createActionButton')) { + await testSubjects.click('createActionButton'); + } else { + await testSubjects.click('createFirstActionButton'); + } + await testSubjects.existOrFail('.slack-card'); + const slackApiCardExists = await testSubjects.exists('.slack_api-card'); + expect(slackApiCardExists).to.be(false); + }); + + it('should create the webhook connector', async () => { + const connectorName = generateUniqueKey(); + await actions.slack.createNewWebhook({ + name: connectorName, + url: 'https://test.com', + }); + + const toastTitle = await pageObjects.common.closeToast(); + expect(toastTitle).to.eql(`Created '${connectorName}'`); + + await pageObjects.triggersActionsUI.searchConnectors(connectorName); + + const searchResults = await pageObjects.triggersActionsUI.getConnectorsList(); + expect(searchResults).to.eql([ + { + name: connectorName, + actionType: 'Slack', + }, + ]); + const connector = await getConnectorByName(connectorName, supertest); + objectRemover.add(connector.id, 'action', 'actions'); + }); + + it('should create the web api connector', async () => { + const connectorName = generateUniqueKey(); + await actions.slack.createNewWebAPI({ + name: connectorName, + token: 'supersecrettoken', + }); + + const toastTitle = await pageObjects.common.closeToast(); + expect(toastTitle).to.eql(`Created '${connectorName}'`); + + await pageObjects.triggersActionsUI.searchConnectors(connectorName); + + const searchResults = await pageObjects.triggersActionsUI.getConnectorsList(); + expect(searchResults).to.eql([ + { + name: connectorName, + actionType: 'Slack API', + }, + ]); + const connector = await getConnectorByName(connectorName, supertest); + objectRemover.add(connector.id, 'action', 'actions'); + }); + }); + + describe('rule creation', async () => { + const webhookConnectorName = generateUniqueKey(); + const webApiConnectorName = generateUniqueKey(); + let webApiAction: { id: string }; + let webhookAction: { id: string }; + + const setupRule = async () => { + const ruleName = generateUniqueKey(); + await retry.try(async () => { + await rules.common.defineIndexThresholdAlert(ruleName); + }); + return ruleName; + }; + + const getRuleIdByName = async (name: string) => { + const response = await supertest + .get(`/api/alerts/_find?search=${name}&search_fields=name`) + .expect(200); + return response.body.data[0].id; + }; + + const selectSlackConnectorInRuleAction = async ({ connectorId }: { connectorId: string }) => { + await testSubjects.click('.slack-alerting-ActionTypeSelectOption'); // "Slack" in connector list + await testSubjects.click('selectActionConnector-.slack-0'); + await testSubjects.click(`dropdown-connector-${connectorId}`); + }; + + before(async () => { + webApiAction = await actions.api.createConnector({ + name: webApiConnectorName, + config: {}, + secrets: { token: 'supersecrettoken' }, + connectorTypeId: '.slack_api', + }); + + webhookAction = await actions.api.createConnector({ + name: webhookConnectorName, + config: {}, + secrets: { webhookUrl: 'https://test.com' }, + connectorTypeId: '.slack', + }); + + objectRemover.add(webhookAction.id, 'action', 'actions'); + objectRemover.add(webApiAction.id, 'action', 'actions'); + await pageObjects.common.navigateToApp('triggersActions'); + }); + + it('should save webhook type slack connectors', async () => { + const ruleName = await setupRule(); + + await selectSlackConnectorInRuleAction({ + connectorId: webhookAction.id, + }); + await testSubjects.click('saveRuleButton'); + await pageObjects.triggersActionsUI.searchAlerts(ruleName); + + const ruleId = await getRuleIdByName(ruleName); + objectRemover.add(ruleId, 'rule', 'alerting'); + + const searchResults = await pageObjects.triggersActionsUI.getAlertsList(); + expect(searchResults).to.eql([ + { + duration: '00:00', + interval: '1 min', + name: `${ruleName}Index threshold`, + tags: '', + }, + ]); + + const toastTitle = await pageObjects.common.closeToast(); + expect(toastTitle).to.eql(`Created rule "${ruleName}"`); + }); + + it('should save webapi type slack connectors', async () => { + await setupRule(); + await selectSlackConnectorInRuleAction({ + connectorId: webApiAction.id, + }); + + await testSubjects.click('saveRuleButton'); + + const toastTitle = await pageObjects.common.closeToast(); + expect(toastTitle).to.eql('Failed to retrieve Slack channels list'); + + // We are not saving the rule yet as we currently have no way + // to mock the internal request that loads the channels list + // uncomment once we have a way to mock the request + + // const ruleName = await setupRule(); + // await selectSlackConnectorInRuleAction({ + // connectorId: webApiAction.id, + // }); + + // await testSubjects.click('saveRuleButton'); + // await pageObjects.triggersActionsUI.searchAlerts(ruleName); + + // const ruleId = await getRuleIdByName(ruleName); + // objectRemover.add(ruleId, 'rule', 'alerting'); + + // const searchResults = await pageObjects.triggersActionsUI.getAlertsList(); + // expect(searchResults).to.eql([ + // { + // duration: '00:00', + // interval: '1 min', + // name: `${ruleName}Index threshold`, + // tags: '', + // }, + // ]); + // const toastTitle = await pageObjects.common.closeToast(); + // expect(toastTitle).to.eql(`Created rule "${ruleName}"`); + }); + }); + }); +}; diff --git a/x-pack/test/functional_with_es_ssl/config.base.ts b/x-pack/test/functional_with_es_ssl/config.base.ts index 71039b211f5db..533fec1944b67 100644 --- a/x-pack/test/functional_with_es_ssl/config.base.ts +++ b/x-pack/test/functional_with_es_ssl/config.base.ts @@ -24,6 +24,7 @@ const enabledActionTypes = [ '.servicenow', '.servicenow-sir', '.slack', + '.slack_api', '.tines', '.webhook', 'test.authorization', diff --git a/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts b/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts index 67478db2c8c00..6bc6b02013ed0 100644 --- a/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts +++ b/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts @@ -57,6 +57,7 @@ export default function ({ getService }: FtrProviderContext) { 'actions:.servicenow-itom', 'actions:.servicenow-sir', 'actions:.slack', + 'actions:.slack_api', 'actions:.swimlane', 'actions:.teams', 'actions:.tines', From 2fad86a6c578408700473200ae8173ca50fb0a5e Mon Sep 17 00:00:00 2001 From: Kevin Logan <56395104+kevinlog@users.noreply.github.com> Date: Tue, 25 Apr 2023 09:31:57 -0400 Subject: [PATCH 51/52] [Security Solution] Remove index false from artifact saved objects mappings (#155204) Updates the mappings for the artifact saved objects This effort is part of https://github.com/elastic/security-team/issues/6268 and https://github.com/elastic/dev/issues/2189 --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Gerard Soldevila --- ...grations_state_action_machine.test.ts.snap | 30 ++++ .../src/core/unused_types.ts | 2 + .../src/initial_state.test.ts | 5 + .../group2/check_registered_types.test.ts | 3 +- .../group3/split_kibana_index.test.ts | 1 - .../delete_unknown_types/mappings.json | 2 - .../deprecations_service/mappings.json | 1 - .../export_transform/mappings.json | 1 - .../hidden_saved_objects/mappings.json | 1 - .../nested_export_transform/mappings.json | 1 - .../visible_in_management/mappings.json | 2 - .../migrate_artifacts_to_fleet.test.ts | 144 ------------------ .../artifacts/migrate_artifacts_to_fleet.ts | 96 ------------ .../lib/artifacts/saved_object_mappings.ts | 65 +------- .../security_solution/server/plugin.ts | 21 ++- .../security_solution/server/saved_objects.ts | 6 +- .../saved_objects/spaces/mappings.json | 2 - .../saved_objects/spaces/mappings.json | 2 - 18 files changed, 49 insertions(+), 336 deletions(-) delete mode 100644 x-pack/plugins/security_solution/server/endpoint/lib/artifacts/migrate_artifacts_to_fleet.test.ts delete mode 100644 x-pack/plugins/security_solution/server/endpoint/lib/artifacts/migrate_artifacts_to_fleet.ts diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/__snapshots__/migrations_state_action_machine.test.ts.snap b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/__snapshots__/migrations_state_action_machine.test.ts.snap index 387dbd87bbafe..0e05eed1e99b7 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/__snapshots__/migrations_state_action_machine.test.ts.snap +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/__snapshots__/migrations_state_action_machine.test.ts.snap @@ -54,6 +54,11 @@ Object { "type": "csp_rule", }, }, + Object { + "term": Object { + "type": "endpoint:user-artifact", + }, + }, Object { "term": Object { "type": "file-upload-telemetry", @@ -272,6 +277,11 @@ Object { "type": "csp_rule", }, }, + Object { + "term": Object { + "type": "endpoint:user-artifact", + }, + }, Object { "term": Object { "type": "file-upload-telemetry", @@ -494,6 +504,11 @@ Object { "type": "csp_rule", }, }, + Object { + "term": Object { + "type": "endpoint:user-artifact", + }, + }, Object { "term": Object { "type": "file-upload-telemetry", @@ -720,6 +735,11 @@ Object { "type": "csp_rule", }, }, + Object { + "term": Object { + "type": "endpoint:user-artifact", + }, + }, Object { "term": Object { "type": "file-upload-telemetry", @@ -994,6 +1014,11 @@ Object { "type": "csp_rule", }, }, + Object { + "term": Object { + "type": "endpoint:user-artifact", + }, + }, Object { "term": Object { "type": "file-upload-telemetry", @@ -1223,6 +1248,11 @@ Object { "type": "csp_rule", }, }, + Object { + "term": Object { + "type": "endpoint:user-artifact", + }, + }, Object { "term": Object { "type": "file-upload-telemetry", diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/unused_types.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/unused_types.ts index 46ddd23251217..2f9dd57afa294 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/unused_types.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/unused_types.ts @@ -47,6 +47,8 @@ export const REMOVED_TYPES: string[] = [ 'csp_rule', // Removed in 8.8 https://github.com/elastic/kibana/pull/151116 'upgrade-assistant-telemetry', + // Removed in 8.8 https://github.com/elastic/kibana/pull/155204 + 'endpoint:user-artifact', ].sort(); export const excludeUnusedTypesQuery: QueryDslQueryContainer = { diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.test.ts index d49e784f77633..3ee201605f1ac 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.test.ts @@ -107,6 +107,11 @@ describe('createInitialState', () => { "type": "csp_rule", }, }, + Object { + "term": Object { + "type": "endpoint:user-artifact", + }, + }, Object { "term": Object { "type": "file-upload-telemetry", diff --git a/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts index 6c7e00b1822b7..05124ffad8140 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts @@ -81,8 +81,7 @@ describe('checking migration metadata changes on all registered SO types', () => "core-usage-stats": "b3c04da317c957741ebcdedfea4524049fdc79ff", "csp-rule-template": "099c229bf97578d9ca72b3a672d397559b84ee0b", "dashboard": "71e3f8dfcffeb5fbd410dec81ce46f5691763c43", - "endpoint:user-artifact": "a5b154962fb6cdf5d9e7452e58690054c95cc72a", - "endpoint:user-artifact-manifest": "5989989c0f84dd2d02da1eb46b6254e334bd2ccd", + "endpoint:user-artifact-manifest": "8ad9bd235dcfdc18b567aef0dc36ac686193dc89", "enterprise_search_telemetry": "4b41830e3b28a16eb92dee0736b44ae6276ced9b", "epm-packages": "8755f947a00613f994b1bc5d5580e104043e27f6", "epm-packages-assets": "00c8b5e5bf059627ffc9fbde920e1ac75926c5f6", diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/split_kibana_index.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/split_kibana_index.test.ts index 8c5f78a574db1..06b8c169cc396 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group3/split_kibana_index.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/split_kibana_index.test.ts @@ -188,7 +188,6 @@ describe('split .kibana index into multiple system indices', () => { "connector_token", "core-usage-stats", "csp-rule-template", - "endpoint:user-artifact", "endpoint:user-artifact-manifest", "enterprise_search_telemetry", "epm-packages", diff --git a/test/api_integration/fixtures/es_archiver/saved_objects/delete_unknown_types/mappings.json b/test/api_integration/fixtures/es_archiver/saved_objects/delete_unknown_types/mappings.json index 4468d6849f6ca..885a64189a059 100644 --- a/test/api_integration/fixtures/es_archiver/saved_objects/delete_unknown_types/mappings.json +++ b/test/api_integration/fixtures/es_archiver/saved_objects/delete_unknown_types/mappings.json @@ -102,7 +102,6 @@ ], ".kibana_security_solution": [ "csp-rule-template", - "endpoint:user-artifact", "endpoint:user-artifact-manifest", "exception-list", "exception-list-agnostic", @@ -750,7 +749,6 @@ ], ".kibana_security_solution": [ "csp-rule-template", - "endpoint:user-artifact", "endpoint:user-artifact-manifest", "exception-list", "exception-list-agnostic", diff --git a/test/functional/fixtures/es_archiver/deprecations_service/mappings.json b/test/functional/fixtures/es_archiver/deprecations_service/mappings.json index f9bec7fdca4d5..33968127fe42c 100644 --- a/test/functional/fixtures/es_archiver/deprecations_service/mappings.json +++ b/test/functional/fixtures/es_archiver/deprecations_service/mappings.json @@ -101,7 +101,6 @@ ], ".kibana_security_solution": [ "csp-rule-template", - "endpoint:user-artifact", "endpoint:user-artifact-manifest", "exception-list", "exception-list-agnostic", diff --git a/test/functional/fixtures/es_archiver/saved_objects_management/export_transform/mappings.json b/test/functional/fixtures/es_archiver/saved_objects_management/export_transform/mappings.json index 963eaa9d47892..63842481820ee 100644 --- a/test/functional/fixtures/es_archiver/saved_objects_management/export_transform/mappings.json +++ b/test/functional/fixtures/es_archiver/saved_objects_management/export_transform/mappings.json @@ -101,7 +101,6 @@ ], ".kibana_security_solution": [ "csp-rule-template", - "endpoint:user-artifact", "endpoint:user-artifact-manifest", "exception-list", "exception-list-agnostic", diff --git a/test/functional/fixtures/es_archiver/saved_objects_management/hidden_saved_objects/mappings.json b/test/functional/fixtures/es_archiver/saved_objects_management/hidden_saved_objects/mappings.json index 4cb716253a8a3..dbb70af6b6233 100644 --- a/test/functional/fixtures/es_archiver/saved_objects_management/hidden_saved_objects/mappings.json +++ b/test/functional/fixtures/es_archiver/saved_objects_management/hidden_saved_objects/mappings.json @@ -101,7 +101,6 @@ ], ".kibana_security_solution": [ "csp-rule-template", - "endpoint:user-artifact", "endpoint:user-artifact-manifest", "exception-list", "exception-list-agnostic", diff --git a/test/functional/fixtures/es_archiver/saved_objects_management/nested_export_transform/mappings.json b/test/functional/fixtures/es_archiver/saved_objects_management/nested_export_transform/mappings.json index 963eaa9d47892..63842481820ee 100644 --- a/test/functional/fixtures/es_archiver/saved_objects_management/nested_export_transform/mappings.json +++ b/test/functional/fixtures/es_archiver/saved_objects_management/nested_export_transform/mappings.json @@ -101,7 +101,6 @@ ], ".kibana_security_solution": [ "csp-rule-template", - "endpoint:user-artifact", "endpoint:user-artifact-manifest", "exception-list", "exception-list-agnostic", diff --git a/test/functional/fixtures/es_archiver/saved_objects_management/visible_in_management/mappings.json b/test/functional/fixtures/es_archiver/saved_objects_management/visible_in_management/mappings.json index 924b2b070ff4d..6ccba0243793d 100644 --- a/test/functional/fixtures/es_archiver/saved_objects_management/visible_in_management/mappings.json +++ b/test/functional/fixtures/es_archiver/saved_objects_management/visible_in_management/mappings.json @@ -101,7 +101,6 @@ ], ".kibana_security_solution": [ "csp-rule-template", - "endpoint:user-artifact", "endpoint:user-artifact-manifest", "exception-list", "exception-list-agnostic", @@ -696,7 +695,6 @@ ], ".kibana_security_solution": [ "csp-rule-template", - "endpoint:user-artifact", "endpoint:user-artifact-manifest", "exception-list", "exception-list-agnostic", diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/migrate_artifacts_to_fleet.test.ts b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/migrate_artifacts_to_fleet.test.ts deleted file mode 100644 index 277772253b92e..0000000000000 --- a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/migrate_artifacts_to_fleet.test.ts +++ /dev/null @@ -1,144 +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 { - loggingSystemMock, - savedObjectsClientMock, - elasticsearchServiceMock, -} from '@kbn/core/server/mocks'; -import type { - SavedObjectsClient, - Logger, - SavedObjectsFindResponse, - SavedObjectsFindResult, -} from '@kbn/core/server'; -import { migrateArtifactsToFleet } from './migrate_artifacts_to_fleet'; -import { createEndpointArtifactClientMock } from '../../services/artifacts/mocks'; -import type { InternalArtifactCompleteSchema } from '../../schemas'; -import { generateArtifactEsGetSingleHitMock } from '@kbn/fleet-plugin/server/services/artifacts/mocks'; -import type { NewArtifact } from '@kbn/fleet-plugin/server/services'; -import type { CreateRequest } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; - -describe('When migrating artifacts to fleet', () => { - let soClient: jest.Mocked; - let logger: jest.Mocked; - let artifactClient: ReturnType; - /** An artifact that was created prior to 7.14 */ - let soArtifactEntry: InternalArtifactCompleteSchema; - - const createSoFindResult = ( - soHits: SavedObjectsFindResult[] = [], - total: number = 15, - page: number = 1 - ): SavedObjectsFindResponse => { - return { - total, - page, - per_page: 10, - saved_objects: soHits, - }; - }; - - beforeEach(async () => { - soClient = savedObjectsClientMock.create() as unknown as jest.Mocked; - logger = loggingSystemMock.create().get() as jest.Mocked; - artifactClient = createEndpointArtifactClientMock(); - // pre-v7.14 artifact, which is compressed - soArtifactEntry = { - identifier: 'endpoint-exceptionlist-macos-v1', - compressionAlgorithm: 'zlib', - encryptionAlgorithm: 'none', - decodedSha256: 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - encodedSha256: 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - decodedSize: 14, - encodedSize: 22, - body: 'eJyrVkrNKynKTC1WsoqOrQUAJxkFKQ==', - }; - - // Mock the esClient create response to include the artifact properties that were provide - // to it by fleet artifact client - artifactClient._esClient.create.mockImplementation((props: CreateRequest) => { - return elasticsearchServiceMock.createSuccessTransportRequestPromise({ - ...generateArtifactEsGetSingleHitMock({ - ...((props?.body ?? {}) as NewArtifact), - }), - _index: '.fleet-artifacts-7', - _id: `endpoint:endpoint-exceptionlist-macos-v1-${ - // @ts-expect-error TS2339 - props?.body?.decodedSha256 ?? 'UNKNOWN?' - }`, - _version: 1, - result: 'created', - _shards: { - total: 1, - successful: 1, - failed: 0, - }, - _seq_no: 0, - _primary_term: 1, - }); - }); - - soClient.find.mockResolvedValue(createSoFindResult([], 0)).mockResolvedValueOnce( - createSoFindResult([ - { - score: 1, - type: '', - id: 'abc123', - references: [], - attributes: soArtifactEntry, - }, - ]) - ); - }); - - it('should do nothing if there are no artifacts', async () => { - soClient.find.mockReset(); - soClient.find.mockResolvedValue(createSoFindResult([], 0)); - await migrateArtifactsToFleet(soClient, artifactClient, logger); - expect(soClient.find).toHaveBeenCalled(); - expect(artifactClient.createArtifact).not.toHaveBeenCalled(); - expect(soClient.delete).not.toHaveBeenCalled(); - }); - - it('should create new artifact via fleet client and delete prior SO one', async () => { - await migrateArtifactsToFleet(soClient, artifactClient, logger); - expect(artifactClient.createArtifact).toHaveBeenCalled(); - expect(soClient.delete).toHaveBeenCalled(); - }); - - it('should create artifact in fleet with attributes that match the SO version', async () => { - await migrateArtifactsToFleet(soClient, artifactClient, logger); - - await expect(artifactClient.createArtifact.mock.results[0].value).resolves.toEqual( - expect.objectContaining({ - ...soArtifactEntry, - compressionAlgorithm: 'zlib', - }) - ); - }); - - it('should ignore 404 responses for SO delete (multi-node kibana setup)', async () => { - const notFoundError: Error & { output?: { statusCode: number } } = new Error('not found'); - notFoundError.output = { statusCode: 404 }; - soClient.delete.mockRejectedValue(notFoundError); - await expect(migrateArtifactsToFleet(soClient, artifactClient, logger)).resolves.toEqual( - undefined - ); - expect(logger.debug).toHaveBeenCalledWith( - 'Artifact Migration: Attempt to delete Artifact SO [abc123] returned 404' - ); - }); - - it('should Throw() and log error if migration fails', async () => { - const error = new Error('test: delete failed'); - soClient.delete.mockRejectedValue(error); - await expect(migrateArtifactsToFleet(soClient, artifactClient, logger)).rejects.toThrow( - 'Artifact SO migration failed' - ); - }); -}); diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/migrate_artifacts_to_fleet.ts b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/migrate_artifacts_to_fleet.ts deleted file mode 100644 index e015019fa8a5d..0000000000000 --- a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/migrate_artifacts_to_fleet.ts +++ /dev/null @@ -1,96 +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 { inflate as _inflate } from 'zlib'; -import { promisify } from 'util'; -import type { SavedObjectsClient, Logger } from '@kbn/core/server'; -import type { EndpointArtifactClientInterface } from '../../services'; -import type { InternalArtifactCompleteSchema, InternalArtifactSchema } from '../../schemas'; -import { ArtifactConstants } from './common'; - -class ArtifactMigrationError extends Error { - constructor(message: string, public readonly meta?: unknown) { - super(message); - } -} - -const inflateAsync = promisify(_inflate); - -function isCompressed(artifact: InternalArtifactSchema) { - return artifact.compressionAlgorithm === 'zlib'; -} - -/** - * With v7.13, artifact storage was moved from a security_solution saved object to a fleet index - * in order to support Fleet Server. - */ -export const migrateArtifactsToFleet = async ( - soClient: SavedObjectsClient, - endpointArtifactClient: EndpointArtifactClientInterface, - logger: Logger -): Promise => { - let totalArtifactsMigrated = -1; - let hasMore = true; - - try { - while (hasMore) { - // Retrieve list of artifact records - const { saved_objects: artifactList, total } = - await soClient.find({ - type: ArtifactConstants.SAVED_OBJECT_TYPE, - page: 1, - perPage: 10, - }); - - if (totalArtifactsMigrated === -1) { - totalArtifactsMigrated = total; - if (total > 0) { - logger.info(`Migrating artifacts from SavedObject`); - } - } - - // If nothing else to process, then exit out - if (total === 0) { - hasMore = false; - if (totalArtifactsMigrated > 0) { - logger.info(`Total Artifacts migrated: ${totalArtifactsMigrated}`); - } - return; - } - - for (const artifact of artifactList) { - if (isCompressed(artifact.attributes)) { - artifact.attributes = { - ...artifact.attributes, - body: (await inflateAsync(Buffer.from(artifact.attributes.body, 'base64'))).toString( - 'base64' - ), - }; - } - - // Create new artifact in fleet index - await endpointArtifactClient.createArtifact(artifact.attributes); - // Delete old artifact from SO and if there are errors here, then ignore 404's - // since multiple kibana instances could be going at this - try { - await soClient.delete(ArtifactConstants.SAVED_OBJECT_TYPE, artifact.id); - } catch (e) { - if (e?.output?.statusCode !== 404) { - throw e; - } - logger.debug( - `Artifact Migration: Attempt to delete Artifact SO [${artifact.id}] returned 404` - ); - } - } - } - } catch (e) { - const error = new ArtifactMigrationError('Artifact SO migration failed', e); - logger.error(error); - throw error; - } -}; diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/saved_object_mappings.ts b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/saved_object_mappings.ts index 826002f5970cd..dba96c2c084de 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/saved_object_mappings.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/saved_object_mappings.ts @@ -14,81 +14,18 @@ import { migrations } from './migrations'; export const exceptionsArtifactSavedObjectType = ArtifactConstants.SAVED_OBJECT_TYPE; export const manifestSavedObjectType = ManifestConstants.SAVED_OBJECT_TYPE; -export const exceptionsArtifactSavedObjectMappings: SavedObjectsType['mappings'] = { - properties: { - identifier: { - type: 'keyword', - }, - compressionAlgorithm: { - type: 'keyword', - index: false, - }, - encryptionAlgorithm: { - type: 'keyword', - index: false, - }, - encodedSha256: { - type: 'keyword', - }, - encodedSize: { - type: 'long', - index: false, - }, - decodedSha256: { - type: 'keyword', - index: false, - }, - decodedSize: { - type: 'long', - index: false, - }, - created: { - type: 'date', - index: false, - }, - body: { - type: 'binary', - }, - }, -}; - export const manifestSavedObjectMappings: SavedObjectsType['mappings'] = { + dynamic: false, properties: { - created: { - type: 'date', - index: false, - }, schemaVersion: { type: 'keyword', }, - semanticVersion: { - type: 'keyword', - index: false, - }, artifacts: { type: 'nested', - properties: { - policyId: { - type: 'keyword', - index: false, - }, - artifactId: { - type: 'keyword', - index: false, - }, - }, }, }, }; -export const exceptionsArtifactType: SavedObjectsType = { - name: exceptionsArtifactSavedObjectType, - indexPattern: SECURITY_SOLUTION_SAVED_OBJECT_INDEX, - hidden: false, - namespaceType: 'agnostic', - mappings: exceptionsArtifactSavedObjectMappings, -}; - export const manifestType: SavedObjectsType = { name: manifestSavedObjectType, indexPattern: SECURITY_SOLUTION_SAVED_OBJECT_INDEX, diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index e74138f61e195..48b963b788bd2 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -69,7 +69,6 @@ import type { ITelemetryReceiver } from './lib/telemetry/receiver'; import { TelemetryReceiver } from './lib/telemetry/receiver'; import { licenseService } from './lib/license'; import { PolicyWatcher } from './endpoint/lib/policy/license_watch'; -import { migrateArtifactsToFleet } from './endpoint/lib/artifacts/migrate_artifacts_to_fleet'; import previewPolicy from './lib/detection_engine/routes/index/preview_policy.json'; import { createRuleExecutionLogService } from './lib/detection_engine/rule_monitoring'; import { getKibanaPrivilegesFeaturePrivileges, getCasesKibanaFeature } from './features'; @@ -450,17 +449,15 @@ export class Plugin implements ISecuritySolutionPlugin { // Migrate artifacts to fleet and then start the minifest task after that is done plugins.fleet.fleetSetupCompleted().then(() => { - migrateArtifactsToFleet(savedObjectsClient, artifactClient, logger).finally(() => { - logger.info('Dependent plugin setup complete - Starting ManifestTask'); - - if (this.manifestTask) { - this.manifestTask.start({ - taskManager, - }); - } else { - logger.error(new Error('User artifacts task not available.')); - } - }); + logger.info('Dependent plugin setup complete - Starting ManifestTask'); + + if (this.manifestTask) { + this.manifestTask.start({ + taskManager, + }); + } else { + logger.error(new Error('User artifacts task not available.')); + } }); // License related start diff --git a/x-pack/plugins/security_solution/server/saved_objects.ts b/x-pack/plugins/security_solution/server/saved_objects.ts index 394cab7f52455..bd6c21a4d489a 100644 --- a/x-pack/plugins/security_solution/server/saved_objects.ts +++ b/x-pack/plugins/security_solution/server/saved_objects.ts @@ -12,10 +12,7 @@ import { noteType, pinnedEventType, timelineType } from './lib/timeline/saved_ob import { legacyType as legacyRuleActionsType } from './lib/detection_engine/rule_actions_legacy'; import { prebuiltRuleAssetType } from './lib/detection_engine/prebuilt_rules'; import { type as signalsMigrationType } from './lib/detection_engine/migrations/saved_objects'; -import { - exceptionsArtifactType, - manifestType, -} from './endpoint/lib/artifacts/saved_object_mappings'; +import { manifestType } from './endpoint/lib/artifacts/saved_object_mappings'; const types = [ noteType, @@ -23,7 +20,6 @@ const types = [ legacyRuleActionsType, prebuiltRuleAssetType, timelineType, - exceptionsArtifactType, manifestType, signalsMigrationType, ]; diff --git a/x-pack/test/saved_object_api_integration/common/fixtures/es_archiver/saved_objects/spaces/mappings.json b/x-pack/test/saved_object_api_integration/common/fixtures/es_archiver/saved_objects/spaces/mappings.json index f1722d291fc1b..407f0fd182fe7 100644 --- a/x-pack/test/saved_object_api_integration/common/fixtures/es_archiver/saved_objects/spaces/mappings.json +++ b/x-pack/test/saved_object_api_integration/common/fixtures/es_archiver/saved_objects/spaces/mappings.json @@ -73,7 +73,6 @@ ], ".kibana_security_solution": [ "csp-rule-template", - "endpoint:user-artifact", "endpoint:user-artifact-manifest", "exception-list", "exception-list-agnostic", @@ -226,7 +225,6 @@ ], ".kibana_security_solution": [ "csp-rule-template", - "endpoint:user-artifact", "endpoint:user-artifact-manifest", "exception-list", "exception-list-agnostic", diff --git a/x-pack/test/spaces_api_integration/common/fixtures/es_archiver/saved_objects/spaces/mappings.json b/x-pack/test/spaces_api_integration/common/fixtures/es_archiver/saved_objects/spaces/mappings.json index a787729a16c35..340a1003d50a9 100644 --- a/x-pack/test/spaces_api_integration/common/fixtures/es_archiver/saved_objects/spaces/mappings.json +++ b/x-pack/test/spaces_api_integration/common/fixtures/es_archiver/saved_objects/spaces/mappings.json @@ -73,7 +73,6 @@ ], ".kibana_security_solution": [ "csp-rule-template", - "endpoint:user-artifact", "endpoint:user-artifact-manifest", "exception-list", "exception-list-agnostic", @@ -213,7 +212,6 @@ ], ".kibana_security_solution": [ "csp-rule-template", - "endpoint:user-artifact", "endpoint:user-artifact-manifest", "exception-list", "exception-list-agnostic", From ddd09ac271414730b9c5b2e4f30367e20215da98 Mon Sep 17 00:00:00 2001 From: Bena Kansara <69037875+benakansara@users.noreply.github.com> Date: Tue, 25 Apr 2023 15:37:03 +0200 Subject: [PATCH 52/52] Add group-by feature in APM rules (#155001) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Adds group-by dropdown in the following APM rules. - APM Latency threshold (Preselected fields: `service.name`, `service.environment`, `transaction.type`) - APM Failed transaction rate (Preselected fields: `service.name`, `service.environment`, `transaction.type`) - APM Error count threshold (Preselected fields: `service.name`, `service.environment`) Screenshot 2023-04-17 at 13 44 34 The preselected fields cannot be removed by user. The `transaction.name` field is selectable by user from the group-by dropdown. - https://github.com/elastic/kibana/issues/154535 - https://github.com/elastic/kibana/issues/154536 - https://github.com/elastic/kibana/issues/154537 Reason message is updated to include group key instead of only service name: - https://github.com/elastic/kibana/issues/155011 The `transaction.name` is added to the alert document: - https://github.com/elastic/kibana/issues/154543 The `transaction.name` action variable is added in UI: - https://github.com/elastic/kibana/issues/154545 The `transaction.name` is added to the context of active alert notifications: - https://github.com/elastic/kibana/issues/154547 There are additional fields in group-by dropdown for Error count threshold rule: https://github.com/elastic/kibana/issues/155633 - error.grouping_key - error.grouping_name ## Fixes - https://github.com/elastic/kibana/issues/154818 ### Update on Alert Id The alert Id is updated for all 3 rules. The new Id is generated from the group key. This is to avoid issues similar to #154818 where alerts are scheduled with same ID. Example of the new alert Ids - `opbeans-java_development_request_GET /flaky`, `opbeans-java_development_GET /fail` ## Out of scope of this PR - Updating the preview chart based on selected group by fields ## Checklist - [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 ## Release note As the alert Id is updated for the APM Latency threshold rule, APM Failed transaction rate rule and APM Error count rule, the existing alerts, if any, will be recovered, and new alerts will be fired in place of them. --------- Co-authored-by: Katerina Patticha Co-authored-by: Søren Louv-Jansen --- .../__snapshots__/es_fields.test.ts.snap | 12 +- .../apm/common/rules/apm_rule_types.ts | 69 ++- x-pack/plugins/apm/common/rules/schema.ts | 3 + .../error_count_rule_type/index.tsx | 50 ++- .../transaction_duration_rule_type/index.tsx | 54 ++- .../index.tsx | 55 ++- .../ui_components/apm_rule_group_by.tsx | 74 ++++ .../apm_rule_params_container/index.tsx | 3 + .../routes/alerts/register_apm_rule_types.ts | 5 + .../register_error_count_rule_type.test.ts | 415 +++++++++++++++++- .../register_error_count_rule_type.ts | 70 +-- ...ter_transaction_duration_rule_type.test.ts | 214 ++++++++- ...register_transaction_duration_rule_type.ts | 77 ++-- ...r_transaction_error_rate_rule_type.test.ts | 300 ++++++++++++- ...gister_transaction_error_rate_rule_type.ts | 85 ++-- .../get_groupby_action_variables.test.ts | 46 ++ .../utils/get_groupby_action_variables.ts | 47 ++ .../utils/get_groupby_terms.test.ts | 34 ++ .../rule_types/utils/get_groupby_terms.ts | 21 + .../translations/translations/fr-FR.json | 3 - .../translations/translations/ja-JP.json | 3 - .../translations/translations/zh-CN.json | 2 - .../alerts/error_count_threshold.spec.ts | 63 ++- .../tests/alerts/transaction_duration.spec.ts | 183 ++++++++ .../alerts/transaction_error_rate.spec.ts | 187 ++++++++ .../tests/alerts/wait_for_rule_status.ts | 30 ++ .../trial/__snapshots__/create_rule.snap | 14 +- 27 files changed, 1943 insertions(+), 176 deletions(-) create mode 100644 x-pack/plugins/apm/public/components/alerting/ui_components/apm_rule_group_by.tsx create mode 100644 x-pack/plugins/apm/server/routes/alerts/rule_types/utils/get_groupby_action_variables.test.ts create mode 100644 x-pack/plugins/apm/server/routes/alerts/rule_types/utils/get_groupby_action_variables.ts create mode 100644 x-pack/plugins/apm/server/routes/alerts/rule_types/utils/get_groupby_terms.test.ts create mode 100644 x-pack/plugins/apm/server/routes/alerts/rule_types/utils/get_groupby_terms.ts create mode 100644 x-pack/test/apm_api_integration/tests/alerts/transaction_duration.spec.ts create mode 100644 x-pack/test/apm_api_integration/tests/alerts/transaction_error_rate.spec.ts diff --git a/x-pack/plugins/apm/common/es_fields/__snapshots__/es_fields.test.ts.snap b/x-pack/plugins/apm/common/es_fields/__snapshots__/es_fields.test.ts.snap index deb363ab5a21b..3ddc94bde0255 100644 --- a/x-pack/plugins/apm/common/es_fields/__snapshots__/es_fields.test.ts.snap +++ b/x-pack/plugins/apm/common/es_fields/__snapshots__/es_fields.test.ts.snap @@ -156,10 +156,10 @@ exports[`Error LABEL_GC 1`] = `undefined`; exports[`Error LABEL_NAME 1`] = `undefined`; -exports[`Error LABEL_TYPE 1`] = `undefined`; - exports[`Error LABEL_TELEMETRY_AUTO_VERSION 1`] = `undefined`; +exports[`Error LABEL_TYPE 1`] = `undefined`; + exports[`Error METRIC_CGROUP_MEMORY_LIMIT_BYTES 1`] = `undefined`; exports[`Error METRIC_CGROUP_MEMORY_USAGE_BYTES 1`] = `undefined`; @@ -479,10 +479,10 @@ exports[`Span LABEL_GC 1`] = `undefined`; exports[`Span LABEL_NAME 1`] = `undefined`; -exports[`Span LABEL_TYPE 1`] = `undefined`; - exports[`Span LABEL_TELEMETRY_AUTO_VERSION 1`] = `undefined`; +exports[`Span LABEL_TYPE 1`] = `undefined`; + exports[`Span METRIC_CGROUP_MEMORY_LIMIT_BYTES 1`] = `undefined`; exports[`Span METRIC_CGROUP_MEMORY_USAGE_BYTES 1`] = `undefined`; @@ -812,10 +812,10 @@ exports[`Transaction LABEL_GC 1`] = `undefined`; exports[`Transaction LABEL_NAME 1`] = `undefined`; -exports[`Transaction LABEL_TYPE 1`] = `undefined`; - exports[`Transaction LABEL_TELEMETRY_AUTO_VERSION 1`] = `undefined`; +exports[`Transaction LABEL_TYPE 1`] = `undefined`; + exports[`Transaction METRIC_CGROUP_MEMORY_LIMIT_BYTES 1`] = `undefined`; exports[`Transaction METRIC_CGROUP_MEMORY_USAGE_BYTES 1`] = `undefined`; diff --git a/x-pack/plugins/apm/common/rules/apm_rule_types.ts b/x-pack/plugins/apm/common/rules/apm_rule_types.ts index b5a640b2d72af..d1009cebc70da 100644 --- a/x-pack/plugins/apm/common/rules/apm_rule_types.ts +++ b/x-pack/plugins/apm/common/rules/apm_rule_types.ts @@ -15,6 +15,14 @@ import type { import type { ActionGroup } from '@kbn/alerting-plugin/common'; import { formatDurationFromTimeUnitChar } from '@kbn/observability-plugin/common'; import { ANOMALY_SEVERITY, ANOMALY_THRESHOLD } from '../ml_constants'; +import { + ERROR_GROUP_ID, + SERVICE_ENVIRONMENT, + SERVICE_NAME, + TRANSACTION_NAME, + TRANSACTION_TYPE, +} from '../es_fields/apm'; +import { getEnvironmentLabel } from '../environment_filter_values'; export const APM_SERVER_FEATURE_ID = 'apm'; @@ -40,29 +48,66 @@ const THRESHOLD_MET_GROUP: ActionGroup = { }), }; +const getFieldNameLabel = (field: string): string => { + switch (field) { + case SERVICE_NAME: + return 'service'; + case SERVICE_ENVIRONMENT: + return 'env'; + case TRANSACTION_TYPE: + return 'type'; + case TRANSACTION_NAME: + return 'name'; + case ERROR_GROUP_ID: + return 'error key'; + default: + return field; + } +}; + +export const getFieldValueLabel = ( + field: string, + fieldValue: string +): string => { + return field === SERVICE_ENVIRONMENT + ? getEnvironmentLabel(fieldValue) + : fieldValue; +}; + +const formatGroupByFields = (groupByFields: Record): string => { + const groupByFieldLabels = Object.keys(groupByFields).map( + (field) => + `${getFieldNameLabel(field)}: ${getFieldValueLabel( + field, + groupByFields[field] + )}` + ); + return groupByFieldLabels.join(', '); +}; + export function formatErrorCountReason({ threshold, measured, - serviceName, windowSize, windowUnit, + groupByFields, }: { threshold: number; measured: number; - serviceName: string; windowSize: number; windowUnit: string; + groupByFields: Record; }) { return i18n.translate('xpack.apm.alertTypes.errorCount.reason', { - defaultMessage: `Error count is {measured} in the last {interval} for {serviceName}. Alert when > {threshold}.`, + defaultMessage: `Error count is {measured} in the last {interval} for {group}. Alert when > {threshold}.`, values: { threshold, measured, - serviceName, interval: formatDurationFromTimeUnitChar( windowSize, windowUnit as TimeUnitChar ), + group: formatGroupByFields(groupByFields), }, }); } @@ -70,19 +115,19 @@ export function formatErrorCountReason({ export function formatTransactionDurationReason({ threshold, measured, - serviceName, asDuration, aggregationType, windowSize, windowUnit, + groupByFields, }: { threshold: number; measured: number; - serviceName: string; asDuration: AsDuration; aggregationType: string; windowSize: number; windowUnit: string; + groupByFields: Record; }) { let aggregationTypeFormatted = aggregationType.charAt(0).toUpperCase() + aggregationType.slice(1); @@ -90,16 +135,16 @@ export function formatTransactionDurationReason({ aggregationTypeFormatted = aggregationTypeFormatted + '.'; return i18n.translate('xpack.apm.alertTypes.transactionDuration.reason', { - defaultMessage: `{aggregationType} latency is {measured} in the last {interval} for {serviceName}. Alert when > {threshold}.`, + defaultMessage: `{aggregationType} latency is {measured} in the last {interval} for {group}. Alert when > {threshold}.`, values: { threshold: asDuration(threshold), measured: asDuration(measured), - serviceName, aggregationType: aggregationTypeFormatted, interval: formatDurationFromTimeUnitChar( windowSize, windowUnit as TimeUnitChar ), + group: formatGroupByFields(groupByFields), }, }); } @@ -107,28 +152,28 @@ export function formatTransactionDurationReason({ export function formatTransactionErrorRateReason({ threshold, measured, - serviceName, asPercent, windowSize, windowUnit, + groupByFields, }: { threshold: number; measured: number; - serviceName: string; asPercent: AsPercent; windowSize: number; windowUnit: string; + groupByFields: Record; }) { return i18n.translate('xpack.apm.alertTypes.transactionErrorRate.reason', { - defaultMessage: `Failed transactions is {measured} in the last {interval} for {serviceName}. Alert when > {threshold}.`, + defaultMessage: `Failed transactions is {measured} in the last {interval} for {group}. Alert when > {threshold}.`, values: { threshold: asPercent(threshold, 100), measured: asPercent(measured, 100), - serviceName, interval: formatDurationFromTimeUnitChar( windowSize, windowUnit as TimeUnitChar ), + group: formatGroupByFields(groupByFields), }, }); } diff --git a/x-pack/plugins/apm/common/rules/schema.ts b/x-pack/plugins/apm/common/rules/schema.ts index ca77e76f6f156..656f6efbe4b25 100644 --- a/x-pack/plugins/apm/common/rules/schema.ts +++ b/x-pack/plugins/apm/common/rules/schema.ts @@ -15,6 +15,7 @@ export const errorCountParamsSchema = schema.object({ threshold: schema.number(), serviceName: schema.maybe(schema.string()), environment: schema.string(), + groupBy: schema.maybe(schema.arrayOf(schema.string())), errorGroupingKey: schema.maybe(schema.string()), }); @@ -31,6 +32,7 @@ export const transactionDurationParamsSchema = schema.object({ schema.literal(AggregationType.P99), ]), environment: schema.string(), + groupBy: schema.maybe(schema.arrayOf(schema.string())), }); export const anomalyParamsSchema = schema.object({ @@ -55,6 +57,7 @@ export const transactionErrorRateParamsSchema = schema.object({ transactionName: schema.maybe(schema.string()), serviceName: schema.maybe(schema.string()), environment: schema.string(), + groupBy: schema.maybe(schema.arrayOf(schema.string())), }); type ErrorCountParamsType = TypeOf; diff --git a/x-pack/plugins/apm/public/components/alerting/rule_types/error_count_rule_type/index.tsx b/x-pack/plugins/apm/public/components/alerting/rule_types/error_count_rule_type/index.tsx index f23dc6f4fb362..f95f117cedc5b 100644 --- a/x-pack/plugins/apm/public/components/alerting/rule_types/error_count_rule_type/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/rule_types/error_count_rule_type/index.tsx @@ -7,13 +7,15 @@ import { i18n } from '@kbn/i18n'; import { defaults, omit } from 'lodash'; -import React, { useEffect } from 'react'; +import React, { useCallback, useEffect } from 'react'; import { CoreStart } from '@kbn/core/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { ForLastExpression, TIME_UNITS, } from '@kbn/triggers-actions-ui-plugin/public'; +import { EuiFormRow } from '@elastic/eui'; +import { EuiSpacer } from '@elastic/eui'; import { ENVIRONMENT_ALL } from '../../../../../common/environment_filter_values'; import { asInteger } from '../../../../../common/utils/formatters'; import { useFetcher } from '../../../../hooks/use_fetcher'; @@ -27,6 +29,13 @@ import { } from '../../utils/fields'; import { AlertMetadata, getIntervalAndTimeRange } from '../../utils/helper'; import { ApmRuleParamsContainer } from '../../ui_components/apm_rule_params_container'; +import { APMRuleGroupBy } from '../../ui_components/apm_rule_group_by'; +import { + SERVICE_ENVIRONMENT, + SERVICE_NAME, + TRANSACTION_NAME, + ERROR_GROUP_ID, +} from '../../../../../common/es_fields/apm'; export interface RuleParams { windowSize?: number; @@ -34,6 +43,7 @@ export interface RuleParams { threshold?: number; serviceName?: string; environment?: string; + groupBy?: string[] | undefined; errorGroupingKey?: string; } @@ -95,6 +105,13 @@ export function ErrorCountRuleType(props: Props) { ] ); + const onGroupByChange = useCallback( + (group: string[] | null) => { + setRuleParams('groupBy', group ?? []); + }, + [setRuleParams] + ); + const fields = [ ); + const groupAlertsBy = ( + <> + + + + + + ); + return ( = { @@ -146,6 +156,13 @@ export function TransactionDurationRuleType(props: Props) { /> ); + const onGroupByChange = useCallback( + (group: string[] | null) => { + setRuleParams('groupBy', group ?? []); + }, + [setRuleParams] + ); + const fields = [ , ]; + const groupAlertsBy = ( + <> + + + + + + ); + return ( diff --git a/x-pack/plugins/apm/public/components/alerting/rule_types/transaction_error_rate_rule_type/index.tsx b/x-pack/plugins/apm/public/components/alerting/rule_types/transaction_error_rate_rule_type/index.tsx index f161ef085b3ea..6f4f36b84778d 100644 --- a/x-pack/plugins/apm/public/components/alerting/rule_types/transaction_error_rate_rule_type/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/rule_types/transaction_error_rate_rule_type/index.tsx @@ -6,13 +6,16 @@ */ import { defaults, omit } from 'lodash'; -import React, { useEffect } from 'react'; +import { i18n } from '@kbn/i18n'; +import React, { useCallback, useEffect } from 'react'; import { CoreStart } from '@kbn/core/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { ForLastExpression, TIME_UNITS, } from '@kbn/triggers-actions-ui-plugin/public'; +import { EuiFormRow } from '@elastic/eui'; +import { EuiSpacer } from '@elastic/eui'; import { ENVIRONMENT_ALL } from '../../../../../common/environment_filter_values'; import { asPercent } from '../../../../../common/utils/formatters'; import { useFetcher } from '../../../../hooks/use_fetcher'; @@ -27,6 +30,13 @@ import { } from '../../utils/fields'; import { AlertMetadata, getIntervalAndTimeRange } from '../../utils/helper'; import { ApmRuleParamsContainer } from '../../ui_components/apm_rule_params_container'; +import { APMRuleGroupBy } from '../../ui_components/apm_rule_group_by'; +import { + SERVICE_NAME, + SERVICE_ENVIRONMENT, + TRANSACTION_TYPE, + TRANSACTION_NAME, +} from '../../../../../common/es_fields/apm'; export interface RuleParams { windowSize?: number; @@ -36,6 +46,7 @@ export interface RuleParams { transactionType?: string; transactionName?: string; environment?: string; + groupBy?: string[] | undefined; } export interface Props { @@ -100,6 +111,13 @@ export function TransactionErrorRateRuleType(props: Props) { ] ); + const onGroupByChange = useCallback( + (group: string[] | null) => { + setRuleParams('groupBy', group ?? []); + }, + [setRuleParams] + ); + const fields = [ ); + const groupAlertsBy = ( + <> + + + + + + ); + return ( void; + errorOptions?: string[]; +} + +export function APMRuleGroupBy({ + options, + fields, + preSelectedOptions, + onChange, + errorOptions, +}: Props) { + const handleChange = useCallback( + (selectedOptions: Array<{ label: string }>) => { + const groupByOption = selectedOptions.map((option) => option.label); + onChange([...new Set(preSelectedOptions.concat(groupByOption))]); + }, + [onChange, preSelectedOptions] + ); + + const getPreSelectedOptions = () => { + return preSelectedOptions.map((field) => ({ + label: field, + color: 'lightgray', + disabled: true, + })); + }; + + const getUserSelectedOptions = (groupBy: string[] | undefined) => { + return (groupBy ?? []) + .filter((group) => !preSelectedOptions.includes(group)) + .map((field) => ({ + label: field, + color: errorOptions?.includes(field) ? 'danger' : undefined, + })); + }; + + const selectedOptions = [ + ...getPreSelectedOptions(), + ...getUserSelectedOptions(options.groupBy), + ]; + + return ( + ({ label: field }))} + onChange={handleChange} + isClearable={false} + /> + ); +} diff --git a/x-pack/plugins/apm/public/components/alerting/ui_components/apm_rule_params_container/index.tsx b/x-pack/plugins/apm/public/components/alerting/ui_components/apm_rule_params_container/index.tsx index 57d27c6cb6e31..b651fb29a824f 100644 --- a/x-pack/plugins/apm/public/components/alerting/ui_components/apm_rule_params_container/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/ui_components/apm_rule_params_container/index.tsx @@ -24,6 +24,7 @@ interface Props { setRuleProperty: (key: string, value: any) => void; defaultParams: Record; fields: React.ReactNode[]; + groupAlertsBy?: React.ReactNode; chartPreview?: React.ReactNode; minimumWindowSize?: MinimumWindowSize; } @@ -31,6 +32,7 @@ interface Props { export function ApmRuleParamsContainer(props: Props) { const { fields, + groupAlertsBy, setRuleParams, defaultParams, chartPreview, @@ -72,6 +74,7 @@ export function ApmRuleParamsContainer(props: Props) { {chartPreview} + {groupAlertsBy} ); } diff --git a/x-pack/plugins/apm/server/routes/alerts/register_apm_rule_types.ts b/x-pack/plugins/apm/server/routes/alerts/register_apm_rule_types.ts index ccfd76b760d66..e34957083d3e4 100644 --- a/x-pack/plugins/apm/server/routes/alerts/register_apm_rule_types.ts +++ b/x-pack/plugins/apm/server/routes/alerts/register_apm_rule_types.ts @@ -17,6 +17,7 @@ import { MlPluginSetup } from '@kbn/ml-plugin/server'; import { legacyExperimentalFieldMap } from '@kbn/alerts-as-data-utils'; import { AGENT_NAME, + ERROR_GROUP_ID, PROCESSOR_EVENT, SERVICE_ENVIRONMENT, SERVICE_LANGUAGE_NAME, @@ -50,6 +51,10 @@ export const apmRuleTypeAlertFieldMap = { type: 'keyword', required: false, }, + [ERROR_GROUP_ID]: { + type: 'keyword', + required: false, + }, [PROCESSOR_EVENT]: { type: 'keyword', required: false, diff --git a/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/register_error_count_rule_type.test.ts b/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/register_error_count_rule_type.test.ts index 4513db455cf6d..781094ca55257 100644 --- a/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/register_error_count_rule_type.test.ts +++ b/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/register_error_count_rule_type.test.ts @@ -44,7 +44,11 @@ describe('Error count alert', () => { registerErrorCountRuleType(dependencies); - const params = { threshold: 2, windowSize: 5, windowUnit: 'm' }; + const params = { + threshold: 2, + windowSize: 5, + windowUnit: 'm', + }; services.scopedClusterClient.asCurrentUser.search.mockResponse({ hits: { @@ -126,11 +130,208 @@ describe('Error count alert', () => { }, }); + await executor({ params }); + ['foo_env-foo', 'foo_env-foo-2', 'bar_env-bar'].forEach((instanceName) => + expect(services.alertFactory.create).toHaveBeenCalledWith(instanceName) + ); + + expect(scheduleActions).toHaveBeenCalledTimes(3); + + expect(scheduleActions).toHaveBeenCalledWith('threshold_met', { + serviceName: 'foo', + environment: 'env-foo', + threshold: 2, + triggerValue: 5, + reason: + 'Error count is 5 in the last 5 mins for service: foo, env: env-foo. Alert when > 2.', + interval: '5 mins', + viewInAppUrl: + 'http://localhost:5601/eyr/app/apm/services/foo/errors?environment=env-foo', + }); + expect(scheduleActions).toHaveBeenCalledWith('threshold_met', { + serviceName: 'foo', + environment: 'env-foo-2', + threshold: 2, + triggerValue: 4, + reason: + 'Error count is 4 in the last 5 mins for service: foo, env: env-foo-2. Alert when > 2.', + interval: '5 mins', + viewInAppUrl: + 'http://localhost:5601/eyr/app/apm/services/foo/errors?environment=env-foo-2', + }); + expect(scheduleActions).toHaveBeenCalledWith('threshold_met', { + serviceName: 'bar', + environment: 'env-bar', + reason: + 'Error count is 3 in the last 5 mins for service: bar, env: env-bar. Alert when > 2.', + threshold: 2, + triggerValue: 3, + interval: '5 mins', + viewInAppUrl: + 'http://localhost:5601/eyr/app/apm/services/bar/errors?environment=env-bar', + }); + }); + + it('sends alert when rule is configured with group by on transaction.name', async () => { + const { services, dependencies, executor, scheduleActions } = + createRuleTypeMocks(); + + registerErrorCountRuleType(dependencies); + + const params = { + threshold: 2, + windowSize: 5, + windowUnit: 'm', + groupBy: ['service.name', 'service.environment', 'transaction.name'], + }; + + services.scopedClusterClient.asCurrentUser.search.mockResponse({ + hits: { + hits: [], + total: { + relation: 'eq', + value: 2, + }, + }, + aggregations: { + error_counts: { + buckets: [ + { + key: ['foo', 'env-foo', 'tx-name-foo'], + doc_count: 5, + }, + { + key: ['foo', 'env-foo-2', 'tx-name-foo-2'], + doc_count: 4, + }, + { + key: ['bar', 'env-bar', 'tx-name-bar'], + doc_count: 3, + }, + { + key: ['bar', 'env-bar-2', 'tx-name-bar-2'], + doc_count: 1, + }, + ], + }, + }, + took: 0, + timed_out: false, + _shards: { + failed: 0, + skipped: 0, + successful: 1, + total: 1, + }, + }); + + await executor({ params }); + [ + 'foo_env-foo_tx-name-foo', + 'foo_env-foo-2_tx-name-foo-2', + 'bar_env-bar_tx-name-bar', + ].forEach((instanceName) => + expect(services.alertFactory.create).toHaveBeenCalledWith(instanceName) + ); + + expect(scheduleActions).toHaveBeenCalledTimes(3); + + expect(scheduleActions).toHaveBeenCalledWith('threshold_met', { + serviceName: 'foo', + environment: 'env-foo', + threshold: 2, + triggerValue: 5, + reason: + 'Error count is 5 in the last 5 mins for service: foo, env: env-foo, name: tx-name-foo. Alert when > 2.', + interval: '5 mins', + viewInAppUrl: + 'http://localhost:5601/eyr/app/apm/services/foo/errors?environment=env-foo', + transactionName: 'tx-name-foo', + }); + expect(scheduleActions).toHaveBeenCalledWith('threshold_met', { + serviceName: 'foo', + environment: 'env-foo-2', + threshold: 2, + triggerValue: 4, + reason: + 'Error count is 4 in the last 5 mins for service: foo, env: env-foo-2, name: tx-name-foo-2. Alert when > 2.', + interval: '5 mins', + viewInAppUrl: + 'http://localhost:5601/eyr/app/apm/services/foo/errors?environment=env-foo-2', + transactionName: 'tx-name-foo-2', + }); + expect(scheduleActions).toHaveBeenCalledWith('threshold_met', { + serviceName: 'bar', + environment: 'env-bar', + reason: + 'Error count is 3 in the last 5 mins for service: bar, env: env-bar, name: tx-name-bar. Alert when > 2.', + threshold: 2, + triggerValue: 3, + interval: '5 mins', + viewInAppUrl: + 'http://localhost:5601/eyr/app/apm/services/bar/errors?environment=env-bar', + transactionName: 'tx-name-bar', + }); + }); + + it('sends alert when rule is configured with group by on error.grouping_key', async () => { + const { services, dependencies, executor, scheduleActions } = + createRuleTypeMocks(); + + registerErrorCountRuleType(dependencies); + + const params = { + threshold: 2, + windowSize: 5, + windowUnit: 'm', + groupBy: ['service.name', 'service.environment', 'error.grouping_key'], + }; + + services.scopedClusterClient.asCurrentUser.search.mockResponse({ + hits: { + hits: [], + total: { + relation: 'eq', + value: 2, + }, + }, + aggregations: { + error_counts: { + buckets: [ + { + key: ['foo', 'env-foo', 'error-key-foo'], + doc_count: 5, + }, + { + key: ['foo', 'env-foo-2', 'error-key-foo-2'], + doc_count: 4, + }, + { + key: ['bar', 'env-bar', 'error-key-bar'], + doc_count: 3, + }, + { + key: ['bar', 'env-bar-2', 'error-key-bar-2'], + doc_count: 1, + }, + ], + }, + }, + took: 0, + timed_out: false, + _shards: { + failed: 0, + skipped: 0, + successful: 1, + total: 1, + }, + }); + await executor({ params }); [ - 'apm.error_rate_foo_env-foo', - 'apm.error_rate_foo_env-foo-2', - 'apm.error_rate_bar_env-bar', + 'foo_env-foo_error-key-foo', + 'foo_env-foo-2_error-key-foo-2', + 'bar_env-bar_error-key-bar', ].forEach((instanceName) => expect(services.alertFactory.create).toHaveBeenCalledWith(instanceName) ); @@ -142,25 +343,225 @@ describe('Error count alert', () => { environment: 'env-foo', threshold: 2, triggerValue: 5, - reason: 'Error count is 5 in the last 5 mins for foo. Alert when > 2.', + reason: + 'Error count is 5 in the last 5 mins for service: foo, env: env-foo, error key: error-key-foo. Alert when > 2.', interval: '5 mins', viewInAppUrl: 'http://localhost:5601/eyr/app/apm/services/foo/errors?environment=env-foo', + errorGroupingKey: 'error-key-foo', }); expect(scheduleActions).toHaveBeenCalledWith('threshold_met', { serviceName: 'foo', environment: 'env-foo-2', threshold: 2, triggerValue: 4, - reason: 'Error count is 4 in the last 5 mins for foo. Alert when > 2.', + reason: + 'Error count is 4 in the last 5 mins for service: foo, env: env-foo-2, error key: error-key-foo-2. Alert when > 2.', interval: '5 mins', viewInAppUrl: 'http://localhost:5601/eyr/app/apm/services/foo/errors?environment=env-foo-2', + errorGroupingKey: 'error-key-foo-2', + }); + expect(scheduleActions).toHaveBeenCalledWith('threshold_met', { + serviceName: 'bar', + environment: 'env-bar', + reason: + 'Error count is 3 in the last 5 mins for service: bar, env: env-bar, error key: error-key-bar. Alert when > 2.', + threshold: 2, + triggerValue: 3, + interval: '5 mins', + viewInAppUrl: + 'http://localhost:5601/eyr/app/apm/services/bar/errors?environment=env-bar', + errorGroupingKey: 'error-key-bar', + }); + }); + + it('sends alert when rule is configured with preselected group by', async () => { + const { services, dependencies, executor, scheduleActions } = + createRuleTypeMocks(); + + registerErrorCountRuleType(dependencies); + + const params = { + threshold: 2, + windowSize: 5, + windowUnit: 'm', + groupBy: ['service.name', 'service.environment'], + }; + + services.scopedClusterClient.asCurrentUser.search.mockResponse({ + hits: { + hits: [], + total: { + relation: 'eq', + value: 2, + }, + }, + aggregations: { + error_counts: { + buckets: [ + { + key: ['foo', 'env-foo'], + doc_count: 5, + }, + { + key: ['foo', 'env-foo-2'], + doc_count: 4, + }, + { + key: ['bar', 'env-bar'], + doc_count: 3, + }, + { + key: ['bar', 'env-bar-2'], + doc_count: 1, + }, + ], + }, + }, + took: 0, + timed_out: false, + _shards: { + failed: 0, + skipped: 0, + successful: 1, + total: 1, + }, + }); + + await executor({ params }); + ['foo_env-foo', 'foo_env-foo-2', 'bar_env-bar'].forEach((instanceName) => + expect(services.alertFactory.create).toHaveBeenCalledWith(instanceName) + ); + + expect(scheduleActions).toHaveBeenCalledTimes(3); + + expect(scheduleActions).toHaveBeenCalledWith('threshold_met', { + serviceName: 'foo', + environment: 'env-foo', + threshold: 2, + triggerValue: 5, + reason: + 'Error count is 5 in the last 5 mins for service: foo, env: env-foo. Alert when > 2.', + interval: '5 mins', + viewInAppUrl: + 'http://localhost:5601/eyr/app/apm/services/foo/errors?environment=env-foo', + }); + expect(scheduleActions).toHaveBeenCalledWith('threshold_met', { + serviceName: 'foo', + environment: 'env-foo-2', + threshold: 2, + triggerValue: 4, + reason: + 'Error count is 4 in the last 5 mins for service: foo, env: env-foo-2. Alert when > 2.', + interval: '5 mins', + viewInAppUrl: + 'http://localhost:5601/eyr/app/apm/services/foo/errors?environment=env-foo-2', + }); + expect(scheduleActions).toHaveBeenCalledWith('threshold_met', { + serviceName: 'bar', + environment: 'env-bar', + reason: + 'Error count is 3 in the last 5 mins for service: bar, env: env-bar. Alert when > 2.', + threshold: 2, + triggerValue: 3, + interval: '5 mins', + viewInAppUrl: + 'http://localhost:5601/eyr/app/apm/services/bar/errors?environment=env-bar', + }); + }); + + it('sends alert when service.environment field does not exist in the source', async () => { + const { services, dependencies, executor, scheduleActions } = + createRuleTypeMocks(); + + registerErrorCountRuleType(dependencies); + + const params = { + threshold: 2, + windowSize: 5, + windowUnit: 'm', + groupBy: ['service.name', 'service.environment'], + }; + + services.scopedClusterClient.asCurrentUser.search.mockResponse({ + hits: { + hits: [], + total: { + relation: 'eq', + value: 2, + }, + }, + aggregations: { + error_counts: { + buckets: [ + { + key: ['foo', 'ENVIRONMENT_NOT_DEFINED'], + doc_count: 5, + }, + { + key: ['foo', 'ENVIRONMENT_NOT_DEFINED'], + doc_count: 4, + }, + { + key: ['bar', 'env-bar'], + doc_count: 3, + }, + { + key: ['bar', 'env-bar-2'], + doc_count: 1, + }, + ], + }, + }, + took: 0, + timed_out: false, + _shards: { + failed: 0, + skipped: 0, + successful: 1, + total: 1, + }, + }); + + await executor({ params }); + [ + 'foo_ENVIRONMENT_NOT_DEFINED', + 'foo_ENVIRONMENT_NOT_DEFINED', + 'bar_env-bar', + ].forEach((instanceName) => + expect(services.alertFactory.create).toHaveBeenCalledWith(instanceName) + ); + + expect(scheduleActions).toHaveBeenCalledTimes(3); + + expect(scheduleActions).toHaveBeenCalledWith('threshold_met', { + serviceName: 'foo', + environment: 'Not defined', + threshold: 2, + triggerValue: 5, + reason: + 'Error count is 5 in the last 5 mins for service: foo, env: Not defined. Alert when > 2.', + interval: '5 mins', + viewInAppUrl: + 'http://localhost:5601/eyr/app/apm/services/foo/errors?environment=ENVIRONMENT_ALL', + }); + expect(scheduleActions).toHaveBeenCalledWith('threshold_met', { + serviceName: 'foo', + environment: 'Not defined', + threshold: 2, + triggerValue: 4, + reason: + 'Error count is 4 in the last 5 mins for service: foo, env: Not defined. Alert when > 2.', + interval: '5 mins', + viewInAppUrl: + 'http://localhost:5601/eyr/app/apm/services/foo/errors?environment=ENVIRONMENT_ALL', }); expect(scheduleActions).toHaveBeenCalledWith('threshold_met', { serviceName: 'bar', environment: 'env-bar', - reason: 'Error count is 3 in the last 5 mins for bar. Alert when > 2.', + reason: + 'Error count is 3 in the last 5 mins for service: bar, env: env-bar. Alert when > 2.', threshold: 2, triggerValue: 3, interval: '5 mins', diff --git a/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/register_error_count_rule_type.ts b/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/register_error_count_rule_type.ts index 695a9a579a35d..6ac9a87789136 100644 --- a/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/register_error_count_rule_type.ts +++ b/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/register_error_count_rule_type.ts @@ -19,11 +19,7 @@ import { createLifecycleRuleTypeFactory } from '@kbn/rule-registry-plugin/server import { termQuery } from '@kbn/observability-plugin/server'; import { addSpaceIdToPath } from '@kbn/spaces-plugin/common'; import { firstValueFrom } from 'rxjs'; -import { - ENVIRONMENT_NOT_DEFINED, - getEnvironmentEsField, - getEnvironmentLabel, -} from '../../../../../common/environment_filter_values'; +import { getEnvironmentEsField } from '../../../../../common/environment_filter_values'; import { ERROR_GROUP_ID, PROCESSOR_EVENT, @@ -50,6 +46,8 @@ import { getServiceGroupFields, getServiceGroupFieldsAgg, } from '../get_service_group_fields'; +import { getGroupByTerms } from '../utils/get_groupby_terms'; +import { getGroupByActionVariables } from '../utils/get_groupby_action_variables'; const ruleTypeConfig = RULE_TYPES_CONFIG[ApmRuleType.ErrorCount]; @@ -78,6 +76,7 @@ export function registerErrorCountRuleType({ apmActionVariables.interval, apmActionVariables.reason, apmActionVariables.serviceName, + apmActionVariables.transactionName, apmActionVariables.errorGroupingKey, apmActionVariables.threshold, apmActionVariables.triggerValue, @@ -88,6 +87,12 @@ export function registerErrorCountRuleType({ minimumLicenseRequired: 'basic', isExportable: true, executor: async ({ params: ruleParams, services, spaceId }) => { + const predefinedGroupby = [SERVICE_NAME, SERVICE_ENVIRONMENT]; + + const allGroupbyFields = Array.from( + new Set([...predefinedGroupby, ...(ruleParams.groupBy ?? [])]) + ); + const config = await firstValueFrom(config$); const { savedObjectsClient, scopedClusterClient } = services; @@ -126,13 +131,7 @@ export function registerErrorCountRuleType({ aggs: { error_counts: { multi_terms: { - terms: [ - { field: SERVICE_NAME }, - { - field: SERVICE_ENVIRONMENT, - missing: ENVIRONMENT_NOT_DEFINED.value, - }, - ], + terms: getGroupByTerms(allGroupbyFields), size: 1000, order: { _count: 'desc' as const }, }, @@ -149,40 +148,42 @@ export function registerErrorCountRuleType({ const errorCountResults = response.aggregations?.error_counts.buckets.map((bucket) => { - const [serviceName, environment] = bucket.key; + const groupByFields = bucket.key.reduce( + (obj, bucketKey, bucketIndex) => { + obj[allGroupbyFields[bucketIndex]] = bucketKey; + return obj; + }, + {} as Record + ); + + const bucketKey = bucket.key; + return { - serviceName, - environment, errorCount: bucket.doc_count, sourceFields: getServiceGroupFields(bucket), + groupByFields, + bucketKey, }; }) ?? []; errorCountResults .filter((result) => result.errorCount >= ruleParams.threshold) .forEach((result) => { - const { serviceName, environment, errorCount, sourceFields } = + const { errorCount, sourceFields, groupByFields, bucketKey } = result; const alertReason = formatErrorCountReason({ - serviceName, threshold: ruleParams.threshold, measured: errorCount, windowSize: ruleParams.windowSize, windowUnit: ruleParams.windowUnit, + groupByFields, }); - const id = [ - ApmRuleType.ErrorCount, - serviceName, - environment, - ruleParams.errorGroupingKey, - ] - .filter((name) => name) - .join('_'); - const relativeViewInAppUrl = getAlertUrlErrorCount( - serviceName, - getEnvironmentEsField(environment)?.[SERVICE_ENVIRONMENT] + groupByFields[SERVICE_NAME], + getEnvironmentEsField(groupByFields[SERVICE_ENVIRONMENT])?.[ + SERVICE_ENVIRONMENT + ] ); const viewInAppUrl = addSpaceIdToPath( @@ -191,32 +192,33 @@ export function registerErrorCountRuleType({ relativeViewInAppUrl ); + const groupByActionVariables = + getGroupByActionVariables(groupByFields); + services .alertWithLifecycle({ - id, + id: bucketKey.join('_'), fields: { - [SERVICE_NAME]: serviceName, - ...getEnvironmentEsField(environment), [PROCESSOR_EVENT]: ProcessorEvent.error, [ALERT_EVALUATION_VALUE]: errorCount, [ALERT_EVALUATION_THRESHOLD]: ruleParams.threshold, [ERROR_GROUP_ID]: ruleParams.errorGroupingKey, [ALERT_REASON]: alertReason, ...sourceFields, + ...groupByFields, }, }) .scheduleActions(ruleTypeConfig.defaultActionGroupId, { - environment: getEnvironmentLabel(environment), interval: formatDurationFromTimeUnitChar( ruleParams.windowSize, ruleParams.windowUnit as TimeUnitChar ), reason: alertReason, - serviceName, threshold: ruleParams.threshold, - errorGroupingKey: ruleParams.errorGroupingKey, + errorGroupingKey: ruleParams.errorGroupingKey, // When group by doesn't include error.grouping_key, the context.error.grouping_key action variable will contain value of the Error Grouping Key filter triggerValue: errorCount, viewInAppUrl, + ...groupByActionVariables, }); }); diff --git a/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_duration/register_transaction_duration_rule_type.test.ts b/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_duration/register_transaction_duration_rule_type.test.ts index 1f91cfc548469..f3993dfed7009 100644 --- a/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_duration/register_transaction_duration_rule_type.test.ts +++ b/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_duration/register_transaction_duration_rule_type.test.ts @@ -27,7 +27,7 @@ describe('registerTransactionDurationRuleType', () => { series: { buckets: [ { - key: ['opbeans-java', 'ENVIRONMENT_NOT_DEFINED', 'request'], + key: ['opbeans-java', 'development', 'request'], avgLatency: { value: 5500000, }, @@ -61,16 +61,226 @@ describe('registerTransactionDurationRuleType', () => { 'http://localhost:5601/eyr/app/observability/alerts/' ), transactionName: 'GET /orders', + environment: 'development', + interval: `5 mins`, + reason: + 'Avg. latency is 5,500 ms in the last 5 mins for service: opbeans-java, env: development, type: request. Alert when > 3,000 ms.', + transactionType: 'request', + serviceName: 'opbeans-java', + threshold: 3000, + triggerValue: '5,500 ms', + viewInAppUrl: + 'http://localhost:5601/eyr/app/apm/services/opbeans-java?transactionType=request&environment=development', + }); + }); + + it('sends alert when rule is configured with group by on transaction.name', async () => { + const { services, dependencies, executor, scheduleActions } = + createRuleTypeMocks(); + + registerTransactionDurationRuleType(dependencies); + + services.scopedClusterClient.asCurrentUser.search.mockResponse({ + hits: { + hits: [], + total: { + relation: 'eq', + value: 2, + }, + }, + aggregations: { + series: { + buckets: [ + { + key: ['opbeans-java', 'development', 'request', 'GET /products'], + avgLatency: { + value: 5500000, + }, + }, + ], + }, + }, + took: 0, + timed_out: false, + _shards: { + failed: 0, + skipped: 0, + successful: 1, + total: 1, + }, + }); + + const params = { + threshold: 3000, + windowSize: 5, + windowUnit: 'm', + transactionType: 'request', + serviceName: 'opbeans-java', + aggregationType: 'avg', + groupBy: [ + 'service.name', + 'service.environment', + 'transaction.type', + 'transaction.name', + ], + }; + await executor({ params }); + expect(scheduleActions).toHaveBeenCalledTimes(1); + expect(scheduleActions).toHaveBeenCalledWith('threshold_met', { + alertDetailsUrl: expect.stringContaining( + 'http://localhost:5601/eyr/app/observability/alerts/' + ), + environment: 'development', + interval: `5 mins`, + reason: + 'Avg. latency is 5,500 ms in the last 5 mins for service: opbeans-java, env: development, type: request, name: GET /products. Alert when > 3,000 ms.', + transactionType: 'request', + serviceName: 'opbeans-java', + threshold: 3000, + triggerValue: '5,500 ms', + viewInAppUrl: + 'http://localhost:5601/eyr/app/apm/services/opbeans-java?transactionType=request&environment=development', + transactionName: 'GET /products', + }); + }); + + it('sends alert when rule is configured with preselected group by', async () => { + const { services, dependencies, executor, scheduleActions } = + createRuleTypeMocks(); + + registerTransactionDurationRuleType(dependencies); + + services.scopedClusterClient.asCurrentUser.search.mockResponse({ + hits: { + hits: [], + total: { + relation: 'eq', + value: 2, + }, + }, + aggregations: { + series: { + buckets: [ + { + key: ['opbeans-java', 'development', 'request'], + avgLatency: { + value: 5500000, + }, + }, + ], + }, + }, + took: 0, + timed_out: false, + _shards: { + failed: 0, + skipped: 0, + successful: 1, + total: 1, + }, + }); + + const params = { + threshold: 3000, + windowSize: 5, + windowUnit: 'm', + transactionType: 'request', + serviceName: 'opbeans-java', + aggregationType: 'avg', + groupBy: ['service.name', 'service.environment', 'transaction.type'], + }; + + await executor({ params }); + expect(scheduleActions).toHaveBeenCalledTimes(1); + expect(scheduleActions).toHaveBeenCalledWith('threshold_met', { + alertDetailsUrl: expect.stringContaining( + 'http://localhost:5601/eyr/app/observability/alerts/' + ), + environment: 'development', + interval: `5 mins`, + reason: + 'Avg. latency is 5,500 ms in the last 5 mins for service: opbeans-java, env: development, type: request. Alert when > 3,000 ms.', + transactionType: 'request', + serviceName: 'opbeans-java', + threshold: 3000, + triggerValue: '5,500 ms', + viewInAppUrl: + 'http://localhost:5601/eyr/app/apm/services/opbeans-java?transactionType=request&environment=development', + }); + }); + + it('sends alert when service.environment field does not exist in the source', async () => { + const { services, dependencies, executor, scheduleActions } = + createRuleTypeMocks(); + + registerTransactionDurationRuleType(dependencies); + + services.scopedClusterClient.asCurrentUser.search.mockResponse({ + hits: { + hits: [], + total: { + relation: 'eq', + value: 2, + }, + }, + aggregations: { + series: { + buckets: [ + { + key: [ + 'opbeans-java', + 'ENVIRONMENT_NOT_DEFINED', + 'request', + 'tx-java', + ], + avgLatency: { + value: 5500000, + }, + }, + ], + }, + }, + took: 0, + timed_out: false, + _shards: { + failed: 0, + skipped: 0, + successful: 1, + total: 1, + }, + }); + + const params = { + threshold: 3000, + windowSize: 5, + windowUnit: 'm', + transactionType: 'request', + serviceName: 'opbeans-java', + aggregationType: 'avg', + groupBy: [ + 'service.name', + 'service.environment', + 'transaction.type', + 'transaction.name', + ], + }; + await executor({ params }); + expect(scheduleActions).toHaveBeenCalledTimes(1); + expect(scheduleActions).toHaveBeenCalledWith('threshold_met', { + alertDetailsUrl: expect.stringContaining( + 'http://localhost:5601/eyr/app/observability/alerts/' + ), environment: 'Not defined', interval: `5 mins`, reason: - 'Avg. latency is 5,500 ms in the last 5 mins for opbeans-java. Alert when > 3,000 ms.', + 'Avg. latency is 5,500 ms in the last 5 mins for service: opbeans-java, env: Not defined, type: request, name: tx-java. Alert when > 3,000 ms.', transactionType: 'request', serviceName: 'opbeans-java', threshold: 3000, triggerValue: '5,500 ms', viewInAppUrl: 'http://localhost:5601/eyr/app/apm/services/opbeans-java?transactionType=request&environment=ENVIRONMENT_ALL', + transactionName: 'tx-java', }); }); }); diff --git a/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_duration/register_transaction_duration_rule_type.ts b/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_duration/register_transaction_duration_rule_type.ts index 9bb21324c22b7..0ac465261c954 100644 --- a/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_duration/register_transaction_duration_rule_type.ts +++ b/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_duration/register_transaction_duration_rule_type.ts @@ -22,12 +22,9 @@ import { import { createLifecycleRuleTypeFactory } from '@kbn/rule-registry-plugin/server'; import { addSpaceIdToPath } from '@kbn/spaces-plugin/common'; import { firstValueFrom } from 'rxjs'; +import { getGroupByTerms } from '../utils/get_groupby_terms'; import { SearchAggregatedTransactionSetting } from '../../../../../common/aggregated_transactions'; -import { - ENVIRONMENT_NOT_DEFINED, - getEnvironmentEsField, - getEnvironmentLabel, -} from '../../../../../common/environment_filter_values'; +import { getEnvironmentEsField } from '../../../../../common/environment_filter_values'; import { PROCESSOR_EVENT, SERVICE_ENVIRONMENT, @@ -66,6 +63,7 @@ import { averageOrPercentileAgg, getMultiTermsSortOrder, } from './average_or_percentile_agg'; +import { getGroupByActionVariables } from '../utils/get_groupby_action_variables'; const ruleTypeConfig = RULE_TYPES_CONFIG[ApmRuleType.TransactionDuration]; @@ -105,6 +103,16 @@ export function registerTransactionDurationRuleType({ minimumLicenseRequired: 'basic', isExportable: true, executor: async ({ params: ruleParams, services, spaceId }) => { + const predefinedGroupby = [ + SERVICE_NAME, + SERVICE_ENVIRONMENT, + TRANSACTION_TYPE, + ]; + + const allGroupbyFields = Array.from( + new Set([...predefinedGroupby, ...(ruleParams.groupBy ?? [])]) + ); + const config = await firstValueFrom(config$); const { getAlertUuid, savedObjectsClient, scopedClusterClient } = @@ -164,14 +172,7 @@ export function registerTransactionDurationRuleType({ aggs: { series: { multi_terms: { - terms: [ - { field: SERVICE_NAME }, - { - field: SERVICE_ENVIRONMENT, - missing: ENVIRONMENT_NOT_DEFINED.value, - }, - { field: TRANSACTION_TYPE }, - ], + terms: [...getGroupByTerms(allGroupbyFields)], size: 1000, ...getMultiTermsSortOrder(ruleParams.aggregationType), }, @@ -202,7 +203,15 @@ export function registerTransactionDurationRuleType({ const triggeredBuckets = []; for (const bucket of response.aggregations.series.buckets) { - const [serviceName, environment, transactionType] = bucket.key; + const groupByFields = bucket.key.reduce( + (obj, bucketKey, bucketIndex) => { + obj[allGroupbyFields[bucketIndex]] = bucketKey; + return obj; + }, + {} as Record + ); + + const bucketKey = bucket.key; const transactionDuration = 'avgLatency' in bucket // only true if ruleParams.aggregationType === 'avg' @@ -214,24 +223,20 @@ export function registerTransactionDurationRuleType({ transactionDuration > thresholdMicroseconds ) { triggeredBuckets.push({ - environment, - serviceName, sourceFields: getServiceGroupFields(bucket), - transactionType, transactionDuration, + groupByFields, + bucketKey, }); } } for (const { - serviceName, - environment, - transactionType, transactionDuration, sourceFields, + groupByFields, + bucketKey, } of triggeredBuckets) { - const environmentLabel = getEnvironmentLabel(environment); - const durationFormatter = getDurationFormatter(transactionDuration); const transactionDurationFormatted = durationFormatter(transactionDuration).formatted; @@ -240,15 +245,13 @@ export function registerTransactionDurationRuleType({ aggregationType: String(ruleParams.aggregationType), asDuration, measured: transactionDuration, - serviceName, threshold: thresholdMicroseconds, windowSize: ruleParams.windowSize, windowUnit: ruleParams.windowUnit, + groupByFields, }); - const id = `${ApmRuleType.TransactionDuration}_${environmentLabel}`; - - const alertUuid = getAlertUuid(id); + const alertUuid = getAlertUuid(bucketKey.join('_')); const alertDetailsUrl = getAlertDetailsUrl( basePath, @@ -260,41 +263,41 @@ export function registerTransactionDurationRuleType({ basePath.publicBaseUrl, spaceId, getAlertUrlTransaction( - serviceName, - getEnvironmentEsField(environment)?.[SERVICE_ENVIRONMENT], - transactionType + groupByFields[SERVICE_NAME], + getEnvironmentEsField(groupByFields[SERVICE_ENVIRONMENT])?.[ + SERVICE_ENVIRONMENT + ], + groupByFields[TRANSACTION_TYPE] ) ); + const groupByActionVariables = getGroupByActionVariables(groupByFields); + services .alertWithLifecycle({ - id, + id: bucketKey.join('_'), fields: { - [SERVICE_NAME]: serviceName, - ...getEnvironmentEsField(environment), - [TRANSACTION_TYPE]: transactionType, [TRANSACTION_NAME]: ruleParams.transactionName, [PROCESSOR_EVENT]: ProcessorEvent.transaction, [ALERT_EVALUATION_VALUE]: transactionDuration, [ALERT_EVALUATION_THRESHOLD]: ruleParams.threshold, [ALERT_REASON]: reason, ...sourceFields, + ...groupByFields, }, }) .scheduleActions(ruleTypeConfig.defaultActionGroupId, { alertDetailsUrl, - environment: environmentLabel, interval: formatDurationFromTimeUnitChar( ruleParams.windowSize, ruleParams.windowUnit as TimeUnitChar ), reason, - serviceName, - transactionName: ruleParams.transactionName, // #Note once we group by transactionName, use the transactionName key from the bucket + transactionName: ruleParams.transactionName, // When group by doesn't include transaction.name, the context.transaction.name action variable will contain value of the Transaction Name filter threshold: ruleParams.threshold, - transactionType, triggerValue: transactionDurationFormatted, viewInAppUrl, + ...groupByActionVariables, }); } diff --git a/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_error_rate/register_transaction_error_rate_rule_type.test.ts b/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_error_rate/register_transaction_error_rate_rule_type.test.ts index a1c28f5dd77e4..708d5c533ba6b 100644 --- a/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_error_rate/register_transaction_error_rate_rule_type.test.ts +++ b/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_error_rate/register_transaction_error_rate_rule_type.test.ts @@ -107,17 +107,120 @@ describe('Transaction error rate alert', () => { }, }); - const params = { threshold: 10, windowSize: 5, windowUnit: 'm' }; + const params = { + threshold: 10, + windowSize: 5, + windowUnit: 'm', + }; + + await executor({ params }); + + expect(services.alertFactory.create).toHaveBeenCalledTimes(1); + + expect(services.alertFactory.create).toHaveBeenCalledWith( + 'foo_env-foo_type-foo' + ); + expect(services.alertFactory.create).not.toHaveBeenCalledWith( + 'bar_env-bar_type-bar' + ); + + expect(scheduleActions).toHaveBeenCalledWith('threshold_met', { + serviceName: 'foo', + transactionType: 'type-foo', + environment: 'env-foo', + reason: + 'Failed transactions is 10% in the last 5 mins for service: foo, env: env-foo, type: type-foo. Alert when > 10%.', + threshold: 10, + triggerValue: '10', + interval: '5 mins', + viewInAppUrl: + 'http://localhost:5601/eyr/app/apm/services/foo?transactionType=type-foo&environment=env-foo', + }); + }); + + it('sends alert when rule is configured with group by on transaction.name', async () => { + const { services, dependencies, executor, scheduleActions } = + createRuleTypeMocks(); + + registerTransactionErrorRateRuleType({ + ...dependencies, + }); + + services.scopedClusterClient.asCurrentUser.search.mockResponse({ + hits: { + hits: [], + total: { + relation: 'eq', + value: 2, + }, + }, + aggregations: { + series: { + buckets: [ + { + key: ['foo', 'env-foo', 'type-foo', 'tx-name-foo'], + outcomes: { + buckets: [ + { + key: 'success', + doc_count: 90, + }, + { + key: 'failure', + doc_count: 10, + }, + ], + }, + }, + { + key: ['bar', 'env-bar', 'type-bar', 'tx-name-bar'], + outcomes: { + buckets: [ + { + key: 'success', + doc_count: 90, + }, + { + key: 'failure', + doc_count: 1, + }, + ], + }, + }, + ], + }, + }, + took: 0, + timed_out: false, + _shards: { + failed: 0, + skipped: 0, + successful: 1, + total: 1, + }, + }); + + const params = { + threshold: 10, + windowSize: 5, + windowUnit: 'm', + groupBy: [ + 'service.name', + 'service.environment', + 'transaction.type', + 'transaction.name', + ], + }; await executor({ params }); expect(services.alertFactory.create).toHaveBeenCalledTimes(1); expect(services.alertFactory.create).toHaveBeenCalledWith( - 'apm.transaction_error_rate_foo_type-foo_env-foo' + 'foo_env-foo_type-foo_tx-name-foo' ); expect(services.alertFactory.create).not.toHaveBeenCalledWith( - 'apm.transaction_error_rate_bar_type-bar_env-bar' + 'bar_env-bar_type-bar_tx-name-bar' ); expect(scheduleActions).toHaveBeenCalledWith('threshold_met', { @@ -125,12 +228,201 @@ describe('Transaction error rate alert', () => { transactionType: 'type-foo', environment: 'env-foo', reason: - 'Failed transactions is 10% in the last 5 mins for foo. Alert when > 10%.', + 'Failed transactions is 10% in the last 5 mins for service: foo, env: env-foo, type: type-foo, name: tx-name-foo. Alert when > 10%.', threshold: 10, triggerValue: '10', interval: '5 mins', viewInAppUrl: 'http://localhost:5601/eyr/app/apm/services/foo?transactionType=type-foo&environment=env-foo', + transactionName: 'tx-name-foo', + }); + }); + + it('sends alert when rule is configured with preselected group by', async () => { + const { services, dependencies, executor, scheduleActions } = + createRuleTypeMocks(); + + registerTransactionErrorRateRuleType({ + ...dependencies, + }); + + services.scopedClusterClient.asCurrentUser.search.mockResponse({ + hits: { + hits: [], + total: { + relation: 'eq', + value: 2, + }, + }, + aggregations: { + series: { + buckets: [ + { + key: ['foo', 'env-foo', 'type-foo'], + outcomes: { + buckets: [ + { + key: 'success', + doc_count: 90, + }, + { + key: 'failure', + doc_count: 10, + }, + ], + }, + }, + { + key: ['bar', 'env-bar', 'type-bar'], + outcomes: { + buckets: [ + { + key: 'success', + doc_count: 90, + }, + { + key: 'failure', + doc_count: 1, + }, + ], + }, + }, + ], + }, + }, + took: 0, + timed_out: false, + _shards: { + failed: 0, + skipped: 0, + successful: 1, + total: 1, + }, + }); + + const params = { + threshold: 10, + windowSize: 5, + windowUnit: 'm', + groupBy: ['service.name', 'service.environment', 'transaction.type'], + }; + + await executor({ params }); + + expect(services.alertFactory.create).toHaveBeenCalledTimes(1); + + expect(services.alertFactory.create).toHaveBeenCalledWith( + 'foo_env-foo_type-foo' + ); + expect(services.alertFactory.create).not.toHaveBeenCalledWith( + 'bar_env-bar_type-bar' + ); + + expect(scheduleActions).toHaveBeenCalledWith('threshold_met', { + serviceName: 'foo', + transactionType: 'type-foo', + environment: 'env-foo', + reason: + 'Failed transactions is 10% in the last 5 mins for service: foo, env: env-foo, type: type-foo. Alert when > 10%.', + threshold: 10, + triggerValue: '10', + interval: '5 mins', + viewInAppUrl: + 'http://localhost:5601/eyr/app/apm/services/foo?transactionType=type-foo&environment=env-foo', + }); + }); + + it('sends alert when service.environment field does not exist in the source', async () => { + const { services, dependencies, executor, scheduleActions } = + createRuleTypeMocks(); + + registerTransactionErrorRateRuleType({ + ...dependencies, + }); + + services.scopedClusterClient.asCurrentUser.search.mockResponse({ + hits: { + hits: [], + total: { + relation: 'eq', + value: 2, + }, + }, + aggregations: { + series: { + buckets: [ + { + key: ['foo', 'ENVIRONMENT_NOT_DEFINED', 'type-foo'], + outcomes: { + buckets: [ + { + key: 'success', + doc_count: 90, + }, + { + key: 'failure', + doc_count: 10, + }, + ], + }, + }, + { + key: ['bar', 'ENVIRONMENT_NOT_DEFINED', 'type-bar'], + outcomes: { + buckets: [ + { + key: 'success', + doc_count: 90, + }, + { + key: 'failure', + doc_count: 1, + }, + ], + }, + }, + ], + }, + }, + took: 0, + timed_out: false, + _shards: { + failed: 0, + skipped: 0, + successful: 1, + total: 1, + }, + }); + + const params = { + threshold: 10, + windowSize: 5, + windowUnit: 'm', + groupBy: ['service.name', 'service.environment', 'transaction.type'], + }; + + await executor({ params }); + + expect(services.alertFactory.create).toHaveBeenCalledTimes(1); + + expect(services.alertFactory.create).toHaveBeenCalledWith( + 'foo_ENVIRONMENT_NOT_DEFINED_type-foo' + ); + expect(services.alertFactory.create).not.toHaveBeenCalledWith( + 'bar_ENVIRONMENT_NOT_DEFINED_type-bar' + ); + + expect(scheduleActions).toHaveBeenCalledWith('threshold_met', { + serviceName: 'foo', + transactionType: 'type-foo', + environment: 'Not defined', + reason: + 'Failed transactions is 10% in the last 5 mins for service: foo, env: Not defined, type: type-foo. Alert when > 10%.', + threshold: 10, + triggerValue: '10', + interval: '5 mins', + viewInAppUrl: + 'http://localhost:5601/eyr/app/apm/services/foo?transactionType=type-foo&environment=ENVIRONMENT_ALL', }); }); }); diff --git a/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_error_rate/register_transaction_error_rate_rule_type.ts b/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_error_rate/register_transaction_error_rate_rule_type.ts index 26b5847a205f1..6fa9319b71753 100644 --- a/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_error_rate/register_transaction_error_rate_rule_type.ts +++ b/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_error_rate/register_transaction_error_rate_rule_type.ts @@ -21,11 +21,7 @@ import { createLifecycleRuleTypeFactory } from '@kbn/rule-registry-plugin/server import { addSpaceIdToPath } from '@kbn/spaces-plugin/common'; import { firstValueFrom } from 'rxjs'; import { SearchAggregatedTransactionSetting } from '../../../../../common/aggregated_transactions'; -import { - ENVIRONMENT_NOT_DEFINED, - getEnvironmentEsField, - getEnvironmentLabel, -} from '../../../../../common/environment_filter_values'; +import { getEnvironmentEsField } from '../../../../../common/environment_filter_values'; import { EVENT_OUTCOME, PROCESSOR_EVENT, @@ -59,6 +55,8 @@ import { getServiceGroupFields, getServiceGroupFieldsAgg, } from '../get_service_group_fields'; +import { getGroupByTerms } from '../utils/get_groupby_terms'; +import { getGroupByActionVariables } from '../utils/get_groupby_action_variables'; const ruleTypeConfig = RULE_TYPES_CONFIG[ApmRuleType.TransactionErrorRate]; @@ -90,6 +88,7 @@ export function registerTransactionErrorRateRuleType({ apmActionVariables.transactionName, apmActionVariables.threshold, apmActionVariables.transactionType, + apmActionVariables.transactionName, apmActionVariables.triggerValue, apmActionVariables.viewInAppUrl, ], @@ -98,6 +97,16 @@ export function registerTransactionErrorRateRuleType({ minimumLicenseRequired: 'basic', isExportable: true, executor: async ({ services, spaceId, params: ruleParams }) => { + const predefinedGroupby = [ + SERVICE_NAME, + SERVICE_ENVIRONMENT, + TRANSACTION_TYPE, + ]; + + const allGroupbyFields = Array.from( + new Set([...predefinedGroupby, ...(ruleParams.groupBy ?? [])]) + ); + const config = await firstValueFrom(config$); const { savedObjectsClient, scopedClusterClient } = services; @@ -160,14 +169,7 @@ export function registerTransactionErrorRateRuleType({ aggs: { series: { multi_terms: { - terms: [ - { field: SERVICE_NAME }, - { - field: SERVICE_ENVIRONMENT, - missing: ENVIRONMENT_NOT_DEFINED.value, - }, - { field: TRANSACTION_TYPE }, - ], + terms: [...getGroupByTerms(allGroupbyFields)], size: 1000, order: { _count: 'desc' as const }, }, @@ -194,8 +196,17 @@ export function registerTransactionErrorRateRuleType({ } const results = []; + for (const bucket of response.aggregations.series.buckets) { - const [serviceName, environment, transactionType] = bucket.key; + const groupByFields = bucket.key.reduce( + (obj, bucketKey, bucketIndex) => { + obj[allGroupbyFields[bucketIndex]] = bucketKey; + return obj; + }, + {} as Record + ); + + const bucketKey = bucket.key; const failedOutcomeBucket = bucket.outcomes.buckets.find( (outcomeBucket) => outcomeBucket.key === EventOutcome.failure @@ -209,47 +220,32 @@ export function registerTransactionErrorRateRuleType({ if (errorRate >= ruleParams.threshold) { results.push({ - serviceName, - environment, - transactionType, errorRate, sourceFields: getServiceGroupFields(failedOutcomeBucket), + groupByFields, + bucketKey, }); } } results.forEach((result) => { - const { - serviceName, - environment, - transactionType, - errorRate, - sourceFields, - } = result; + const { errorRate, sourceFields, groupByFields, bucketKey } = result; const reasonMessage = formatTransactionErrorRateReason({ threshold: ruleParams.threshold, measured: errorRate, asPercent, - serviceName, windowSize: ruleParams.windowSize, windowUnit: ruleParams.windowUnit, + groupByFields, }); - const id = [ - ApmRuleType.TransactionErrorRate, - serviceName, - transactionType, - environment, - ruleParams.transactionName, - ] - .filter((name) => name) - .join('_'); - const relativeViewInAppUrl = getAlertUrlTransaction( - serviceName, - getEnvironmentEsField(environment)?.[SERVICE_ENVIRONMENT], - transactionType + groupByFields[SERVICE_NAME], + getEnvironmentEsField(groupByFields[SERVICE_ENVIRONMENT])?.[ + SERVICE_ENVIRONMENT + ], + groupByFields[TRANSACTION_TYPE] ); const viewInAppUrl = addSpaceIdToPath( @@ -258,34 +254,33 @@ export function registerTransactionErrorRateRuleType({ relativeViewInAppUrl ); + const groupByActionVariables = + getGroupByActionVariables(groupByFields); + services .alertWithLifecycle({ - id, + id: bucketKey.join('_'), fields: { - [SERVICE_NAME]: serviceName, - ...getEnvironmentEsField(environment), - [TRANSACTION_TYPE]: transactionType, [TRANSACTION_NAME]: ruleParams.transactionName, [PROCESSOR_EVENT]: ProcessorEvent.transaction, [ALERT_EVALUATION_VALUE]: errorRate, [ALERT_EVALUATION_THRESHOLD]: ruleParams.threshold, [ALERT_REASON]: reasonMessage, ...sourceFields, + ...groupByFields, }, }) .scheduleActions(ruleTypeConfig.defaultActionGroupId, { - environment: getEnvironmentLabel(environment), interval: formatDurationFromTimeUnitChar( ruleParams.windowSize, ruleParams.windowUnit as TimeUnitChar ), reason: reasonMessage, - serviceName, threshold: ruleParams.threshold, - transactionType, transactionName: ruleParams.transactionName, triggerValue: asDecimalOrInteger(errorRate), viewInAppUrl, + ...groupByActionVariables, }); }); diff --git a/x-pack/plugins/apm/server/routes/alerts/rule_types/utils/get_groupby_action_variables.test.ts b/x-pack/plugins/apm/server/routes/alerts/rule_types/utils/get_groupby_action_variables.test.ts new file mode 100644 index 0000000000000..96e0cbd7f09c1 --- /dev/null +++ b/x-pack/plugins/apm/server/routes/alerts/rule_types/utils/get_groupby_action_variables.test.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getGroupByActionVariables } from './get_groupby_action_variables'; + +describe('getGroupByActionVariables', () => { + it('should rename action variables', () => { + const result = getGroupByActionVariables({ + 'service.name': 'opbeans-java', + 'service.environment': 'development', + 'transaction.type': 'request', + 'transaction.name': 'tx-java', + 'error.grouping_key': 'error-key-0', + }); + expect(result).toMatchInlineSnapshot(` + Object { + "environment": "development", + "errorGroupingKey": "error-key-0", + "serviceName": "opbeans-java", + "transactionName": "tx-java", + "transactionType": "request", + } + `); + }); + + it('environment action variable should have value "Not defined"', () => { + const result = getGroupByActionVariables({ + 'service.name': 'opbeans-java', + 'service.environment': 'ENVIRONMENT_NOT_DEFINED', + 'transaction.type': 'request', + 'transaction.name': 'tx-java', + }); + expect(result).toMatchInlineSnapshot(` + Object { + "environment": "Not defined", + "serviceName": "opbeans-java", + "transactionName": "tx-java", + "transactionType": "request", + } + `); + }); +}); diff --git a/x-pack/plugins/apm/server/routes/alerts/rule_types/utils/get_groupby_action_variables.ts b/x-pack/plugins/apm/server/routes/alerts/rule_types/utils/get_groupby_action_variables.ts new file mode 100644 index 0000000000000..fd245bb3a1d0c --- /dev/null +++ b/x-pack/plugins/apm/server/routes/alerts/rule_types/utils/get_groupby_action_variables.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 { getFieldValueLabel } from '../../../../../common/rules/apm_rule_types'; +import { + ERROR_GROUP_ID, + SERVICE_ENVIRONMENT, + SERVICE_NAME, + TRANSACTION_NAME, + TRANSACTION_TYPE, +} from '../../../../../common/es_fields/apm'; + +const renameActionVariable = (field: string): string => { + switch (field) { + case SERVICE_NAME: + return 'serviceName'; + case SERVICE_ENVIRONMENT: + return 'environment'; + case TRANSACTION_TYPE: + return 'transactionType'; + case TRANSACTION_NAME: + return 'transactionName'; + case ERROR_GROUP_ID: + return 'errorGroupingKey'; + default: + return field; + } +}; + +export const getGroupByActionVariables = ( + groupByFields: Record +): Record => { + return Object.keys(groupByFields).reduce>( + (acc, cur) => { + acc[renameActionVariable(cur)] = getFieldValueLabel( + cur, + groupByFields[cur] + ); + return acc; + }, + {} + ); +}; diff --git a/x-pack/plugins/apm/server/routes/alerts/rule_types/utils/get_groupby_terms.test.ts b/x-pack/plugins/apm/server/routes/alerts/rule_types/utils/get_groupby_terms.test.ts new file mode 100644 index 0000000000000..01d96f787bd9b --- /dev/null +++ b/x-pack/plugins/apm/server/routes/alerts/rule_types/utils/get_groupby_terms.test.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getGroupByTerms } from './get_groupby_terms'; + +describe('get terms fields for multi-terms aggregation', () => { + it('returns terms array based on the group-by fields', () => { + const ruleParams = { + groupBy: [ + 'service.name', + 'service.environment', + 'transaction.type', + 'transaction.name', + ], + }; + const terms = getGroupByTerms(ruleParams.groupBy); + expect(terms).toEqual([ + { field: 'service.name' }, + { field: 'service.environment', missing: 'ENVIRONMENT_NOT_DEFINED' }, + { field: 'transaction.type' }, + { field: 'transaction.name' }, + ]); + }); + + it('returns an empty terms array when group-by is undefined', () => { + const ruleParams = { groupBy: undefined }; + const terms = getGroupByTerms(ruleParams.groupBy); + expect(terms).toEqual([]); + }); +}); diff --git a/x-pack/plugins/apm/server/routes/alerts/rule_types/utils/get_groupby_terms.ts b/x-pack/plugins/apm/server/routes/alerts/rule_types/utils/get_groupby_terms.ts new file mode 100644 index 0000000000000..22b52fa6a3116 --- /dev/null +++ b/x-pack/plugins/apm/server/routes/alerts/rule_types/utils/get_groupby_terms.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ENVIRONMENT_NOT_DEFINED } from '../../../../../common/environment_filter_values'; +import { SERVICE_ENVIRONMENT } from '../../../../../common/es_fields/apm'; + +export const getGroupByTerms = (groupByFields: string[] | undefined = []) => { + return groupByFields.map((groupByField) => { + return { + field: groupByField, + missing: + groupByField === SERVICE_ENVIRONMENT + ? ENVIRONMENT_NOT_DEFINED.value + : undefined, + }; + }); +}; diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index d08257e729f6a..d3a86b78b9551 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -7094,11 +7094,8 @@ "xpack.apm.agentExplorerInstanceTable.noServiceNodeName.tooltip.linkToDocs": "Vous pouvez configurer le nom du nœud de service via {seeDocs}.", "xpack.apm.agentExplorerTable.agentVersionColumnLabel.multipleVersions": "{versionsCount, plural, one {1 version} other {# versions}}", "xpack.apm.alerts.anomalySeverity.scoreDetailsDescription": "score {value} {value, select, critical {} other {et supérieur}}", - "xpack.apm.alertTypes.errorCount.reason": "Le nombre d'erreurs est {measured} dans le dernier {interval} pour {serviceName}. Alerte lorsque > {threshold}.", "xpack.apm.alertTypes.minimumWindowSize.description": "La valeur minimale recommandée est {sizeValue} {sizeUnit}. Elle permet de s'assurer que l'alerte comporte suffisamment de données à évaluer. Si vous choisissez une valeur trop basse, l'alerte ne se déclenchera peut-être pas comme prévu.", - "xpack.apm.alertTypes.transactionDuration.reason": "La latence de {aggregationType} est {measured} dans le dernier {interval} pour {serviceName}. Alerte lorsque > {threshold}.", "xpack.apm.alertTypes.transactionDurationAnomaly.reason": "Une anomalie {severityLevel} avec un score de {measured} a été détectée dans le dernier {interval} pour {serviceName}.", - "xpack.apm.alertTypes.transactionErrorRate.reason": "L'échec des transactions est {measured} dans le dernier {interval} pour {serviceName}. Alerte lorsque > {threshold}.", "xpack.apm.anomalyDetection.createJobs.failed.text": "Une erreur est survenue lors de la création d'une ou de plusieurs tâches de détection des anomalies pour les environnements de service APM [{environments}]. Erreur : \"{errorMessage}\"", "xpack.apm.anomalyDetection.createJobs.succeeded.text": "Tâches de détection des anomalies créées avec succès pour les environnements de service APM [{environments}]. Le démarrage de l'analyse du trafic à la recherche d'anomalies par le Machine Learning va prendre un certain temps.", "xpack.apm.anomalyDetectionSetup.notEnabledForEnvironmentText": "La détection des anomalies n'est pas encore activée pour l'environnement \"{currentEnvironment}\". Cliquez pour continuer la configuration.", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 1ebc1aeae602e..723902ad29739 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -7095,11 +7095,8 @@ "xpack.apm.agentExplorerInstanceTable.noServiceNodeName.tooltip.linkToDocs": "{seeDocs}を使用してサービスノード名を構成できます。", "xpack.apm.agentExplorerTable.agentVersionColumnLabel.multipleVersions": "{versionsCount, plural, other {#個のバージョン}}", "xpack.apm.alerts.anomalySeverity.scoreDetailsDescription": "スコア{value}{value, select, critical {} other {以上}}", - "xpack.apm.alertTypes.errorCount.reason": "エラーカウントは{serviceName}の最後の{interval}で{measured}です。> {threshold}のときにアラートを通知します。", "xpack.apm.alertTypes.minimumWindowSize.description": "推奨される最小値は{sizeValue} {sizeUnit}です。これにより、アラートに評価する十分なデータがあることが保証されます。低すぎる値を選択した場合、アラートが想定通りに実行されない可能性があります。", - "xpack.apm.alertTypes.transactionDuration.reason": "{serviceName}の{aggregationType}のレイテンシは{measured}で最後の{interval}で測定されます。> {threshold}のときにアラートを通知します。", "xpack.apm.alertTypes.transactionDurationAnomaly.reason": "{severityLevel}の異常が{measured}のスコアで{serviceName}の最後の{interval}で検出されました。", - "xpack.apm.alertTypes.transactionErrorRate.reason": "{serviceName}の失敗したトランザクションは最後の{interval}で{measured}されます。> {threshold}のときにアラートを通知します。", "xpack.apm.anomalyDetection.createJobs.failed.text": "APMサービス環境[{environments}]用に1つ以上の異常検知ジョブを作成しているときに問題が発生しました。エラー「{errorMessage}」", "xpack.apm.anomalyDetection.createJobs.succeeded.text": "APMサービス環境[{environments}]の異常検知ジョブが正常に作成されました。機械学習がトラフィック異常値の分析を開始するには、少し時間がかかります。", "xpack.apm.anomalyDetectionSetup.notEnabledForEnvironmentText": "「{currentEnvironment}」環境では、まだ異常検知が有効ではありません。クリックすると、セットアップを続行します。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 0c2b495e1e3fa..469bb9a2cf748 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -7095,10 +7095,8 @@ "xpack.apm.agentExplorerInstanceTable.noServiceNodeName.tooltip.linkToDocs": "您可以通过 {seeDocs} 配置服务节点名称。", "xpack.apm.agentExplorerTable.agentVersionColumnLabel.multipleVersions": "{versionsCount, plural, other {# 个版本}}", "xpack.apm.alerts.anomalySeverity.scoreDetailsDescription": "分数 {value} {value, select, critical {} other {及以上}}", - "xpack.apm.alertTypes.errorCount.reason": "对于 {serviceName},过去 {interval}的错误计数为 {measured}。大于 {threshold} 时告警。", "xpack.apm.alertTypes.minimumWindowSize.description": "建议的最小值为 {sizeValue} {sizeUnit}这是为了确保告警具有足够的待评估数据。如果选择的值太小,可能无法按预期触发告警。", "xpack.apm.alertTypes.transactionDurationAnomaly.reason": "对于 {serviceName},过去 {interval}检测到分数为 {measured} 的 {severityLevel} 异常。", - "xpack.apm.alertTypes.transactionErrorRate.reason": "对于 {serviceName},过去 {interval}的失败事务数为 {measured}。大于 {threshold} 时告警。", "xpack.apm.anomalyDetection.createJobs.failed.text": "为 APM 服务环境 [{environments}] 创建一个或多个异常检测作业时出现问题。错误:“{errorMessage}”", "xpack.apm.anomalyDetection.createJobs.succeeded.text": "APM 服务环境 [{environments}] 的异常检测作业已成功创建。Machine Learning 要过一些时间才会开始分析流量以发现异常。", "xpack.apm.anomalyDetectionSetup.notEnabledForEnvironmentText": "尚未针对环境“{currentEnvironment}”启用异常检测。单击可继续设置。", diff --git a/x-pack/test/apm_api_integration/tests/alerts/error_count_threshold.spec.ts b/x-pack/test/apm_api_integration/tests/alerts/error_count_threshold.spec.ts index 3edbabcf023f9..64ced57167ada 100644 --- a/x-pack/test/apm_api_integration/tests/alerts/error_count_threshold.spec.ts +++ b/x-pack/test/apm_api_integration/tests/alerts/error_count_threshold.spec.ts @@ -8,6 +8,7 @@ import { ApmRuleType } from '@kbn/apm-plugin/common/rules/apm_rule_types'; import { errorCountMessage } from '@kbn/apm-plugin/common/rules/default_action_message'; import { apm, timerange } from '@kbn/apm-synthtrace-client'; +import { getErrorGroupingKey } from '@kbn/apm-synthtrace-client/src/lib/apm/instance'; import expect from '@kbn/expect'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { @@ -16,7 +17,11 @@ import { fetchServiceInventoryAlertCounts, fetchServiceTabAlertCount, } from './alerting_api_helper'; -import { waitForRuleStatus, waitForDocumentInIndex } from './wait_for_rule_status'; +import { + waitForRuleStatus, + waitForDocumentInIndex, + waitForAlertInIndex, +} from './wait_for_rule_status'; export default function ApiTest({ getService }: FtrProviderContext) { const registry = getService('registry'); @@ -32,7 +37,11 @@ export default function ApiTest({ getService }: FtrProviderContext) { let ruleId: string; let actionId: string | undefined; - const INDEX_NAME = 'error-count'; + const APM_ALERTS_INDEX = '.alerts-observability.apm.alerts-default'; + const ALERT_ACTION_INDEX_NAME = 'alert-action-error-count'; + + const errorMessage = '[ResponseError] index_not_found_exception'; + const errorGroupingKey = getErrorGroupingKey(errorMessage); before(async () => { const opbeansJava = apm @@ -50,11 +59,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { .timestamp(timestamp) .duration(100) .failure() - .errors( - opbeansJava - .error({ message: '[ResponseError] index_not_found_exception' }) - .timestamp(timestamp + 50) - ), + .errors(opbeansJava.error({ message: errorMessage }).timestamp(timestamp + 50)), opbeansNode .transaction({ transactionName: 'tx-node' }) .timestamp(timestamp) @@ -69,8 +74,11 @@ export default function ApiTest({ getService }: FtrProviderContext) { await synthtraceEsClient.clean(); await supertest.delete(`/api/alerting/rule/${ruleId}`).set('kbn-xsrf', 'foo'); await supertest.delete(`/api/actions/connector/${actionId}`).set('kbn-xsrf', 'foo'); - await esDeleteAllIndices(INDEX_NAME); - await es.deleteByQuery({ index: '.alerts*', query: { match_all: {} } }); + await esDeleteAllIndices(ALERT_ACTION_INDEX_NAME); + await es.deleteByQuery({ + index: APM_ALERTS_INDEX, + query: { term: { 'kibana.alert.rule.uuid': ruleId } }, + }); await es.deleteByQuery({ index: '.kibana-event-log-*', query: { term: { 'kibana.alert.rule.consumer': 'apm' } }, @@ -82,7 +90,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { actionId = await createIndexConnector({ supertest, name: 'Error count API test', - indexName: INDEX_NAME, + indexName: ALERT_ACTION_INDEX_NAME, }); const createdRule = await createApmRule({ supertest, @@ -93,13 +101,25 @@ export default function ApiTest({ getService }: FtrProviderContext) { threshold: 1, windowSize: 1, windowUnit: 'h', + groupBy: [ + 'service.name', + 'service.environment', + 'transaction.name', + 'error.grouping_key', + ], }, actions: [ { group: 'threshold_met', id: actionId, params: { - documents: [{ message: errorCountMessage }], + documents: [ + { + message: `${errorCountMessage} +- Transaction name: {{context.transactionName}} +- Error grouping key: {{context.errorGroupingKey}}`, + }, + ], }, frequency: { notify_when: 'onActionGroupChange', @@ -113,7 +133,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { ruleId = createdRule.id; }); - it('checks if alert is active', async () => { + it('checks if rule is active', async () => { const executionStatus = await waitForRuleStatus({ id: ruleId, expectedStatus: 'active', @@ -125,7 +145,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns correct message', async () => { const resp = await waitForDocumentInIndex<{ message: string }>({ es, - indexName: INDEX_NAME, + indexName: ALERT_ACTION_INDEX_NAME, }); expect(resp.hits.hits[0]._source?.message).eql( @@ -134,10 +154,25 @@ export default function ApiTest({ getService }: FtrProviderContext) { - Service name: opbeans-java - Environment: production - Threshold: 1 -- Triggered value: 15 errors over the last 1 hr` +- Triggered value: 15 errors over the last 1 hr +- Transaction name: tx-java +- Error grouping key: ${errorGroupingKey}` ); }); + it('indexes alert document with all group-by fields', async () => { + const resp = await waitForAlertInIndex({ + es, + indexName: APM_ALERTS_INDEX, + ruleId, + }); + + expect(resp.hits.hits[0]._source).property('service.name', 'opbeans-java'); + expect(resp.hits.hits[0]._source).property('service.environment', 'production'); + expect(resp.hits.hits[0]._source).property('transaction.name', 'tx-java'); + expect(resp.hits.hits[0]._source).property('error.grouping_key', errorGroupingKey); + }); + it('shows the correct alert count for each service on service inventory', async () => { const serviceInventoryAlertCounts = await fetchServiceInventoryAlertCounts(apmApiClient); expect(serviceInventoryAlertCounts).to.eql({ diff --git a/x-pack/test/apm_api_integration/tests/alerts/transaction_duration.spec.ts b/x-pack/test/apm_api_integration/tests/alerts/transaction_duration.spec.ts new file mode 100644 index 0000000000000..4fa40d566552f --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/alerts/transaction_duration.spec.ts @@ -0,0 +1,183 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { AggregationType, ApmRuleType } from '@kbn/apm-plugin/common/rules/apm_rule_types'; +import { apm, timerange } from '@kbn/apm-synthtrace-client'; +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { + createApmRule, + createIndexConnector, + fetchServiceInventoryAlertCounts, + fetchServiceTabAlertCount, +} from './alerting_api_helper'; +import { + waitForRuleStatus, + waitForDocumentInIndex, + waitForAlertInIndex, +} from './wait_for_rule_status'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const registry = getService('registry'); + + const supertest = getService('supertest'); + const es = getService('es'); + const apmApiClient = getService('apmApiClient'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); + + const synthtraceEsClient = getService('synthtraceEsClient'); + + registry.when('transaction duration alert', { config: 'basic', archives: [] }, () => { + let ruleId: string; + let actionId: string | undefined; + + const APM_ALERTS_INDEX = '.alerts-observability.apm.alerts-default'; + const ALERT_ACTION_INDEX_NAME = 'alert-action-transaction-duration'; + + before(async () => { + const opbeansJava = apm + .service({ name: 'opbeans-java', environment: 'production', agentName: 'java' }) + .instance('instance'); + const opbeansNode = apm + .service({ name: 'opbeans-node', environment: 'production', agentName: 'node' }) + .instance('instance'); + const events = timerange('now-15m', 'now') + .ratePerMinute(1) + .generator((timestamp) => { + return [ + opbeansJava + .transaction({ transactionName: 'tx-java' }) + .timestamp(timestamp) + .duration(5000) + .success(), + opbeansNode + .transaction({ transactionName: 'tx-node' }) + .timestamp(timestamp) + .duration(4000) + .success(), + ]; + }); + await synthtraceEsClient.index(events); + }); + + after(async () => { + await synthtraceEsClient.clean(); + await supertest.delete(`/api/alerting/rule/${ruleId}`).set('kbn-xsrf', 'foo'); + await supertest.delete(`/api/actions/connector/${actionId}`).set('kbn-xsrf', 'foo'); + await esDeleteAllIndices([ALERT_ACTION_INDEX_NAME]); + await es.deleteByQuery({ + index: APM_ALERTS_INDEX, + query: { term: { 'kibana.alert.rule.uuid': ruleId } }, + }); + await es.deleteByQuery({ + index: '.kibana-event-log-*', + query: { term: { 'kibana.alert.rule.consumer': 'apm' } }, + }); + }); + + describe('create alert with transaction.name group by', () => { + before(async () => { + actionId = await createIndexConnector({ + supertest, + name: 'Transation duration API test', + indexName: ALERT_ACTION_INDEX_NAME, + }); + const createdRule = await createApmRule({ + supertest, + ruleTypeId: ApmRuleType.TransactionDuration, + name: 'Apm transaction duration', + params: { + threshold: 3000, + windowSize: 5, + windowUnit: 'm', + transactionType: 'request', + serviceName: 'opbeans-java', + environment: 'production', + aggregationType: AggregationType.Avg, + groupBy: [ + 'service.name', + 'service.environment', + 'transaction.type', + 'transaction.name', + ], + }, + actions: [ + { + group: 'threshold_met', + id: actionId, + params: { + documents: [{ message: 'Transaction Name: {{context.transactionName}}' }], + }, + frequency: { + notify_when: 'onActionGroupChange', + throttle: null, + summary: false, + }, + }, + ], + }); + expect(createdRule.id).to.not.eql(undefined); + ruleId = createdRule.id; + }); + + it('checks if rule is active', async () => { + const executionStatus = await waitForRuleStatus({ + id: ruleId, + expectedStatus: 'active', + supertest, + }); + expect(executionStatus.status).to.be('active'); + }); + + it('returns correct message', async () => { + const resp = await waitForDocumentInIndex<{ message: string }>({ + es, + indexName: ALERT_ACTION_INDEX_NAME, + }); + + expect(resp.hits.hits[0]._source?.message).eql(`Transaction Name: tx-java`); + }); + + it('indexes alert document with all group-by fields', async () => { + const resp = await waitForAlertInIndex({ + es, + indexName: APM_ALERTS_INDEX, + ruleId, + }); + + expect(resp.hits.hits[0]._source).property('service.name', 'opbeans-java'); + expect(resp.hits.hits[0]._source).property('service.environment', 'production'); + expect(resp.hits.hits[0]._source).property('transaction.type', 'request'); + expect(resp.hits.hits[0]._source).property('transaction.name', 'tx-java'); + }); + + it('shows the correct alert count for each service on service inventory', async () => { + const serviceInventoryAlertCounts = await fetchServiceInventoryAlertCounts(apmApiClient); + expect(serviceInventoryAlertCounts).to.eql({ + 'opbeans-node': 0, + 'opbeans-java': 1, + }); + }); + + it('shows the correct alert count in opbeans-java service', async () => { + const serviceTabAlertCount = await fetchServiceTabAlertCount({ + apmApiClient, + serviceName: 'opbeans-java', + }); + expect(serviceTabAlertCount).to.be(1); + }); + + it('shows the correct alert count in opbeans-node service', async () => { + const serviceTabAlertCount = await fetchServiceTabAlertCount({ + apmApiClient, + serviceName: 'opbeans-node', + }); + expect(serviceTabAlertCount).to.be(0); + }); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/alerts/transaction_error_rate.spec.ts b/x-pack/test/apm_api_integration/tests/alerts/transaction_error_rate.spec.ts new file mode 100644 index 0000000000000..d4d6a0cf3585d --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/alerts/transaction_error_rate.spec.ts @@ -0,0 +1,187 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { ApmRuleType } from '@kbn/apm-plugin/common/rules/apm_rule_types'; +import { apm, timerange } from '@kbn/apm-synthtrace-client'; +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { + createApmRule, + createIndexConnector, + fetchServiceInventoryAlertCounts, + fetchServiceTabAlertCount, +} from './alerting_api_helper'; +import { + waitForRuleStatus, + waitForDocumentInIndex, + waitForAlertInIndex, +} from './wait_for_rule_status'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const registry = getService('registry'); + + const supertest = getService('supertest'); + const es = getService('es'); + const apmApiClient = getService('apmApiClient'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); + + const synthtraceEsClient = getService('synthtraceEsClient'); + + registry.when('transaction error rate alert', { config: 'basic', archives: [] }, () => { + let ruleId: string; + let actionId: string | undefined; + + const APM_ALERTS_INDEX = '.alerts-observability.apm.alerts-default'; + const ALERT_ACTION_INDEX_NAME = 'alert-action-transaction-error-rate'; + + before(async () => { + const opbeansJava = apm + .service({ name: 'opbeans-java', environment: 'production', agentName: 'java' }) + .instance('instance'); + const opbeansNode = apm + .service({ name: 'opbeans-node', environment: 'production', agentName: 'node' }) + .instance('instance'); + const events = timerange('now-15m', 'now') + .ratePerMinute(1) + .generator((timestamp) => { + return [ + opbeansJava + .transaction({ transactionName: 'tx-java' }) + .timestamp(timestamp) + .duration(100) + .failure(), + opbeansJava + .transaction({ transactionName: 'tx-java' }) + .timestamp(timestamp) + .duration(200) + .success(), + opbeansNode + .transaction({ transactionName: 'tx-node' }) + .timestamp(timestamp) + .duration(400) + .success(), + ]; + }); + await synthtraceEsClient.index(events); + }); + + after(async () => { + await synthtraceEsClient.clean(); + await supertest.delete(`/api/alerting/rule/${ruleId}`).set('kbn-xsrf', 'foo'); + await supertest.delete(`/api/actions/connector/${actionId}`).set('kbn-xsrf', 'foo'); + await esDeleteAllIndices([ALERT_ACTION_INDEX_NAME]); + await es.deleteByQuery({ + index: APM_ALERTS_INDEX, + query: { term: { 'kibana.alert.rule.uuid': ruleId } }, + }); + await es.deleteByQuery({ + index: '.kibana-event-log-*', + query: { term: { 'kibana.alert.rule.consumer': 'apm' } }, + }); + }); + + describe('create alert with transaction.name group by', () => { + before(async () => { + actionId = await createIndexConnector({ + supertest, + name: 'Transation error rate API test', + indexName: ALERT_ACTION_INDEX_NAME, + }); + const createdRule = await createApmRule({ + supertest, + ruleTypeId: ApmRuleType.TransactionErrorRate, + name: 'Apm error rate duration', + params: { + threshold: 50, + windowSize: 5, + windowUnit: 'm', + transactionType: 'request', + serviceName: 'opbeans-java', + environment: 'production', + groupBy: [ + 'service.name', + 'service.environment', + 'transaction.type', + 'transaction.name', + ], + }, + actions: [ + { + group: 'threshold_met', + id: actionId, + params: { + documents: [{ message: 'Transaction Name: {{context.transactionName}}' }], + }, + frequency: { + notify_when: 'onActionGroupChange', + throttle: null, + summary: false, + }, + }, + ], + }); + expect(createdRule.id).to.not.eql(undefined); + ruleId = createdRule.id; + }); + + it('checks if rule is active', async () => { + const executionStatus = await waitForRuleStatus({ + id: ruleId, + expectedStatus: 'active', + supertest, + }); + expect(executionStatus.status).to.be('active'); + }); + + it('returns correct message', async () => { + const resp = await waitForDocumentInIndex<{ message: string }>({ + es, + indexName: ALERT_ACTION_INDEX_NAME, + }); + + expect(resp.hits.hits[0]._source?.message).eql(`Transaction Name: tx-java`); + }); + + it('indexes alert document with all group-by fields', async () => { + const resp = await waitForAlertInIndex({ + es, + indexName: APM_ALERTS_INDEX, + ruleId, + }); + + expect(resp.hits.hits[0]._source).property('service.name', 'opbeans-java'); + expect(resp.hits.hits[0]._source).property('service.environment', 'production'); + expect(resp.hits.hits[0]._source).property('transaction.type', 'request'); + expect(resp.hits.hits[0]._source).property('transaction.name', 'tx-java'); + }); + + it('shows the correct alert count for each service on service inventory', async () => { + const serviceInventoryAlertCounts = await fetchServiceInventoryAlertCounts(apmApiClient); + expect(serviceInventoryAlertCounts).to.eql({ + 'opbeans-node': 0, + 'opbeans-java': 1, + }); + }); + + it('shows the correct alert count in opbeans-java service', async () => { + const serviceTabAlertCount = await fetchServiceTabAlertCount({ + apmApiClient, + serviceName: 'opbeans-java', + }); + expect(serviceTabAlertCount).to.be(1); + }); + + it('shows the correct alert count in opbeans-node service', async () => { + const serviceTabAlertCount = await fetchServiceTabAlertCount({ + apmApiClient, + serviceName: 'opbeans-node', + }); + expect(serviceTabAlertCount).to.be(0); + }); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/alerts/wait_for_rule_status.ts b/x-pack/test/apm_api_integration/tests/alerts/wait_for_rule_status.ts index 44da2bea36bf2..f02285d5056eb 100644 --- a/x-pack/test/apm_api_integration/tests/alerts/wait_for_rule_status.ts +++ b/x-pack/test/apm_api_integration/tests/alerts/wait_for_rule_status.ts @@ -53,3 +53,33 @@ export async function waitForDocumentInIndex({ { retries: 10 } ); } + +export async function waitForAlertInIndex({ + es, + indexName, + ruleId, +}: { + es: Client; + indexName: string; + ruleId: string; +}): Promise>> { + return pRetry( + async () => { + const response = await es.search({ + index: indexName, + body: { + query: { + term: { + 'kibana.alert.rule.uuid': ruleId, + }, + }, + }, + }); + if (response.hits.hits.length === 0) { + throw new Error('No hits found'); + } + return response; + }, + { retries: 10 } + ); +} diff --git a/x-pack/test/rule_registry/spaces_only/tests/trial/__snapshots__/create_rule.snap b/x-pack/test/rule_registry/spaces_only/tests/trial/__snapshots__/create_rule.snap index e76a791652f93..a66368fe5a8ec 100644 --- a/x-pack/test/rule_registry/spaces_only/tests/trial/__snapshots__/create_rule.snap +++ b/x-pack/test/rule_registry/spaces_only/tests/trial/__snapshots__/create_rule.snap @@ -21,10 +21,10 @@ Object { false, ], "kibana.alert.instance.id": Array [ - "apm.transaction_error_rate_opbeans-go_request_ENVIRONMENT_NOT_DEFINED", + "opbeans-go_ENVIRONMENT_NOT_DEFINED_request", ], "kibana.alert.reason": Array [ - "Failed transactions is 50% in the last 5 mins for opbeans-go. Alert when > 30%.", + "Failed transactions is 50% in the last 5 mins for service: opbeans-go, env: Not defined, type: request. Alert when > 30%.", ], "kibana.alert.rule.category": Array [ "Failed transaction rate threshold", @@ -70,6 +70,9 @@ Object { "processor.event": Array [ "transaction", ], + "service.environment": Array [ + "ENVIRONMENT_NOT_DEFINED", + ], "service.name": Array [ "opbeans-go", ], @@ -101,10 +104,10 @@ Object { false, ], "kibana.alert.instance.id": Array [ - "apm.transaction_error_rate_opbeans-go_request_ENVIRONMENT_NOT_DEFINED", + "opbeans-go_ENVIRONMENT_NOT_DEFINED_request", ], "kibana.alert.reason": Array [ - "Failed transactions is 50% in the last 5 mins for opbeans-go. Alert when > 30%.", + "Failed transactions is 50% in the last 5 mins for service: opbeans-go, env: Not defined, type: request. Alert when > 30%.", ], "kibana.alert.rule.category": Array [ "Failed transaction rate threshold", @@ -150,6 +153,9 @@ Object { "processor.event": Array [ "transaction", ], + "service.environment": Array [ + "ENVIRONMENT_NOT_DEFINED", + ], "service.name": Array [ "opbeans-go", ],