From 1095375fe39e960d0569d818191bef468c93a44b Mon Sep 17 00:00:00 2001 From: Antonio Date: Mon, 24 Apr 2023 16:42:37 +0200 Subject: [PATCH 01/26] [Cases] Close FilePreview with Escape key. (#155592) Fixes #155036 ## Summary Allow users to close the file preview in cases by using the Escape key. (e2e coming in a different PR with other tests) --- .../components/files/file_preview.test.tsx | 20 +++++++++++++++++++ .../public/components/files/file_preview.tsx | 18 +++++++++++++++-- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/cases/public/components/files/file_preview.test.tsx b/x-pack/plugins/cases/public/components/files/file_preview.test.tsx index b02df3a82228f4..c1d7fe20bee483 100644 --- a/x-pack/plugins/cases/public/components/files/file_preview.test.tsx +++ b/x-pack/plugins/cases/public/components/files/file_preview.test.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import type { AppMockRenderer } from '../../common/mock'; @@ -35,4 +36,23 @@ describe('FilePreview', () => { expect(await screen.findByTestId('cases-files-image-preview')).toBeInTheDocument(); }); + + it('pressing escape calls closePreview', async () => { + const closePreview = jest.fn(); + + appMockRender.render(); + + await waitFor(() => + expect(appMockRender.getFilesClient().getDownloadHref).toHaveBeenCalledWith({ + id: basicFileMock.id, + fileKind: constructFileKindIdByOwner(mockedTestProvidersOwner[0]), + }) + ); + + expect(await screen.findByTestId('cases-files-image-preview')).toBeInTheDocument(); + + userEvent.keyboard('{esc}'); + + await waitFor(() => expect(closePreview).toHaveBeenCalled()); + }); }); diff --git a/x-pack/plugins/cases/public/components/files/file_preview.tsx b/x-pack/plugins/cases/public/components/files/file_preview.tsx index 1bb91c5b53ff74..09cee1320ec2a7 100644 --- a/x-pack/plugins/cases/public/components/files/file_preview.tsx +++ b/x-pack/plugins/cases/public/components/files/file_preview.tsx @@ -4,12 +4,12 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React from 'react'; +import React, { useEffect } from 'react'; import styled from 'styled-components'; import type { FileJSON } from '@kbn/shared-ux-file-types'; -import { EuiOverlayMask, EuiFocusTrap, EuiImage } from '@elastic/eui'; +import { EuiOverlayMask, EuiFocusTrap, EuiImage, keys } from '@elastic/eui'; import { useFilesContext } from '@kbn/shared-ux-file-context'; import type { Owner } from '../../../common/constants/types'; @@ -36,6 +36,20 @@ export const FilePreview = ({ closePreview, selectedFile }: FilePreviewProps) => const { client: filesClient } = useFilesContext(); const { owner } = useCasesContext(); + useEffect(() => { + const keyboardListener = (event: KeyboardEvent) => { + if (event.key === keys.ESCAPE || event.code === 'Escape') { + closePreview(); + } + }; + + window.addEventListener('keyup', keyboardListener); + + return () => { + window.removeEventListener('keyup', keyboardListener); + }; + }, [closePreview]); + return ( From a03d20be039d1c449b2848f46463bc423b6f5183 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Mon, 24 Apr 2023 15:51:36 +0100 Subject: [PATCH 02/26] skip flaky suite (#154970) --- .../sections/alerts_table/bulk_actions/bulk_actions.test.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/bulk_actions/bulk_actions.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/bulk_actions/bulk_actions.test.tsx index f9d209549da0c2..23fac59fca2081 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/bulk_actions/bulk_actions.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/bulk_actions/bulk_actions.test.tsx @@ -691,7 +691,8 @@ describe('AlertsTable.BulkActions', () => { ).toBeTruthy(); }); - describe('and clear the selection is clicked', () => { + // FLAKY: https://github.com/elastic/kibana/issues/154970 + describe.skip('and clear the selection is clicked', () => { it('should turn off the toolbar', async () => { const props = { ...tablePropsWithBulkActions, From 2c14b584f8f736f65211b5f738f9e0d764681346 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Mon, 24 Apr 2023 15:55:40 +0100 Subject: [PATCH 03/26] skip flaky suite (#155222) --- x-pack/test/functional/apps/aiops/explain_log_rate_spikes.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 92320dad62087a..54b1baae454bde 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 @@ -209,7 +209,8 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { }); } - describe('explain log rate spikes', async function () { + // FLAKY: https://github.com/elastic/kibana/issues/155222 + describe.skip('explain log rate spikes', async function () { for (const testData of explainLogRateSpikesTestData) { describe(`with '${testData.sourceIndexOrSavedSearch}'`, function () { before(async () => { From 3d78370aa584e179ae9e9d30fabe080242812d22 Mon Sep 17 00:00:00 2001 From: Kathleen DeRusso Date: Mon, 24 Apr 2023 11:02:43 -0400 Subject: [PATCH 04/26] Fix API links when generating API key snippet (#155435) Fixes the Search Applications API page to set an URL to the ES plugin rather than Enterprise Search URL. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../engine_connect/engine_api_integration.tsx | 19 ++++++++++--------- .../engine_connect/search_application_api.tsx | 14 +++++++++++--- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/engine_api_integration.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/engine_api_integration.tsx index b61614838d7a1c..2fe691e262b64b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/engine_api_integration.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/engine_api_integration.tsx @@ -12,23 +12,24 @@ import { useValues } from 'kea'; import { EuiCodeBlock, EuiSpacer, EuiText, EuiTabs, EuiTab } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { getEnterpriseSearchUrl } from '../../../../shared/enterprise_search_url'; - +import { useCloudDetails } from '../../../../shared/cloud_details/cloud_details'; import { EngineViewLogic } from '../engine_view_logic'; import { EngineApiLogic } from './engine_api_logic'; -const SearchUISnippet = (enterpriseSearchUrl: string, engineName: string, apiKey: string) => ` +import { elasticsearchUrl } from './search_application_api'; + +const SearchUISnippet = (esUrl: string, engineName: string, apiKey: string) => `6 import EnginesAPIConnector from "@elastic/search-ui-engines-connector"; const connector = new EnginesAPIConnector({ - host: "${enterpriseSearchUrl}", + host: "${esUrl}", engineName: "${engineName}", apiKey: "${apiKey || ''}" });`; -const cURLSnippet = (enterpriseSearchUrl: string, engineName: string, apiKey: string) => ` -curl --location --request GET '${enterpriseSearchUrl}/api/engines/${engineName}/_search' \\ +const cURLSnippet = (esUrl: string, engineName: string, apiKey: string) => ` +curl --location --request GET '${esUrl}/${engineName}/_search' \\ --header 'Authorization: apiKey ${apiKey || ''}' \\ --header 'Content-Type: application/json' \\ --data-raw '{ @@ -47,19 +48,19 @@ interface Tab { export const EngineApiIntegrationStage: React.FC = () => { const [selectedTab, setSelectedTab] = React.useState('curl'); const { engineName } = useValues(EngineViewLogic); - const enterpriseSearchUrl = getEnterpriseSearchUrl(); const { apiKey } = useValues(EngineApiLogic); + const cloudContext = useCloudDetails(); const Tabs: Record = { curl: { - code: cURLSnippet(enterpriseSearchUrl, engineName, apiKey), + code: cURLSnippet(elasticsearchUrl(cloudContext), engineName, apiKey), language: 'bash', title: i18n.translate('xpack.enterpriseSearch.content.engine.api.step3.curlTitle', { defaultMessage: 'cURL', }), }, searchui: { - code: SearchUISnippet(enterpriseSearchUrl, engineName, apiKey), + code: SearchUISnippet(elasticsearchUrl(cloudContext), engineName, apiKey), language: 'javascript', title: i18n.translate('xpack.enterpriseSearch.content.engine.api.step3.searchUITitle', { defaultMessage: 'Search UI', diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/search_application_api.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/search_application_api.tsx index 9d3c27895657fc..6934de4051bdb4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/search_application_api.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/search_application_api.tsx @@ -23,9 +23,10 @@ import { i18n } from '@kbn/i18n'; import { ANALYTICS_PLUGIN } from '../../../../../../common/constants'; import { COLLECTION_INTEGRATE_PATH } from '../../../../analytics/routes'; +import { CloudDetails, useCloudDetails } from '../../../../shared/cloud_details/cloud_details'; +import { decodeCloudId } from '../../../../shared/decode_cloud_id/decode_cloud_id'; import { docLinks } from '../../../../shared/doc_links'; import { generateEncodedPath } from '../../../../shared/encode_path_params'; -import { getEnterpriseSearchUrl } from '../../../../shared/enterprise_search_url'; import { KibanaLogic } from '../../../../shared/kibana'; import { EngineViewLogic } from '../engine_view_logic'; @@ -34,12 +35,19 @@ import { EngineApiIntegrationStage } from './engine_api_integration'; import { EngineApiLogic } from './engine_api_logic'; import { GenerateEngineApiKeyModal } from './generate_engine_api_key_modal/generate_engine_api_key_modal'; +export const elasticsearchUrl = (cloudContext: CloudDetails): string => { + const defaultUrl = 'https://localhost:9200'; + const url = + (cloudContext.cloudId && decodeCloudId(cloudContext.cloudId)?.elasticsearchUrl) || defaultUrl; + return url; +}; + export const SearchApplicationAPI = () => { const { engineName } = useValues(EngineViewLogic); const { isGenerateModalOpen } = useValues(EngineApiLogic); const { openGenerateModal, closeGenerateModal } = useActions(EngineApiLogic); - const enterpriseSearchUrl = getEnterpriseSearchUrl(); const { navigateToUrl } = useValues(KibanaLogic); + const cloudContext = useCloudDetails(); const steps = [ { @@ -132,7 +140,7 @@ export const SearchApplicationAPI = () => { - {enterpriseSearchUrl} + {elasticsearchUrl(cloudContext)} 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 05/26] [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 532b35450d5418..9a608b2d6dda3a 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 330eb67be42780..3214e7bf00a72d 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 df0d1e22bc1067..d1e83d7dd2e93f 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 62c83fb5b94548..0245af450fb8a3 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 1612649f80bdac..2191a3c9b7eeee 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 0e716e6d59cf34..c8e734a289451f 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 4a00f2de005163..52d1ca09fd1707 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 4c49124f7149ff..86b9e47fdab43e 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 54646e9199f9a8..13828d0ee366cb 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 0f453a1b81e6af..014e57b41d2b10 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 4247f567802edc..96795ac93b3876 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 25d7f463de03ad..3492eb8e5f12c4 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 a81a9d2ea5220d..6749e06254100b 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 becdd05fa0e2ca..3ee4e5f52720c8 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 18f031b84e5c1e..0688c5a7edecbc 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 c5f4956af372f0..d430038dcdddcd 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 7dce1986237a7f..9c7091516d46e0 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 2a73b69bea0173..303d5e1c5d1a72 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 06/26] [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 855dd3a3fe439b..fdf75da0a134ec 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 d9082f8aa1d8c9..f8f5da28943b83 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 c3f94871c8f674..8d5d8907924f6d 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 746b9201cd2008..c2595c625b7faa 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 6360b55b1c06f9..6fc62dd6cf8519 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 07/26] [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 22ec8e8a3213e0..2ed86eb91ae52e 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 bfc872f3680a88..4fea1d04b64e4f 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 bd9c0032984c33..e4ef7cbbb6e67c 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 08/26] [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 00000000000000..9fdaa443f4c751 --- /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 ce69ea9fea3ae8..b30e3a7d1e6fbb 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 80640f59901bc8..fb9ce01c633918 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 c888694a7b0c3b..747f90d5423541 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 b673d8a498a213..7c7d0001aea428 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 b319db0088d4dc..ca55b43907bd69 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 00000000000000..16c91f8d3851fe --- /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 82359b3d2b1aa5..0984c76a4b1708 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 e9924307c1e273..1b906eb56e9886 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 5f30abb2f6cec9..bd7741bb452bff 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 00000000000000..9388cf147c8ff3 --- /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 c390582ccae1af..62f4c596cc60e6 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 89c236ba25c992..6c9aafffa26d04 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 54b1baae454bde..7731eea1d9d7a0 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 b6d3293aeba81f..95e21fbfc78007 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 01733a8e1a2af2..2093d4d9613634 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 18cadebbf9afd9..b533c50677944d 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 1a80ac679f29b8..228d47bbc746ff 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 3da9ed7c760b76..736437a1d39767 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 4816d37bcff049..8c208f182f3bd1 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 00000000000000..37872b8d7c0515 --- /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 09/26] [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 bcfeeafcee06fd..80e5e501169d63 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 c9ce48c909f9a4..6250d20750e296 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 9a2472949f54cf..34e536aaf37d2b 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 0c965feca8e9ec..d42944857af344 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 396c0bd72ad719..14a617682bf25a 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 2dbd0c4324ecac..c3f751d26befbc 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 a95f18b4a10ee8..89eebeefd240e9 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 a3e82b99014227..e2bd7d0c74daef 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 00000000000000..1d8ce4f9c9e87a --- /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 00000000000000..ef515cc018839b --- /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 d5cc0b0f021d76..6813dee1caa105 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 28d07b94d94377..f81228957107ae 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 e307dde0d09e52..7f3dac7a3af163 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 168f825a9d2d18..00000000000000 --- 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 b854120a868879..69cfc446d00950 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 7a895591d68c7f..200bff521d86a7 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 94e3a963075bea..83fedf42929370 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 00000000000000..5575c46e621f15 --- /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 a921a0daeb011f..5619a788b19a7b 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 2d2d6c9d7f8e44..7350f402c57ec0 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 d0df961dc7ef9c..f84acf5931ea1a 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 950bd9cf3c94e7..e242f58054c6ca 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 861f3c26472e81..bae9f2ed3f713f 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 6b948fb0da6c98..080b47f54d4da3 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 bc9131d1d52fc3..f39eaafdd039e0 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 10/26] 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 a1d73b452cf720..2635dbc05399ff 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 1f5ea87c6d6f91..f093b20eaaeb4e 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 02c44024421cee..f8cd73901e0d70 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 11/26] [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 d9fbd1dfc85545..dc75ebee9a5ebd 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 92dc64e9166445..efe5e95f238d00 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 a1bbe6c39956f3..840010d3aa219e 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 fc0f3cca3e06c8..673861d80e6efd 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 938aaa88d1bdf2..cb6663eebf6abc 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 d09b84345903e4..f6610dba543b2a 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 0fc77aa25087d8..91baf79cfaaa0a 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 1ce46a22423078..ed0f6a5c2fedb9 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 12/26] [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 00000000000000..6f67c4dd39ea8e --- /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 00000000000000..62ba5621e0e1e0 --- /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 964a793d8f81c1..3a02ce0d1a9838 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 fc4381fa0c9c3d..37713167e9a341 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 77ef11e88bfe30..8c253cb644feec 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 f1a67c568b67fe..eee078591a3a7a 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 f3f0aa8f6469bf..baa6ee80a8e539 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 60e7e82966864d..6d716b5d3c235f 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 1eb7d99b93bda3..764dd728b13479 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 13/26] [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 2dd55304b60353..a6c29285f4f660 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 4bcd3fcf4f2158..285846f7ccb1c5 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 4ceba9cccb1424..fd10624188ff0c 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 35e56f825d33ad..6bbfe9b1de9673 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 ea5672222014fd..7b5bc7bf286f58 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 11458565f781a6..dcb343a2beec45 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 f06e32b9e30c82..7fbc354ff725f6 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 f6610dba543b2a..f7cf6a952a7961 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 91baf79cfaaa0a..2e5fdf22d200bb 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 ed0f6a5c2fedb9..7199ea360f5e5b 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 14/26] [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 f6297befa615ce..148867337fedde 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 bf601934324887..695eaa42e064db 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 482553e2f002fd..228db7138fd543 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 0d3dd592dcc343..4e98a6dd310248 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 4a2ac8f41d6a65..14067f0b6aa68f 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 6cef7e858b1655..e7c7daa2bcc274 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 8283144e1c1553..cbd514be8ba135 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 03b609ae99736d..708af176d785d4 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 7e9529a90be8b5..cc96c816ce8b78 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 b840fcd408977d..0055e24685b89d 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 45aa70fd50febc..39ff6ebc484184 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 f04e5e29d960b7..f8715220ddf378 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 77b51874319baa..e570e1eadd6ca7 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 ef4f4dc7ea5c9b..c708937e3d56e4 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 cb2ce9af37bcd8..7b02001a93c6c4 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 7609a4f3eb95fe..f0a20e832e431f 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 5b7a589afa950b..d5a5385e779b36 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 70bf3a7d659896..86a58bb72f639b 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 6e8ff1f5c98a05..544317d9f6bccf 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 dcaa3880678abb..ea7c150bf38b8a 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 4cc5a7ccb6e11f..eb2722dcf98690 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 15/26] [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 0a75b9a44499bd..885f44eaa5ae1d 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 70816a69c2188b..00000000000000 --- 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 aca280e74fcb23..ff297267dcb62d 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 d6b927bbc43b3c..e4fcee12f65a51 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 a98f78249adbe3..2e4eb6ca03a31c 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 394da8aefc0868..fe726d0cbe3d2e 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 7369ce0917e5a9..78c0d9484149ed 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 62b48676e39653..2e009cc0b89d21 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 d3134c60f8fd37..14c912b07ce99a 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 62cbce9bfe05b5..26bf2b50b8325b 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 adab53c9d42683..3c62f99f7e67be 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 9e2038d05962a0..836143d55f014e 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 c4561f3ee9e00b..87a10dbee9a8eb 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 4b15f4da43515d..ca4a18e88d5d95 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 0bc4f656901f45..79af4d6cfc7187 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 f7cf6a952a7961..fda996fca0a651 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 2e5fdf22d200bb..11ccd44a6eee76 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 7199ea360f5e5b..4eae043b7b8846 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 b1bc58abe71138..e6a8ae14cda1ae 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 f20a2cdf61a45c..bf4447a1b59699 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 5394ca64545e69..00772c5550ac17 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 25aad0704cddd0..625dbdac61608f 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 d031a6c505c8fa..bf68da4c148f5b 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 16/26] 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 9e5c383e825d17..631b67cd496014 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 225206cca4b0da..42235cae665749 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 4e3db8c657e047..e01eb21cbe68cc 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 f03621899c800b..f5d6bc41ee349b 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 a7fab953dad385..0fdc6cf32a69c0 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 ba61f6b85aaab4..df3b571714b298 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 17/26] [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 00000000000000..620aabd1c842bc --- /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 315c60fab6a6f2..523f59c32f2249 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 9d3db04fa40de4..4f9ad5245cf91f 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 18/26] [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 b08d26b8c657d9..3123aa7f391dfb 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 19/26] [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 8b4c04d15cfc43..1fa0806effdef7 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 0392d0cdb3667c..e41140f8fc9187 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 706630edfbd4ad..8bd3f7d3e0b492 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 123f374f79b057..e46bc07463e2f6 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 a74147d15890ce..d26f8494e10611 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 2415dbe74b53dc..c9ea00ef170de8 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 b581a011630a98..e9262b3e510791 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 2cd5ff9ba09948..0cb663043d57ac 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 dc01beeef148ab..b92281373817d1 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 7778b4d6213592..5e636245871525 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 00000000000000..af534d325ebca9 --- /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 4af63c6d1b4062..4f7ce1b811f702 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 00000000000000..9d3c28429df5b4 --- /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 00000000000000..20a8e0d1f2f94a --- /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 00000000000000..878347dc37c98e --- /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 00000000000000..21071aee618a16 --- /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 00000000000000..3603cafbda935a --- /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 95e0857874423b..03231f2030eb2b 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 20/26] [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 3780fa1bcddd6e..c472f58ec3e2f7 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 eef0ce05eed6e5..f5b5c2bce0cd06 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 21/26] [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 63cf2327516fac..2da78ed653a0d9 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 56fdffa498b050..06bec180ca93b7 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 00000000000000..32c28b9fc4807b --- /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 00000000000000..12594660136d8f --- /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 00000000000000..b1e7646521e265 --- /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 00000000000000..06779896a47c45 --- /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 00000000000000..8b59a10c3d4294 --- /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 00000000000000..7de04c3c13451d --- /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 00000000000000..1f15c69947f633 --- /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 e96e5439e9c2ca..929f3e791ad1e2 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 daafc194a37fcb..f389e3fea36e77 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 272e0e1eb5f2e7..535a15e5a23a6f 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 d5a7351226c237..c19713e6416599 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 932765899da223..065d44fdc0681f 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 5a031c675a32c2..3063980a561f3c 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 9e501ba66559c2..398139fe00d344 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 d120721650856a..bc57b5fc7d2836 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 22/26] [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 00000000000000..07af65877da013 --- /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 75a34735abff1a..e3e68152f4c068 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 160b300e08934e..132e8b2fc302b2 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 00000000000000..7c72289290942d --- /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 e9a85c2908b893..8153380c140c3b 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 00000000000000..8732f27702bc2d --- /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 2efc9355c710f5..145686785cafa1 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 d3df809215d087..2b58ea98b8b28a 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 00000000000000..7dde4254708b7d --- /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 00000000000000..8b7c9b01e3c5e6 --- /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 00000000000000..3c996dd095dc88 --- /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 00000000000000..ee4977ebe78582 --- /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 00000000000000..9b59a78430e1c1 --- /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 00000000000000..45e8f1ba1b4adb --- /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 00000000000000..09ed53402a89fd --- /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 00000000000000..366fb487309c35 --- /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 00000000000000..26340b31286fac --- /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 b5f77c455c1a25..6b8ffed70f8c9b 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 00000000000000..0f27f307f79137 --- /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 87aed178043ccb..69de3b8c110e58 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 00000000000000..5085db2a93e51d --- /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 3ff69274ba06ed..9245b0adee84c8 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 00000000000000..064ec92a1ca81f --- /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 b9e0fc61ec5456..8f80e3fa3cab55 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 00000000000000..1954e92ae5fc7c --- /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 caac710cf8d13d..35a4a74cca875d 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 00000000000000..a1b6346eb2b8d1 --- /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 a40094c96b3991..2336abe79c6513 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 c6874d861ddb80..d3f9ad9d233038 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 00000000000000..02b04225a544ed --- /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 00000000000000..549f420c3fa9d1 --- /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 00000000000000..fd457193a9c6fe --- /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 cd1ce0940b391b..c65b3f7559071e 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 00000000000000..5f96cfa9953a63 --- /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 4d07d4826f5214..30f314c73ea3bc 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 00000000000000..f2aa7a2666c33d --- /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 ab5de8d7ccdcbc..ef768249aa2a45 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 00000000000000..7d139121afbc4b --- /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 d659d81d93199e..db533767462812 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 4683a96a769874..00000000000000 --- 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 00000000000000..f57d9f52737d74 --- /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 a0b70daee0e405..3214b704dc685d 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 00000000000000..6efe7579d73253 --- /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 3aa738d4cb7888..82664778becb08 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 00000000000000..1c37ec799c53c5 --- /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 80c9151eaa050c..33283d11649a8a 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 00000000000000..6bb3b72ed3ecef --- /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 a9d2e1f6a74d9f..ee06b2f4468585 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 00000000000000..e0644fdc4a5c0b --- /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 00000000000000..b6914daef0e7da --- /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 34fac241a2d82d..ca9eda507f8ddb 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 0ea7a13d1c710f..b51a49ecc89dfa 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 9137bb346d660e..32424dff66883c 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 84e701c0aa2446..7eda6d039ffe02 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 10e460d9c24f59..00000000000000 --- 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 00000000000000..c40f6a73ffa03e --- /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 efa9dbfa69d5e5..40d0bbfb262932 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 4fa3b348849296..d9b002a63dc688 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 bd6479490dab8c..cb430d75ef12e3 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 a8ae7a673b92e4..233de9fc93a1f5 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 00000000000000..dfa285d60b40a5 --- /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 dcf28487a091c9..4a51880404542b 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 776ceef7761728..79375b19561697 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 94983d262404bb..0c4a439f999bcb 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 afaad5d08ed6e0..1c52f29858ac4b 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 00000000000000..f523c96cc08019 --- /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 00000000000000..b42cd6072ad820 --- /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 00000000000000..0cf39beae7b2d5 --- /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 00000000000000..a5edca17291d28 --- /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 00000000000000..f60cb2366cf362 --- /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 00000000000000..e913d38dbb07c3 --- /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 e3789fce0bb5b8..89e0d78fddb6b3 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 00000000000000..235ec61a204af7 --- /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 7b02add1518031..2dd2c4e214dc0e 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 1d8de8ac57593d..7701db46d6c987 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 bfffbd1393ef6c..09f7136d7108ae 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 00000000000000..c0dc6a8aaafe22 --- /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 a98ad92ba6cd9e..c0cbebd45cb8d5 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 e82bfd4d2efdc1..54babce560f250 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 2e44ec53142b5e..1f4e0b62b1c58b 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 f4def1393c07cc..2fa4fcdb4f8ed3 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 cf83cf96d28122..2714d1002c40c8 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 054ac5e003326b..6d36fdd50370ab 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 00000000000000..64e78a4a88cfb3 --- /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 4ab85f87e01d02..1f728e3b60c860 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 087470b7e86dc6..c830ac2f6c7be2 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 21dd3e0450f40e..b7c05e75300d5f 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 00000000000000..30a5e9d13e4fcf --- /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 14448d1a3baacf..81e968bfe73a45 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 ea8a50a41580f9..7cb638ad11550d 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 00000000000000..417f1419a7ca5c --- /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 72affc8aec4912..5f6711814a9042 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 05ccaf9c4fc5e4..dde54d8e97f647 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 654d608d7d3519..be445b6ee6b516 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 bf7bc667c9aafc..b5b35064a73204 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 00000000000000..e63bae0530961b --- /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 00000000000000..42b22d9c99aaab --- /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 00000000000000..5e15bb4e2efddc --- /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 cbca7ab9e19659..39c25cbc77c10c 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 776f02f9f4e74c..3ece4fb4c248f9 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 2b39901b9c954c..f26ef180a3641e 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 0362dcd70a53f6..14465e815ad473 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 00000000000000..393dd15ce9a201 --- /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 d54ea9d6316e2a..6fbf130d01b8f9 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 771faa301cee34..53bedadd9361d9 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 9edc76bbe62207..d51e908bd7b381 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 00000000000000..e1865c31c85df6 --- /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 00000000000000..f25903adff8235 --- /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 995ac8eac86c40..dbad0364904f57 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 da5745deaa3cd7..1976b5e150de35 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 00000000000000..2f80ba5e2cc7a5 --- /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 1fa8991ce9264b..e1ee93b72b283b 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 2d75eee57becea..07cc2a491cf025 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 35a991e6ae7ad2..4f7df917152474 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 23/26] [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 9f6783794ad2ce..9c6b8a0a1c6a59 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 f41313a90546b3..bfad5ad4db0586 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 50cb3ba36668eb..e772deb88951a6 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 264f3c10d43f39..81ad124973bc68 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 05cd2479d10f49..da7722790ce0e9 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 39984c4c85e921..2bb04a1e4c9608 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 3fdc2e696e3f07..428201f0e8998b 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 f79d12ad047ef6..6ea84c4870d6d3 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 fc6dbe517b369e..e597e32169fde6 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 bbeae3846322ff..e4438fdd61abe5 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 f41f5e5efe59af..ce453c5849ecff 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 15ec086b8eaf2f..d5913a5ac4189c 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 8a55c2821385f1..cc0458a58cf89c 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 c09cbda53a15ce..c8a0f4283501f9 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 53a1a634088ebd..8e17fcb2735821 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 cf1dca265856b4..21fc2533c79896 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 815fae9c2fb420..88b3059b376bf8 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 c56ad03988470d..eb6d552316f326 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 7b95ed904f226c..2869a4d8b827d4 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 2f84a61fdae2e9..0f4d045b8511e1 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 21f71777a11e18..c885da19468990 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 42f533bf0fddc4..ce95034b22ea97 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 c280197a6417a7..7ee1e24ae78ab6 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 53316962496a52..ee60cc5194c9a8 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 ee4d46dc9145f6..ff02cd4aa5a3c9 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 e79d7c3abd944e..35930f40372572 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 a1ab4045a706cc..a5f45916bbd6d7 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 f58d4af3b6e61b..034d48ef5dd4f5 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 8fd1bc5eca79f4..44c7b6a806f882 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 f56c297a354bad..5618420b300941 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 776cf231e1e627..1d3df0cb9f4b17 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 4ac05486048f37..316058cdc98df1 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 1e27fc11d34a9e..742c53d11f6374 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 5dab9a07d047d2..dd2c5312568f6d 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 57c1558717fa70..1ece2df53bcd59 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 1a34b399f959c0..9a015b1c8a057e 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 b98c2fea8fc272..5249f3b7fb8aad 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 b49d0197cbea07..06e3460f2f31f9 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 1a29e749e4dc75..a7dfcd567c0eb9 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 73de9827ebc22f..e84818ff57329a 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 cde28fb9b20e0c..ae5fc044dbe15c 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 79aea49ce02396..0348a2e433ad19 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 ec0192478b19ad..14d381530a39e6 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 59ef54b7c5b64e..1d2998c46bd417 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 1de1e6f809d6bb..73a43cf574ccfc 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 24c39169fe91b4..251b605711d5b7 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 6004d2b66f1792..a5a15684543057 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 68f4430bbeaaed..6e28518c2984dc 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 9e32f4ae6e75bc..3e5429e3146228 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 77ff102f16bcbc..33152035253ec0 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 b3075cced41d85..6d0ddc64e7fd37 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 5e2e0f6c10292e..5419658ffe67e2 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 7315c69e133779..f6a72d2496321b 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 4f23479cfbfc62..2c3e1cf2cf74a4 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 3c92eace2b8e38..6247a438a185e9 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 0a35f4158d1793..fd86849aaaae65 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 92cf80d1ab61a0..938c8ccfae5011 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 62c32bae5f5f1f..a2b8ec78d3043d 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 8ccde120fd543b..b40ce4eb2d8951 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 6432d4714f1e30..dad1a3bd2c5214 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 d8a88c5d959603..355111b7a08f90 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 a238dd8036fd16..8dc18439775b07 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 333d1ba0e6264a..0679966fa33ac7 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 079a9d1bbced99..91a6a255d10563 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 8af091771bd3ed..151a78bae5094a 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 1f6d6a9ba1c684..fe5a5e96b411b2 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 631a20f0bcdf6c..b6c561254afa3a 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 6dedd249971e18..2da84edae66dcf 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 fc537a030e52bc..9fb8bb51290811 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 624bbf01a981dd..8f9d6678489903 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 a5d56dd4feea10..5ae8b6fe06dc3b 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 2e7af1d73120f6..a5b8ee2210ad09 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 49c9a40ff89468..c09c2b6c46ab0e 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 fe95c91406e4c2..5ce7f17fbba770 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 37e44d7a88a33e..cadcb64046cb3e 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 475a8a2c05da36..6b231e46f696ef 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 f62e10db504615..9c3bd452e4b3e5 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 3477ece3819e0f..0c1734a4fc1bcd 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 51b3707ff4cdaa..923ea0cf3aa3e8 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 7e62154cabf299..a05fd39524e3ff 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 c9e30b7cf0fed2..9807a95bd3e38d 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 f95130c7fda77e..01d3e95ad371ce 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 627ca307b241f4..7d973531defa7f 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 5794f30eaf8673..133c39b185221a 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 ec79dddfe1fdbb..52ec43f9cd7a60 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 027622873bb5d2..922819bab9d75f 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 3ba110b4d641eb..f54e0a324c7707 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 9209559d4c5114..1f9b0096c77dac 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 fd625f04df24b2..c0711e797420b6 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 3529d77abdd16e..aa8bf3cf410c92 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 c39f9c81be7533..4ebd0dabd55662 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 90704b67958d8b..b85a463e73ea78 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 5bdc58d85dc6f7..8e1d4258bfb99a 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 98e6154aa8a60a..e759f05ccf0a7a 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 5cedf9017c0c3f..d5cdbeb3d218c2 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 1900aafa94eb79..33b5b599ff9e54 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 506c2ef17b8a8e..a4322612120fe9 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 51e1daf9c0a4cf..9c2065eac13c21 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 28663f15a680c0..119d56dbb3003b 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 1b0583f4267b79..984ef45d231200 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 080fc9faa8d699..d4f621b3f9380e 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 9509d2d37a5f2f..53c237da8263fa 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 c9bac589f98e14..32b32fc6447e65 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 9609f525002a3e..9b53e44e3c6a25 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 936150c592f658..06a1af606f4ddd 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 55c5bc95f33893..11a7574a92b3fc 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 81991f4c3596ae..c45f16e2e80f55 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 3eeac77d30d965..60bd4e161066fa 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 4210e48cd996cf..33fc563195c535 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 dd1eeae4185e81..14c447938b0110 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 674d7849ea2780..6799c9eb75262b 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 2352502cb61686..e8336376665f3d 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 299b419bc62ee3..b0313bea59b091 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 d0b05022cb683c..9dd7c36be8f170 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 25ba7ec1f2e1f3..f410ac6a9047f3 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 c9cb8f54bf0b46..783c496cbd07bf 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 11c17046984bdd..0b2e28d2fcc013 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 54ab8505a8c85a..f86f0d415b6d4c 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 8f1e0a68c22aef..cfd72c12a42e57 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 4c8960148995de..73765de18a394e 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 00000000000000..9f1ddd3e9ad60c --- /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 00000000000000..311ffb835ca3da --- /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 61fd9c2863537c..b64655db6f0853 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 8a3895b5351715..f2698137129148 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 685a28bea6242e..99a930f9d224f8 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 5db1b0e6e16a68..a1ed9cf575661e 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 4272953c4d7634..bfa8aecc9291fa 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 e0082181ed9499..e66607e2cde5af 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 d3f1f8cfa09250..d7b3c1d1d5d2fd 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 6ff37b3089b427..a1e664d7b53893 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 02a3fd158ec196..a745b6da5f6a0d 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 d6a70b2b0157f7..518ab4af770c29 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 d7c038b7a711f4..de4d727c14fab5 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 eb6b4e1963417a..b87d5048a69053 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 1fcfd9f49e0fec..a3c8d5603427ef 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 7d8bb7252a8664..4914748e901c12 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 69cc08dec91906..40d46a7687fddd 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 eef24003ab6608..1463fb87cb8fba 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 58aff21c3f6337..54c64f8b8fda9e 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 14f3d8712b6f38..0965192dcfdbe0 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 d0b5db8e3f5b1d..ad84e496b20799 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 ced47f174e1bf7..8f38c2b6bdad6c 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 236d416f8d3f55..11d9eb04639096 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 c286a69723583c..6b89a10f6ec777 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 c4201d23a52f5e..22da384234cedc 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 5524f5d6c7f3aa..70fcaf9182e517 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 c06544f0947568..5031c4b861035b 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 47db10d316e4b7..1de9b8ddba1543 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 74eb25b3e0989c..087844e3c88bf2 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 2852e74f1d2725..80375fc9358090 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 6b39d835633258..8f06e9041f1734 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 efcb0ef0d682e5..b6d11a52ac6a13 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 1b84c3249cedb9..ccf31dd049ecc7 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 d9183c76749eb1..26a111f2dbf6b9 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 2dabde1158feb7..d06dc3b228af6b 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 1f150624000c09..a885d35bc4a9c9 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 f6098418f65b87..e7d74aed912615 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 18f41498f20227..35fa1b024d8869 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 4da7cae29afc17..0ba73bbd364fa8 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 7efaa1d260ede8..1c2e9f62733f0e 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 3a8b4f7733b890..db91ea53044997 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 6d30b7f184a66f..cfe58fb7263b43 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 7e04c291cf2e54..7bdf93b209089e 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 bd6d7e53603552..794d174977ede5 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 108a226e773287..a85edbed68ebb3 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 36da5cccdf92f8..d3fd0ffaa55a9c 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 a54b680f5d90df..f07a80ead0b91b 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 22c91cf01d3754..bb44b77ef391ae 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 50e70544b210b7..be34c24b4d5206 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 f538a5f8e0b4ef..e31b3a81bdabc6 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 8f1461db2e66f9..d2e61d23962c0e 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 77a377a7db9c45..6df94b463ca90f 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 d7dcc38efe4717..497fddcf7e231c 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 d1ac9f6597bd68..856e7f9d76fb43 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 4d4c0cdc35fd77..e06e18473ee934 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 d81bd38aa19168..a7aaa90cb5c416 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 05d5f2ae35765b..dbf268c236e98b 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 68eb012274a212..13fccc87faa203 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 4bdeef98bbfdeb..48cac2e369e1d7 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 d94568e3ed0e60..2b5d84f23701a8 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 306f52219527d6..acf212d2c68467 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 ab7a2167ee8d30..8e3cf5be8daf60 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 3a484f5c5bb947..ebb1175bcb40a9 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 4c6f719e62bb63..4728e428fba30f 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 867ab6116747a7..6fd18f3be652a3 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 ad72f4f7242bc6..8bd37f962ce100 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 8bca969b220645..24d10c696553fd 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 408b53256f7dbb..bed254fe88fcd0 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 c87dfa9f9fccfb..a23262b66cac92 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 f165267f56e336..a56c06d0404df1 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 e9e7dc64f09420..79d55f9e4eeb81 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 f0cdaa85851dc4..067b9306999e98 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 f38f74d167a987..3914ccd95f841b 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 c59347545a3283..df116067986833 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 c38585619f6c07..a3ce906c0279d5 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 0fe31a6d87fa12..45a7e9042ce1bf 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 a9a56e81b982df..609a0a4818ab3e 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 ec02bcea6b7f70..37c4c3ee83c088 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 1a2c897736245c..d84bd90a40383e 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 3c162c91897528..530e498c01a258 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 494208c4c59143..591163fb8790d4 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 a037d65732f722..d4657d880f3f5e 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 99067d79f0a9f2..171b4c8439822b 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 534bb78d4eaba5..aa224ae9c82c70 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 81ac149482e3c4..5f88d588307f33 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 26ba420c08d5c3..b5fb67855d7721 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 9ba0f825772fa8..42cd8929384796 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 a960a65461e1d5..5cbeb9a6b2d771 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 3fd09f6263c27f..8f2e8da57a9d02 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 f3acbc7fc8045a..203f90c1d41eed 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 c3ea743d26d841..b0fcac62cba046 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 71e8faae9eed43..95725dc4f0853e 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 2eabbc2bacc62c..8601cebc03b08f 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 f37d52033dd7a8..2b6f90835423cb 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 a7217eaf16db7b..fd0f9f47b6c609 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 59ea42f319e9db..38722e91549164 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 466745e2ce4992..7e520e3b42b451 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 98986e42b5bb04..b6faa54b4347e2 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 d8b70870879305..42104c5c026459 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 b45a40d461b522..f2069ec00fad8d 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 c8536a941c46f7..afa264cdbe4b26 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 7146b20a88240c..847bf67c4aa8da 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 2518700bd2a9d4..0d34a5cd902a38 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 38f32ad1bd2c1a..3fd6130cdb8d8e 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 d158e2d4ff9534..8ecd79bb3a3e84 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 e2c79208c06749..2fc8b367d6fdd0 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 8d655eb267f5a1..6fb4b8ec4c90ea 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 77ea3d979a1fa4..4705b9ed25bf71 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 44b22b20623c48..8b10022d6b74e5 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 c032049f2dd4f3..3404d9772c1adf 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 08eca2b5022064..787d6e83c77aee 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 971dc60fbd4027..f5261db81d189f 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 f7cb4f618bbf9f..af9157cecdeafb 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 91d2f9cd5b3a77..0dd9d376a4e676 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 e114fbfb6c8709..142f2fa27e62ab 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 12234cdb3c57c5..eee680ca1b5e1e 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 2ae4ebd0c693ba..dfaeecf2543c41 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 55646f1f382b6c..d04aab2eb21b93 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 165d452af821e1..7cbd01cb6659dc 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 99c35f7983158b..ccbf3695bf3d2e 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 1978b36015f7eb..128e9e835c0296 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 79c73b9220d0cb..d0775b712d605e 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 10eb5868ecf479..6f1f46aa38e73d 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 4615be9c11ce10..86d42b6b8c6087 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 a37c978cfb3431..d65aee63687a29 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 706710e2cb8245..f676d4fca46308 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 e0122dffcacb0e..98be6ceff59833 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 08cc43ae6a54dc..4460fd0b02f899 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 49dcee745ce388..38281e01c8bbe2 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 612f215e8d9224..47e427a65c326d 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 391665fd14f8c9..60e7cc8bfb0889 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 8f7af4ecd077da..63c8b4908cd45f 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 0a1e5d1292b0a3..3fef61e10359b8 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 b99ce47f08e8a8..9fa7764ac307f4 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 de9b1e18cae5de..9bbd560f514cd5 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 301494e7b2388d..1fcb770a7f8364 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 df15953abc315f..ac91c3bbbe4911 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 075a12e1c6f248..3b84bab2c12faf 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 d47e6f46490f11..545b8c3c51a642 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 a38afc5d4cc28a..f3fc43285ef757 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 ef13c55e07c618..fb6c5ca01698c6 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 cf75dcf5478b40..aef8e4d42c71c7 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 f11a869c92ee1a..4374ec559a5d66 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 484dd22728fbc3..e6b16af9bcb5d9 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 63cd2de86d6e42..7383ce4111dae3 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 54a121b8df78a2..1235740a86f62d 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 a21b5fa4ab4543..23b5c986ca5223 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 e741c75cc66b0a..2931039207d991 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 92c21046ca2bb9..1889f6b78437a1 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 9b8babfa1fcf37..7eb24fbc7fb7b0 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 b95f699d6d0503..2450d22c66fb14 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 a88c23c489891e..eaeb3e4a7e9212 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 06432816c1788c..631a6b4af48d1a 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 e1477ab3eeb87c..9be618bb239316 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 914a5468667b51..336935840823f8 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 b5596d8ce5bc7f..5bff292e0ba662 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 c5fc08f39d31ed..59d9348dc71109 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 ff42ee5669ec3a..382f3531352876 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 6af342c88de2cd..d0bd92809e4862 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 845a8d13f62681..ba85e5db86b159 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 3b0b33c77b2276..d846010affe30e 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 afafe20cb2e1e6..dde7c69400f0cb 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 4de5774e126932..266c8b25546455 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 683f4f5f7821b8..e4d487eed68f81 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 36602e1f31d550..9e553a8d2b1f27 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 b01beac37dc53a..496205404ca2d1 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 3aaf17560db473..3764d3e6efd5bd 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 b1465811e22950..69318531901df5 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 ead5751fa35c30..3b0041e92a94ff 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 17263518fb4d1a..6c7650530715b3 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 6b880006797c03..ba5be3d9b27503 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 0b8d0d939e4aea..c29d5b8dd29078 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 2969a379e7d3c4..5e7e203e9099ce 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 eaf29c3077a8bd..1b4b48beebca7b 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 ab41505be21e9c..58bb73e76d2ec8 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 9dac7b564cfc45..1e36c23a4ae002 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 9d1a75a856f823..958d349ebcbf8a 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 cd2e3feaf71280..6a3f9acac51867 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 d6f3aa5be6f6b1..bbe08f3124717c 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 4d30f7635f93fb..7535845c13c1f7 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 602d4b76e4b693..a9f638b69ffe36 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 512b1b3bf1c1c1..e89bcb91ea00bf 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 c0b9ff1ab00b0b..27613f3c7e4b88 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 3927471f085142..3969bb18a85a99 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 0e35a01c9f842f..296b6f32ef717f 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 fa3960f635cfa2..5b5f25b8f9af4c 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 52466ad20426f8..b3a5386218da5a 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 cd8245459b82a6..312114890ac9a4 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 f6df58ece93400..a37d1ecb7a39e4 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 65d82c8aa5eb72..51e5023b979244 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 139ab20896b0dd..6cfc3ecb359954 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 c5d491f697f42f..74290f7b26982c 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 50c599f41ff0c7..0ccb8e7051a6da 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 d6377597c1189d..066367700c6c00 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 4d82a84859ef64..cd0d3ea9a262d0 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 9b5b0e68022542..40cd0884ecb9da 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 d739403a3224c4..eed88f2ab3ade2 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 479812a7adc8eb..d4acef9dbda431 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 66f07ded661988..be4e1dfa7edf1c 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 9ba905d6961339..04f04d6bc73d30 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 9b3835108da87e..60bbd043f4e995 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 34e8d2903cb26d..9ac9f4294c0311 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 6c2e32868b8c70..2a8855f2702e03 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 c5fa54588432d1..2a1b7e6dfd5d81 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 a0492d6bad71a4..35fce2db8fb5df 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 2cfeb2a59556cb..fd94f66416a73e 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 0faf79405aec0e..bbb48df7e90d29 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 6d638b542f37fc..3664a673b4ccbb 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 39fbc8c7d9cc26..af897555cb6a4c 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 22473afe43b101..0d5eec908ea3f3 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 6418b8eaa65f67..15741af3c7fd77 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 b6ab8338b1be15..64961e33e9aab5 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 20e06ed847ebbb..a73f49add8a1c6 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 e17712d9bddd99..0b45c5cd26d281 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 ceef8788887602..35f0a8b02a6b64 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 e065a2d921874b..587c8453128d15 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 619f44aa959044..e1c844420142c2 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 c338805fe37b6d..51adc66a13288b 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 77df8b9fc96a93..f1eb955d2fb50a 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 271d8d798bb8bc..21a68c43a88e9a 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 0d286dc40dd463..dd405dac0663b0 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 bf8dc9c9b2edb0..f608b578c43e87 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 109f0b15690e79..3497a15c009c85 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 2be790b6675bf4..9d6ec4585eae96 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 c03883615b27e9..ae408b9a0c7ebc 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 39eafb2553e2a4..50d4446c8492dc 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 fe2a28edfb759c..766ec15375419f 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 dfafc2995046a9..8ea785d2a6232a 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 90bfd0727dc864..91024484857b2b 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 894f6e1897cb77..2090a6ff74782d 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 9baafe7d47198c..9898246c243f3a 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 0dcb15d07b2b06..f2350045bb743d 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 44df1cb8c7ba89..609ccdea584758 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 3725fe5986da20..309b78834d066c 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 660528756fcdf5..934a85e2f0a205 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 ee1fd9df0086e1..991b131f660345 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 56b40b98c61113..db06808e4b24b2 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 ac3326c810bdff..1f5facec95281f 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 dbd27615a28866..8d753b443430da 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 31bc6c9d9cbc97..6a7febcc12770c 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 f51ac2903abc58..40bf71465317b1 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 cbb7736cb66be4..ae067bd3813005 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 2d549e62753a9d..226588554d7150 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 966df1d43affcf..5659087472cfd6 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 ef163cd54d784d..8825da7e861915 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 710ef66fad8aaa..d3c5052958e2dc 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 548914f17feb8e..2b716679e092a5 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 012e37a5de10c8..e2bbd21ba5d00f 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 c19977b427358a..b16aed40d059c2 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 61c51d7af0af1a..16ac8b89a12977 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 97859ef4b0ff62..4ad236560201d1 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 087b27374d68be..0a2855949754aa 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 aadca05334b923..652e9b2b4eee7e 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 2d19d6111ef538..b9f07add0070be 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 e5f63ce69a7917..8f978e102091b9 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 9fbc74f2281fae..6dae9754b092b0 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 d99c895cfbb075..0c8dfd59d07566 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 fc1a7126299da7..4040df55dfbb5f 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 5b42ba79461cd3..7e0d6a471876b9 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 c538168844e590..32506057fb398e 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 7c066060f57b37..b046e0bf3a0b1f 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 57b4b0757c666f..72d7f5a5a936b3 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 0e0a7de0598540..027126e63d38b6 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 cbe1bbc5921053..1f698d252027e3 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 fd754396fe38d4..0cb0c6e1200975 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 704bf0d55a5885..4db2e15234b2c4 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 d5cbbf14400083..dcf9d9bec380ee 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 6e187d6a67d6d7..f36cb7d427e592 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 f1eafa0073b758..463a82c4199c4a 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 345346e831748c..a497f9b7b3b39a 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 e242bd651fa0da..5a10a184e86127 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 4e391b9d8ab271..487bdb38fb3834 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 662c634769dd1a..03da8f2f7777b0 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 1e22cbdd34b006..ab7439a55c7666 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 de6c162159d55c..bd0ba52bf411ac 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 43c10d7423ef71..426d6f89d33eaf 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 7a684a3ddf9b69..17278d3391172d 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 7a12cb245836a9..90687689300cf3 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 dc82b967602d2c..45abcd789302e8 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 18b157f0504187..abad65bf4bc03e 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 8d43b2bc2609dc..582712bf883ad4 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 6ad8500d4ffa1f..3f11bf724f8fff 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 da16a3294719e7..a73b2130810da8 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 a79ab025d8f394..be46cd48c00e3e 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 675d8ca547baa5..9f218fb8b7711f 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 87063d4968ca9e..d1fb5d6a6f424d 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 d83b54deb377fa..6a03b3207fd5ad 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 a915fe7ce8d2fc..b0a172e51dcd40 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 d386725c0f96e4..0f4655e1c7b2cb 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 b609f61949e3a9..5b97d695996a63 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 3b8a6aea768c4d..d0c153561221cd 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 b549d3be8ce485..20d822602caace 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 57b7f77917dbfb..f59ea8eee9b013 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 cde75adfcbd03a..7c1a23d42acbb4 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 449ec14d0e89b0..a3b987ec20f3f6 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 54e8a60fb3a71f..1ead3393a1d1e1 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 64431ebf0a9f65..b85098cc1faf91 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 8f903a6a4599b8..1a2dfe73599e1e 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 aa8aeb5ed628b6..cd277b50d00af6 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 1f594a6b275037..12a97778fad87d 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 35030e8f65b692..0c09f2472b024a 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 b86313f3a585f7..b14e5c2841704e 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 8a4c14deb442e3..714f8f1f61bdaf 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 8a33d6033f83b7..be0949d0ede01b 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 8bb33b6f9fc6c9..3bc8c63d56fa1b 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 0dcbe8b65314aa..576f6d4cd96da0 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 009ae2d00a260c..1b27ea92514102 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 077c288dbbd069..69b0b634373808 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 ac6e29488cd2d1..503316ebfede3e 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 4b38e19d2c1445..5c043b0c4a6c19 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 1e093e1c87a673..493ea7c87e5edf 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 ec139fac154dcb..9f58d89939bd43 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 c335bc8053866e..9cdd2a92c31ffc 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 e4f48183850088..06135cf53284de 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 cfc3a2c905dbb9..e4fcda7eaf227b 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 2306fe2c1523ed..8918f46eeb6f01 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 1ecdc4cf5f6b89..57ddb467440df5 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 9678a8f3d941ec..05e8e2b467830e 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 0847fe990eba30..e9bda657218a87 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 bd7b6dc7e7ca05..1aec92203b0086 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 3bb37ce9fb475e..31a898abdcd9cd 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 89bec78fbe1d08..2f26f43d82140e 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 35b6ab39c77a03..ed7132ef96976d 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 277ae838dc455c..2f572069ba16c4 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 9710a9b016f88a..e7081fb0b123e1 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 e9b23fb4642c82..3a47dfb8f81907 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 7e35673a3ef08e..d5ba1063609b4e 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 18e54115992df5..3fb2d265e634f2 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 f791025b3e3532..04136e7075bf20 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 348e30ab776d89..67c1ef29b5996a 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 7a4f5df515fb3d..580fb0856897ae 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 e86efc700e7eaf..927666bf772e0d 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 d5ef557d051167..ba6ae74332111c 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 97034dddf8ac65..bd11ed47d4e376 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 d977d474923842..bd43f592ea0806 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 90c474ac78f1ab..4ccdc037193575 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 8d7ae64cb77bab..55dddb743db0d5 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 59a2eae887fb53..4c01c76df48847 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 f1ec49c987768b..9eb555cf9ed2a4 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 bf6aade55bc14f..ea77584e992137 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 01cac0e1d423ae..666a83cdf4e15e 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 321906e5ba6ff5..fccba2b5c522fd 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 171effe7c54674..365e4ce0d265e6 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 e0ee93073348ba..95a66f4fb67fa8 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 bb5dd22da4a5de..d0383bd897a408 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 fa7c514839f1d4..5fe59c8d1a5c86 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 052f465958e43c..e611da2cbb2497 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 b3e643f23c8247..488ef2fa16eea5 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 3ad92d06484ef4..8a51e3435ed0c2 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 2230c7e475fbf3..0281bc7f316c33 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 fbb641e9ec07bf..3922bd02399384 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 4775f7bab3e251..14d55ddb58f466 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 c5cc15bc31edd1..2fcbcc7d88c0bb 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 8ed4d9ce7ea8b3..e5e861b2107043 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 28d4dfa912c949..f89a3144758786 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 c4e422b6d4bf5f..56588f7a39e295 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 b41919c5f029f1..ba64470873a2dd 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 eee83610aebe7c..2032cc95f5e46b 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 8b105e90157cad..a0d34f1ce02df2 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 6a3ef50e5d725e..8f05b6b6c70228 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 49cd868cf6aee3..ff027a894fe003 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 89f21abe997f4a..2f539f8601a3f1 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 41725af018c9eb..8a5c1e3d3b3ee9 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 582bfce7f3253d..c4b821d22e3749 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 2467dff1cd4645..4b5964d1329b20 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 f2fd4e7fff1d3a..b5a783c0352c6b 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 12ffc30623e60f..76676c70e8167c 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 ec588948cbe188..48a65ec9218bc6 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 439a2431b43764..dbc433c4a6906b 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 a445edd5a138f2..4a2d5ddcb33402 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 29ee2d61784382..206cd595f282b8 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 fbe7face22441d..7d83295bd815bc 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 5ca7b1d27e7d42..6e08d1f6c67f86 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 2cc9a92f167062..4d5c557d27e355 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 41df96ec361dd0..13a6ab02db0459 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 e0a7c8eca1a70f..195ea7e628c28d 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 0dfa7fa50555b6..c6ada20ba14692 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 ba54dfe3328c83..124cea9d3ee499 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 0111328be34a0c..4c7f1c13c386b9 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 97fceac316e4da..b54f077ec640fa 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 e0acc17411f179..3f72b6536df3b5 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 9655a20a1dda9d..dc4452def70504 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 f70714d1e5938b..65aca0296752f8 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 fb8870f2325d26..b6383facf7fcd7 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 569cd4f8baf097..24515c95a5c7a3 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 71a2488190eb83..53918746384ba5 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 aa3747f8476f0d..81ac0a71e3af84 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 24c99b2aa19f8e..2ee372bc06670c 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 d7649559452e86..4d368c89743f07 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 c493a75a570760..94e0c5d1dcb8b8 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 b954ce0b719d21..17258d78b7ad49 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 b98b43d726106f..2cbe6e2b666d48 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 7a80a887ba186b..517213167e2fd6 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 b760568fac4e3f..fad9653e3d3eab 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 27174661c88453..dfe878c269ab14 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 f64e0c6adbde73..e774540c6c7934 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 a8c2abb735484b..ea85d8d3f48ce5 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 24/26] [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 2ed86eb91ae52e..700f339516215b 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 4fea1d04b64e4f..876f0270c20ce7 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 25/26] [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 6fae43af920e56..a78710dd9994e3 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 82f2874f9ffa2c..a5eaeacf6c7ed8 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 26/26] [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 528e3c9d6aa1b9..9bd25a715dff1a 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 025688af45242b..551696614d749e 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; +};