From 870cb9f483fa9eaf0303d478f4c2395ee0b3067a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yulia=20=C4=8Cech?= <6585477+yuliacech@users.noreply.github.com> Date: Fri, 10 Nov 2023 17:16:57 +0100 Subject: [PATCH 01/12] [Index Management] Add ingest pipelines link to the index details page (#170906) ## Summary Fixes https://github.com/elastic/kibana/issues/165107 This PR adds a function to the extensions service in Index Management to register additional content that is rendered in the bottom right corner under the mappings docs link. This PR also adds a panel with the link to the ingest pipelines search docs for the serverless search project. ### Screenshots Screenshot 2023-11-08 at 21 51 28 ### Checklist - [ ] 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> --- x-pack/plugins/index_management/README.md | 5 +- .../index_details_page.test.tsx | 22 ++++ .../details_page/details_page_content.tsx | 2 +- .../details_page/details_page_mappings.tsx | 106 ++-------------- .../details_page_mappings_content.tsx | 119 ++++++++++++++++++ .../plugins/index_management/public/index.ts | 2 +- .../plugins/index_management/public/plugin.ts | 6 +- .../services/extensions_service.mock.ts | 1 + .../public/services/extensions_service.ts | 33 ++++- .../index_management/public/services/index.ts | 2 +- .../plugins/index_management/public/types.ts | 4 + x-pack/plugins/serverless_search/kibana.jsonc | 4 +- .../components/index_mappings_docs_link.tsx | 68 ++++++++++ .../serverless_search/public/plugin.ts | 4 +- .../plugins/serverless_search/public/types.ts | 2 + .../plugins/serverless_search/tsconfig.json | 1 + 16 files changed, 272 insertions(+), 109 deletions(-) create mode 100644 x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_mappings_content.tsx create mode 100644 x-pack/plugins/serverless_search/public/application/components/index_mappings_docs_link.tsx diff --git a/x-pack/plugins/index_management/README.md b/x-pack/plugins/index_management/README.md index 8673447fc577c..867ccbd74335c 100644 --- a/x-pack/plugins/index_management/README.md +++ b/x-pack/plugins/index_management/README.md @@ -34,14 +34,15 @@ interface IndexDetailsTab { An example of adding an ILM tab can be found in [this file](https://github.com/elastic/kibana/blob/main/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/index_lifecycle_summary.tsx#L250). -- `setIndexOverviewContent(content: IndexOverviewContent)`: replaces the default content in the overview tab (code block describing adding documents to the index) with the custom content. The custom content has the following interface: +- `setIndexOverviewContent(content: IndexContent)`: replaces the default content in the overview tab (code block describing adding documents to the index) with the custom content. The custom content has the following interface: ```ts -interface IndexOverviewContent { +interface IndexContent { renderContent: (args: { index: Index; getUrlForApp: ApplicationStart['getUrlForApp']; }) => ReturnType; ``` +- `setIndexMappingsContent(content: IndexContent)`: adds content to the mappings tab of the index details page. The content is displayed in the right bottom corner, below the mappings docs link. ## Indices tab diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.tsx index afdfa15c917aa..24938682ac519 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.tsx @@ -501,6 +501,28 @@ describe('', () => { expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests + 1); }); }); + + it('renders the content set via the extensions service', async () => { + const mappingsContent = 'test mappings extension'; + await act(async () => { + testBed = await setup({ + httpSetup, + dependencies: { + services: { + extensionsService: { + _indexMappingsContent: { + renderContent: () => mappingsContent, + }, + }, + }, + }, + }); + }); + testBed.component.update(); + await testBed.actions.clickIndexDetailsTab(IndexDetailsSection.Mappings); + const content = testBed.actions.getActiveTabContent(); + expect(content).toContain(mappingsContent); + }); }); describe('Settings tab', () => { diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_content.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_content.tsx index 0360df733c945..adeadee6132c0 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_content.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_content.tsx @@ -50,7 +50,7 @@ const defaultTabs: IndexDetailsTab[] = [ name: ( ), - renderTabContent: ({ index }) => , + renderTabContent: ({ index }) => , order: 20, }, { diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_mappings.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_mappings.tsx index a83eebd395b1e..c6f74207c92d7 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_mappings.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_mappings.tsx @@ -6,27 +6,18 @@ */ import React, { FunctionComponent, useEffect } from 'react'; -import { - EuiButton, - EuiCodeBlock, - EuiFlexGroup, - EuiFlexItem, - EuiIcon, - EuiLink, - EuiPageTemplate, - EuiPanel, - EuiSpacer, - EuiText, - EuiTitle, -} from '@elastic/eui'; -import { css } from '@emotion/react'; +import { EuiButton, EuiPageTemplate, EuiSpacer, EuiText } from '@elastic/eui'; + import { FormattedMessage } from '@kbn/i18n-react'; import { SectionLoading } from '@kbn/es-ui-shared-plugin/public'; -import { useLoadIndexMappings, documentationService } from '../../../../services'; + +import { DetailsPageMappingsContent } from './details_page_mappings_content'; +import { Index } from '../../../../../../common'; +import { useLoadIndexMappings } from '../../../../services'; import { breadcrumbService, IndexManagementBreadcrumb } from '../../../../services/breadcrumbs'; -export const DetailsPageMappings: FunctionComponent<{ indexName: string }> = ({ indexName }) => { - const { isLoading, data, error, resendRequest } = useLoadIndexMappings(indexName); +export const DetailsPageMappings: FunctionComponent<{ index: Index }> = ({ index }) => { + const { isLoading, data, error, resendRequest } = useLoadIndexMappings(index.name); useEffect(() => { breadcrumbService.setBreadcrumbs(IndexManagementBreadcrumb.indexDetailsMappings); @@ -63,7 +54,7 @@ export const DetailsPageMappings: FunctionComponent<{ indexName: string }> = ({ id="xpack.idxMgmt.indexDetails.mappings.errorDescription" defaultMessage="We encountered an error loading mappings for index {indexName}. Make sure that the index name in the URL is correct and try again." values={{ - indexName, + indexName: index.name, }} /> @@ -86,82 +77,5 @@ export const DetailsPageMappings: FunctionComponent<{ indexName: string }> = ({ ); } - return ( - // using "rowReverse" to keep docs links on the top of the mappings code block on smaller screen - - - - - - - - - -

- -

-
-
-
- - -

- -

-
- - - - -
-
- - - - - {JSON.stringify(data, null, 2)} - - - -
- ); + return ; }; diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_mappings_content.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_mappings_content.tsx new file mode 100644 index 0000000000000..40544bb18e1ff --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_mappings_content.tsx @@ -0,0 +1,119 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FunctionComponent } from 'react'; +import { + EuiCodeBlock, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiLink, + EuiPanel, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { css } from '@emotion/react'; + +import { Index } from '../../../../../../common'; +import { documentationService } from '../../../../services'; +import { useAppContext } from '../../../../app_context'; + +export const DetailsPageMappingsContent: FunctionComponent<{ index: Index; data: any }> = ({ + index, + data, +}) => { + const { + services: { extensionsService }, + core: { getUrlForApp }, + } = useAppContext(); + return ( + // using "rowReverse" to keep docs links on the top of the mappings code block on smaller screen + + + + + + + + + +

+ +

+
+
+
+ + +

+ +

+
+ + + + +
+ {extensionsService.indexMappingsContent && ( + <> + + {extensionsService.indexMappingsContent.renderContent({ index, getUrlForApp })} + + )} +
+ + + + + {JSON.stringify(data, null, 2)} + + + +
+ ); +}; diff --git a/x-pack/plugins/index_management/public/index.ts b/x-pack/plugins/index_management/public/index.ts index b8cde1fdf36e7..8fb836ba7ffd9 100644 --- a/x-pack/plugins/index_management/public/index.ts +++ b/x-pack/plugins/index_management/public/index.ts @@ -14,7 +14,7 @@ export const plugin = (ctx: PluginInitializerContext) => { return new IndexMgmtUIPlugin(ctx); }; -export type { IndexManagementPluginSetup } from './types'; +export type { IndexManagementPluginSetup, IndexManagementPluginStart } from './types'; export { getIndexListUri, getTemplateDetailsLink } from './application/services/routing'; diff --git a/x-pack/plugins/index_management/public/plugin.ts b/x-pack/plugins/index_management/public/plugin.ts index 87b4ff5be220c..f4038a4e2677f 100644 --- a/x-pack/plugins/index_management/public/plugin.ts +++ b/x-pack/plugins/index_management/public/plugin.ts @@ -81,6 +81,10 @@ export class IndexMgmtUIPlugin { }; } - public start() {} + public start() { + return { + extensionsService: this.extensionsService.setup(), + }; + } public stop() {} } diff --git a/x-pack/plugins/index_management/public/services/extensions_service.mock.ts b/x-pack/plugins/index_management/public/services/extensions_service.mock.ts index 3c0886b0fe4a3..8f4968ad35e41 100644 --- a/x-pack/plugins/index_management/public/services/extensions_service.mock.ts +++ b/x-pack/plugins/index_management/public/services/extensions_service.mock.ts @@ -18,6 +18,7 @@ const createServiceMock = (): ExtensionsSetupMock => ({ addToggle: jest.fn(), addIndexDetailsTab: jest.fn(), setIndexOverviewContent: jest.fn(), + setIndexMappingsContent: jest.fn(), }); const createMock = () => { diff --git a/x-pack/plugins/index_management/public/services/extensions_service.ts b/x-pack/plugins/index_management/public/services/extensions_service.ts index 94211b32dbd70..1eb68e9a0b746 100644 --- a/x-pack/plugins/index_management/public/services/extensions_service.ts +++ b/x-pack/plugins/index_management/public/services/extensions_service.ts @@ -12,7 +12,7 @@ import { EuiBadgeProps } from '@elastic/eui'; import type { IndexDetailsTab } from '../../common/constants'; import { Index } from '..'; -export interface IndexOverviewContent { +export interface IndexContent { renderContent: (args: { index: Index; getUrlForApp: ApplicationStart['getUrlForApp']; @@ -28,13 +28,22 @@ export interface IndexBadge { } export interface ExtensionsSetup { + // adds an option to the "manage index" menu addAction(action: any): void; + // adds a banner to the indices list addBanner(banner: any): void; + // adds a filter to the indices list addFilter(filter: any): void; + // adds a badge to the index name addBadge(badge: IndexBadge): void; + // adds a toggle to the indices list addToggle(toggle: any): void; + // adds a tab to the index details page addIndexDetailsTab(tab: IndexDetailsTab): void; - setIndexOverviewContent(content: IndexOverviewContent): void; + // sets content to render instead of the code block on the overview tab of the index page + setIndexOverviewContent(content: IndexContent): void; + // sets content to render below the docs link on the mappings tab of the index page + setIndexMappingsContent(content: IndexContent): void; } export class ExtensionsService { @@ -55,7 +64,8 @@ export class ExtensionsService { ]; private _toggles: any[] = []; private _indexDetailsTabs: IndexDetailsTab[] = []; - private _indexOverviewContent: IndexOverviewContent | null = null; + private _indexOverviewContent: IndexContent | null = null; + private _indexMappingsContent: IndexContent | null = null; private service?: ExtensionsSetup; public setup(): ExtensionsSetup { @@ -66,7 +76,8 @@ export class ExtensionsService { addFilter: this.addFilter.bind(this), addToggle: this.addToggle.bind(this), addIndexDetailsTab: this.addIndexDetailsTab.bind(this), - setIndexOverviewContent: this.setIndexOverviewMainContent.bind(this), + setIndexOverviewContent: this.setIndexOverviewContent.bind(this), + setIndexMappingsContent: this.setIndexMappingsContent.bind(this), }; return this.service; @@ -96,7 +107,7 @@ export class ExtensionsService { this._indexDetailsTabs.push(tab); } - private setIndexOverviewMainContent(content: IndexOverviewContent) { + private setIndexOverviewContent(content: IndexContent) { if (this._indexOverviewContent) { throw new Error(`The content for index overview has already been set.`); } else { @@ -104,6 +115,14 @@ export class ExtensionsService { } } + private setIndexMappingsContent(content: IndexContent) { + if (this._indexMappingsContent) { + throw new Error(`The content for index mappings has already been set.`); + } else { + this._indexMappingsContent = content; + } + } + public get actions() { return this._actions; } @@ -131,4 +150,8 @@ export class ExtensionsService { public get indexOverviewContent() { return this._indexOverviewContent; } + + public get indexMappingsContent() { + return this._indexMappingsContent; + } } diff --git a/x-pack/plugins/index_management/public/services/index.ts b/x-pack/plugins/index_management/public/services/index.ts index 8f4ddbeffba35..bca35e09c9776 100644 --- a/x-pack/plugins/index_management/public/services/index.ts +++ b/x-pack/plugins/index_management/public/services/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -export type { ExtensionsSetup } from './extensions_service'; +export type { ExtensionsSetup, IndexContent } from './extensions_service'; export { ExtensionsService } from './extensions_service'; export type { PublicApiServiceSetup } from './public_api_service'; diff --git a/x-pack/plugins/index_management/public/types.ts b/x-pack/plugins/index_management/public/types.ts index 57ddf10c767fd..d00e6ba4d5ac7 100644 --- a/x-pack/plugins/index_management/public/types.ts +++ b/x-pack/plugins/index_management/public/types.ts @@ -16,6 +16,10 @@ export interface IndexManagementPluginSetup { extensionsService: ExtensionsSetup; } +export interface IndexManagementPluginStart { + extensionsService: ExtensionsSetup; +} + export interface SetupDependencies { fleet?: unknown; usageCollection: UsageCollectionSetup; diff --git a/x-pack/plugins/serverless_search/kibana.jsonc b/x-pack/plugins/serverless_search/kibana.jsonc index 2ae8f0dbff987..0ac92bc197468 100644 --- a/x-pack/plugins/serverless_search/kibana.jsonc +++ b/x-pack/plugins/serverless_search/kibana.jsonc @@ -25,7 +25,9 @@ "share", "visualizations" ], - "optionalPlugins": [], + "optionalPlugins": [ + "indexManagement", + ], "requiredBundles": [ "kibanaReact" ] diff --git a/x-pack/plugins/serverless_search/public/application/components/index_mappings_docs_link.tsx b/x-pack/plugins/serverless_search/public/application/components/index_mappings_docs_link.tsx new file mode 100644 index 0000000000000..fc1699beffa8f --- /dev/null +++ b/x-pack/plugins/serverless_search/public/application/components/index_mappings_docs_link.tsx @@ -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. + */ + +import React, { FunctionComponent } from 'react'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiLink, + EuiPanel, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { CoreStart } from '@kbn/core/public'; +import { IndexContent } from '@kbn/index-management-plugin/public/services'; + +const IndexMappingsDocsLink: FunctionComponent<{ docLinks: CoreStart['docLinks'] }> = ({ + docLinks, +}) => { + return ( + + + + + + + +

+ +

+
+
+
+ + +

+ +

+
+ + + + +
+ ); +}; + +export const createIndexMappingsDocsLinkContent = (core: CoreStart): IndexContent => { + return { + renderContent: () => , + }; +}; diff --git a/x-pack/plugins/serverless_search/public/plugin.ts b/x-pack/plugins/serverless_search/public/plugin.ts index 6f1cb6465106c..4203925650385 100644 --- a/x-pack/plugins/serverless_search/public/plugin.ts +++ b/x-pack/plugins/serverless_search/public/plugin.ts @@ -15,6 +15,7 @@ import { import { i18n } from '@kbn/i18n'; import { appIds } from '@kbn/management-cards-navigation'; import { AuthenticatedUser } from '@kbn/security-plugin/common'; +import { createIndexMappingsDocsLinkContent as createIndexMappingsContent } from './application/components/index_mappings_docs_link'; import { createServerlessSearchSideNavComponent as createComponent } from './layout/nav'; import { docLinks } from '../common/doc_links'; import { @@ -85,7 +86,7 @@ export class ServerlessSearchPlugin public start( core: CoreStart, - { serverless, management, cloud }: ServerlessSearchPluginStartDependencies + { serverless, management, cloud, indexManagement }: ServerlessSearchPluginStartDependencies ): ServerlessSearchPluginStart { serverless.setProjectHome('/app/elasticsearch'); serverless.setSideNavComponent(createComponent(core, { serverless, cloud })); @@ -94,6 +95,7 @@ export class ServerlessSearchPlugin enabled: true, hideLinksTo: [appIds.MAINTENANCE_WINDOWS], }); + indexManagement?.extensionsService.setIndexMappingsContent(createIndexMappingsContent(core)); return {}; } diff --git a/x-pack/plugins/serverless_search/public/types.ts b/x-pack/plugins/serverless_search/public/types.ts index 5b984289e2bd7..039353fa4e867 100644 --- a/x-pack/plugins/serverless_search/public/types.ts +++ b/x-pack/plugins/serverless_search/public/types.ts @@ -10,6 +10,7 @@ import { ManagementSetup, ManagementStart } from '@kbn/management-plugin/public' import { SecurityPluginStart } from '@kbn/security-plugin/public'; import { ServerlessPluginSetup, ServerlessPluginStart } from '@kbn/serverless/public'; import { SharePluginStart } from '@kbn/share-plugin/public'; +import { IndexManagementPluginStart } from '@kbn/index-management-plugin/public'; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface ServerlessSearchPluginSetup {} @@ -29,4 +30,5 @@ export interface ServerlessSearchPluginStartDependencies { security: SecurityPluginStart; serverless: ServerlessPluginStart; share: SharePluginStart; + indexManagement?: IndexManagementPluginStart; } diff --git a/x-pack/plugins/serverless_search/tsconfig.json b/x-pack/plugins/serverless_search/tsconfig.json index 1886f0ccb21b9..c07cff77aa19e 100644 --- a/x-pack/plugins/serverless_search/tsconfig.json +++ b/x-pack/plugins/serverless_search/tsconfig.json @@ -36,5 +36,6 @@ "@kbn/search-connectors", "@kbn/shared-ux-router", "@kbn/kibana-utils-plugin", + "@kbn/index-management-plugin", ] } From 8370020cd69b944a13d1c78bf6e15d016504d7b5 Mon Sep 17 00:00:00 2001 From: Davis McPhee Date: Fri, 10 Nov 2023 12:30:01 -0400 Subject: [PATCH 02/12] [Unified Data Table] Stop escaping column names when copying (#170997) ## Summary When support for copying column values was added to Discover in #132330, it was designed for copying into spreadsheets, so column names and values are escaped when copied. We used the same approach for the the "Copy name" button, but this functionality isn't specific to spreadsheets and is more likely used for copying into search bars, etc. This PR updates the copy name functionality to stop escaping field names. Resolves #170957. ### Checklist - [ ] 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 - [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 - [ ] 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) ### 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) --- .../utils/copy_value_to_clipboard.test.tsx | 2 +- .../src/utils/copy_value_to_clipboard.ts | 19 +++++-------------- .../group2/_data_grid_copy_to_clipboard.ts | 2 +- 3 files changed, 7 insertions(+), 16 deletions(-) diff --git a/packages/kbn-unified-data-table/src/utils/copy_value_to_clipboard.test.tsx b/packages/kbn-unified-data-table/src/utils/copy_value_to_clipboard.test.tsx index 7ff5c9b3f19b6..f49a7ef95ed6d 100644 --- a/packages/kbn-unified-data-table/src/utils/copy_value_to_clipboard.test.tsx +++ b/packages/kbn-unified-data-table/src/utils/copy_value_to_clipboard.test.tsx @@ -93,7 +93,7 @@ describe('copyValueToClipboard', () => { columnDisplayName: 'text_message', }); - expect(result).toBe('"text_message"'); + expect(result).toBe('text_message'); expect(execCommandMock).toHaveBeenCalledWith('copy'); expect(servicesMock.toastNotifications.addInfo).toHaveBeenCalledWith({ title: 'Copied to clipboard', diff --git a/packages/kbn-unified-data-table/src/utils/copy_value_to_clipboard.ts b/packages/kbn-unified-data-table/src/utils/copy_value_to_clipboard.ts index 2e9620b42728b..2fc0606624a0b 100644 --- a/packages/kbn-unified-data-table/src/utils/copy_value_to_clipboard.ts +++ b/packages/kbn-unified-data-table/src/utils/copy_value_to_clipboard.ts @@ -131,9 +131,7 @@ export const copyColumnNameToClipboard = ({ columnDisplayName: string; toastNotifications: ToastsStart; }): string | null => { - const nameFormattedResult = convertNameToString(columnDisplayName); - const textToCopy = nameFormattedResult.formattedString; - const copied = copyToClipboard(textToCopy); + const copied = copyToClipboard(columnDisplayName); if (!copied) { toastNotifications.addWarning({ @@ -147,16 +145,9 @@ export const copyColumnNameToClipboard = ({ defaultMessage: 'Copied to clipboard', }); - if (nameFormattedResult.withFormula) { - toastNotifications.addWarning({ - title: toastTitle, - text: WARNING_FOR_FORMULAS, - }); - } else { - toastNotifications.addInfo({ - title: toastTitle, - }); - } + toastNotifications.addInfo({ + title: toastTitle, + }); - return textToCopy; + return columnDisplayName; }; diff --git a/test/functional/apps/discover/group2/_data_grid_copy_to_clipboard.ts b/test/functional/apps/discover/group2/_data_grid_copy_to_clipboard.ts index 06fe279dbd534..27d847f9c49ce 100644 --- a/test/functional/apps/discover/group2/_data_grid_copy_to_clipboard.ts +++ b/test/functional/apps/discover/group2/_data_grid_copy_to_clipboard.ts @@ -79,7 +79,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dataGrid.clickCopyColumnName('@timestamp'); if (canReadClipboard) { const copiedTimestampName = await browser.getClipboardValue(); - expect(copiedTimestampName).to.be('"\'@timestamp"'); + expect(copiedTimestampName).to.be('@timestamp'); } expect(await toasts.getToastCount()).to.be(1); From 2c90ba9725605089d480a960bad22ec4bfc96974 Mon Sep 17 00:00:00 2001 From: Vadim Kibana <82822460+vadimkibana@users.noreply.github.com> Date: Fri, 10 Nov 2023 17:34:59 +0100 Subject: [PATCH 03/12] File hashing (#171015) ## Summary Closes https://github.com/elastic/kibana/issues/167737 In this PR: - Adds ability to specify a list of hashes to compute per file kind definition. You specify `hashes: ["sha256"]` and the hash will be computed and stored automatically on `.upload()` call. - Enables SHA256 hash computation for the default Kibana file kind, used for images on the Dashboard app. ### Checklist Delete any items that are not applicable to this PR. - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [x] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios ### 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) --- src/plugins/files/common/types.ts | 7 ++++ src/plugins/files/server/file/file.ts | 27 ++++---------- .../file_client/create_es_file_client.ts | 1 + .../files/server/file_client/file_client.ts | 37 +++++++++++++++++-- .../integration_tests/es_file_client.test.ts | 20 ++++++++++ src/plugins/files/server/plugin.ts | 1 + 6 files changed, 70 insertions(+), 23 deletions(-) diff --git a/src/plugins/files/common/types.ts b/src/plugins/files/common/types.ts index 8b6059bdb5637..e2efdb3bdea33 100644 --- a/src/plugins/files/common/types.ts +++ b/src/plugins/files/common/types.ts @@ -20,6 +20,7 @@ import type { } from '@kbn/shared-ux-file-types'; import type { UploadOptions } from '../server/blob_storage_service'; import type { ES_FIXED_SIZE_INDEX_BLOB_STORE } from './constants'; +import type { SupportedFileHashAlgorithm } from '../server/saved_objects/file'; export type { FileKindBase, @@ -94,6 +95,12 @@ export interface FileKind extends FileKindBase { */ share?: HttpEndpointDefinition; }; + + /** + * A list of hashes to compute for this file kind. The hashes will be computed + * during the file upload process and stored in the file metadata. + */ + hashes?: SupportedFileHashAlgorithm[]; } /** Definition for an endpoint that the File's service will generate */ diff --git a/src/plugins/files/server/file/file.ts b/src/plugins/files/server/file/file.ts index eeec150cfc78c..495d233fd421b 100644 --- a/src/plugins/files/server/file/file.ts +++ b/src/plugins/files/server/file/file.ts @@ -19,7 +19,6 @@ import { Observable, lastValueFrom, } from 'rxjs'; -import { isFileHashTransform } from '../file_client/stream_transforms/file_hash_transform/file_hash_transform'; import { UploadOptions } from '../blob_storage_service'; import type { FileShareJSON, FileShareJSONWithToken } from '../../common/types'; import type { File as IFile, UpdatableFileMetadata, FileJSON } from '../../common'; @@ -72,10 +71,7 @@ export class File implements IFile { return this; } - private upload( - content: Readable, - options?: Partial> - ): Observable<{ size: number }> { + private upload(content: Readable, options?: Partial>) { return defer(() => this.fileClient.upload(this.metadata, content, options)); } @@ -104,26 +100,17 @@ export class File implements IFile { ) ) ), - mergeMap(({ size }) => { + mergeMap(({ size, hashes }) => { const updatedStateAction: Action & { action: 'uploaded' } = { action: 'uploaded', payload: { size }, }; - if (options && options.transforms) { - options.transforms.some((transform) => { - if (isFileHashTransform(transform)) { - const fileHash = transform.getFileHash(); - - updatedStateAction.payload.hash = { - [fileHash.algorithm]: fileHash.value, - }; - - return true; - } - - return false; - }); + if (hashes && hashes.length) { + updatedStateAction.payload.hash = {}; + for (const { algorithm, value } of hashes) { + updatedStateAction.payload.hash[algorithm] = value; + } } return this.updateFileState(updatedStateAction); diff --git a/src/plugins/files/server/file_client/create_es_file_client.ts b/src/plugins/files/server/file_client/create_es_file_client.ts index 755071d66328c..f9ada86768c2f 100644 --- a/src/plugins/files/server/file_client/create_es_file_client.ts +++ b/src/plugins/files/server/file_client/create_es_file_client.ts @@ -70,6 +70,7 @@ export function createEsFileClient(arg: CreateEsFileClientArgs): FileClient { id: NO_FILE_KIND, http: {}, maxSizeBytes, + hashes: ['md5', 'sha1', 'sha256', 'sha512'], }, new EsIndexFilesMetadataClient(metadataIndex, elasticsearchClient, logger, indexIsAlias), new ElasticsearchBlobStorageClient( diff --git a/src/plugins/files/server/file_client/file_client.ts b/src/plugins/files/server/file_client/file_client.ts index 3bce97f6bcade..26dbc90a44b90 100644 --- a/src/plugins/files/server/file_client/file_client.ts +++ b/src/plugins/files/server/file_client/file_client.ts @@ -40,6 +40,9 @@ import { withReportPerformanceMetric, FILE_DOWNLOAD_PERFORMANCE_EVENT_NAME, } from '../performance'; +import { createFileHashTransform } from './stream_transforms/file_hash_transform'; +import { isFileHashTransform } from './stream_transforms/file_hash_transform/file_hash_transform'; +import { SupportedFileHashAlgorithm } from '../saved_objects/file'; export type UploadOptions = Omit; @@ -216,8 +219,8 @@ export class FileClientImpl implements FileClient { file: FileJSON, rs: Readable, options?: UploadOptions - ): ReturnType => { - const { maxSizeBytes } = this.fileKindDescriptor; + ): Promise => { + const { maxSizeBytes, hashes } = this.fileKindDescriptor; const { transforms = [], ...blobOptions } = options || {}; let maxFileSize: number = typeof maxSizeBytes === 'number' ? maxSizeBytes : fourMiB; @@ -231,11 +234,30 @@ export class FileClientImpl implements FileClient { transforms.push(enforceMaxByteSizeTransform(maxFileSize)); - return this.blobStorageClient.upload(rs, { + if (hashes && hashes.length) { + for (const hash of hashes) { + transforms.push(createFileHashTransform(hash)); + } + } + + const uploadResult = await this.blobStorageClient.upload(rs, { ...blobOptions, transforms, id: file.id, }); + + const result: UploadResult = { ...uploadResult, hashes: [] }; + + if (transforms && transforms.length) { + for (const transform of transforms) { + if (isFileHashTransform(transform)) { + const fileHash = transform.getFileHash(); + result.hashes.push(fileHash); + } + } + } + + return result; }; public download: BlobStorageClient['download'] = async (args) => { @@ -300,3 +322,12 @@ export class FileClientImpl implements FileClient { return this.internalFileShareService.list(args); }; } + +export interface UploadResult { + id: string; + size: number; + hashes: Array<{ + algorithm: SupportedFileHashAlgorithm; + value: string; + }>; +} diff --git a/src/plugins/files/server/file_client/integration_tests/es_file_client.test.ts b/src/plugins/files/server/file_client/integration_tests/es_file_client.test.ts index cbe80422e388b..d4c6d268fa7dc 100644 --- a/src/plugins/files/server/file_client/integration_tests/es_file_client.test.ts +++ b/src/plugins/files/server/file_client/integration_tests/es_file_client.test.ts @@ -97,6 +97,26 @@ describe('ES-index-backed file client', () => { await deleteFile({ id: file.id, hasContent: true }); }); + test('computes file hashes', async () => { + const file = await fileClient.create({ + id: '123', + metadata: { + name: 'cool name', + }, + }); + await file.uploadContent(Readable.from([Buffer.from('test')])); + + expect(file.toJSON().hash).toStrictEqual({ + md5: '098f6bcd4621d373cade4e832627b4f6', + sha1: 'a94a8fe5ccb19ba61c4c0873d391e987982fbbd3', + sha256: '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08', + sha512: + 'ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff', + }); + + await deleteFile({ id: file.id, hasContent: true }); + }); + test('searches across files', async () => { const { id: id1 } = await fileClient.create({ id: '123', diff --git a/src/plugins/files/server/plugin.ts b/src/plugins/files/server/plugin.ts index ba63e08b2ed21..9e608d8f38a59 100755 --- a/src/plugins/files/server/plugin.ts +++ b/src/plugins/files/server/plugin.ts @@ -139,6 +139,7 @@ export class FilesPlugin implements Plugin Date: Fri, 10 Nov 2023 17:47:35 +0100 Subject: [PATCH 04/12] Enable custom threshold and inventory rules in Serverless (#170351) Closes #170327 Relates to https://github.com/elastic/kibana/issues/168034 ## Summary This PR enables the custom threshold and inventory rules in Serverless. |Custom threshold rule|Inventory rule| |---|---| |![image](https://github.com/elastic/kibana/assets/12370520/35468ded-d936-4bb1-b16b-0bcfe987e0f1)|![image](https://github.com/elastic/kibana/assets/12370520/1d1a65e0-78f5-4550-a620-9342b28ec5a9)| Related PR: [Add the query delay mechanism to the inventory rule type and change consumer to Observability](https://github.com/elastic/kibana/pull/170628) --- x-pack/plugins/infra/server/plugin.ts | 4 ++-- x-pack/plugins/observability/server/index.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/infra/server/plugin.ts b/x-pack/plugins/infra/server/plugin.ts index 6a2734b399199..afcbe69c01f5c 100644 --- a/x-pack/plugins/infra/server/plugin.ts +++ b/x-pack/plugins/infra/server/plugin.ts @@ -98,7 +98,7 @@ export const config: PluginConfigDescriptor = { }), inventoryThresholdAlertRuleEnabled: offeringBasedSchema({ traditional: schema.boolean({ defaultValue: true }), - serverless: schema.boolean({ defaultValue: false }), + serverless: schema.boolean({ defaultValue: true }), }), metricThresholdAlertRuleEnabled: offeringBasedSchema({ traditional: schema.boolean({ defaultValue: true }), @@ -110,7 +110,7 @@ export const config: PluginConfigDescriptor = { }), alertsAndRulesDropdownEnabled: offeringBasedSchema({ traditional: schema.boolean({ defaultValue: true }), - serverless: schema.boolean({ defaultValue: false }), + serverless: schema.boolean({ defaultValue: true }), }), }), }), diff --git a/x-pack/plugins/observability/server/index.ts b/x-pack/plugins/observability/server/index.ts index 96231115e3fa2..2153d00abdbc6 100644 --- a/x-pack/plugins/observability/server/index.ts +++ b/x-pack/plugins/observability/server/index.ts @@ -48,7 +48,7 @@ const configSchema = schema.object({ }), thresholdRule: schema.object({ enabled: offeringBasedSchema({ - serverless: schema.boolean({ defaultValue: false }), + serverless: schema.boolean({ defaultValue: true }), traditional: schema.boolean({ defaultValue: true }), }), }), From 41cacd1e0c5c3f480eaad84e9d914731c448bb02 Mon Sep 17 00:00:00 2001 From: Davis Plumlee <56367316+dplumlee@users.noreply.github.com> Date: Fri, 10 Nov 2023 12:01:34 -0500 Subject: [PATCH 05/12] [Security Solution] Fixes create/edit rule form subtechnique selection bug (#170465) --- .../components/rules/mitre/subtechnique_fields.tsx | 9 ++++----- .../components/rules/mitre/technique_fields.tsx | 10 +++++----- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/mitre/subtechnique_fields.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/mitre/subtechnique_fields.tsx index efc542f9757a1..7645bc2c2c579 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/mitre/subtechnique_fields.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/mitre/subtechnique_fields.tsx @@ -13,7 +13,6 @@ import { EuiFlexGroup, EuiFlexItem, } from '@elastic/eui'; -import { camelCase } from 'lodash/fp'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import styled from 'styled-components'; @@ -110,9 +109,9 @@ export const MitreAttackSubtechniqueFields: React.FC = ({ }, [field, onFieldChange, techniqueIndex, technique, threatIndex]); const updateSubtechnique = useCallback( - (index: number, value: string) => { + (index: number, optionId: string) => { const threats = [...(field.value as Threats)]; - const { id, reference, name } = subtechniquesOptions.find((t) => t.value === value) || { + const { id, reference, name } = subtechniquesOptions.find((t) => t.id === optionId) ?? { id: '', name: '', reference: '', @@ -170,7 +169,7 @@ export const MitreAttackSubtechniqueFields: React.FC = ({ : []), ...options.map((option) => ({ inputDisplay: <>{option.label}, - value: option.value, + value: option.id, disabled, })), ]} @@ -178,7 +177,7 @@ export const MitreAttackSubtechniqueFields: React.FC = ({ aria-label="" onChange={updateSubtechnique.bind(null, index)} fullWidth={true} - valueOfSelected={camelCase(subtechnique.name)} + valueOfSelected={subtechnique.id} data-test-subj="mitreAttackSubtechnique" disabled={disabled} placeholder={i18n.SUBTECHNIQUE_PLACEHOLDER} diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/mitre/technique_fields.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/mitre/technique_fields.tsx index 0215204a98cbd..bb677801ecaa9 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/mitre/technique_fields.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/mitre/technique_fields.tsx @@ -13,7 +13,7 @@ import { EuiFlexGroup, EuiFlexItem, } from '@elastic/eui'; -import { kebabCase, camelCase } from 'lodash/fp'; +import { kebabCase } from 'lodash/fp'; import React, { useCallback, useEffect, useState } from 'react'; import styled, { css } from 'styled-components'; @@ -105,9 +105,9 @@ export const MitreAttackTechniqueFields: React.FC = ({ }, [field, threatIndex, onFieldChange]); const updateTechnique = useCallback( - (index: number, value: string) => { + (index: number, optionId: string) => { const threats = [...(field.value as Threats)]; - const { id, reference, name } = techniquesOptions.find((t) => t.value === value) || { + const { id, reference, name } = techniquesOptions.find((t) => t.id === optionId) ?? { id: '', name: '', reference: '', @@ -153,7 +153,7 @@ export const MitreAttackTechniqueFields: React.FC = ({ : []), ...options.map((option) => ({ inputDisplay: <>{option.label}, - value: option.value, + value: option.id, disabled, })), ]} @@ -161,7 +161,7 @@ export const MitreAttackTechniqueFields: React.FC = ({ aria-label="" onChange={updateTechnique.bind(null, index)} fullWidth={true} - valueOfSelected={camelCase(technique.name)} + valueOfSelected={technique.id} data-test-subj="mitreAttackTechnique" disabled={disabled} placeholder={i18n.TECHNIQUE_PLACEHOLDER} From 33757f64cac68f3fab44522fe6fdb28001908bdd Mon Sep 17 00:00:00 2001 From: Alex Szabo Date: Fri, 10 Nov 2023 18:26:45 +0100 Subject: [PATCH 06/12] fix: incorrect import causes storybook build to fail (#171033) ## Summary [This change](https://github.com/darnautov/kibana/blob/b6f20b2219b5ccf22316ee36e0c079b9e3d1327c/x-pack/plugins/ml/public/application/components/collapsible_panel/panel_header_items.tsx#L9) in this PR: https://github.com/elastic/kibana/pull/167998 is causing an error in building storybooks: https://buildkite.com/elastic/kibana-on-merge/builds/38046#018bb9d2-7820-43ad-9144-e40d33d28c3b In brief, the import looks like this: ```typescript import { css } from '@emotion/react/dist/emotion-react.cjs'; ``` but it should be like this: ```typescript import { css } from '@emotion/react'; ``` It looks it's a bad import, we should set up a pre-merge check for these accidental auto-imports. cc: @darnautov please review --- .../components/collapsible_panel/panel_header_items.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/ml/public/application/components/collapsible_panel/panel_header_items.tsx b/x-pack/plugins/ml/public/application/components/collapsible_panel/panel_header_items.tsx index fa766abadfeb4..75d43e6ebe6f5 100644 --- a/x-pack/plugins/ml/public/application/components/collapsible_panel/panel_header_items.tsx +++ b/x-pack/plugins/ml/public/application/components/collapsible_panel/panel_header_items.tsx @@ -6,7 +6,7 @@ */ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { css } from '@emotion/react/dist/emotion-react.cjs'; +import { css } from '@emotion/react'; import React, { type FC } from 'react'; import { useCurrentThemeVars } from '../../contexts/kibana'; From 9b29b1898eeac2cef258b954e6711c1602d8854a Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Fri, 10 Nov 2023 18:57:04 +0100 Subject: [PATCH 07/12] [ML] AIOps Log Rate Analysis: Rename SignificantTerm to SignificantItem. (#169756) Log rate analysis now supports both keywords and log patterns derived from text fields. The type `SignificantTerm` originally was used for the results of the `significant_terms` agg to get the p-values for keyword fields. Since it's now used for both cases (keyword fields and log patterns) this PR renames the type and related variables etc. to `SignificantItem` (we used the wording `item` already in some cases in the context of groups). --- x-pack/packages/ml/agg_utils/index.ts | 18 ++-- .../ml/agg_utils/src/type_guards.test.ts | 14 +-- .../packages/ml/agg_utils/src/type_guards.ts | 8 +- x-pack/packages/ml/agg_utils/src/types.ts | 88 +++++++-------- ...ps.ts => final_significant_item_groups.ts} | 4 +- ...inal_significant_item_groups_textfield.ts} | 4 +- ...m_groups.ts => significant_item_groups.ts} | 4 +- .../significant_log_patterns.ts | 5 +- .../artificial_logs/significant_terms.ts | 5 +- ...m_groups.ts => significant_item_groups.ts} | 4 +- .../common/api/log_rate_analysis/actions.ts | 72 ++++++------- .../aiops/common/api/stream_reducer.test.ts | 12 +-- .../aiops/common/api/stream_reducer.ts | 6 +- x-pack/plugins/aiops/common/types.ts | 10 +- ...uild_extended_base_filter_criteria.test.ts | 12 +-- .../build_extended_base_filter_criteria.ts | 36 +++---- .../log_rate_analysis_content.tsx | 22 ++-- .../log_rate_analysis_page.tsx | 4 +- .../log_rate_analysis_results.tsx | 16 +-- .../get_group_table_items.test.ts | 4 +- .../get_group_table_items.ts | 6 +- .../get_table_item_as_kql.test.ts | 8 +- .../get_table_item_as_kql.ts | 6 +- .../log_rate_analysis_results_table.tsx | 100 +++++++++--------- ...log_rate_analysis_results_table_groups.tsx | 16 +-- ...te_analysis_results_table_row_provider.tsx | 56 +++++----- .../log_rate_analysis_results_table/types.ts | 8 +- .../use_copy_to_clipboard_action.test.tsx | 10 +- .../use_copy_to_clipboard_action.tsx | 12 +-- .../use_view_in_discover_action.tsx | 6 +- ...se_view_in_log_pattern_analysis_action.tsx | 10 +- .../mini_histogram/mini_histogram.tsx | 4 +- .../aiops/public/get_document_stats.ts | 14 +-- x-pack/plugins/aiops/public/hooks/use_data.ts | 22 ++-- .../public/hooks/use_document_count_stats.ts | 2 +- .../queries/duplicate_identifier.ts | 8 +- .../queries/fetch_frequent_item_sets.ts | 28 ++--- .../queries/fetch_significant_categories.ts | 6 +- .../fetch_significant_term_p_values.ts | 8 +- .../fetch_terms_2_categories_counts.ts | 6 +- .../get_field_value_pair_counts.test.ts | 4 +- .../queries/get_field_value_pair_counts.ts | 4 +- .../queries/get_group_filter.test.ts | 6 +- .../queries/get_group_filter.ts | 14 +-- ...get_groups_with_readded_duplicates.test.ts | 10 +- .../get_groups_with_readded_duplicates.ts | 12 +-- .../queries/get_marked_duplicates.test.ts | 10 +- .../queries/get_marked_duplicates.ts | 6 +- ... => get_missing_significant_items.test.ts} | 20 ++-- ...ms.ts => get_missing_significant_items.ts} | 12 +-- ...ts => get_significant_item_groups.test.ts} | 14 +-- ...oups.ts => get_significant_item_groups.ts} | 34 +++--- .../queries/get_simple_hierarchical_tree.ts | 24 ++--- .../get_simple_hierarchical_tree_leaves.ts | 6 +- ...ansform_significant_item_to_group.test.ts} | 26 ++--- ...=> transform_significant_item_to_group.ts} | 16 +-- .../route_handler_factory.ts | 62 +++++------ .../common/hooks/use_document_count_stats.ts | 10 +- .../components/log_rate_analysis.tsx | 2 +- .../translations/translations/fr-FR.json | 2 +- .../translations/translations/ja-JP.json | 2 +- .../translations/translations/zh-CN.json | 2 +- .../aiops/log_rate_analysis_full_analysis.ts | 4 +- .../aiops/log_rate_analysis_groups_only.ts | 4 +- .../api_integration/apis/aiops/test_data.ts | 22 ++-- .../test/api_integration/apis/aiops/types.ts | 6 +- 66 files changed, 512 insertions(+), 506 deletions(-) rename x-pack/plugins/aiops/common/__mocks__/artificial_logs/{final_significant_term_groups.ts => final_significant_item_groups.ts} (94%) rename x-pack/plugins/aiops/common/__mocks__/artificial_logs/{final_significant_term_groups_textfield.ts => final_significant_item_groups_textfield.ts} (95%) rename x-pack/plugins/aiops/common/__mocks__/artificial_logs/{significant_term_groups.ts => significant_item_groups.ts} (85%) rename x-pack/plugins/aiops/common/__mocks__/farequote/{significant_term_groups.ts => significant_item_groups.ts} (90%) rename x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/{get_missing_significant_terms.test.ts => get_missing_significant_items.test.ts} (75%) rename x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/{get_missing_significant_terms.ts => get_missing_significant_items.ts} (57%) rename x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/{get_significant_term_groups.test.ts => get_significant_item_groups.test.ts} (59%) rename x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/{get_significant_term_groups.ts => get_significant_item_groups.ts} (70%) rename x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/{transform_significant_term_to_group.test.ts => transform_significant_item_to_group.test.ts} (66%) rename x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/{transform_significant_term_to_group.ts => transform_significant_item_to_group.ts} (77%) diff --git a/x-pack/packages/ml/agg_utils/index.ts b/x-pack/packages/ml/agg_utils/index.ts index dd3b694d332c7..6aaf0ff099646 100644 --- a/x-pack/packages/ml/agg_utils/index.ts +++ b/x-pack/packages/ml/agg_utils/index.ts @@ -18,17 +18,17 @@ export type { NumericHistogramField, } from './src/fetch_histograms_for_fields'; export { isMultiBucketAggregate } from './src/is_multi_bucket_aggregate'; -export { isSignificantTerm } from './src/type_guards'; -export { SIGNIFICANT_TERM_TYPE } from './src/types'; +export { isSignificantItem } from './src/type_guards'; +export { SIGNIFICANT_ITEM_TYPE } from './src/types'; export type { AggCardinality, - SignificantTerm, - SignificantTermGroup, - SignificantTermGroupItem, - SignificantTermGroupHistogram, - SignificantTermHistogram, - SignificantTermHistogramItem, - SignificantTermType, + SignificantItem, + SignificantItemGroup, + SignificantItemGroupItem, + SignificantItemGroupHistogram, + SignificantItemHistogram, + SignificantItemHistogramItem, + SignificantItemType, HistogramField, NumericColumnStats, NumericColumnStatsMap, diff --git a/x-pack/packages/ml/agg_utils/src/type_guards.test.ts b/x-pack/packages/ml/agg_utils/src/type_guards.test.ts index 9cf472abcfb03..7a2c13a025d0b 100644 --- a/x-pack/packages/ml/agg_utils/src/type_guards.test.ts +++ b/x-pack/packages/ml/agg_utils/src/type_guards.test.ts @@ -5,15 +5,15 @@ * 2.0. */ -import { isSignificantTerm } from './type_guards'; +import { isSignificantItem } from './type_guards'; -describe('isSignificantTerm', () => { - it('identifies significant terms', () => { - expect(isSignificantTerm({})).toBeFalsy(); - expect(isSignificantTerm({ fieldName: 'response_code' })).toBeFalsy(); - expect(isSignificantTerm({ fieldValue: '500' })).toBeFalsy(); +describe('isSignificantItem', () => { + it('identifies significant items', () => { + expect(isSignificantItem({})).toBeFalsy(); + expect(isSignificantItem({ fieldName: 'response_code' })).toBeFalsy(); + expect(isSignificantItem({ fieldValue: '500' })).toBeFalsy(); expect( - isSignificantTerm({ + isSignificantItem({ key: 'response_code:500', type: 'keyword', fieldName: 'response_code', diff --git a/x-pack/packages/ml/agg_utils/src/type_guards.ts b/x-pack/packages/ml/agg_utils/src/type_guards.ts index 0e33052c8658b..7d8dbc69b265e 100644 --- a/x-pack/packages/ml/agg_utils/src/type_guards.ts +++ b/x-pack/packages/ml/agg_utils/src/type_guards.ts @@ -7,17 +7,17 @@ import { isPopulatedObject } from '@kbn/ml-is-populated-object'; -import type { SignificantTerm } from './types'; +import type { SignificantItem } from './types'; /** - * Type guard for a significant term. + * Type guard for a significant item. * Note this is used as a custom type within Log Rate Analysis * for a p-value based variant, not a generic significant terms * aggregation type. * @param arg The unknown type to be evaluated - * @returns whether arg is of type SignificantTerm + * @returns whether arg is of type SignificantItem */ -export function isSignificantTerm(arg: unknown): arg is SignificantTerm { +export function isSignificantItem(arg: unknown): arg is SignificantItem { return isPopulatedObject(arg, [ 'key', 'type', diff --git a/x-pack/packages/ml/agg_utils/src/types.ts b/x-pack/packages/ml/agg_utils/src/types.ts index d8e76239d0e09..1f441fce09a6c 100644 --- a/x-pack/packages/ml/agg_utils/src/types.ts +++ b/x-pack/packages/ml/agg_utils/src/types.ts @@ -88,25 +88,25 @@ export interface HistogramField { } /** - * Enumeration of significant term types. + * Enumeration of significant item types. */ -export const SIGNIFICANT_TERM_TYPE = { +export const SIGNIFICANT_ITEM_TYPE = { KEYWORD: 'keyword', LOG_PATTERN: 'log_pattern', } as const; /** - * Type for significant term type keys. + * Type for significant item type keys. */ -type SignificantTermTypeKeys = keyof typeof SIGNIFICANT_TERM_TYPE; +type SignificantItemTypeKeys = keyof typeof SIGNIFICANT_ITEM_TYPE; /** - * Represents the type of significant term as determined by the SIGNIFICANT_TERM_TYPE enumeration. + * Represents the type of significant item as determined by the SIGNIFICANT_ITEM_TYPE enumeration. */ -export type SignificantTermType = typeof SIGNIFICANT_TERM_TYPE[SignificantTermTypeKeys]; +export type SignificantItemType = typeof SIGNIFICANT_ITEM_TYPE[SignificantItemTypeKeys]; /** - * Represents significant term metadata for a field/value pair. + * Represents significant item metadata for a field/value pair. * This interface is used as a custom type within Log Rate Analysis * for a p-value based variant, not related to the generic * significant terms aggregation type. @@ -114,42 +114,42 @@ export type SignificantTermType = typeof SIGNIFICANT_TERM_TYPE[SignificantTermTy * @interface * @extends FieldValuePair */ -export interface SignificantTerm extends FieldValuePair { - /** The key associated with the significant term. */ +export interface SignificantItem extends FieldValuePair { + /** The key associated with the significant item. */ key: string; - /** The type of the significant term. */ - type: SignificantTermType; + /** The type of the significant item. */ + type: SignificantItemType; - /** The document count for the significant term. */ + /** The document count for the significant item. */ doc_count: number; - /** The background count for the significant term. */ + /** The background count for the significant item. */ bg_count: number; - /** The total document count for all terms. */ + /** The total document count for all items. */ total_doc_count: number; - /** The total background count for all terms. */ + /** The total background count for all items. */ total_bg_count: number; - /** The score associated with the significant term. */ + /** The score associated with the significant item. */ score: number; - /** The p-value for the significant term, or null if not available. */ + /** The p-value for the significant item, or null if not available. */ pValue: number | null; - /** The normalized score for the significant term. */ + /** The normalized score for the significant item. */ normalizedScore: number; - /** An optional histogram of significant term items. */ - histogram?: SignificantTermHistogramItem[]; + /** An optional histogram for the significant item. */ + histogram?: SignificantItemHistogramItem[]; - /** Indicates if the significant term is unique within a group. */ + /** Indicates if the significant item is unique within a group. */ unique?: boolean; } -interface SignificantTermHistogramItemBase { +interface SignificantItemHistogramItemBase { /** The document count for this item in the overall context. */ doc_count_overall: number; @@ -163,12 +163,12 @@ interface SignificantTermHistogramItemBase { /** * @deprecated since version 2 of internal log rate analysis REST API endpoint */ -interface SignificantTermHistogramItemV1 extends SignificantTermHistogramItemBase { +interface SignificantItemHistogramItemV1 extends SignificantItemHistogramItemBase { /** The document count for this item in the significant term context. */ doc_count_significant_term: number; } -interface SignificantTermHistogramItemV2 extends SignificantTermHistogramItemBase { +interface SignificantItemHistogramItemV2 extends SignificantItemHistogramItemBase { /** The document count for this histogram item in the significant item context. */ doc_count_significant_item: number; } @@ -176,41 +176,41 @@ interface SignificantTermHistogramItemV2 extends SignificantTermHistogramItemBas /** * Represents a data item in a significant term histogram. */ -export type SignificantTermHistogramItem = - | SignificantTermHistogramItemV1 - | SignificantTermHistogramItemV2; +export type SignificantItemHistogramItem = + | SignificantItemHistogramItemV1 + | SignificantItemHistogramItemV2; /** * Represents histogram data for a field/value pair. * @interface */ -export interface SignificantTermHistogram extends FieldValuePair { - /** An array of significant term histogram items. */ - histogram: SignificantTermHistogramItem[]; +export interface SignificantItemHistogram extends FieldValuePair { + /** An array of significant item histogram items. */ + histogram: SignificantItemHistogramItem[]; } /** * Represents histogram data for a group of field/value pairs. * @interface */ -export interface SignificantTermGroupHistogram { +export interface SignificantItemGroupHistogram { /** The identifier for the group. */ id: string; - /** An array of significant term histogram items. */ - histogram: SignificantTermHistogramItem[]; + /** An array of significant item histogram items. */ + histogram: SignificantItemHistogramItem[]; } /** - * Represents an item in a significant term group. + * Represents an item in a significant item group. * @interface */ -export interface SignificantTermGroupItem extends FieldValuePair { - /** The key associated with the significant term. */ +export interface SignificantItemGroupItem extends FieldValuePair { + /** The key associated with the significant item. */ key: string; - /** The type of the significant term. */ - type: SignificantTermType; + /** The type of the significant item. */ + type: SignificantItemType; /** The document count associated with this item. */ docCount: number; @@ -223,15 +223,15 @@ export interface SignificantTermGroupItem extends FieldValuePair { } /** - * Represents a significant term group. + * Represents a significant item group. * @interface */ -export interface SignificantTermGroup { +export interface SignificantItemGroup { /** The identifier for the item. */ id: string; - /** An array of significant term group items. */ - group: SignificantTermGroupItem[]; + /** An array of significant item group items. */ + group: SignificantItemGroupItem[]; /** The document count associated with this item. */ docCount: number; @@ -239,6 +239,6 @@ export interface SignificantTermGroup { /** The p-value for this item, or null if not available. */ pValue: number | null; - /** An optional array of significant term histogram items. */ - histogram?: SignificantTermHistogramItem[]; + /** An optional array of significant item histogram items. */ + histogram?: SignificantItemHistogramItem[]; } diff --git a/x-pack/plugins/aiops/common/__mocks__/artificial_logs/final_significant_term_groups.ts b/x-pack/plugins/aiops/common/__mocks__/artificial_logs/final_significant_item_groups.ts similarity index 94% rename from x-pack/plugins/aiops/common/__mocks__/artificial_logs/final_significant_term_groups.ts rename to x-pack/plugins/aiops/common/__mocks__/artificial_logs/final_significant_item_groups.ts index 7166e548449eb..8f28e49ca3d7a 100644 --- a/x-pack/plugins/aiops/common/__mocks__/artificial_logs/final_significant_term_groups.ts +++ b/x-pack/plugins/aiops/common/__mocks__/artificial_logs/final_significant_item_groups.ts @@ -5,9 +5,9 @@ * 2.0. */ -import type { SignificantTermGroup } from '@kbn/ml-agg-utils'; +import type { SignificantItemGroup } from '@kbn/ml-agg-utils'; -export const finalSignificantTermGroups: SignificantTermGroup[] = [ +export const finalSignificantItemGroups: SignificantItemGroup[] = [ { docCount: 632, group: [ diff --git a/x-pack/plugins/aiops/common/__mocks__/artificial_logs/final_significant_term_groups_textfield.ts b/x-pack/plugins/aiops/common/__mocks__/artificial_logs/final_significant_item_groups_textfield.ts similarity index 95% rename from x-pack/plugins/aiops/common/__mocks__/artificial_logs/final_significant_term_groups_textfield.ts rename to x-pack/plugins/aiops/common/__mocks__/artificial_logs/final_significant_item_groups_textfield.ts index f959d9408c418..617a42b930f45 100644 --- a/x-pack/plugins/aiops/common/__mocks__/artificial_logs/final_significant_term_groups_textfield.ts +++ b/x-pack/plugins/aiops/common/__mocks__/artificial_logs/final_significant_item_groups_textfield.ts @@ -5,9 +5,9 @@ * 2.0. */ -import type { SignificantTermGroup } from '@kbn/ml-agg-utils'; +import type { SignificantItemGroup } from '@kbn/ml-agg-utils'; -export const finalSignificantTermGroupsTextfield: SignificantTermGroup[] = [ +export const finalSignificantItemGroupsTextfield: SignificantItemGroup[] = [ { docCount: 636, group: [ diff --git a/x-pack/plugins/aiops/common/__mocks__/artificial_logs/significant_term_groups.ts b/x-pack/plugins/aiops/common/__mocks__/artificial_logs/significant_item_groups.ts similarity index 85% rename from x-pack/plugins/aiops/common/__mocks__/artificial_logs/significant_term_groups.ts rename to x-pack/plugins/aiops/common/__mocks__/artificial_logs/significant_item_groups.ts index 160ce3967cd43..b66738704b250 100644 --- a/x-pack/plugins/aiops/common/__mocks__/artificial_logs/significant_term_groups.ts +++ b/x-pack/plugins/aiops/common/__mocks__/artificial_logs/significant_item_groups.ts @@ -5,9 +5,9 @@ * 2.0. */ -import type { SignificantTermGroup } from '@kbn/ml-agg-utils'; +import type { SignificantItemGroup } from '@kbn/ml-agg-utils'; -export const significantTermGroups: SignificantTermGroup[] = [ +export const significantItemGroups: SignificantItemGroup[] = [ { id: '2038579476', group: [ diff --git a/x-pack/plugins/aiops/common/__mocks__/artificial_logs/significant_log_patterns.ts b/x-pack/plugins/aiops/common/__mocks__/artificial_logs/significant_log_patterns.ts index ab3ebe02dc536..9efef525ccd57 100644 --- a/x-pack/plugins/aiops/common/__mocks__/artificial_logs/significant_log_patterns.ts +++ b/x-pack/plugins/aiops/common/__mocks__/artificial_logs/significant_log_patterns.ts @@ -5,9 +5,10 @@ * 2.0. */ -import type { SignificantTerm } from '@kbn/ml-agg-utils'; +import type { SignificantItem } from '@kbn/ml-agg-utils'; -export const significantLogPatterns: SignificantTerm[] = [ +// Named significantLogPatterns since all these items are of type `log_pattern`. +export const significantLogPatterns: SignificantItem[] = [ { bg_count: 0, doc_count: 1266, diff --git a/x-pack/plugins/aiops/common/__mocks__/artificial_logs/significant_terms.ts b/x-pack/plugins/aiops/common/__mocks__/artificial_logs/significant_terms.ts index 4512f5943f4d0..538bf1bc65eb1 100644 --- a/x-pack/plugins/aiops/common/__mocks__/artificial_logs/significant_terms.ts +++ b/x-pack/plugins/aiops/common/__mocks__/artificial_logs/significant_terms.ts @@ -5,9 +5,10 @@ * 2.0. */ -import type { SignificantTerm } from '@kbn/ml-agg-utils'; +import type { SignificantItem } from '@kbn/ml-agg-utils'; -export const significantTerms: SignificantTerm[] = [ +// Named significantTerms since all these items are of type `keyword`. +export const significantTerms: SignificantItem[] = [ { key: 'user:Peter', type: 'keyword', diff --git a/x-pack/plugins/aiops/common/__mocks__/farequote/significant_term_groups.ts b/x-pack/plugins/aiops/common/__mocks__/farequote/significant_item_groups.ts similarity index 90% rename from x-pack/plugins/aiops/common/__mocks__/farequote/significant_term_groups.ts rename to x-pack/plugins/aiops/common/__mocks__/farequote/significant_item_groups.ts index 5058f0dbe7e98..95b359f31ea36 100644 --- a/x-pack/plugins/aiops/common/__mocks__/farequote/significant_term_groups.ts +++ b/x-pack/plugins/aiops/common/__mocks__/farequote/significant_item_groups.ts @@ -5,9 +5,9 @@ * 2.0. */ -import type { SignificantTermGroup } from '@kbn/ml-agg-utils'; +import type { SignificantItemGroup } from '@kbn/ml-agg-utils'; -export const significantTermGroups: SignificantTermGroup[] = [ +export const significantItemGroups: SignificantItemGroup[] = [ { id: 'group-1', group: [ diff --git a/x-pack/plugins/aiops/common/api/log_rate_analysis/actions.ts b/x-pack/plugins/aiops/common/api/log_rate_analysis/actions.ts index 4075ddffd27f1..bd3afd3152ae5 100644 --- a/x-pack/plugins/aiops/common/api/log_rate_analysis/actions.ts +++ b/x-pack/plugins/aiops/common/api/log_rate_analysis/actions.ts @@ -6,10 +6,10 @@ */ import type { - SignificantTerm, - SignificantTermHistogram, - SignificantTermGroup, - SignificantTermGroupHistogram, + SignificantItem, + SignificantItemHistogram, + SignificantItemGroup, + SignificantItemGroupHistogram, } from '@kbn/ml-agg-utils'; import type { AiopsLogRateAnalysisApiVersion as ApiVersion } from './schema'; @@ -40,108 +40,108 @@ export const API_ACTION_NAME = { } as const; export type ApiActionName = typeof API_ACTION_NAME[keyof typeof API_ACTION_NAME]; -interface ApiActionAddSignificantTerms { +interface ApiActionAddSignificantItems { type: T extends '1' ? typeof API_ACTION_NAME.ADD_SIGNIFICANT_TERMS : T extends '2' ? typeof API_ACTION_NAME.ADD_SIGNIFICANT_ITEMS : never; - payload: SignificantTerm[]; + payload: SignificantItem[]; } -export function addSignificantTermsAction( - payload: ApiActionAddSignificantTerms['payload'], +export function addSignificantItemsAction( + payload: ApiActionAddSignificantItems['payload'], version: T -): ApiActionAddSignificantTerms { +): ApiActionAddSignificantItems { if (version === '1') { return { type: API_ACTION_NAME.ADD_SIGNIFICANT_TERMS, payload, - } as ApiActionAddSignificantTerms; + } as ApiActionAddSignificantItems; } return { type: API_ACTION_NAME.ADD_SIGNIFICANT_ITEMS, payload, - } as ApiActionAddSignificantTerms; + } as ApiActionAddSignificantItems; } -interface ApiActionAddSignificantTermsHistogram { +interface ApiActionAddSignificantItemsHistogram { type: T extends '1' ? typeof API_ACTION_NAME.ADD_SIGNIFICANT_TERMS_HISTOGRAM : T extends '2' ? typeof API_ACTION_NAME.ADD_SIGNIFICANT_ITEMS_HISTOGRAM : never; - payload: SignificantTermHistogram[]; + payload: SignificantItemHistogram[]; } -export function addSignificantTermsHistogramAction( - payload: ApiActionAddSignificantTermsHistogram['payload'], +export function addSignificantItemsHistogramAction( + payload: ApiActionAddSignificantItemsHistogram['payload'], version: T -): ApiActionAddSignificantTermsHistogram { +): ApiActionAddSignificantItemsHistogram { if (version === '1') { return { type: API_ACTION_NAME.ADD_SIGNIFICANT_TERMS_HISTOGRAM, payload, - } as ApiActionAddSignificantTermsHistogram; + } as ApiActionAddSignificantItemsHistogram; } return { type: API_ACTION_NAME.ADD_SIGNIFICANT_ITEMS_HISTOGRAM, payload, - } as ApiActionAddSignificantTermsHistogram; + } as ApiActionAddSignificantItemsHistogram; } -interface ApiActionAddSignificantTermsGroup { +interface ApiActionAddSignificantItemsGroup { type: T extends '1' ? typeof API_ACTION_NAME.ADD_SIGNIFICANT_TERMS_GROUP : T extends '2' ? typeof API_ACTION_NAME.ADD_SIGNIFICANT_ITEMS_GROUP : never; - payload: SignificantTermGroup[]; + payload: SignificantItemGroup[]; } -export function addSignificantTermsGroupAction( - payload: ApiActionAddSignificantTermsGroup['payload'], +export function addSignificantItemsGroupAction( + payload: ApiActionAddSignificantItemsGroup['payload'], version: T -): ApiActionAddSignificantTermsGroup { +): ApiActionAddSignificantItemsGroup { if (version === '1') { return { type: API_ACTION_NAME.ADD_SIGNIFICANT_TERMS_GROUP, payload, - } as ApiActionAddSignificantTermsGroup; + } as ApiActionAddSignificantItemsGroup; } return { type: API_ACTION_NAME.ADD_SIGNIFICANT_ITEMS_GROUP, payload, - } as ApiActionAddSignificantTermsGroup; + } as ApiActionAddSignificantItemsGroup; } -interface ApiActionAddSignificantTermsGroupHistogram { +interface ApiActionAddSignificantItemsGroupHistogram { type: T extends '1' ? typeof API_ACTION_NAME.ADD_SIGNIFICANT_TERMS_GROUP_HISTOGRAM : T extends '2' ? typeof API_ACTION_NAME.ADD_SIGNIFICANT_ITEMS_GROUP_HISTOGRAM : never; - payload: SignificantTermGroupHistogram[]; + payload: SignificantItemGroupHistogram[]; } -export function addSignificantTermsGroupHistogramAction( - payload: ApiActionAddSignificantTermsGroupHistogram['payload'], +export function addSignificantItemsGroupHistogramAction( + payload: ApiActionAddSignificantItemsGroupHistogram['payload'], version: T -): ApiActionAddSignificantTermsGroupHistogram { +): ApiActionAddSignificantItemsGroupHistogram { if (version === '1') { return { type: API_ACTION_NAME.ADD_SIGNIFICANT_TERMS_GROUP_HISTOGRAM, payload, - } as ApiActionAddSignificantTermsGroupHistogram; + } as ApiActionAddSignificantItemsGroupHistogram; } return { type: API_ACTION_NAME.ADD_SIGNIFICANT_ITEMS_GROUP_HISTOGRAM, payload, - } as ApiActionAddSignificantTermsGroupHistogram; + } as ApiActionAddSignificantItemsGroupHistogram; } interface ApiActionAddError { @@ -211,10 +211,10 @@ export function updateLoadingStateAction( } export type AiopsLogRateAnalysisApiAction = - | ApiActionAddSignificantTerms - | ApiActionAddSignificantTermsGroup - | ApiActionAddSignificantTermsHistogram - | ApiActionAddSignificantTermsGroupHistogram + | ApiActionAddSignificantItems + | ApiActionAddSignificantItemsGroup + | ApiActionAddSignificantItemsHistogram + | ApiActionAddSignificantItemsGroupHistogram | ApiActionAddError | ApiActionPing | ApiActionResetAll diff --git a/x-pack/plugins/aiops/common/api/stream_reducer.test.ts b/x-pack/plugins/aiops/common/api/stream_reducer.test.ts index 7802d61e1781e..f3dd6cce856c7 100644 --- a/x-pack/plugins/aiops/common/api/stream_reducer.test.ts +++ b/x-pack/plugins/aiops/common/api/stream_reducer.test.ts @@ -6,11 +6,11 @@ */ import { significantTerms } from '../__mocks__/artificial_logs/significant_terms'; -import { finalSignificantTermGroups } from '../__mocks__/artificial_logs/final_significant_term_groups'; +import { finalSignificantItemGroups } from '../__mocks__/artificial_logs/final_significant_item_groups'; import { - addSignificantTermsAction, - addSignificantTermsGroupAction, + addSignificantItemsAction, + addSignificantItemsGroupAction, resetAllAction, resetGroupsAction, updateLoadingStateAction, @@ -37,7 +37,7 @@ describe('streamReducer', () => { it('adds significant item, then resets all state again', () => { const state1 = streamReducer( initialState, - addSignificantTermsAction( + addSignificantItemsAction( [ { key: 'the-field-name:the-field-value', @@ -65,14 +65,14 @@ describe('streamReducer', () => { }); it('adds significant items and groups, then resets groups only', () => { - const state1 = streamReducer(initialState, addSignificantTermsAction(significantTerms, '2')); + const state1 = streamReducer(initialState, addSignificantItemsAction(significantTerms, '2')); expect(state1.significantItems).toHaveLength(4); expect(state1.significantItemsGroups).toHaveLength(0); const state2 = streamReducer( state1, - addSignificantTermsGroupAction(finalSignificantTermGroups, '2') + addSignificantItemsGroupAction(finalSignificantItemGroups, '2') ); expect(state2.significantItems).toHaveLength(4); diff --git a/x-pack/plugins/aiops/common/api/stream_reducer.ts b/x-pack/plugins/aiops/common/api/stream_reducer.ts index ba9e8014665c8..05d3fce52c22e 100644 --- a/x-pack/plugins/aiops/common/api/stream_reducer.ts +++ b/x-pack/plugins/aiops/common/api/stream_reducer.ts @@ -5,14 +5,14 @@ * 2.0. */ -import type { SignificantTerm, SignificantTermGroup } from '@kbn/ml-agg-utils'; +import type { SignificantItem, SignificantItemGroup } from '@kbn/ml-agg-utils'; import { API_ACTION_NAME, AiopsLogRateAnalysisApiAction } from './log_rate_analysis/actions'; interface StreamState { ccsWarning: boolean; - significantItems: SignificantTerm[]; - significantItemsGroups: SignificantTermGroup[]; + significantItems: SignificantItem[]; + significantItemsGroups: SignificantItemGroup[]; errors: string[]; loaded: number; loadingState: string; diff --git a/x-pack/plugins/aiops/common/types.ts b/x-pack/plugins/aiops/common/types.ts index 4b26e30c76a72..67fca0b473549 100644 --- a/x-pack/plugins/aiops/common/types.ts +++ b/x-pack/plugins/aiops/common/types.ts @@ -5,11 +5,11 @@ * 2.0. */ -import type { SignificantTerm, SignificantTermType, FieldValuePair } from '@kbn/ml-agg-utils'; +import type { SignificantItem, SignificantItemType, FieldValuePair } from '@kbn/ml-agg-utils'; -export interface SignificantTermDuplicateGroup { - keys: Pick; - group: SignificantTerm[]; +export interface SignificantItemDuplicateGroup { + keys: Pick; + group: SignificantItem[]; } export type FieldValuePairCounts = Record>; @@ -31,7 +31,7 @@ export interface FetchFrequentItemSetsResponse { interface SimpleHierarchicalTreeNodeSet extends FieldValuePair { key: string; - type: SignificantTermType; + type: SignificantItemType; docCount: number; pValue: number | null; } diff --git a/x-pack/plugins/aiops/public/application/utils/build_extended_base_filter_criteria.test.ts b/x-pack/plugins/aiops/public/application/utils/build_extended_base_filter_criteria.test.ts index 797f6f1e36a00..37a984dae490d 100644 --- a/x-pack/plugins/aiops/public/application/utils/build_extended_base_filter_criteria.test.ts +++ b/x-pack/plugins/aiops/public/application/utils/build_extended_base_filter_criteria.test.ts @@ -5,13 +5,13 @@ * 2.0. */ -import type { SignificantTerm } from '@kbn/ml-agg-utils'; +import type { SignificantItem } from '@kbn/ml-agg-utils'; import type { GroupTableItem } from '../../components/log_rate_analysis_results_table/types'; import { buildExtendedBaseFilterCriteria } from './build_extended_base_filter_criteria'; -const selectedSignificantTermMock: SignificantTerm = { +const selectedSignificantItemMock: SignificantItem = { key: 'meta.cloud.instance_id.keyword:1234', type: 'keyword', doc_count: 53408, @@ -123,13 +123,13 @@ describe('query_utils', () => { ]); }); - it('includes a term filter when including a selectedSignificantTerm', () => { + it('includes a term filter when including a selectedSignificantItem', () => { const baseFilterCriteria = buildExtendedBaseFilterCriteria( '@timestamp', 1640082000012, 1640103600906, { match_all: {} }, - selectedSignificantTermMock + selectedSignificantItemMock ); expect(baseFilterCriteria).toEqual([ @@ -147,13 +147,13 @@ describe('query_utils', () => { ]); }); - it('includes a term filter with must_not when excluding a selectedSignificantTerm', () => { + it('includes a term filter with must_not when excluding a selectedSignificantItem', () => { const baseFilterCriteria = buildExtendedBaseFilterCriteria( '@timestamp', 1640082000012, 1640103600906, { match_all: {} }, - selectedSignificantTermMock, + selectedSignificantItemMock, false ); diff --git a/x-pack/plugins/aiops/public/application/utils/build_extended_base_filter_criteria.ts b/x-pack/plugins/aiops/public/application/utils/build_extended_base_filter_criteria.ts index ee4e707304ff4..d01951b6b8655 100644 --- a/x-pack/plugins/aiops/public/application/utils/build_extended_base_filter_criteria.ts +++ b/x-pack/plugins/aiops/public/application/utils/build_extended_base_filter_criteria.ts @@ -11,7 +11,7 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { Query } from '@kbn/es-query'; -import { type SignificantTerm, SIGNIFICANT_TERM_TYPE } from '@kbn/ml-agg-utils'; +import { type SignificantItem, SIGNIFICANT_ITEM_TYPE } from '@kbn/ml-agg-utils'; import { buildBaseFilterCriteria } from '@kbn/ml-query-utils'; @@ -30,8 +30,8 @@ export function buildExtendedBaseFilterCriteria( earliestMs?: number, latestMs?: number, query?: Query['query'], - selectedSignificantTerm?: SignificantTerm, - includeSelectedSignificantTerm = true, + selectedSignificantItem?: SignificantItem, + includeSelectedSignificantItem = true, selectedGroup?: GroupTableItem | null ): estypes.QueryDslQueryContainer[] { const filterCriteria = buildBaseFilterCriteria(timeFieldName, earliestMs, latestMs, query); @@ -41,7 +41,7 @@ export function buildExtendedBaseFilterCriteria( const allItems = selectedGroup.groupItemsSortedByUniqueness; for (const item of allItems) { const { fieldName, fieldValue, key, type, docCount } = item; - if (type === SIGNIFICANT_TERM_TYPE.KEYWORD) { + if (type === SIGNIFICANT_ITEM_TYPE.KEYWORD) { groupFilter.push({ term: { [fieldName]: fieldValue } }); } else { groupFilter.push( @@ -57,18 +57,18 @@ export function buildExtendedBaseFilterCriteria( } } - if (includeSelectedSignificantTerm) { - if (selectedSignificantTerm) { - if (selectedSignificantTerm.type === 'keyword') { + if (includeSelectedSignificantItem) { + if (selectedSignificantItem) { + if (selectedSignificantItem.type === 'keyword') { filterCriteria.push({ - term: { [selectedSignificantTerm.fieldName]: selectedSignificantTerm.fieldValue }, + term: { [selectedSignificantItem.fieldName]: selectedSignificantItem.fieldValue }, }); } else { filterCriteria.push( - getCategoryQuery(selectedSignificantTerm.fieldName, [ + getCategoryQuery(selectedSignificantItem.fieldName, [ { - key: `${selectedSignificantTerm.key}`, - count: selectedSignificantTerm.doc_count, + key: `${selectedSignificantItem.key}`, + count: selectedSignificantItem.doc_count, examples: [], }, ]) @@ -77,13 +77,13 @@ export function buildExtendedBaseFilterCriteria( } else if (selectedGroup) { filterCriteria.push(...groupFilter); } - } else if (selectedSignificantTerm && !includeSelectedSignificantTerm) { - if (selectedSignificantTerm.type === 'keyword') { + } else if (selectedSignificantItem && !includeSelectedSignificantItem) { + if (selectedSignificantItem.type === 'keyword') { filterCriteria.push({ bool: { must_not: [ { - term: { [selectedSignificantTerm.fieldName]: selectedSignificantTerm.fieldValue }, + term: { [selectedSignificantItem.fieldName]: selectedSignificantItem.fieldValue }, }, ], }, @@ -92,10 +92,10 @@ export function buildExtendedBaseFilterCriteria( filterCriteria.push({ bool: { must_not: [ - getCategoryQuery(selectedSignificantTerm.fieldName, [ + getCategoryQuery(selectedSignificantItem.fieldName, [ { - key: `${selectedSignificantTerm.key}`, - count: selectedSignificantTerm.doc_count, + key: `${selectedSignificantItem.key}`, + count: selectedSignificantItem.doc_count, examples: [], }, ]), @@ -103,7 +103,7 @@ export function buildExtendedBaseFilterCriteria( }, }); } - } else if (selectedGroup && !includeSelectedSignificantTerm) { + } else if (selectedGroup && !includeSelectedSignificantItem) { filterCriteria.push({ bool: { must_not: [ diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_content.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_content.tsx index 5e2af3d4cb917..0770cfea83771 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_content.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_content.tsx @@ -20,7 +20,7 @@ import { type LogRateAnalysisType, type WindowParameters, } from '@kbn/aiops-utils'; -import type { SignificantTerm } from '@kbn/ml-agg-utils'; +import type { SignificantItem } from '@kbn/ml-agg-utils'; import { useData } from '../../../hooks/use_data'; @@ -35,11 +35,11 @@ import { useLogRateAnalysisResultsTableRowContext } from '../../log_rate_analysi const DEFAULT_SEARCH_QUERY = { match_all: {} }; export function getDocumentCountStatsSplitLabel( - significantTerm?: SignificantTerm, + significantItem?: SignificantItem, group?: GroupTableItem ) { - if (significantTerm) { - return `${significantTerm?.fieldName}:${significantTerm?.fieldValue}`; + if (significantItem) { + return `${significantItem?.fieldName}:${significantItem?.fieldValue}`; } else if (group) { return i18n.translate('xpack.aiops.logRateAnalysis.page.documentCountStatsSplitGroupLabel', { defaultMessage: 'Selected group', @@ -94,11 +94,11 @@ export const LogRateAnalysisContent: FC = ({ }, [windowParameters]); const { - currentSelectedSignificantTerm, + currentSelectedSignificantItem, currentSelectedGroup, - setPinnedSignificantTerm, + setPinnedSignificantItem, setPinnedGroup, - setSelectedSignificantTerm, + setSelectedSignificantItem, setSelectedGroup, } = useLogRateAnalysisResultsTableRowContext(); @@ -107,7 +107,7 @@ export const LogRateAnalysisContent: FC = ({ 'log_rate_analysis', esSearchQuery, setGlobalState, - currentSelectedSignificantTerm, + currentSelectedSignificantItem, currentSelectedGroup, undefined, timeRange @@ -132,9 +132,9 @@ export const LogRateAnalysisContent: FC = ({ function clearSelection() { setWindowParameters(undefined); - setPinnedSignificantTerm(null); + setPinnedSignificantItem(null); setPinnedGroup(null); - setSelectedSignificantTerm(null); + setSelectedSignificantItem(null); setSelectedGroup(null); setIsBrushCleared(true); setInitialAnalysisStart(undefined); @@ -148,7 +148,7 @@ export const LogRateAnalysisContent: FC = ({ documentCountStats={documentCountStats} documentCountStatsSplit={documentCountStatsCompare} documentCountStatsSplitLabel={getDocumentCountStatsSplitLabel( - currentSelectedSignificantTerm, + currentSelectedSignificantItem, currentSelectedGroup )} isBrushCleared={isBrushCleared} diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_page.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_page.tsx index c3d53fb999023..b3c9f256fb2ea 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_page.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_page.tsx @@ -37,7 +37,7 @@ export const LogRateAnalysisPage: FC = ({ stickyHistogram }) => { const { data: dataService } = useAiopsAppContext(); const { dataView, savedSearch } = useDataSource(); - const { currentSelectedSignificantTerm, currentSelectedGroup } = + const { currentSelectedSignificantItem, currentSelectedGroup } = useLogRateAnalysisResultsTableRowContext(); const [aiopsListState, setAiopsListState] = usePageUrlState( @@ -88,7 +88,7 @@ export const LogRateAnalysisPage: FC = ({ stickyHistogram }) => { 'log_rate_analysis', searchQuery, setGlobalState, - currentSelectedSignificantTerm, + currentSelectedSignificantItem, currentSelectedGroup ); diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_results.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_results.tsx index 3a236085cd6cf..457419c8b0501 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_results.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_results.tsx @@ -31,7 +31,7 @@ import { } from '@kbn/aiops-utils'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import type { SignificantTerm, SignificantTermGroup } from '@kbn/ml-agg-utils'; +import type { SignificantItem, SignificantItemGroup } from '@kbn/ml-agg-utils'; import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; import { initialState, streamReducer } from '../../../common/api/stream_reducer'; @@ -81,9 +81,9 @@ export interface LogRateAnalysisResultsData { /** The type of analysis, whether it's a spike or dip */ analysisType: LogRateAnalysisType; /** Statistically significant field/value items. */ - significantTerms: SignificantTerm[]; + significantItems: SignificantItem[]; /** Statistically significant groups of field/value items. */ - significantTermsGroups: SignificantTermGroup[]; + significantItemsGroups: SignificantItemGroup[]; } /** @@ -234,8 +234,8 @@ export const LogRateAnalysisResults: FC = ({ if (onAnalysisCompleted) { onAnalysisCompleted({ analysisType, - significantTerms: data.significantItems, - significantTermsGroups: data.significantItemsGroups, + significantItems: data.significantItems, + significantItemsGroups: data.significantItemsGroups, }); } } @@ -246,7 +246,7 @@ export const LogRateAnalysisResults: FC = ({ const errors = useMemo(() => [...streamErrors, ...data.errors], [streamErrors, data.errors]); // Start handler clears possibly hovered or pinned - // significant terms on analysis refresh. + // significant items on analysis refresh. function startHandler(continueAnalysis = false, resetGroupButton = true) { if (!continueAnalysis) { setOverrides(undefined); @@ -482,7 +482,7 @@ export const LogRateAnalysisResults: FC = ({ > {showLogRateAnalysisResultsTable && groupResults ? ( = ({ ) : null} {showLogRateAnalysisResultsTable && !groupResults ? ( { it('transforms groups into table items', () => { - const groupTableItems = getGroupTableItems(finalSignificantTermGroups); + const groupTableItems = getGroupTableItems(finalSignificantItemGroups); expect(groupTableItems).toEqual([ { diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/get_group_table_items.ts b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/get_group_table_items.ts index 11331037de481..6767b82444946 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/get_group_table_items.ts +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/get_group_table_items.ts @@ -7,14 +7,14 @@ import { sortBy } from 'lodash'; -import type { SignificantTermGroup } from '@kbn/ml-agg-utils'; +import type { SignificantItemGroup } from '@kbn/ml-agg-utils'; import type { GroupTableItem, GroupTableItemGroup } from './types'; export function getGroupTableItems( - significantTermsGroups: SignificantTermGroup[] + significantItemsGroups: SignificantItemGroup[] ): GroupTableItem[] { - const tableItems = significantTermsGroups.map(({ id, group, docCount, histogram, pValue }) => { + const tableItems = significantItemsGroups.map(({ id, group, docCount, histogram, pValue }) => { const sortedGroup = sortBy(group, [(d) => d.fieldName]); const dedupedGroup: GroupTableItemGroup[] = []; diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/get_table_item_as_kql.test.ts b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/get_table_item_as_kql.test.ts index 99b5e9830b08a..64f4fde55a660 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/get_table_item_as_kql.test.ts +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/get_table_item_as_kql.test.ts @@ -5,21 +5,21 @@ * 2.0. */ -import { finalSignificantTermGroups } from '../../../common/__mocks__/artificial_logs/final_significant_term_groups'; +import { finalSignificantItemGroups } from '../../../common/__mocks__/artificial_logs/final_significant_item_groups'; import { significantTerms } from '../../../common/__mocks__/artificial_logs/significant_terms'; import { getGroupTableItems } from './get_group_table_items'; import { getTableItemAsKQL } from './get_table_item_as_kql'; describe('getTableItemAsKQL', () => { - it('returns a KQL syntax for a significant term', () => { + it('returns a KQL syntax for a significant item', () => { expect(getTableItemAsKQL(significantTerms[0])).toBe('user:Peter'); expect(getTableItemAsKQL(significantTerms[1])).toBe('response_code:500'); expect(getTableItemAsKQL(significantTerms[2])).toBe('url:home.php'); expect(getTableItemAsKQL(significantTerms[3])).toBe('url:login.php'); }); - it('returns a KQL syntax for a group of significant terms', () => { - const groupTableItems = getGroupTableItems(finalSignificantTermGroups); + it('returns a KQL syntax for a group of significant items', () => { + const groupTableItems = getGroupTableItems(finalSignificantItemGroups); expect(getTableItemAsKQL(groupTableItems[0])).toBe('user:Peter AND url:login.php'); expect(getTableItemAsKQL(groupTableItems[1])).toBe('response_code:500 AND url:home.php'); expect(getTableItemAsKQL(groupTableItems[2])).toBe('url:login.php AND response_code:500'); diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/get_table_item_as_kql.ts b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/get_table_item_as_kql.ts index fb13e3d92173c..4374da0cec276 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/get_table_item_as_kql.ts +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/get_table_item_as_kql.ts @@ -6,12 +6,12 @@ */ import { escapeKuery } from '@kbn/es-query'; -import { isSignificantTerm, type SignificantTerm } from '@kbn/ml-agg-utils'; +import { isSignificantItem, type SignificantItem } from '@kbn/ml-agg-utils'; import type { GroupTableItem } from './types'; -export const getTableItemAsKQL = (tableItem: GroupTableItem | SignificantTerm) => { - if (isSignificantTerm(tableItem)) { +export const getTableItemAsKQL = (tableItem: GroupTableItem | SignificantItem) => { + if (isSignificantItem(tableItem)) { return `${escapeKuery(tableItem.fieldName)}:${escapeKuery(String(tableItem.fieldValue))}`; } diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table.tsx index 95f4460c7f918..d8845145f1ace 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table.tsx @@ -27,7 +27,7 @@ import type { FieldStatsServices } from '@kbn/unified-field-list/src/components/ import type { DataView } from '@kbn/data-views-plugin/public'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { type SignificantTerm, SIGNIFICANT_TERM_TYPE } from '@kbn/ml-agg-utils'; +import { type SignificantItem, SIGNIFICANT_ITEM_TYPE } from '@kbn/ml-agg-utils'; import type { TimeRange as TimeRangeMs } from '@kbn/ml-date-picker'; import { getCategoryQuery } from '../../../common/api/log_categorization/get_category_query'; @@ -56,7 +56,7 @@ const DEFAULT_SORT_DIRECTION = 'asc'; const TRUNCATE_TEXT_LINES = 3; interface LogRateAnalysisResultsTableProps { - significantTerms: SignificantTerm[]; + significantItems: SignificantItem[]; dataView: DataView; loading: boolean; isExpandedRow?: boolean; @@ -69,7 +69,7 @@ interface LogRateAnalysisResultsTableProps { } export const LogRateAnalysisResultsTable: FC = ({ - significantTerms, + significantItems, dataView, loading, isExpandedRow, @@ -84,16 +84,16 @@ export const LogRateAnalysisResultsTable: FC = const { pinnedGroup, - pinnedSignificantTerm, + pinnedSignificantItem, selectedGroup, - selectedSignificantTerm, - setPinnedSignificantTerm, - setSelectedSignificantTerm, + selectedSignificantItem, + setPinnedSignificantItem, + setSelectedSignificantItem, } = useLogRateAnalysisResultsTableRowContext(); const [pageIndex, setPageIndex] = useState(0); const [pageSize, setPageSize] = useState(10); - const [sortField, setSortField] = useState(DEFAULT_SORT_FIELD); + const [sortField, setSortField] = useState(DEFAULT_SORT_FIELD); const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>(DEFAULT_SORT_DIRECTION); const { data, uiSettings, fieldFormats, charts } = useAiopsAppContext(); @@ -112,7 +112,7 @@ export const LogRateAnalysisResultsTable: FC = const viewInDiscoverAction = useViewInDiscoverAction(dataViewId); const viewInLogPatternAnalysisAction = useViewInLogPatternAnalysisAction(dataViewId); - const columns: Array> = [ + const columns: Array> = [ { 'data-test-subj': 'aiopsLogRateAnalysisResultsTableColumnFieldName', field: 'fieldName', @@ -121,7 +121,7 @@ export const LogRateAnalysisResultsTable: FC = }), render: (_, { fieldName, fieldValue, key, type, doc_count: count }) => { const dslQuery = - type === SIGNIFICANT_TERM_TYPE.KEYWORD + type === SIGNIFICANT_ITEM_TYPE.KEYWORD ? searchQuery : getCategoryQuery(fieldName, [ { @@ -132,17 +132,17 @@ export const LogRateAnalysisResultsTable: FC = ]); return ( <> - {type === SIGNIFICANT_TERM_TYPE.KEYWORD && ( + {type === SIGNIFICANT_ITEM_TYPE.KEYWORD && ( )} - {type === SIGNIFICANT_TERM_TYPE.LOG_PATTERN && ( + {type === SIGNIFICANT_ITEM_TYPE.LOG_PATTERN && ( = const { pagination, pageOfItems, sorting } = useMemo(() => { const pageStart = pageIndex * pageSize; - const itemCount = significantTerms?.length ?? 0; + const itemCount = significantItems?.length ?? 0; - let items: SignificantTerm[] = significantTerms ?? []; + let items: SignificantItem[] = significantItems ?? []; const sortIteratees = [ - (item: SignificantTerm) => { + (item: SignificantItem) => { if (item && typeof item[sortField] === 'string') { // @ts-ignore Object is possibly null or undefined return item[sortField].toLowerCase(); @@ -368,11 +368,11 @@ export const LogRateAnalysisResultsTable: FC = // Only if the table is sorted by p-value, add a secondary sort by doc count. if (sortField === 'pValue') { - sortIteratees.push((item: SignificantTerm) => item.doc_count); + sortIteratees.push((item: SignificantItem) => item.doc_count); sortDirections.push(sortDirection); } - items = orderBy(significantTerms, sortIteratees, sortDirections); + items = orderBy(significantItems, sortIteratees, sortDirections); return { pageOfItems: items.slice(pageStart, pageStart + pageSize), @@ -389,59 +389,59 @@ export const LogRateAnalysisResultsTable: FC = }, }, }; - }, [pageIndex, pageSize, sortField, sortDirection, significantTerms]); + }, [pageIndex, pageSize, sortField, sortDirection, significantItems]); useEffect(() => { // If no row is hovered or pinned or the user switched to a new page, // fall back to set the first row into a hovered state to make the // main document count chart show a comparison view by default. if ( - (selectedSignificantTerm === null || - !pageOfItems.some((item) => isEqual(item, selectedSignificantTerm))) && - pinnedSignificantTerm === null && + (selectedSignificantItem === null || + !pageOfItems.some((item) => isEqual(item, selectedSignificantItem))) && + pinnedSignificantItem === null && pageOfItems.length > 0 && selectedGroup === null && pinnedGroup === null ) { - setSelectedSignificantTerm(pageOfItems[0]); + setSelectedSignificantItem(pageOfItems[0]); } // If a user switched pages and a pinned row is no longer visible // on the current page, set the status of pinned rows back to `null`. if ( - pinnedSignificantTerm !== null && - !pageOfItems.some((item) => isEqual(item, pinnedSignificantTerm)) && + pinnedSignificantItem !== null && + !pageOfItems.some((item) => isEqual(item, pinnedSignificantItem)) && selectedGroup === null && pinnedGroup === null ) { - setPinnedSignificantTerm(null); + setPinnedSignificantItem(null); } }, [ selectedGroup, - selectedSignificantTerm, - setSelectedSignificantTerm, - setPinnedSignificantTerm, + selectedSignificantItem, + setSelectedSignificantItem, + setPinnedSignificantItem, pageOfItems, pinnedGroup, - pinnedSignificantTerm, + pinnedSignificantItem, ]); // When the analysis results table unmounts, // make sure to reset any hovered or pinned rows. useEffect( () => () => { - setSelectedSignificantTerm(null); - setPinnedSignificantTerm(null); + setSelectedSignificantItem(null); + setPinnedSignificantItem(null); }, // eslint-disable-next-line react-hooks/exhaustive-deps [] ); - const getRowStyle = (significantTerm: SignificantTerm) => { + const getRowStyle = (significantItem: SignificantItem) => { if ( - pinnedSignificantTerm && - pinnedSignificantTerm.fieldName === significantTerm.fieldName && - pinnedSignificantTerm.fieldValue === significantTerm.fieldValue + pinnedSignificantItem && + pinnedSignificantItem.fieldName === significantItem.fieldName && + pinnedSignificantItem.fieldValue === significantItem.fieldValue ) { return { backgroundColor: primaryBackgroundColor, @@ -449,9 +449,9 @@ export const LogRateAnalysisResultsTable: FC = } if ( - selectedSignificantTerm && - selectedSignificantTerm.fieldName === significantTerm.fieldName && - selectedSignificantTerm.fieldValue === significantTerm.fieldValue + selectedSignificantItem && + selectedSignificantItem.fieldName === significantItem.fieldName && + selectedSignificantItem.fieldValue === significantItem.fieldValue ) { return { backgroundColor: euiTheme.euiColorLightestShade, @@ -479,29 +479,29 @@ export const LogRateAnalysisResultsTable: FC = onChange={onChange} pagination={pagination.totalItemCount > pagination.pageSize ? pagination : undefined} loading={false} - sorting={sorting as EuiTableSortingType} - rowProps={(significantTerm) => { + sorting={sorting as EuiTableSortingType} + rowProps={(significantItem) => { return { - 'data-test-subj': `aiopsLogRateAnalysisResultsTableRow row-${significantTerm.fieldName}-${significantTerm.fieldValue}`, + 'data-test-subj': `aiopsLogRateAnalysisResultsTableRow row-${significantItem.fieldName}-${significantItem.fieldValue}`, onClick: () => { if ( - significantTerm.fieldName === pinnedSignificantTerm?.fieldName && - significantTerm.fieldValue === pinnedSignificantTerm?.fieldValue + significantItem.fieldName === pinnedSignificantItem?.fieldName && + significantItem.fieldValue === pinnedSignificantItem?.fieldValue ) { - setPinnedSignificantTerm(null); + setPinnedSignificantItem(null); } else { - setPinnedSignificantTerm(significantTerm); + setPinnedSignificantItem(significantItem); } }, onMouseEnter: () => { - if (pinnedSignificantTerm === null) { - setSelectedSignificantTerm(significantTerm); + if (pinnedSignificantItem === null) { + setSelectedSignificantItem(significantItem); } }, onMouseLeave: () => { - setSelectedSignificantTerm(null); + setSelectedSignificantItem(null); }, - style: getRowStyle(significantTerm), + style: getRowStyle(significantItem), }; }} /> diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table_groups.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table_groups.tsx index 9f45c79b0746d..f6961e49c2c78 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table_groups.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table_groups.tsx @@ -28,7 +28,7 @@ import { import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import type { SignificantTerm } from '@kbn/ml-agg-utils'; +import type { SignificantItem } from '@kbn/ml-agg-utils'; import type { TimeRange as TimeRangeMs } from '@kbn/ml-date-picker'; import type { DataView } from '@kbn/data-views-plugin/public'; @@ -53,7 +53,7 @@ const DEFAULT_SORT_FIELD = 'pValue'; const DEFAULT_SORT_DIRECTION = 'asc'; interface LogRateAnalysisResultsTableProps { - significantTerms: SignificantTerm[]; + significantItems: SignificantItem[]; groupTableItems: GroupTableItem[]; loading: boolean; searchQuery: estypes.QueryDslQueryContainer; @@ -66,7 +66,7 @@ interface LogRateAnalysisResultsTableProps { } export const LogRateAnalysisResultsGroupsTable: FC = ({ - significantTerms, + significantItems, groupTableItems, loading, dataView, @@ -98,9 +98,9 @@ export const LogRateAnalysisResultsGroupsTable: FC( + significantItems={item.groupItemsSortedByUniqueness.reduce( (p, groupItem) => { - const st = significantTerms.find( + const st = significantItems.find( (d) => d.fieldName === groupItem.fieldName && d.fieldValue === groupItem.fieldValue ); @@ -139,7 +139,11 @@ export const LogRateAnalysisResultsGroupsTable: FC - Expand rows + + {i18n.translate('xpack.aiops.logRateAnalysis.resultsTable.expandRowsLabel', { + defaultMessage: 'Expand rows', + })} + ), render: (item: GroupTableItem) => ( diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table_row_provider.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table_row_provider.tsx index f4d6c77c9756f..a7b3398e4e7cc 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table_row_provider.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table_row_provider.tsx @@ -15,23 +15,23 @@ import React, { type SetStateAction, } from 'react'; -import type { SignificantTerm } from '@kbn/ml-agg-utils'; +import type { SignificantItem } from '@kbn/ml-agg-utils'; import type { GroupTableItem } from './types'; -type SignificantTermOrNull = SignificantTerm | null; +type SignificantItemOrNull = SignificantItem | null; type GroupOrNull = GroupTableItem | null; interface LogRateAnalysisResultsTableRow { - pinnedSignificantTerm: SignificantTermOrNull; - setPinnedSignificantTerm: Dispatch>; + pinnedSignificantItem: SignificantItemOrNull; + setPinnedSignificantItem: Dispatch>; pinnedGroup: GroupOrNull; setPinnedGroup: Dispatch>; - selectedSignificantTerm: SignificantTermOrNull; - setSelectedSignificantTerm: Dispatch>; + selectedSignificantItem: SignificantItemOrNull; + setSelectedSignificantItem: Dispatch>; selectedGroup: GroupOrNull; setSelectedGroup: Dispatch>; - currentSelectedSignificantTerm?: SignificantTerm; + currentSelectedSignificantItem?: SignificantItem; currentSelectedGroup?: GroupTableItem; clearAllRowState: () => void; } @@ -42,20 +42,20 @@ export const logRateAnalysisResultsTableRowContext = createContext< export const LogRateAnalysisResultsTableRowStateProvider: FC = ({ children }) => { // State that will be shared with all components - const [pinnedSignificantTerm, setPinnedSignificantTerm] = useState(null); + const [pinnedSignificantItem, setPinnedSignificantItem] = useState(null); const [pinnedGroup, setPinnedGroup] = useState(null); - const [selectedSignificantTerm, setSelectedSignificantTerm] = - useState(null); + const [selectedSignificantItem, setSelectedSignificantItem] = + useState(null); const [selectedGroup, setSelectedGroup] = useState(null); // If a row is pinned, still overrule with a potentially hovered row. - const currentSelectedSignificantTerm = useMemo(() => { - if (selectedSignificantTerm) { - return selectedSignificantTerm; - } else if (pinnedSignificantTerm) { - return pinnedSignificantTerm; + const currentSelectedSignificantItem = useMemo(() => { + if (selectedSignificantItem) { + return selectedSignificantItem; + } else if (pinnedSignificantItem) { + return pinnedSignificantItem; } - }, [pinnedSignificantTerm, selectedSignificantTerm]); + }, [pinnedSignificantItem, selectedSignificantItem]); // If a group is pinned, still overrule with a potentially hovered group. const currentSelectedGroup = useMemo(() => { @@ -68,33 +68,33 @@ export const LogRateAnalysisResultsTableRowStateProvider: FC = ({ children }) => const contextValue: LogRateAnalysisResultsTableRow = useMemo( () => ({ - pinnedSignificantTerm, - setPinnedSignificantTerm, + pinnedSignificantItem, + setPinnedSignificantItem, pinnedGroup, setPinnedGroup, - selectedSignificantTerm, - setSelectedSignificantTerm, + selectedSignificantItem, + setSelectedSignificantItem, selectedGroup, setSelectedGroup, - currentSelectedSignificantTerm, + currentSelectedSignificantItem, currentSelectedGroup, clearAllRowState: () => { - setPinnedSignificantTerm(null); + setPinnedSignificantItem(null); setPinnedGroup(null); - setSelectedSignificantTerm(null); + setSelectedSignificantItem(null); setSelectedGroup(null); }, }), [ - pinnedSignificantTerm, - setPinnedSignificantTerm, + pinnedSignificantItem, + setPinnedSignificantItem, pinnedGroup, setPinnedGroup, - selectedSignificantTerm, - setSelectedSignificantTerm, + selectedSignificantItem, + setSelectedSignificantItem, selectedGroup, setSelectedGroup, - currentSelectedSignificantTerm, + currentSelectedSignificantItem, currentSelectedGroup, ] ); diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/types.ts b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/types.ts index 66a8a7fe5ab00..400e4534b54f1 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/types.ts +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/types.ts @@ -7,10 +7,10 @@ import type { EuiTableActionsColumnType } from '@elastic/eui'; -import type { SignificantTerm, SignificantTermGroupItem } from '@kbn/ml-agg-utils'; +import type { SignificantItem, SignificantItemGroupItem } from '@kbn/ml-agg-utils'; export type GroupTableItemGroup = Pick< - SignificantTermGroupItem, + SignificantItemGroupItem, 'key' | 'type' | 'fieldName' | 'fieldValue' | 'docCount' | 'pValue' | 'duplicate' >; @@ -20,9 +20,9 @@ export interface GroupTableItem { pValue: number | null; uniqueItemsCount: number; groupItemsSortedByUniqueness: GroupTableItemGroup[]; - histogram: SignificantTerm['histogram']; + histogram: SignificantItem['histogram']; } export type TableItemAction = EuiTableActionsColumnType< - SignificantTerm | GroupTableItem + SignificantItem | GroupTableItem >['actions'][number]; diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_copy_to_clipboard_action.test.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_copy_to_clipboard_action.test.tsx index 0984c76a4b170..52ffafd29bcc5 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_copy_to_clipboard_action.test.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_copy_to_clipboard_action.test.tsx @@ -10,9 +10,9 @@ import userEvent from '@testing-library/user-event'; import { render, act } from '@testing-library/react'; import { renderHook } from '@testing-library/react-hooks'; -import type { SignificantTerm } from '@kbn/ml-agg-utils'; +import type { SignificantItem } from '@kbn/ml-agg-utils'; -import { finalSignificantTermGroups } from '../../../common/__mocks__/artificial_logs/final_significant_term_groups'; +import { finalSignificantItemGroups } from '../../../common/__mocks__/artificial_logs/final_significant_item_groups'; import { significantTerms } from '../../../common/__mocks__/artificial_logs/significant_terms'; import { getGroupTableItems } from './get_group_table_items'; @@ -20,14 +20,14 @@ import { useCopyToClipboardAction } from './use_copy_to_clipboard_action'; import type { GroupTableItem } from './types'; interface Action { - render: (tableItem: SignificantTerm | GroupTableItem) => ReactElement; + render: (tableItem: SignificantItem | GroupTableItem) => ReactElement; } const execCommandMock = (global.document.execCommand = jest.fn()); const warn = jest.spyOn(console, 'warn').mockImplementation(() => {}); describe('useCopyToClipboardAction', () => { - it('renders the action for a single significant term', async () => { + it('renders the action for a single significant item', async () => { execCommandMock.mockImplementationOnce(() => true); const { result } = renderHook(() => useCopyToClipboardAction()); const { findByText, getByTestId } = render( @@ -57,7 +57,7 @@ describe('useCopyToClipboardAction', () => { it('renders the action for a group of items', async () => { execCommandMock.mockImplementationOnce(() => true); - const groupTableItems = getGroupTableItems(finalSignificantTermGroups); + const groupTableItems = getGroupTableItems(finalSignificantItemGroups); const { result } = renderHook(useCopyToClipboardAction); const { findByText, getByText } = render((result.current as Action).render(groupTableItems[0])); diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_copy_to_clipboard_action.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_copy_to_clipboard_action.tsx index 87b5239843940..b7dc328f1a468 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_copy_to_clipboard_action.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_copy_to_clipboard_action.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { EuiCopy, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { isSignificantTerm, type SignificantTerm } from '@kbn/ml-agg-utils'; +import { isSignificantItem, type SignificantItem } from '@kbn/ml-agg-utils'; import { TableActionButton } from './table_action_button'; import { getTableItemAsKQL } from './get_table_item_as_kql'; @@ -23,8 +23,8 @@ const copyToClipboardButtonLabel = i18n.translate( } ); -const copyToClipboardSignificantTermMessage = i18n.translate( - 'xpack.aiops.logRateAnalysis.resultsTable.linksMenu.copyToClipboardSignificantTermMessage', +const copyToClipboardSignificantItemMessage = i18n.translate( + 'xpack.aiops.logRateAnalysis.resultsTable.linksMenu.copyToClipboardSignificantItemMessage', { defaultMessage: 'Copy field/value pair as KQL syntax to clipboard', } @@ -38,9 +38,9 @@ const copyToClipboardGroupMessage = i18n.translate( ); export const useCopyToClipboardAction = (): TableItemAction => ({ - render: (tableItem: SignificantTerm | GroupTableItem) => { - const message = isSignificantTerm(tableItem) - ? copyToClipboardSignificantTermMessage + render: (tableItem: SignificantItem | GroupTableItem) => { + const message = isSignificantItem(tableItem) + ? copyToClipboardSignificantItemMessage : copyToClipboardGroupMessage; return ( diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_view_in_discover_action.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_view_in_discover_action.tsx index 1547daba0017e..7f467ded4c24b 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_view_in_discover_action.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_view_in_discover_action.tsx @@ -8,7 +8,7 @@ import React, { useMemo } from 'react'; import { i18n } from '@kbn/i18n'; -import type { SignificantTerm } from '@kbn/ml-agg-utils'; +import type { SignificantItem } from '@kbn/ml-agg-utils'; import { SEARCH_QUERY_LANGUAGE } from '@kbn/ml-query-utils'; import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; @@ -65,7 +65,7 @@ export const useViewInDiscoverAction = (dataViewId?: string): TableItemAction => } }, [application.capabilities.discover?.show, dataViewId, discoverLocator]); - const generateDiscoverUrl = async (groupTableItem: GroupTableItem | SignificantTerm) => { + const generateDiscoverUrl = async (groupTableItem: GroupTableItem | SignificantItem) => { if (discoverLocator !== undefined) { const url = await discoverLocator.getRedirectUrl({ indexPatternId: dataViewId, @@ -82,7 +82,7 @@ export const useViewInDiscoverAction = (dataViewId?: string): TableItemAction => }; return { - render: (tableItem: SignificantTerm | GroupTableItem) => { + render: (tableItem: SignificantItem | GroupTableItem) => { const tooltipText = discoverUrlError ? discoverUrlError : viewInDiscoverMessage; const clickHandler = async () => { diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_view_in_log_pattern_analysis_action.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_view_in_log_pattern_analysis_action.tsx index ba25db2b76aac..86f468e207389 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_view_in_log_pattern_analysis_action.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_view_in_log_pattern_analysis_action.tsx @@ -10,7 +10,7 @@ import React, { useMemo } from 'react'; import { SerializableRecord } from '@kbn/utility-types'; import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; -import { isSignificantTerm, type SignificantTerm, SIGNIFICANT_TERM_TYPE } from '@kbn/ml-agg-utils'; +import { isSignificantItem, type SignificantItem, SIGNIFICANT_ITEM_TYPE } from '@kbn/ml-agg-utils'; import { SEARCH_QUERY_LANGUAGE } from '@kbn/ml-query-utils'; import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; @@ -19,8 +19,8 @@ import { TableActionButton } from './table_action_button'; import { getTableItemAsKQL } from './get_table_item_as_kql'; import type { GroupTableItem, TableItemAction } from './types'; -const isLogPattern = (tableItem: SignificantTerm | GroupTableItem) => - isSignificantTerm(tableItem) && tableItem.type === SIGNIFICANT_TERM_TYPE.LOG_PATTERN; +const isLogPattern = (tableItem: SignificantItem | GroupTableItem) => + isSignificantItem(tableItem) && tableItem.type === SIGNIFICANT_ITEM_TYPE.LOG_PATTERN; const viewInLogPatternAnalysisMessage = i18n.translate( 'xpack.aiops.logRateAnalysis.resultsTable.linksMenu.viewInLogPatternAnalysis', @@ -35,7 +35,7 @@ export const useViewInLogPatternAnalysisAction = (dataViewId?: string): TableIte const mlLocator = useMemo(() => share.url.locators.get('ML_APP_LOCATOR'), [share.url.locators]); const generateLogPatternAnalysisUrl = async ( - groupTableItem: GroupTableItem | SignificantTerm + groupTableItem: GroupTableItem | SignificantItem ) => { if (mlLocator !== undefined) { const searchString = getTableItemAsKQL(groupTableItem); @@ -85,7 +85,7 @@ export const useViewInLogPatternAnalysisAction = (dataViewId?: string): TableIte }, [dataViewId, mlLocator]); return { - render: (tableItem: SignificantTerm | GroupTableItem) => { + render: (tableItem: SignificantItem | GroupTableItem) => { const message = logPatternAnalysisUrlError ? logPatternAnalysisUrlError : viewInLogPatternAnalysisMessage; diff --git a/x-pack/plugins/aiops/public/components/mini_histogram/mini_histogram.tsx b/x-pack/plugins/aiops/public/components/mini_histogram/mini_histogram.tsx index 5b6a28cc59463..6f1564cb6f86c 100644 --- a/x-pack/plugins/aiops/public/components/mini_histogram/mini_histogram.tsx +++ b/x-pack/plugins/aiops/public/components/mini_histogram/mini_histogram.tsx @@ -20,14 +20,14 @@ import { import { EuiLoadingChart, EuiTextColor } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import type { SignificantTermHistogramItem } from '@kbn/ml-agg-utils'; +import type { SignificantItemHistogramItem } from '@kbn/ml-agg-utils'; import { i18n } from '@kbn/i18n'; import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; import { useEuiTheme } from '../../hooks/use_eui_theme'; interface MiniHistogramProps { - chartData?: SignificantTermHistogramItem[]; + chartData?: SignificantItemHistogramItem[]; isLoading: boolean; label: string; /** Optional color override for the default bar color for charts */ diff --git a/x-pack/plugins/aiops/public/get_document_stats.ts b/x-pack/plugins/aiops/public/get_document_stats.ts index 9c5a53c8870e7..d4933d103eb7e 100644 --- a/x-pack/plugins/aiops/public/get_document_stats.ts +++ b/x-pack/plugins/aiops/public/get_document_stats.ts @@ -10,7 +10,7 @@ import { each, get } from 'lodash'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; -import type { SignificantTerm } from '@kbn/ml-agg-utils'; +import type { SignificantItem } from '@kbn/ml-agg-utils'; import type { Query } from '@kbn/es-query'; import type { RandomSamplerWrapper } from '@kbn/ml-random-sampler-utils'; @@ -34,8 +34,8 @@ export interface DocumentStatsSearchStrategyParams { timeFieldName?: string; runtimeFieldMap?: estypes.MappingRuntimeFields; fieldsToFetch?: string[]; - selectedSignificantTerm?: SignificantTerm; - includeSelectedSignificantTerm?: boolean; + selectedSignificantItem?: SignificantItem; + includeSelectedSignificantItem?: boolean; selectedGroup?: GroupTableItem | null; trackTotalHits?: boolean; } @@ -54,8 +54,8 @@ export const getDocumentCountStatsRequest = ( searchQuery, intervalMs, fieldsToFetch, - selectedSignificantTerm, - includeSelectedSignificantTerm, + selectedSignificantItem, + includeSelectedSignificantItem, selectedGroup, trackTotalHits, } = params; @@ -66,8 +66,8 @@ export const getDocumentCountStatsRequest = ( earliestMs, latestMs, searchQuery, - selectedSignificantTerm, - includeSelectedSignificantTerm, + selectedSignificantItem, + includeSelectedSignificantItem, selectedGroup ); diff --git a/x-pack/plugins/aiops/public/hooks/use_data.ts b/x-pack/plugins/aiops/public/hooks/use_data.ts index d44eb1661db34..934a470588960 100644 --- a/x-pack/plugins/aiops/public/hooks/use_data.ts +++ b/x-pack/plugins/aiops/public/hooks/use_data.ts @@ -11,7 +11,7 @@ import type { Moment } from 'moment'; import { useExecutionContext } from '@kbn/kibana-react-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public'; -import type { SignificantTerm } from '@kbn/ml-agg-utils'; +import type { SignificantItem } from '@kbn/ml-agg-utils'; import type { Dictionary } from '@kbn/ml-url-state'; import { mlTimefilterRefresh$, useTimefilter } from '@kbn/ml-date-picker'; @@ -34,7 +34,7 @@ export const useData = ( contextId: string, searchQuery: estypes.QueryDslQueryContainer, onUpdate?: (params: Dictionary) => void, - selectedSignificantTerm?: SignificantTerm, + selectedSignificantItem?: SignificantItem, selectedGroup: GroupTableItem | null = null, barTarget: number = DEFAULT_BAR_TARGET, timeRange?: { min: Moment; max: Moment } @@ -78,27 +78,27 @@ export const useData = ( return fieldStatsRequest ? { ...fieldStatsRequest, - selectedSignificantTerm, + selectedSignificantItem, selectedGroup, - includeSelectedSignificantTerm: false, + includeSelectedSignificantItem: false, } : undefined; - }, [fieldStatsRequest, selectedSignificantTerm, selectedGroup]); + }, [fieldStatsRequest, selectedSignificantItem, selectedGroup]); - const selectedSignificantTermStatsRequest = useMemo(() => { - return fieldStatsRequest && (selectedSignificantTerm || selectedGroup) + const selectedSignificantItemStatsRequest = useMemo(() => { + return fieldStatsRequest && (selectedSignificantItem || selectedGroup) ? { ...fieldStatsRequest, - selectedSignificantTerm, + selectedSignificantItem, selectedGroup, - includeSelectedSignificantTerm: true, + includeSelectedSignificantItem: true, } : undefined; - }, [fieldStatsRequest, selectedSignificantTerm, selectedGroup]); + }, [fieldStatsRequest, selectedSignificantItem, selectedGroup]); const documentStats = useDocumentCountStats( overallStatsRequest, - selectedSignificantTermStatsRequest, + selectedSignificantItemStatsRequest, lastRefresh ); diff --git a/x-pack/plugins/aiops/public/hooks/use_document_count_stats.ts b/x-pack/plugins/aiops/public/hooks/use_document_count_stats.ts index 8cfaa074286d6..c154640acd4e8 100644 --- a/x-pack/plugins/aiops/public/hooks/use_document_count_stats.ts +++ b/x-pack/plugins/aiops/public/hooks/use_document_count_stats.ts @@ -94,7 +94,7 @@ export function useDocumentCountStats = [ +export const duplicateIdentifier: Array = [ 'doc_count', 'bg_count', 'total_doc_count', diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_frequent_item_sets.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_frequent_item_sets.ts index c74030efaea7f..ff02251b64f95 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_frequent_item_sets.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_frequent_item_sets.ts @@ -12,12 +12,12 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import type { Logger } from '@kbn/logging'; -import { type SignificantTerm } from '@kbn/ml-agg-utils'; +import { type SignificantItem } from '@kbn/ml-agg-utils'; import { createRandomSamplerWrapper } from '@kbn/ml-random-sampler-utils'; import { RANDOM_SAMPLER_SEED, LOG_RATE_ANALYSIS_SETTINGS } from '../../../../common/constants'; import type { - SignificantTermDuplicateGroup, + SignificantItemDuplicateGroup, ItemSet, FetchFrequentItemSetsResponse, } from '../../../../common/types'; @@ -29,10 +29,10 @@ interface FrequentItemSetsAggregation extends estypes.AggregationsSamplerAggrega } export function groupDuplicates( - cps: SignificantTerm[], - uniqueFields: Array + cps: SignificantItem[], + uniqueFields: Array ) { - const groups: SignificantTermDuplicateGroup[] = []; + const groups: SignificantItemDuplicateGroup[] = []; for (const cp of cps) { const compareAttributes = pick(cp, uniqueFields); @@ -51,16 +51,16 @@ export function groupDuplicates( return groups; } -export function getShouldClauses(significantTerms: SignificantTerm[]) { +export function getShouldClauses(significantItems: SignificantItem[]) { return Array.from( - group(significantTerms, ({ fieldName }) => fieldName), + group(significantItems, ({ fieldName }) => fieldName), ([field, values]) => ({ terms: { [field]: values.map((d) => d.fieldValue) } }) ); } -export function getFrequentItemSetsAggFields(significantTerms: SignificantTerm[]) { +export function getFrequentItemSetsAggFields(significantItems: SignificantItem[]) { return Array.from( - group(significantTerms, ({ fieldName }) => fieldName), + group(significantItems, ({ fieldName }) => fieldName), ([field, values]) => ({ field, include: values.map((d) => String(d.fieldValue)) }) ); } @@ -69,7 +69,7 @@ export async function fetchFrequentItemSets( client: ElasticsearchClient, index: string, searchQuery: estypes.QueryDslQueryContainer, - significantTerms: SignificantTerm[], + significantItems: SignificantItem[], timeFieldName: string, deviationMin: number, deviationMax: number, @@ -80,7 +80,7 @@ export async function fetchFrequentItemSets( abortSignal?: AbortSignal ): Promise { // Sort significant terms by ascending p-value, necessary to apply the field limit correctly. - const sortedSignificantTerms = significantTerms.slice().sort((a, b) => { + const sortedSignificantItems = significantItems.slice().sort((a, b) => { return (a.pValue ?? 0) - (b.pValue ?? 0); }); @@ -98,7 +98,7 @@ export async function fetchFrequentItemSets( }, }, ], - should: getShouldClauses(sortedSignificantTerms), + should: getShouldClauses(sortedSignificantItems), }, }; @@ -108,7 +108,7 @@ export async function fetchFrequentItemSets( minimum_set_size: 2, size: 200, minimum_support: LOG_RATE_ANALYSIS_SETTINGS.FREQUENT_ITEMS_SETS_MINIMUM_SUPPORT, - fields: getFrequentItemSetsAggFields(sortedSignificantTerms), + fields: getFrequentItemSetsAggFields(sortedSignificantItems), }, }, }; @@ -177,7 +177,7 @@ export async function fetchFrequentItemSets( Object.entries(fis.key).forEach(([key, value]) => { result.set[key] = value[0]; - const pValue = sortedSignificantTerms.find( + const pValue = sortedSignificantItems.find( (t) => t.fieldName === key && t.fieldValue === value[0] )?.pValue; diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_significant_categories.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_significant_categories.ts index 4cf106a369464..49617a1661ba6 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_significant_categories.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_significant_categories.ts @@ -10,7 +10,7 @@ import { uniq } from 'lodash'; import { ElasticsearchClient } from '@kbn/core/server'; import type { Logger } from '@kbn/logging'; import { criticalTableLookup, type Histogram } from '@kbn/ml-chi2test'; -import { type SignificantTerm, SIGNIFICANT_TERM_TYPE } from '@kbn/ml-agg-utils'; +import { type SignificantItem, SIGNIFICANT_ITEM_TYPE } from '@kbn/ml-agg-utils'; import type { Category } from '../../../../common/api/log_categorization/types'; import type { AiopsLogRateAnalysisSchema } from '../../../../common/api/log_rate_analysis/schema'; @@ -83,7 +83,7 @@ export const fetchSignificantCategories = async ( if (categoriesOverall.length !== fieldNames.length) return []; - const significantCategories: SignificantTerm[] = []; + const significantCategories: SignificantItem[] = []; // Using for...of to allow `await` within the loop. for (const [i, fieldName] of fieldNames.entries()) { @@ -152,7 +152,7 @@ export const fetchSignificantCategories = async ( score, pValue, normalizedScore: getNormalizedScore(score), - type: SIGNIFICANT_TERM_TYPE.LOG_PATTERN, + type: SIGNIFICANT_ITEM_TYPE.LOG_PATTERN, }); } }); diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_significant_term_p_values.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_significant_term_p_values.ts index 10b488940ce2c..6cdf6983d5827 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_significant_term_p_values.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_significant_term_p_values.ts @@ -9,7 +9,7 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { ElasticsearchClient } from '@kbn/core/server'; import type { Logger } from '@kbn/logging'; -import { type SignificantTerm, SIGNIFICANT_TERM_TYPE } from '@kbn/ml-agg-utils'; +import { type SignificantItem, SIGNIFICANT_ITEM_TYPE } from '@kbn/ml-agg-utils'; import { createRandomSamplerWrapper, type RandomSamplerWrapper, @@ -111,13 +111,13 @@ export const fetchSignificantTermPValues = async ( sampleProbability: number = 1, emitError: (m: string) => void, abortSignal?: AbortSignal -): Promise => { +): Promise => { const randomSamplerWrapper = createRandomSamplerWrapper({ probability: sampleProbability, seed: RANDOM_SAMPLER_SEED, }); - const result: SignificantTerm[] = []; + const result: SignificantItem[] = []; const settledPromises = await Promise.allSettled( fieldNames.map((fieldName) => @@ -168,7 +168,7 @@ export const fetchSignificantTermPValues = async ( if (typeof pValue === 'number' && pValue < LOG_RATE_ANALYSIS_SETTINGS.P_VALUE_THRESHOLD) { result.push({ key: `${fieldName}:${String(bucket.key)}`, - type: SIGNIFICANT_TERM_TYPE.KEYWORD, + type: SIGNIFICANT_ITEM_TYPE.KEYWORD, fieldName, fieldValue: String(bucket.key), doc_count: bucket.doc_count, diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_terms_2_categories_counts.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_terms_2_categories_counts.ts index 14be571331fd0..b9eac01041be1 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_terms_2_categories_counts.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_terms_2_categories_counts.ts @@ -11,7 +11,7 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import type { Logger } from '@kbn/logging'; -import type { FieldValuePair, SignificantTerm } from '@kbn/ml-agg-utils'; +import type { FieldValuePair, SignificantItem } from '@kbn/ml-agg-utils'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; import type { AiopsLogRateAnalysisSchema } from '../../../../common/api/log_rate_analysis/schema'; @@ -68,9 +68,9 @@ export async function fetchTerms2CategoriesCounts( esClient: ElasticsearchClient, params: AiopsLogRateAnalysisSchema, searchQuery: estypes.QueryDslQueryContainer, - significantTerms: SignificantTerm[], + significantTerms: SignificantItem[], itemSets: ItemSet[], - significantCategories: SignificantTerm[], + significantCategories: SignificantItem[], from: number, to: number, logger: Logger, diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_field_value_pair_counts.test.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_field_value_pair_counts.test.ts index fb04844c5bd3d..903755eca45d5 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_field_value_pair_counts.test.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_field_value_pair_counts.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { significantTermGroups } from '../../../../common/__mocks__/farequote/significant_term_groups'; +import { significantItemGroups } from '../../../../common/__mocks__/farequote/significant_item_groups'; import { fields } from '../../../../common/__mocks__/artificial_logs/fields'; import { filteredFrequentItemSets } from '../../../../common/__mocks__/artificial_logs/filtered_frequent_item_sets'; import { significantTerms } from '../../../../common/__mocks__/artificial_logs/significant_terms'; @@ -16,7 +16,7 @@ import { getSimpleHierarchicalTreeLeaves } from './get_simple_hierarchical_tree_ describe('getFieldValuePairCounts', () => { it('returns a nested record with field/value pair counts for farequote', () => { - const fieldValuePairCounts = getFieldValuePairCounts(significantTermGroups); + const fieldValuePairCounts = getFieldValuePairCounts(significantItemGroups); expect(fieldValuePairCounts).toEqual({ airline: { diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_field_value_pair_counts.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_field_value_pair_counts.ts index 429306139d402..6d0e15f2117f8 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_field_value_pair_counts.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_field_value_pair_counts.ts @@ -5,14 +5,14 @@ * 2.0. */ -import type { SignificantTermGroup } from '@kbn/ml-agg-utils'; +import type { SignificantItemGroup } from '@kbn/ml-agg-utils'; import type { FieldValuePairCounts } from '../../../../common/types'; /** * Get a nested record of field/value pairs with counts */ -export function getFieldValuePairCounts(cpgs: SignificantTermGroup[]): FieldValuePairCounts { +export function getFieldValuePairCounts(cpgs: SignificantItemGroup[]): FieldValuePairCounts { return cpgs.reduce((p, { group }) => { group.forEach(({ fieldName, fieldValue }) => { if (p[fieldName] === undefined) { diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_group_filter.test.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_group_filter.test.ts index e4d30a4438c6e..9f583f9e199df 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_group_filter.test.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_group_filter.test.ts @@ -5,13 +5,13 @@ * 2.0. */ -import { finalSignificantTermGroups } from '../../../../common/__mocks__/artificial_logs/final_significant_term_groups'; +import { finalSignificantItemGroups } from '../../../../common/__mocks__/artificial_logs/final_significant_item_groups'; import { getGroupFilter } from './get_group_filter'; describe('getGroupFilter', () => { - it('gets a query filter for the significant terms of a group', () => { - expect(getGroupFilter(finalSignificantTermGroups[0])).toStrictEqual([ + it('gets a query filter for the significant items of a group', () => { + expect(getGroupFilter(finalSignificantItemGroups[0])).toStrictEqual([ { term: { url: 'login.php', diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_group_filter.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_group_filter.ts index d968ce90ec91b..e2ae795004e18 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_group_filter.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_group_filter.ts @@ -7,21 +7,21 @@ import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { type SignificantTermGroup, SIGNIFICANT_TERM_TYPE } from '@kbn/ml-agg-utils'; +import { type SignificantItemGroup, SIGNIFICANT_ITEM_TYPE } from '@kbn/ml-agg-utils'; import { getCategoryQuery } from '../../../../common/api/log_categorization/get_category_query'; -// Transforms a list of significant terms from a group in a query filter. +// Transforms a list of significant items from a group in a query filter. // Uses a `term` filter for single field value combinations. // For fields with multiple values it creates a single `terms` filter that includes // all values. This avoids queries not returning any results otherwise because // separate `term` filter for multiple values for the same field would rule each other out. export function getGroupFilter( - significantTermGroup: SignificantTermGroup + significantItemGroup: SignificantItemGroup ): estypes.QueryDslQueryContainer[] { const groupKeywordFilter = Object.entries( - significantTermGroup.group - .filter((d) => d.type === SIGNIFICANT_TERM_TYPE.KEYWORD) + significantItemGroup.group + .filter((d) => d.type === SIGNIFICANT_ITEM_TYPE.KEYWORD) .reduce>>((p, c) => { if (p[c.fieldName]) { p[c.fieldName].push(c.fieldValue); @@ -35,8 +35,8 @@ export function getGroupFilter( return p; }, []); - const groupLogPatternFilter = significantTermGroup.group - .filter((d) => d.type === SIGNIFICANT_TERM_TYPE.LOG_PATTERN) + const groupLogPatternFilter = significantItemGroup.group + .filter((d) => d.type === SIGNIFICANT_ITEM_TYPE.LOG_PATTERN) .map((d) => getCategoryQuery(d.fieldName, [ { diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_groups_with_readded_duplicates.test.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_groups_with_readded_duplicates.test.ts index 3c7325cdb49eb..5a0e1507f398d 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_groups_with_readded_duplicates.test.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_groups_with_readded_duplicates.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { significantTermGroups } from '../../../../common/__mocks__/artificial_logs/significant_term_groups'; +import { significantItemGroups } from '../../../../common/__mocks__/artificial_logs/significant_item_groups'; import { significantTerms } from '../../../../common/__mocks__/artificial_logs/significant_terms'; import { duplicateIdentifier } from './duplicate_identifier'; @@ -16,15 +16,15 @@ import { getMarkedDuplicates } from './get_marked_duplicates'; describe('getGroupsWithReaddedDuplicates', () => { it('gets groups with readded duplicates', () => { - const groupedSignificantTerms = groupDuplicates(significantTerms, duplicateIdentifier).filter( + const groupedSignificantItems = groupDuplicates(significantTerms, duplicateIdentifier).filter( (g) => g.group.length > 1 ); - const fieldValuePairCounts = getFieldValuePairCounts(significantTermGroups); - const markedDuplicates = getMarkedDuplicates(significantTermGroups, fieldValuePairCounts); + const fieldValuePairCounts = getFieldValuePairCounts(significantItemGroups); + const markedDuplicates = getMarkedDuplicates(significantItemGroups, fieldValuePairCounts); const groupsWithReaddedDuplicates = getGroupsWithReaddedDuplicates( markedDuplicates, - groupedSignificantTerms + groupedSignificantItems ); expect(groupsWithReaddedDuplicates).toEqual([ diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_groups_with_readded_duplicates.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_groups_with_readded_duplicates.ts index 6defb9d886662..e3e012ce299e3 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_groups_with_readded_duplicates.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_groups_with_readded_duplicates.ts @@ -7,20 +7,20 @@ import { uniqWith, isEqual } from 'lodash'; -import type { SignificantTermGroup } from '@kbn/ml-agg-utils'; +import type { SignificantItemGroup } from '@kbn/ml-agg-utils'; -import type { SignificantTermDuplicateGroup } from '../../../../common/types'; +import type { SignificantItemDuplicateGroup } from '../../../../common/types'; export function getGroupsWithReaddedDuplicates( - groups: SignificantTermGroup[], - groupedSignificantTerms: SignificantTermDuplicateGroup[] -): SignificantTermGroup[] { + groups: SignificantItemGroup[], + groupedSignificantItems: SignificantItemDuplicateGroup[] +): SignificantItemGroup[] { return groups.map((g) => { const group = [...g.group]; for (const groupItem of g.group) { const { duplicate } = groupItem; - const duplicates = groupedSignificantTerms.find((d) => + const duplicates = groupedSignificantItems.find((d) => d.group.some( (dg) => dg.fieldName === groupItem.fieldName && dg.fieldValue === groupItem.fieldValue ) diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_marked_duplicates.test.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_marked_duplicates.test.ts index 9c0d86a392e4d..fcc2448d4cd46 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_marked_duplicates.test.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_marked_duplicates.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { significantTermGroups } from '../../../../common/__mocks__/farequote/significant_term_groups'; +import { significantItemGroups } from '../../../../common/__mocks__/farequote/significant_item_groups'; import { fields } from '../../../../common/__mocks__/artificial_logs/fields'; import { filteredFrequentItemSets } from '../../../../common/__mocks__/artificial_logs/filtered_frequent_item_sets'; import { significantTerms } from '../../../../common/__mocks__/artificial_logs/significant_terms'; @@ -16,9 +16,9 @@ import { getSimpleHierarchicalTree } from './get_simple_hierarchical_tree'; import { getSimpleHierarchicalTreeLeaves } from './get_simple_hierarchical_tree_leaves'; describe('markDuplicates', () => { - it('marks duplicates based on significant terms groups for farequote', () => { - const fieldValuePairCounts = getFieldValuePairCounts(significantTermGroups); - const markedDuplicates = getMarkedDuplicates(significantTermGroups, fieldValuePairCounts); + it('marks duplicates based on significant items groups for farequote', () => { + const fieldValuePairCounts = getFieldValuePairCounts(significantItemGroups); + const markedDuplicates = getMarkedDuplicates(significantItemGroups, fieldValuePairCounts); expect(markedDuplicates).toEqual([ { @@ -74,7 +74,7 @@ describe('markDuplicates', () => { ]); }); - it('marks duplicates based on significant terms groups for artificial logs', () => { + it('marks duplicates based on significant items groups for artificial logs', () => { const simpleHierarchicalTree = getSimpleHierarchicalTree( filteredFrequentItemSets, true, diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_marked_duplicates.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_marked_duplicates.ts index 7708d0634bda3..8ddc13cfee8df 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_marked_duplicates.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_marked_duplicates.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { SignificantTermGroup } from '@kbn/ml-agg-utils'; +import type { SignificantItemGroup } from '@kbn/ml-agg-utils'; import type { FieldValuePairCounts } from '../../../../common/types'; @@ -13,9 +13,9 @@ import type { FieldValuePairCounts } from '../../../../common/types'; * Analyse duplicate field/value pairs in groups. */ export function getMarkedDuplicates( - cpgs: SignificantTermGroup[], + cpgs: SignificantItemGroup[], fieldValuePairCounts: FieldValuePairCounts -): SignificantTermGroup[] { +): SignificantItemGroup[] { return cpgs.map((cpg) => { return { ...cpg, diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_missing_significant_terms.test.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_missing_significant_items.test.ts similarity index 75% rename from x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_missing_significant_terms.test.ts rename to x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_missing_significant_items.test.ts index 412b7013b64d6..8ac70af16d298 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_missing_significant_terms.test.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_missing_significant_items.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { significantTermGroups } from '../../../../common/__mocks__/artificial_logs/significant_term_groups'; +import { significantItemGroups } from '../../../../common/__mocks__/artificial_logs/significant_item_groups'; import { significantTerms } from '../../../../common/__mocks__/artificial_logs/significant_terms'; import { duplicateIdentifier } from './duplicate_identifier'; @@ -13,27 +13,27 @@ import { getGroupsWithReaddedDuplicates } from './get_groups_with_readded_duplic import { groupDuplicates } from './fetch_frequent_item_sets'; import { getFieldValuePairCounts } from './get_field_value_pair_counts'; import { getMarkedDuplicates } from './get_marked_duplicates'; -import { getMissingSignificantTerms } from './get_missing_significant_terms'; +import { getMissingSignificantItems } from './get_missing_significant_items'; -describe('getMissingSignificantTerms', () => { - it('get missing significant terms', () => { - const groupedSignificantTerms = groupDuplicates(significantTerms, duplicateIdentifier).filter( +describe('getMissingSignificantItems', () => { + it('get missing significant items', () => { + const groupedSignificantItems = groupDuplicates(significantTerms, duplicateIdentifier).filter( (g) => g.group.length > 1 ); - const fieldValuePairCounts = getFieldValuePairCounts(significantTermGroups); - const markedDuplicates = getMarkedDuplicates(significantTermGroups, fieldValuePairCounts); + const fieldValuePairCounts = getFieldValuePairCounts(significantItemGroups); + const markedDuplicates = getMarkedDuplicates(significantItemGroups, fieldValuePairCounts); const groupsWithReaddedDuplicates = getGroupsWithReaddedDuplicates( markedDuplicates, - groupedSignificantTerms + groupedSignificantItems ); - const missingSignificantTerms = getMissingSignificantTerms( + const missingSignificantItems = getMissingSignificantItems( significantTerms, groupsWithReaddedDuplicates ); - expect(missingSignificantTerms).toEqual([ + expect(missingSignificantItems).toEqual([ { key: 'user:Peter', type: 'keyword', diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_missing_significant_terms.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_missing_significant_items.ts similarity index 57% rename from x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_missing_significant_terms.ts rename to x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_missing_significant_items.ts index 7daa797f16a37..c2847f1592dd1 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_missing_significant_terms.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_missing_significant_items.ts @@ -5,14 +5,14 @@ * 2.0. */ -import type { SignificantTerm, SignificantTermGroup } from '@kbn/ml-agg-utils'; +import type { SignificantItem, SignificantItemGroup } from '@kbn/ml-agg-utils'; -export function getMissingSignificantTerms( - significantTerms: SignificantTerm[], - significantTermGroups: SignificantTermGroup[] +export function getMissingSignificantItems( + significantItems: SignificantItem[], + significantItemGroups: SignificantItemGroup[] ) { - return significantTerms.filter((cp) => { - return !significantTermGroups.some((cpg) => { + return significantItems.filter((cp) => { + return !significantItemGroups.some((cpg) => { return cpg.group.some((d) => d.fieldName === cp.fieldName && d.fieldValue === cp.fieldValue); }); }); diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_significant_term_groups.test.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_significant_item_groups.test.ts similarity index 59% rename from x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_significant_term_groups.test.ts rename to x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_significant_item_groups.test.ts index 05a682bc4ea34..cf5ba41fe5216 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_significant_term_groups.test.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_significant_item_groups.test.ts @@ -10,20 +10,20 @@ import { orderBy } from 'lodash'; import { fields } from '../../../../common/__mocks__/artificial_logs/fields'; import { frequentItemSets } from '../../../../common/__mocks__/artificial_logs/frequent_item_sets'; import { significantTerms } from '../../../../common/__mocks__/artificial_logs/significant_terms'; -import { finalSignificantTermGroups } from '../../../../common/__mocks__/artificial_logs/final_significant_term_groups'; +import { finalSignificantItemGroups } from '../../../../common/__mocks__/artificial_logs/final_significant_item_groups'; -import { getSignificantTermGroups } from './get_significant_term_groups'; +import { getSignificantItemGroups } from './get_significant_item_groups'; -describe('getSignificantTermGroups', () => { - it('gets significant terms groups', () => { - const significantTermGroups = getSignificantTermGroups( +describe('getSignificantItemGroups', () => { + it('gets significant items groups', () => { + const significantItemGroups = getSignificantItemGroups( frequentItemSets, significantTerms, fields ); - expect(orderBy(significantTermGroups, ['docCount'])).toEqual( - orderBy(finalSignificantTermGroups, ['docCount']) + expect(orderBy(significantItemGroups, ['docCount'])).toEqual( + orderBy(finalSignificantItemGroups, ['docCount']) ); }); }); diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_significant_term_groups.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_significant_item_groups.ts similarity index 70% rename from x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_significant_term_groups.ts rename to x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_significant_item_groups.ts index 1b498b0d76595..27dc06ad343b9 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_significant_term_groups.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_significant_item_groups.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { SignificantTerm, SignificantTermGroup } from '@kbn/ml-agg-utils'; +import type { SignificantItem, SignificantItemGroup } from '@kbn/ml-agg-utils'; import { duplicateIdentifier } from './duplicate_identifier'; import { groupDuplicates } from './fetch_frequent_item_sets'; @@ -13,18 +13,18 @@ import { getFieldValuePairCounts } from './get_field_value_pair_counts'; import { getMarkedDuplicates } from './get_marked_duplicates'; import { getSimpleHierarchicalTree } from './get_simple_hierarchical_tree'; import { getSimpleHierarchicalTreeLeaves } from './get_simple_hierarchical_tree_leaves'; -import { getMissingSignificantTerms } from './get_missing_significant_terms'; -import { transformSignificantTermToGroup } from './transform_significant_term_to_group'; +import { getMissingSignificantItems } from './get_missing_significant_items'; +import { transformSignificantItemToGroup } from './transform_significant_item_to_group'; import type { ItemSet } from '../../../../common/types'; -export function getSignificantTermGroups( +export function getSignificantItemGroups( itemsets: ItemSet[], - significantTerms: SignificantTerm[], + significantItems: SignificantItem[], fields: string[] -): SignificantTermGroup[] { - // We use the grouped significant terms to later repopulate +): SignificantItemGroup[] { + // We use the grouped significant items to later repopulate // the `frequent_item_sets` result with the missing duplicates. - const groupedSignificantTerms = groupDuplicates(significantTerms, duplicateIdentifier).filter( + const groupedSignificantItems = groupDuplicates(significantItems, duplicateIdentifier).filter( (g) => g.group.length > 1 ); @@ -33,7 +33,7 @@ export function getSignificantTermGroups( // and then summarize them in larger groups where possible. // Get a tree structure based on `frequent_item_sets`. - const { root } = getSimpleHierarchicalTree(itemsets, false, false, significantTerms, fields); + const { root } = getSimpleHierarchicalTree(itemsets, false, false, significantItems, fields); // Each leave of the tree will be a summarized group of co-occuring field/value pairs. const treeLeaves = getSimpleHierarchicalTreeLeaves(root, []); @@ -42,21 +42,21 @@ export function getSignificantTermGroups( // that occur in multiple groups. This will allow us to highlight field/value pairs that are // unique to a group in a better way. const fieldValuePairCounts = getFieldValuePairCounts(treeLeaves); - const significantTermGroups = getMarkedDuplicates(treeLeaves, fieldValuePairCounts); + const significantItemGroups = getMarkedDuplicates(treeLeaves, fieldValuePairCounts); // Some field/value pairs might not be part of the `frequent_item_sets` result set, for example // because they don't co-occur with other field/value pairs or because of the limits we set on the query. // In this next part we identify those missing pairs and add them as individual groups. - const missingSignificantTerms = getMissingSignificantTerms( - significantTerms, - significantTermGroups + const missingSignificantItems = getMissingSignificantItems( + significantItems, + significantItemGroups ); - significantTermGroups.push( - ...missingSignificantTerms.map((significantTerm) => - transformSignificantTermToGroup(significantTerm, groupedSignificantTerms) + significantItemGroups.push( + ...missingSignificantItems.map((significantItem) => + transformSignificantItemToGroup(significantItem, groupedSignificantItems) ) ); - return significantTermGroups; + return significantItemGroups; } diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_simple_hierarchical_tree.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_simple_hierarchical_tree.ts index 2ffcc184fa71e..54cce87526bb1 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_simple_hierarchical_tree.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_simple_hierarchical_tree.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { SignificantTerm } from '@kbn/ml-agg-utils'; +import type { SignificantItem } from '@kbn/ml-agg-utils'; import type { ItemSet, SimpleHierarchicalTreeNode } from '../../../../common/types'; @@ -34,7 +34,7 @@ function NewNodeFactory(name: string): SimpleHierarchicalTreeNode { * The resulting tree components are non-overlapping subsets of the data. * In summary, we start with the most inclusive itemset (highest count), and perform a depth first search in field order. * - * @param significantTerms + * @param significantItems * @param fields * @param displayParent * @param parentDocCount @@ -47,7 +47,7 @@ function NewNodeFactory(name: string): SimpleHierarchicalTreeNode { * @returns */ function dfDepthFirstSearch( - significantTerms: SignificantTerm[], + significantItems: SignificantItem[], fields: string[], displayParent: SimpleHierarchicalTreeNode, parentDocCount: number, @@ -79,10 +79,10 @@ function dfDepthFirstSearch( let displayNode: SimpleHierarchicalTreeNode; - const significantTerm = significantTerms.find( + const significantItem = significantItems.find( (d) => d.fieldName === field && d.fieldValue === value ); - if (!significantTerm) { + if (!significantItem) { return 0; } @@ -91,8 +91,8 @@ function dfDepthFirstSearch( displayParent.name += ` ${value}`; displayParent.set.push({ - key: significantTerm.key, - type: significantTerm.type, + key: significantItem.key, + type: significantItem.type, fieldName: field, fieldValue: value, docCount, @@ -105,8 +105,8 @@ function dfDepthFirstSearch( displayNode = NewNodeFactory(`${docCount}/${totalDocCount}${label}`); displayNode.set = [...displayParent.set]; displayNode.set.push({ - key: significantTerm.key, - type: significantTerm.type, + key: significantItem.key, + type: significantItem.type, fieldName: field, fieldValue: value, docCount, @@ -148,7 +148,7 @@ function dfDepthFirstSearch( let subCount = 0; for (const nextValue of getValuesDescending(filteredItemSets, nextField)) { subCount += dfDepthFirstSearch( - significantTerms, + significantItems, fields, displayNode, docCount, @@ -181,7 +181,7 @@ export function getSimpleHierarchicalTree( itemSets: ItemSet[], collapseRedundant: boolean, displayOther: boolean, - significantTerms: SignificantTerm[], + significantItems: SignificantItem[], fields: string[] = [] ) { const totalDocCount = Math.max(...itemSets.map((d) => d.total_doc_count)); @@ -191,7 +191,7 @@ export function getSimpleHierarchicalTree( for (const field of fields) { for (const value of getValuesDescending(itemSets, field)) { dfDepthFirstSearch( - significantTerms, + significantItems, fields, newRoot, totalDocCount + 1, diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_simple_hierarchical_tree_leaves.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_simple_hierarchical_tree_leaves.ts index bd183239eeadf..619ec2896d4d4 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_simple_hierarchical_tree_leaves.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_simple_hierarchical_tree_leaves.ts @@ -6,7 +6,7 @@ */ import { orderBy } from 'lodash'; -import type { SignificantTermGroup } from '@kbn/ml-agg-utils'; +import type { SignificantItemGroup } from '@kbn/ml-agg-utils'; import { stringHash } from '@kbn/ml-string-hash'; import type { SimpleHierarchicalTreeNode } from '../../../../common/types'; @@ -16,7 +16,7 @@ import type { SimpleHierarchicalTreeNode } from '../../../../common/types'; */ export function getSimpleHierarchicalTreeLeaves( tree: SimpleHierarchicalTreeNode, - leaves: SignificantTermGroup[], + leaves: SignificantItemGroup[], level = 1 ) { if (tree.children.length === 0) { @@ -43,7 +43,7 @@ export function getSimpleHierarchicalTreeLeaves( const sortedLeaves = orderBy(leaves, [(d) => d.group.length], ['desc']); // Checks if a group is a subset of items already present in a larger group. - const filteredLeaves = sortedLeaves.reduce((p, c) => { + const filteredLeaves = sortedLeaves.reduce((p, c) => { const isSubset = p.some((pG) => c.group.every((cGI) => pG.group.some((pGI) => pGI.fieldName === cGI.fieldName && pGI.fieldValue === cGI.fieldValue) diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/transform_significant_term_to_group.test.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/transform_significant_item_to_group.test.ts similarity index 66% rename from x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/transform_significant_term_to_group.test.ts rename to x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/transform_significant_item_to_group.test.ts index 860baa3c800a2..f1a2a540dc877 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/transform_significant_term_to_group.test.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/transform_significant_item_to_group.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { significantTermGroups } from '../../../../common/__mocks__/artificial_logs/significant_term_groups'; +import { significantItemGroups } from '../../../../common/__mocks__/artificial_logs/significant_item_groups'; import { significantTerms } from '../../../../common/__mocks__/artificial_logs/significant_terms'; import { duplicateIdentifier } from './duplicate_identifier'; @@ -13,30 +13,30 @@ import { getGroupsWithReaddedDuplicates } from './get_groups_with_readded_duplic import { groupDuplicates } from './fetch_frequent_item_sets'; import { getFieldValuePairCounts } from './get_field_value_pair_counts'; import { getMarkedDuplicates } from './get_marked_duplicates'; -import { getMissingSignificantTerms } from './get_missing_significant_terms'; -import { transformSignificantTermToGroup } from './transform_significant_term_to_group'; +import { getMissingSignificantItems } from './get_missing_significant_items'; +import { transformSignificantItemToGroup } from './transform_significant_item_to_group'; -describe('getMissingSignificantTerms', () => { - it('get missing significant terms', () => { - const groupedSignificantTerms = groupDuplicates(significantTerms, duplicateIdentifier).filter( +describe('getMissingSignificantItems', () => { + it('get missing significant items', () => { + const groupedSignificantItems = groupDuplicates(significantTerms, duplicateIdentifier).filter( (g) => g.group.length > 1 ); - const fieldValuePairCounts = getFieldValuePairCounts(significantTermGroups); - const markedDuplicates = getMarkedDuplicates(significantTermGroups, fieldValuePairCounts); + const fieldValuePairCounts = getFieldValuePairCounts(significantItemGroups); + const markedDuplicates = getMarkedDuplicates(significantItemGroups, fieldValuePairCounts); const groupsWithReaddedDuplicates = getGroupsWithReaddedDuplicates( markedDuplicates, - groupedSignificantTerms + groupedSignificantItems ); - const missingSignificantTerms = getMissingSignificantTerms( + const missingSignificantItems = getMissingSignificantItems( significantTerms, groupsWithReaddedDuplicates ); - const transformed = transformSignificantTermToGroup( - missingSignificantTerms[0], - groupedSignificantTerms + const transformed = transformSignificantItemToGroup( + missingSignificantItems[0], + groupedSignificantItems ); expect(transformed).toEqual({ diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/transform_significant_term_to_group.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/transform_significant_item_to_group.ts similarity index 77% rename from x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/transform_significant_term_to_group.ts rename to x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/transform_significant_item_to_group.ts index 9f95a5c0fa2db..05a1473acc2bd 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/transform_significant_term_to_group.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/transform_significant_item_to_group.ts @@ -6,17 +6,17 @@ */ import { stringHash } from '@kbn/ml-string-hash'; -import type { SignificantTerm, SignificantTermGroup } from '@kbn/ml-agg-utils'; +import type { SignificantItem, SignificantItemGroup } from '@kbn/ml-agg-utils'; -import type { SignificantTermDuplicateGroup } from '../../../../common/types'; +import type { SignificantItemDuplicateGroup } from '../../../../common/types'; -export function transformSignificantTermToGroup( - significantTerm: SignificantTerm, - groupedSignificantTerms: SignificantTermDuplicateGroup[] -): SignificantTermGroup { - const { key, type, fieldName, fieldValue, doc_count: docCount, pValue } = significantTerm; +export function transformSignificantItemToGroup( + significantItem: SignificantItem, + groupedSignificantItems: SignificantItemDuplicateGroup[] +): SignificantItemGroup { + const { key, type, fieldName, fieldValue, doc_count: docCount, pValue } = significantItem; - const duplicates = groupedSignificantTerms.find((d) => + const duplicates = groupedSignificantItems.find((d) => d.group.some((dg) => dg.fieldName === fieldName && dg.fieldValue === fieldValue) ); diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/route_handler_factory.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/route_handler_factory.ts index b92458cc1e3de..30f3dbe55f446 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/route_handler_factory.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/route_handler_factory.ts @@ -21,23 +21,23 @@ import { i18n } from '@kbn/i18n'; import { KBN_FIELD_TYPES } from '@kbn/field-types'; import { streamFactory } from '@kbn/ml-response-stream/server'; import type { - SignificantTerm, - SignificantTermGroup, - SignificantTermHistogramItem, + SignificantItem, + SignificantItemGroup, + SignificantItemHistogramItem, NumericChartData, NumericHistogramField, } from '@kbn/ml-agg-utils'; -import { SIGNIFICANT_TERM_TYPE } from '@kbn/ml-agg-utils'; +import { SIGNIFICANT_ITEM_TYPE } from '@kbn/ml-agg-utils'; import { fetchHistogramsForFields } from '@kbn/ml-agg-utils'; import { createExecutionContext } from '@kbn/ml-route-utils'; import type { UsageCounter } from '@kbn/usage-collection-plugin/server'; import { RANDOM_SAMPLER_SEED, AIOPS_TELEMETRY_ID } from '../../../common/constants'; import { - addSignificantTermsAction, - addSignificantTermsGroupAction, - addSignificantTermsGroupHistogramAction, - addSignificantTermsHistogramAction, + addSignificantItemsAction, + addSignificantItemsGroupAction, + addSignificantItemsGroupHistogramAction, + addSignificantItemsHistogramAction, addErrorAction, pingAction, resetAllAction, @@ -65,7 +65,7 @@ import { fetchFrequentItemSets } from './queries/fetch_frequent_item_sets'; import { fetchTerms2CategoriesCounts } from './queries/fetch_terms_2_categories_counts'; import { getHistogramQuery } from './queries/get_histogram_query'; import { getGroupFilter } from './queries/get_group_filter'; -import { getSignificantTermGroups } from './queries/get_significant_term_groups'; +import { getSignificantItemGroups } from './queries/get_significant_item_groups'; import { trackAIOpsRouteUsage } from '../../lib/track_route_usage'; // 10s ping frequency to keep the stream alive. @@ -287,19 +287,19 @@ export function routeHandlerFactory( } } - // Step 2: Significant Categories and Terms + // Step 2: Significant Categories and Items // This will store the combined count of detected significant log patterns and keywords let fieldValuePairsCount = 0; - const significantCategories: SignificantTerm[] = []; + const significantCategories: SignificantItem[] = []; if (version === '1') { significantCategories.push( ...(( request.body as AiopsLogRateAnalysisSchema<'1'> ).overrides?.significantTerms?.filter( - (d) => d.type === SIGNIFICANT_TERM_TYPE.LOG_PATTERN + (d) => d.type === SIGNIFICANT_ITEM_TYPE.LOG_PATTERN ) ?? []) ); } @@ -309,7 +309,7 @@ export function routeHandlerFactory( ...(( request.body as AiopsLogRateAnalysisSchema<'2'> ).overrides?.significantItems?.filter( - (d) => d.type === SIGNIFICANT_TERM_TYPE.LOG_PATTERN + (d) => d.type === SIGNIFICANT_ITEM_TYPE.LOG_PATTERN ) ?? []) ); } @@ -329,18 +329,18 @@ export function routeHandlerFactory( ); if (significantCategories.length > 0) { - push(addSignificantTermsAction(significantCategories, version)); + push(addSignificantItemsAction(significantCategories, version)); } } - const significantTerms: SignificantTerm[] = []; + const significantTerms: SignificantItem[] = []; if (version === '1') { significantTerms.push( ...(( request.body as AiopsLogRateAnalysisSchema<'1'> ).overrides?.significantTerms?.filter( - (d) => d.type === SIGNIFICANT_TERM_TYPE.KEYWORD + (d) => d.type === SIGNIFICANT_ITEM_TYPE.KEYWORD ) ?? []) ); } @@ -350,7 +350,7 @@ export function routeHandlerFactory( ...(( request.body as AiopsLogRateAnalysisSchema<'2'> ).overrides?.significantItems?.filter( - (d) => d.type === SIGNIFICANT_TERM_TYPE.KEYWORD + (d) => d.type === SIGNIFICANT_ITEM_TYPE.KEYWORD ) ?? []) ); } @@ -411,7 +411,7 @@ export function routeHandlerFactory( }); significantTerms.push(...pValues); - push(addSignificantTermsAction(pValues, version)); + push(addSignificantItemsAction(pValues, version)); } push( @@ -571,7 +571,7 @@ export function routeHandlerFactory( } if (fields.length > 0 && itemSets.length > 0) { - const significantTermGroups = getSignificantTermGroups( + const significantItemGroups = getSignificantItemGroups( itemSets, [...significantTerms, ...significantCategories], fields @@ -579,10 +579,10 @@ export function routeHandlerFactory( // We'll find out if there's at least one group with at least two items, // only then will we return the groups to the clients and make the grouping option available. - const maxItems = Math.max(...significantTermGroups.map((g) => g.group.length)); + const maxItems = Math.max(...significantItemGroups.map((g) => g.group.length)); if (maxItems > 1) { - push(addSignificantTermsGroupAction(significantTermGroups, version)); + push(addSignificantItemsGroupAction(significantItemGroups, version)); } loaded += PROGRESS_STEP_GROUPING; @@ -595,9 +595,9 @@ export function routeHandlerFactory( return; } - logDebugMessage(`Fetch ${significantTermGroups.length} group histograms.`); + logDebugMessage(`Fetch ${significantItemGroups.length} group histograms.`); - const groupHistogramQueue = queue(async function (cpg: SignificantTermGroup) { + const groupHistogramQueue = queue(async function (cpg: SignificantItemGroup) { if (shouldStop) { logDebugMessage('shouldStop abort fetching group histograms.'); groupHistogramQueue.kill(); @@ -644,7 +644,7 @@ export function routeHandlerFactory( } return; } - const histogram: SignificantTermHistogramItem[] = + const histogram: SignificantItemHistogramItem[] = overallTimeSeries.data.map((o) => { const current = cpgTimeSeries.data.find( (d1) => d1.key_as_string === o.key_as_string @@ -670,7 +670,7 @@ export function routeHandlerFactory( }) ?? []; push( - addSignificantTermsGroupHistogramAction( + addSignificantItemsGroupHistogramAction( [ { id: cpg.id, @@ -683,7 +683,7 @@ export function routeHandlerFactory( } }, MAX_CONCURRENT_QUERIES); - groupHistogramQueue.push(significantTermGroups); + groupHistogramQueue.push(significantItemGroups); await groupHistogramQueue.drain(); } } catch (e) { @@ -706,7 +706,7 @@ export function routeHandlerFactory( overallTimeSeries !== undefined && !request.body.overrides?.regroupOnly ) { - const fieldValueHistogramQueue = queue(async function (cp: SignificantTerm) { + const fieldValueHistogramQueue = queue(async function (cp: SignificantItem) { if (shouldStop) { logDebugMessage('shouldStop abort fetching field/value histograms.'); fieldValueHistogramQueue.kill(); @@ -759,7 +759,7 @@ export function routeHandlerFactory( return; } - const histogram: SignificantTermHistogramItem[] = + const histogram: SignificantItemHistogramItem[] = overallTimeSeries.data.map((o) => { const current = cpTimeSeries.data.find( (d1) => d1.key_as_string === o.key_as_string @@ -788,7 +788,7 @@ export function routeHandlerFactory( loaded += (1 / fieldValuePairsCount) * PROGRESS_STEP_HISTOGRAMS; pushHistogramDataLoadingState(); push( - addSignificantTermsHistogramAction( + addSignificantItemsHistogramAction( [ { fieldName, @@ -863,7 +863,7 @@ export function routeHandlerFactory( return; } - const histogram: SignificantTermHistogramItem[] = + const histogram: SignificantItemHistogramItem[] = overallTimeSeries.data.map((o) => { const current = catTimeSeries.data.find( (d1) => d1.key_as_string === o.key_as_string @@ -893,7 +893,7 @@ export function routeHandlerFactory( loaded += (1 / fieldValuePairsCount) * PROGRESS_STEP_HISTOGRAMS; pushHistogramDataLoadingState(); push( - addSignificantTermsHistogramAction( + addSignificantItemsHistogramAction( [ { fieldName, diff --git a/x-pack/plugins/data_visualizer/public/application/common/hooks/use_document_count_stats.ts b/x-pack/plugins/data_visualizer/public/application/common/hooks/use_document_count_stats.ts index 87a9a37d4d2c5..93bf37eb83916 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/hooks/use_document_count_stats.ts +++ b/x-pack/plugins/data_visualizer/public/application/common/hooks/use_document_count_stats.ts @@ -10,7 +10,7 @@ import { stringHash } from '@kbn/ml-string-hash'; import { extractErrorProperties } from '@kbn/ml-error-utils'; import { Query } from '@kbn/es-query'; import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { SignificantTerm } from '@kbn/ml-agg-utils'; +import { SignificantItem } from '@kbn/ml-agg-utils'; import { createRandomSamplerWrapper, RandomSampler, @@ -50,8 +50,8 @@ export interface DocumentStatsSearchStrategyParams { timeFieldName?: string; runtimeFieldMap?: estypes.MappingRuntimeFields; fieldsToFetch?: string[]; - selectedSignificantTerm?: SignificantTerm; - includeSelectedSignificantTerm?: boolean; + selectedSignificantItem?: SignificantItem; + includeSelectedSignificantItem?: boolean; trackTotalHits?: boolean; } @@ -167,8 +167,8 @@ export interface DocumentStatsSearchStrategyParams { timeFieldName?: string; runtimeFieldMap?: estypes.MappingRuntimeFields; fieldsToFetch?: string[]; - selectedSignificantTerm?: SignificantTerm; - includeSelectedSignificantTerm?: boolean; + selectedSignificantItem?: SignificantItem; + includeSelectedSignificantItem?: boolean; trackTotalHits?: boolean; } diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_details_app_section/components/log_rate_analysis.tsx b/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_details_app_section/components/log_rate_analysis.tsx index b9ff53140b02c..a8ebb5c884b6b 100644 --- a/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_details_app_section/components/log_rate_analysis.tsx +++ b/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_details_app_section/components/log_rate_analysis.tsx @@ -162,7 +162,7 @@ export const LogRateAnalysis: FC = ({ r const onAnalysisCompleted = (analysisResults: LogRateAnalysisResultsData | undefined) => { const significantFieldValues = orderBy( - analysisResults?.significantTerms?.map((item) => ({ + analysisResults?.significantItems?.map((item) => ({ field: item.fieldName, value: item.fieldValue, docCount: item.doc_count, diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index a5ed852718005..8edd2b48ce470 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -7956,7 +7956,7 @@ "xpack.aiops.logRateAnalysis.resultsTable.impactLabelColumnTooltip": "Le niveau d'impact du champ sur la différence de taux de messages.", "xpack.aiops.logRateAnalysis.resultsTable.linksMenu.copyToClipboardButtonLabel": "Copier dans le presse-papiers", "xpack.aiops.logRateAnalysis.resultsTable.linksMenu.copyToClipboardGroupMessage": "Copier les éléments de groupe en tant que syntaxe KQL dans le Presse-papiers", - "xpack.aiops.logRateAnalysis.resultsTable.linksMenu.copyToClipboardSignificantTermMessage": "Copier la paire clé-valeur en tant que syntaxe KQL dans le Presse-papiers", + "xpack.aiops.logRateAnalysis.resultsTable.linksMenu.copyToClipboardSignificantItemMessage": "Copier la paire clé-valeur en tant que syntaxe KQL dans le Presse-papiers", "xpack.aiops.logRateAnalysis.resultsTable.linksMenu.viewInDiscover": "Afficher dans Discover", "xpack.aiops.logRateAnalysis.resultsTable.linksMenu.viewInLogPatternAnalysis": "Voir dans l'analyse du modèle de log", "xpack.aiops.logRateAnalysis.resultsTable.logPatternLinkNotAvailableTooltipMessage": "Le lien n'est pas disponible si l'élément du tableau est lui-même un modèle de log.", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 58375f665fa0c..0451262451432 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -7971,7 +7971,7 @@ "xpack.aiops.logRateAnalysis.resultsTable.impactLabelColumnTooltip": "メッセージレート差異に対するフィールドの影響のレベル。", "xpack.aiops.logRateAnalysis.resultsTable.linksMenu.copyToClipboardButtonLabel": "クリップボードにコピー", "xpack.aiops.logRateAnalysis.resultsTable.linksMenu.copyToClipboardGroupMessage": "グループアイテムをKQL構文としてクリップボードにコピー", - "xpack.aiops.logRateAnalysis.resultsTable.linksMenu.copyToClipboardSignificantTermMessage": "フィールド/値のペアをKQL構文としてクリップボードにコピー", + "xpack.aiops.logRateAnalysis.resultsTable.linksMenu.copyToClipboardSignificantItemMessage": "フィールド/値のペアをKQL構文としてクリップボードにコピー", "xpack.aiops.logRateAnalysis.resultsTable.linksMenu.viewInDiscover": "Discoverに表示", "xpack.aiops.logRateAnalysis.resultsTable.linksMenu.viewInLogPatternAnalysis": "ログパターン分析で表示", "xpack.aiops.logRateAnalysis.resultsTable.logPatternLinkNotAvailableTooltipMessage": "テーブル項目がログパターン自体の場合は、このリンクは使用できません。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 003312fef8d19..90a4ae742e05d 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -7970,7 +7970,7 @@ "xpack.aiops.logRateAnalysis.resultsTable.impactLabelColumnTooltip": "字段对消息速率差异的影响水平。", "xpack.aiops.logRateAnalysis.resultsTable.linksMenu.copyToClipboardButtonLabel": "复制到剪贴板", "xpack.aiops.logRateAnalysis.resultsTable.linksMenu.copyToClipboardGroupMessage": "将组项目作为 KQL 语法复制到剪贴板", - "xpack.aiops.logRateAnalysis.resultsTable.linksMenu.copyToClipboardSignificantTermMessage": "将字段/值对作为 KQL 语法复制到剪贴板", + "xpack.aiops.logRateAnalysis.resultsTable.linksMenu.copyToClipboardSignificantItemMessage": "将字段/值对作为 KQL 语法复制到剪贴板", "xpack.aiops.logRateAnalysis.resultsTable.linksMenu.viewInDiscover": "在 Discover 中查看", "xpack.aiops.logRateAnalysis.resultsTable.linksMenu.viewInLogPatternAnalysis": "在日志模式分析中查看", "xpack.aiops.logRateAnalysis.resultsTable.logPatternLinkNotAvailableTooltipMessage": "如果表项目为日志模式本身,则此链接不可用。", diff --git a/x-pack/test/api_integration/apis/aiops/log_rate_analysis_full_analysis.ts b/x-pack/test/api_integration/apis/aiops/log_rate_analysis_full_analysis.ts index 4c3d787402864..3168dfdfc5ecf 100644 --- a/x-pack/test/api_integration/apis/aiops/log_rate_analysis_full_analysis.ts +++ b/x-pack/test/api_integration/apis/aiops/log_rate_analysis_full_analysis.ts @@ -70,8 +70,8 @@ export default ({ getService }: FtrProviderContext) => { ); expect(significantItems).to.eql( - testData.expected.significantTerms, - 'Significant terms do not match expected values.' + testData.expected.significantItems, + 'Significant items do not match expected values.' ); const histogramActions = getHistogramActions(data, apiVersion); diff --git a/x-pack/test/api_integration/apis/aiops/log_rate_analysis_groups_only.ts b/x-pack/test/api_integration/apis/aiops/log_rate_analysis_groups_only.ts index 3ffcf7b04fdd2..fcc4bafabdf3c 100644 --- a/x-pack/test/api_integration/apis/aiops/log_rate_analysis_groups_only.ts +++ b/x-pack/test/api_integration/apis/aiops/log_rate_analysis_groups_only.ts @@ -41,7 +41,7 @@ export default ({ getService }: FtrProviderContext) => { overrides = { loaded: 0, remainingFieldCandidates: [], - significantTerms: testData.expected.significantTerms, + significantTerms: testData.expected.significantItems, regroupOnly: true, } as AiopsLogRateAnalysisSchema['overrides']; } @@ -51,7 +51,7 @@ export default ({ getService }: FtrProviderContext) => { loaded: 0, remainingFieldCandidates: [], significantItems: testData.expected - .significantTerms as AiopsLogRateAnalysisSchemaSignificantItem[], + .significantItems as AiopsLogRateAnalysisSchemaSignificantItem[], regroupOnly: true, } as AiopsLogRateAnalysisSchema['overrides']; } diff --git a/x-pack/test/api_integration/apis/aiops/test_data.ts b/x-pack/test/api_integration/apis/aiops/test_data.ts index f323900e3437c..291779ed6c7b2 100644 --- a/x-pack/test/api_integration/apis/aiops/test_data.ts +++ b/x-pack/test/api_integration/apis/aiops/test_data.ts @@ -10,8 +10,8 @@ // that also the jest unit tests use mocks that are not outdated. import { significantTerms as artificialLogSignificantTerms } from '@kbn/aiops-plugin/common/__mocks__/artificial_logs/significant_terms'; import { significantLogPatterns as artificialLogSignificantLogPatterns } from '@kbn/aiops-plugin/common/__mocks__/artificial_logs/significant_log_patterns'; -import { finalSignificantTermGroups as artificialLogsSignificantTermGroups } from '@kbn/aiops-plugin/common/__mocks__/artificial_logs/final_significant_term_groups'; -import { finalSignificantTermGroupsTextfield as artificialLogsSignificantTermGroupsTextfield } from '@kbn/aiops-plugin/common/__mocks__/artificial_logs/final_significant_term_groups_textfield'; +import { finalSignificantItemGroups as artificialLogsSignificantItemGroups } from '@kbn/aiops-plugin/common/__mocks__/artificial_logs/final_significant_item_groups'; +import { finalSignificantItemGroupsTextfield as artificialLogsSignificantItemGroupsTextfield } from '@kbn/aiops-plugin/common/__mocks__/artificial_logs/final_significant_item_groups_textfield'; import type { AiopsLogRateAnalysisSchema, @@ -45,7 +45,7 @@ export const getLogRateAnalysisTestData = (): Array(): Array(): Array(): Array(): Array { actionsLengthGroupOnly: number; noIndexChunksLength: number; noIndexActionsLength: number; - significantTerms: SignificantTerm[]; - groups: SignificantTermGroup[]; + significantItems: SignificantItem[]; + groups: SignificantItemGroup[]; histogramLength: number; }; } From 02ccb7788a82ffe1c1fd8d6800ca5aeccc139e4e Mon Sep 17 00:00:00 2001 From: Ignacio Rivas Date: Fri, 10 Nov 2023 18:59:49 +0100 Subject: [PATCH 08/12] [Index Management] Add data retention to component template UI (#170837) --- .../common/types/component_templates.ts | 3 + .../component_template_create.test.tsx | 25 +++++- .../component_template_details.test.ts | 2 + .../component_template_details.helpers.ts | 1 + .../component_template_form.helpers.ts | 22 ++++- .../tab_summary.tsx | 19 ++++- .../component_template_form.tsx | 22 ++++- .../steps/step_logistics.tsx | 69 +++++++++++++++ .../steps/step_logistics_schema.tsx | 83 +++++++++++++++++++ .../steps/step_review.tsx | 13 +++ .../component_templates/shared_imports.ts | 2 + .../component_templates/register_get_route.ts | 4 +- .../component_templates/schema_validation.ts | 6 ++ .../index_management/component_templates.ts | 4 + 14 files changed, 267 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/index_management/common/types/component_templates.ts b/x-pack/plugins/index_management/common/types/component_templates.ts index d2842b0486303..68c58aefc9d06 100644 --- a/x-pack/plugins/index_management/common/types/component_templates.ts +++ b/x-pack/plugins/index_management/common/types/component_templates.ts @@ -8,15 +8,18 @@ import { IndexSettings } from './indices'; import { Aliases } from './aliases'; import { Mappings } from './mappings'; +import { DataStream, DataRetention } from '.'; export interface ComponentTemplateSerialized { template: { settings?: IndexSettings; aliases?: Aliases; mappings?: Mappings; + lifecycle?: DataStream['lifecycle']; }; version?: number; _meta?: { [key: string]: any }; + lifecycle?: DataRetention; } export interface ComponentTemplateDeserialized extends ComponentTemplateSerialized { diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_create.test.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_create.test.tsx index a8ef1278f4d73..eaf9c8389ae95 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_create.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_create.test.tsx @@ -12,6 +12,7 @@ import { breadcrumbService, IndexManagementBreadcrumb } from '../../../../servic import { setupEnvironment } from './helpers'; import { API_BASE_PATH } from './helpers/constants'; import { setup, ComponentTemplateCreateTestBed } from './helpers/component_template_create.helpers'; +import { serializeAsESLifecycle } from '../../../../../../common/lib/data_stream_serialization'; jest.mock('@kbn/kibana-react-plugin/public', () => { const original = jest.requireActual('@kbn/kibana-react-plugin/public'); @@ -98,6 +99,19 @@ describe('', () => { expect(exists('metaEditor')).toBe(true); }); + test('should toggle the data retention field', async () => { + const { exists, component, form } = testBed; + + expect(exists('valueDataRetentionField')).toBe(false); + + await act(async () => { + form.toggleEuiSwitch('dataRetentionToggle.input'); + }); + component.update(); + + expect(exists('valueDataRetentionField')).toBe(true); + }); + describe('Validation', () => { test('should require a name', async () => { const { form, actions, component, find } = testBed; @@ -120,6 +134,11 @@ describe('', () => { const COMPONENT_TEMPLATE_NAME = 'comp-1'; const SETTINGS = { number_of_shards: 1 }; const ALIASES = { my_alias: {} }; + const LIFECYCLE = { + enabled: true, + value: 2, + unit: 'd', + }; const BOOLEAN_MAPPING_FIELD = { name: 'boolean_datatype', @@ -136,7 +155,10 @@ describe('', () => { component.update(); // Complete step 1 (logistics) - await actions.completeStepLogistics({ name: COMPONENT_TEMPLATE_NAME }); + await actions.completeStepLogistics({ + name: COMPONENT_TEMPLATE_NAME, + lifecycle: LIFECYCLE, + }); // Complete step 2 (index settings) await actions.completeStepSettings(SETTINGS); @@ -199,6 +221,7 @@ describe('', () => { }, }, aliases: ALIASES, + lifecycle: serializeAsESLifecycle(LIFECYCLE), }, _kbnMeta: { usedBy: [], isManaged: false }, }), diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_details.test.ts b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_details.test.ts index 95495af1272c3..501d14042def3 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_details.test.ts +++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_details.test.ts @@ -19,6 +19,7 @@ const COMPONENT_TEMPLATE: ComponentTemplateDeserialized = { mappings: { properties: { ip_address: { type: 'ip' } } }, aliases: { mydata: {} }, settings: { number_of_shards: 1 }, + lifecycle: { enabled: true, data_retention: '4d' }, }, version: 1, _meta: { description: 'component template test' }, @@ -72,6 +73,7 @@ describe('', () => { expect(exists('summaryTabContent.usedByTitle')).toBe(true); expect(exists('summaryTabContent.versionTitle')).toBe(true); expect(exists('summaryTabContent.metaTitle')).toBe(true); + expect(exists('summaryTabContent.dataRetentionTitle')).toBe(true); // [Settings tab] Navigate to tab and verify content act(() => { diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_details.helpers.ts b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_details.helpers.ts index e2f41bbe246f2..44a78e0d0666f 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_details.helpers.ts +++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_details.helpers.ts @@ -78,6 +78,7 @@ export type ComponentTemplateDetailsTestSubjects = | 'summaryTabContent.usedByTitle' | 'summaryTabContent.versionTitle' | 'summaryTabContent.metaTitle' + | 'summaryTabContent.dataRetentionTitle' | 'notInUseCallout' | 'aliasesTabContent' | 'noAliasesCallout' diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_form.helpers.ts b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_form.helpers.ts index 0db29dffff510..d809bb230ffaa 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_form.helpers.ts +++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_form.helpers.ts @@ -8,6 +8,7 @@ import { act } from 'react-dom/test-utils'; import { TestBed } from '@kbn/test-jest-helpers'; +import { DataRetention } from '../../../../../../../common'; interface MappingField { name: string; @@ -52,11 +53,28 @@ export const getFormActions = (testBed: TestBed) => { .simulate('click'); }; - const completeStepLogistics = async ({ name }: { name: string }) => { + const completeStepLogistics = async ({ + name, + lifecycle, + }: { + name: string; + lifecycle: DataRetention; + }) => { const { form, component } = testBed; // Add name field form.setInputValue('nameField.input', name); + if (lifecycle && lifecycle.enabled) { + act(() => { + form.toggleEuiSwitch('dataRetentionToggle.input'); + }); + component.update(); + + act(() => { + form.setInputValue('valueDataRetentionField', String(lifecycle.value)); + }); + } + await act(async () => { clickNextButton(); }); @@ -164,6 +182,8 @@ export type ComponentTemplateFormTestSubjects = | 'stepReview.content' | 'stepReview.summaryTab' | 'stepReview.requestTab' + | 'valueDataRetentionField' + | 'dataRetentionToggle.input' | 'versionField' | 'aliasesEditor' | 'mappingsEditor' diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/tab_summary.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/tab_summary.tsx index 3e9f0c38f2a52..cebd88cf75965 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/tab_summary.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/tab_summary.tsx @@ -19,6 +19,7 @@ import { EuiLink, } from '@elastic/eui'; +import { getLifecycleValue } from '../../../lib/data_streams'; import { ComponentTemplateDeserialized } from '../shared_imports'; import { useComponentTemplatesContext } from '../component_templates_context'; @@ -27,13 +28,15 @@ interface Props { showCallToAction?: boolean; } +const INFINITE_AS_ICON = true; + export const TabSummary: React.FunctionComponent = ({ componentTemplateDetails, showCallToAction, }) => { const { getUrlForApp } = useComponentTemplatesContext(); - const { version, _meta, _kbnMeta } = componentTemplateDetails; + const { version, _meta, _kbnMeta, template } = componentTemplateDetails; const { usedBy } = _kbnMeta; const templateIsInUse = usedBy.length > 0; @@ -118,6 +121,20 @@ export const TabSummary: React.FunctionComponent = ({ )} + {template.lifecycle && ( + <> + + + + + {getLifecycleValue(template.lifecycle, INFINITE_AS_ICON)} + + + )} + {/* Version (optional) */} {typeof version !== 'undefined' && ( <> diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_form/component_template_form.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_form/component_template_form.tsx index 6ec857e9c2c30..38814ece0c17c 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_form/component_template_form.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_form/component_template_form.tsx @@ -19,6 +19,10 @@ import { StepMappingsContainer, StepAliasesContainer, } from '../../shared_imports'; +import { + serializeAsESLifecycle, + deserializeESLifecycle, +} from '../../../../../../common/lib/data_stream_serialization'; import { useComponentTemplatesContext } from '../../component_templates_context'; import { StepLogisticsContainer, StepReviewContainer } from './steps'; @@ -96,14 +100,17 @@ export const ComponentTemplateForm = ({ onStepChange, }: Props) => { const { - template: { settings, mappings, aliases }, + template: { settings, mappings, aliases, lifecycle }, ...logistics } = defaultValue; const { documentation } = useComponentTemplatesContext(); const wizardDefaultValue: WizardContent = { - logistics, + logistics: { + ...logistics, + ...(lifecycle ? { lifecycle: deserializeESLifecycle(lifecycle) } : {}), + }, settings, mappings, aliases, @@ -162,6 +169,10 @@ export const ComponentTemplateForm = ({ delete outputTemplate.template.aliases; } + if (outputTemplate.lifecycle) { + delete outputTemplate.lifecycle; + } + return outputTemplate; }; @@ -177,9 +188,14 @@ export const ComponentTemplateForm = ({ settings: wizardData.settings, mappings: wizardData.mappings, aliases: wizardData.aliases, + lifecycle: wizardData.logistics.lifecycle + ? serializeAsESLifecycle(wizardData.logistics.lifecycle) + : undefined, }, }; - return cleanupComponentTemplateObject(outputComponentTemplate); + return cleanupComponentTemplateObject( + outputComponentTemplate as ComponentTemplateDeserialized + ); }, [] ); diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_form/steps/step_logistics.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_form/steps/step_logistics.tsx index 745f2839c5f69..f892e650c1e1b 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_form/steps/step_logistics.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_form/steps/step_logistics.tsx @@ -24,8 +24,12 @@ import { getFormRow, Field, Forms, + NumericField, JsonEditorField, + useFormData, } from '../../../shared_imports'; +import { DataRetention } from '../../../../../../../common'; +import { UnitField, timeUnits } from '../../../../shared'; import { useComponentTemplatesContext } from '../../../component_templates_context'; import { logisticsFormSchema } from './step_logistics_schema'; @@ -48,6 +52,13 @@ export const StepLogistics: React.FunctionComponent = React.memo( const { isValid: isFormValid, submit, getFormData, subscribe } = form; + const [{ lifecycle }] = useFormData<{ + lifecycle: DataRetention; + }>({ + form, + watch: ['lifecycle.enabled', 'lifecycle.infiniteDataRetention'], + }); + const { documentation } = useComponentTemplatesContext(); const [isMetaVisible, setIsMetaVisible] = useState( @@ -134,6 +145,64 @@ export const StepLogistics: React.FunctionComponent = React.memo( /> + {/* Data retention field */} + + } + description={ + <> + + + + + } + > + {lifecycle?.enabled && ( + + } + componentProps={{ + euiFieldProps: { + disabled: lifecycle?.infiniteDataRetention, + 'data-test-subj': 'valueDataRetentionField', + min: 1, + append: ( + + ), + }, + }} + /> + )} + + {/* version field */} { + // If infiniteRetentionPeriod is set, we dont need to validate the data retention field + if (formData['lifecycle.infiniteDataRetention']) { + return undefined; + } + + if (!value) { + return { + message: i18n.translate( + 'xpack.idxMgmt.dataStreamsDetailsPanel.stepLogistics.dataRetentionFieldRequiredError', + { + defaultMessage: 'A data retention value is required.', + } + ), + }; + } + + if (value <= 0) { + return { + message: i18n.translate( + 'xpack.idxMgmt.dataStreamsDetailsPanel.stepLogistics.dataRetentionFieldNonNegativeError', + { + defaultMessage: `A positive value is required.`, + } + ), + }; + } + + if (value % 1 !== 0) { + return { + message: i18n.translate( + 'xpack.idxMgmt.dataStreamsDetailsPanel.stepLogistics.dataRetentionFieldDecimalError', + { + defaultMessage: `The value should be an integer number.`, + } + ), + }; + } + }, + }, + ], + }, + 'lifecycle.unit': { + type: FIELD_TYPES.TEXT, + label: i18n.translate( + 'xpack.idxMgmt.componentTemplateForm.stepLogistics.fieldDataRetentionUnitLabel', + { + defaultMessage: 'Time unit', + } + ), + defaultValue: 'd', + }, version: { type: FIELD_TYPES.NUMBER, label: i18n.translate('xpack.idxMgmt.componentTemplateForm.stepLogistics.versionFieldLabel', { diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_form/steps/step_review.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_form/steps/step_review.tsx index 159f25ab010b7..72746d426d2e1 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_form/steps/step_review.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_form/steps/step_review.tsx @@ -28,7 +28,9 @@ import { serializeComponentTemplate, } from '../../../shared_imports'; import { MANAGED_BY_FLEET } from '../../../constants'; +import { getLifecycleValue } from '../../../../../lib/data_streams'; +const INFINITE_AS_ICON = true; const { stripEmptyFields } = serializers; const getDescriptionText = (data: any) => { @@ -123,6 +125,17 @@ export const StepReview: React.FunctionComponent = React.memo( {getDescriptionText(serializedTemplate?.aliases)} + + {/* Data retention */} + + + + + {getLifecycleValue(serializedTemplate?.lifecycle, INFINITE_AS_ICON)} + {isFleetDatastreamsVisible && dataStreams && ( diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/shared_imports.ts b/x-pack/plugins/index_management/public/application/components/component_templates/shared_imports.ts index 78dd0395b6e34..d70ed32e2cdf8 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/shared_imports.ts +++ b/x-pack/plugins/index_management/public/application/components/component_templates/shared_imports.ts @@ -41,12 +41,14 @@ export { useForm, Form, getUseField, + useFormData, } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; export { getFormRow, Field, JsonEditorField, + NumericField, } from '@kbn/es-ui-shared-plugin/static/forms/components'; export { isJSON } from '@kbn/es-ui-shared-plugin/static/validators/string'; diff --git a/x-pack/plugins/index_management/server/routes/api/component_templates/register_get_route.ts b/x-pack/plugins/index_management/server/routes/api/component_templates/register_get_route.ts index 73224c0356ad5..2ccc3919e377a 100644 --- a/x-pack/plugins/index_management/server/routes/api/component_templates/register_get_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/component_templates/register_get_route.ts @@ -33,9 +33,9 @@ export function registerGetAllRoute({ router, lib: { handleEsError } }: RouteDep const { index_templates: indexTemplates } = await client.asCurrentUser.indices.getIndexTemplate(); - const body = componentTemplates.map((componentTemplate: ComponentTemplateFromEs) => { + const body = componentTemplates.map((componentTemplate) => { const deserializedComponentTemplateListItem = deserializeComponentTemplateList( - componentTemplate, + componentTemplate as ComponentTemplateFromEs, // @ts-expect-error TemplateSerialized.index_patterns not compatible with IndicesIndexTemplate.index_patterns indexTemplates ); diff --git a/x-pack/plugins/index_management/server/routes/api/component_templates/schema_validation.ts b/x-pack/plugins/index_management/server/routes/api/component_templates/schema_validation.ts index c436c70ba7a46..586e9db911028 100644 --- a/x-pack/plugins/index_management/server/routes/api/component_templates/schema_validation.ts +++ b/x-pack/plugins/index_management/server/routes/api/component_templates/schema_validation.ts @@ -13,6 +13,12 @@ export const componentTemplateSchema = schema.object({ settings: schema.maybe(schema.object({}, { unknowns: 'allow' })), aliases: schema.maybe(schema.object({}, { unknowns: 'allow' })), mappings: schema.maybe(schema.object({}, { unknowns: 'allow' })), + lifecycle: schema.maybe( + schema.object({ + enabled: schema.boolean(), + data_retention: schema.maybe(schema.string()), + }) + ), }), version: schema.maybe(schema.number()), _meta: schema.maybe(schema.object({}, { unknowns: 'allow' })), diff --git a/x-pack/test/api_integration/apis/management/index_management/component_templates.ts b/x-pack/test/api_integration/apis/management/index_management/component_templates.ts index 6e2b7ba71e86b..01e7ae46ce8fe 100644 --- a/x-pack/test/api_integration/apis/management/index_management/component_templates.ts +++ b/x-pack/test/api_integration/apis/management/index_management/component_templates.ts @@ -147,6 +147,10 @@ export default function ({ getService }: FtrProviderContext) { }, }, }, + lifecycle: { + enabled: true, + data_retention: '2d', + }, }, _meta: { description: 'set number of shards to one', From c62df52c0461dd6b12e5d9f8498b7dde45faa45d Mon Sep 17 00:00:00 2001 From: Achyut Jhunjhunwala Date: Fri, 10 Nov 2023 19:37:34 +0100 Subject: [PATCH 09/12] [Log Explorer] Add logic to display highlights in Flyout (#170650) ## Summary Closes https://github.com/elastic/kibana/issues/169504 ![Highlights](https://github.com/elastic/kibana/assets/7416358/06f21ac5-38e1-4521-843f-064c16ddd034) ## What's pending - [ ] FTR Tests --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- packages/kbn-optimizer/limits.yml | 2 +- .../plugins/log_explorer/common/constants.ts | 13 + .../flyout_detail/flyout_detail.tsx | 48 +-- .../flyout_detail/flyout_header.tsx | 46 +++ .../flyout_detail/flyout_highlights.tsx | 207 +++++++++++++ .../sub_components/highlight_container.tsx | 42 +++ .../sub_components/highlight_field.tsx | 103 +++++++ .../sub_components/highlight_section.tsx | 61 ++++ .../sub_components/hover_action.tsx | 86 ++++++ .../components/flyout_detail/translations.ts | 148 +++++++++ .../public/components/flyout_detail/types.ts | 35 ++- .../flyout_detail/use_doc_detail.ts | 61 +++- .../public/hooks/use_discover_action.ts | 17 + .../public/hooks/use_flyouot_column_width.tsx | 26 ++ .../apps/observability_log_explorer/flyout.ts | 4 +- .../flyout_highlights.ts | 283 +++++++++++++++++ .../apps/observability_log_explorer/index.ts | 1 + .../observability_log_explorer.ts | 36 ++- .../flyout_highlights.ts | 291 ++++++++++++++++++ .../observability_log_explorer/index.ts | 2 + 20 files changed, 1457 insertions(+), 55 deletions(-) create mode 100644 x-pack/plugins/log_explorer/public/components/flyout_detail/flyout_header.tsx create mode 100644 x-pack/plugins/log_explorer/public/components/flyout_detail/flyout_highlights.tsx create mode 100644 x-pack/plugins/log_explorer/public/components/flyout_detail/sub_components/highlight_container.tsx create mode 100644 x-pack/plugins/log_explorer/public/components/flyout_detail/sub_components/highlight_field.tsx create mode 100644 x-pack/plugins/log_explorer/public/components/flyout_detail/sub_components/highlight_section.tsx create mode 100644 x-pack/plugins/log_explorer/public/components/flyout_detail/sub_components/hover_action.tsx create mode 100644 x-pack/plugins/log_explorer/public/hooks/use_discover_action.ts create mode 100644 x-pack/plugins/log_explorer/public/hooks/use_flyouot_column_width.tsx create mode 100644 x-pack/test/functional/apps/observability_log_explorer/flyout_highlights.ts create mode 100644 x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/flyout_highlights.ts diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index a3f99b4f93186..d7a5d1c9d09f2 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -89,7 +89,7 @@ pageLoadAssetSize: licensing: 29004 links: 44490 lists: 22900 - logExplorer: 39045 + logExplorer: 54342 logsShared: 281060 logstash: 53548 management: 46112 diff --git a/x-pack/plugins/log_explorer/common/constants.ts b/x-pack/plugins/log_explorer/common/constants.ts index 4a93192b13acb..5aeb491b5a9d2 100644 --- a/x-pack/plugins/log_explorer/common/constants.ts +++ b/x-pack/plugins/log_explorer/common/constants.ts @@ -13,6 +13,19 @@ export const HOST_NAME_FIELD = 'host.name'; export const LOG_LEVEL_FIELD = 'log.level'; export const MESSAGE_FIELD = 'message'; export const SERVICE_NAME_FIELD = 'service.name'; +export const TRACE_ID_FIELD = 'trace.id'; + +export const AGENT_NAME_FIELD = 'agent.name'; +export const ORCHESTRATOR_CLUSTER_NAME_FIELD = 'orchestrator.cluster.name'; +export const ORCHESTRATOR_RESOURCE_ID_FIELD = 'orchestrator.resource.id'; +export const CLOUD_PROVIDER_FIELD = 'cloud.provider'; +export const CLOUD_REGION_FIELD = 'cloud.region'; +export const CLOUD_AVAILABILITY_ZONE_FIELD = 'cloud.availability_zone'; +export const CLOUD_PROJECT_ID_FIELD = 'cloud.project.id'; +export const CLOUD_INSTANCE_ID_FIELD = 'cloud.instance.id'; +export const LOG_FILE_PATH_FIELD = 'log.file.path'; +export const DATASTREAM_NAMESPACE_FIELD = 'data_stream.namespace'; +export const DATASTREAM_DATASET_FIELD = 'data_stream.dataset'; // Sizing export const DATA_GRID_COLUMN_WIDTH_SMALL = 240; diff --git a/x-pack/plugins/log_explorer/public/components/flyout_detail/flyout_detail.tsx b/x-pack/plugins/log_explorer/public/components/flyout_detail/flyout_detail.tsx index 012e5c914ed61..66c4788a32ae2 100644 --- a/x-pack/plugins/log_explorer/public/components/flyout_detail/flyout_detail.tsx +++ b/x-pack/plugins/log_explorer/public/components/flyout_detail/flyout_detail.tsx @@ -6,42 +6,22 @@ */ import React from 'react'; -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { LogLevel } from './sub_components/log_level'; -import { Timestamp } from './sub_components/timestamp'; import { FlyoutProps, LogDocument } from './types'; -import { getDocDetailRenderFlags, useDocDetail } from './use_doc_detail'; -import { Message } from './sub_components/message'; +import { useDocDetail } from './use_doc_detail'; +import { FlyoutHeader } from './flyout_header'; +import { FlyoutHighlights } from './flyout_highlights'; -export function FlyoutDetail({ dataView, doc }: Pick) { +export function FlyoutDetail({ + dataView, + doc, + actions, +}: Pick) { const parsedDoc = useDocDetail(doc as LogDocument, { dataView }); - const { hasTimestamp, hasLogLevel, hasMessage, hasBadges, hasFlyoutHeader } = - getDocDetailRenderFlags(parsedDoc); - - return hasFlyoutHeader ? ( - - - {hasBadges && ( - - {hasLogLevel && ( - - - - )} - {hasTimestamp && ( - - - - )} - - )} - - {hasMessage && ( - - - - )} - - ) : null; + return ( + <> + + + + ); } diff --git a/x-pack/plugins/log_explorer/public/components/flyout_detail/flyout_header.tsx b/x-pack/plugins/log_explorer/public/components/flyout_detail/flyout_header.tsx new file mode 100644 index 0000000000000..babca8f273860 --- /dev/null +++ b/x-pack/plugins/log_explorer/public/components/flyout_detail/flyout_header.tsx @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { FlyoutDoc } from './types'; +import { getDocDetailHeaderRenderFlags } from './use_doc_detail'; +import { LogLevel } from './sub_components/log_level'; +import { Timestamp } from './sub_components/timestamp'; +import { Message } from './sub_components/message'; +import * as constants from '../../../common/constants'; + +export function FlyoutHeader({ doc }: { doc: FlyoutDoc }) { + const { hasTimestamp, hasLogLevel, hasMessage, hasBadges, hasFlyoutHeader } = + getDocDetailHeaderRenderFlags(doc); + + return hasFlyoutHeader ? ( + + + {hasBadges && ( + + {hasLogLevel && ( + + + + )} + {hasTimestamp && ( + + + + )} + + )} + + {hasMessage && ( + + + + )} + + ) : null; +} diff --git a/x-pack/plugins/log_explorer/public/components/flyout_detail/flyout_highlights.tsx b/x-pack/plugins/log_explorer/public/components/flyout_detail/flyout_highlights.tsx new file mode 100644 index 0000000000000..74d65d73f23b8 --- /dev/null +++ b/x-pack/plugins/log_explorer/public/components/flyout_detail/flyout_highlights.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 React from 'react'; +import { FlyoutContentActions } from '@kbn/discover-plugin/public'; +import { DataTableRecord } from '@kbn/discover-utils/src/types'; +import { useMeasure } from 'react-use/lib'; +import { FlyoutDoc } from './types'; +import * as constants from '../../../common/constants'; +import { HighlightField } from './sub_components/highlight_field'; +import { + cloudAccordionTitle, + flyoutCloudAvailabilityZoneLabel, + flyoutCloudInstanceIdLabel, + flyoutCloudProjectIdLabel, + flyoutCloudProviderLabel, + flyoutCloudRegionLabel, + flyoutDatasetLabel, + flyoutHostNameLabel, + flyoutLogPathFileLabel, + flyoutNamespaceLabel, + flyoutOrchestratorClusterNameLabel, + flyoutOrchestratorResourceIdLabel, + flyoutServiceLabel, + flyoutShipperLabel, + flyoutTraceLabel, + infraAccordionTitle, + otherAccordionTitle, + serviceAccordionTitle, +} from './translations'; +import { HighlightSection } from './sub_components/highlight_section'; +import { DiscoverActionsProvider } from '../../hooks/use_discover_action'; +import { HighlightContainer } from './sub_components/highlight_container'; +import { useFlyoutColumnWidth } from '../../hooks/use_flyouot_column_width'; + +export function FlyoutHighlights({ + formattedDoc, + flattenedDoc, + actions, +}: { + formattedDoc: FlyoutDoc; + flattenedDoc: DataTableRecord['flattened']; + actions: FlyoutContentActions; +}) { + const [ref, dimensions] = useMeasure(); + const { columns, fieldWidth } = useFlyoutColumnWidth(dimensions.width); + return ( + + + + {formattedDoc[constants.SERVICE_NAME_FIELD] && ( + + )} + {formattedDoc[constants.TRACE_ID_FIELD] && ( + + )} + + + + {formattedDoc[constants.HOST_NAME_FIELD] && ( + + )} + {formattedDoc[constants.ORCHESTRATOR_CLUSTER_NAME_FIELD] && ( + + )} + {formattedDoc[constants.ORCHESTRATOR_RESOURCE_ID_FIELD] && ( + + )} + + + + {formattedDoc[constants.CLOUD_PROVIDER_FIELD] && ( + + )} + {formattedDoc[constants.CLOUD_REGION_FIELD] && ( + + )} + {formattedDoc[constants.CLOUD_AVAILABILITY_ZONE_FIELD] && ( + + )} + {formattedDoc[constants.CLOUD_PROJECT_ID_FIELD] && ( + + )} + {formattedDoc[constants.CLOUD_INSTANCE_ID_FIELD] && ( + + )} + + + + {formattedDoc[constants.LOG_FILE_PATH_FIELD] && ( + + )} + {formattedDoc[constants.DATASTREAM_NAMESPACE_FIELD] && ( + + )} + {formattedDoc[constants.DATASTREAM_DATASET_FIELD] && ( + + )} + {formattedDoc[constants.AGENT_NAME_FIELD] && ( + + )} + + + + ); +} diff --git a/x-pack/plugins/log_explorer/public/components/flyout_detail/sub_components/highlight_container.tsx b/x-pack/plugins/log_explorer/public/components/flyout_detail/sub_components/highlight_container.tsx new file mode 100644 index 0000000000000..9e00948699404 --- /dev/null +++ b/x-pack/plugins/log_explorer/public/components/flyout_detail/sub_components/highlight_container.tsx @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiHorizontalRule, EuiPanel } from '@elastic/eui'; + +interface HighlightContainerProps { + children: React.ReactNode; +} + +const hasNonUndefinedSubChild = (children: React.ReactNode[]): boolean => { + return children.some((child) => { + if (React.isValidElement(child)) { + const subChildren = React.Children.toArray(child.props.children); + return subChildren.some((subChild) => subChild !== undefined && subChild !== null); + } + return false; + }); +}; + +export const HighlightContainer = React.forwardRef( + ({ children }, ref) => { + const validChildren = React.Children.toArray(children).filter(Boolean); + const hasChildren = validChildren.length > 0; + const shouldRender = hasChildren && hasNonUndefinedSubChild(validChildren); + + const flexChildren = validChildren.map((child, idx) =>
{child}
); + + return shouldRender ? ( +
+ + + {flexChildren} + +
+ ) : null; + } +); diff --git a/x-pack/plugins/log_explorer/public/components/flyout_detail/sub_components/highlight_field.tsx b/x-pack/plugins/log_explorer/public/components/flyout_detail/sub_components/highlight_field.tsx new file mode 100644 index 0000000000000..ca47b10548236 --- /dev/null +++ b/x-pack/plugins/log_explorer/public/components/flyout_detail/sub_components/highlight_field.tsx @@ -0,0 +1,103 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiFlexGroup, EuiFlexItem, EuiText, copyToClipboard } from '@elastic/eui'; +import React, { ReactNode, useMemo, useState } from 'react'; +import { HoverAction, HoverActionType } from './hover_action'; +import { + flyoutHoverActionFilterForText, + flyoutHoverActionFilterOutText, + flyoutHoverActionFilterForFieldPresentText, + flyoutHoverActionToggleColumnText, + flyoutHoverActionCopyToClipboardText, +} from '../translations'; +import { useDiscoverActionsContext } from '../../../hooks/use_discover_action'; + +interface HighlightFieldProps { + label: string | ReactNode; + field: string; + value: unknown; + formattedValue: string; + dataTestSubj: string; + width: number; +} + +export function HighlightField({ + label, + field, + value, + formattedValue, + dataTestSubj, + width, +}: HighlightFieldProps) { + const filterForText = flyoutHoverActionFilterForText(value); + const filterOutText = flyoutHoverActionFilterOutText(value); + const actions = useDiscoverActionsContext(); + const [columnAdded, setColumnAdded] = useState(false); + + const hoverActions: HoverActionType[] = useMemo( + () => [ + { + id: 'addToFilterAction', + tooltipContent: filterForText, + iconType: 'plusInCircle', + onClick: () => actions?.addFilter && actions.addFilter(field, value, '+'), + display: true, + }, + { + id: 'removeFromFilterAction', + tooltipContent: filterOutText, + iconType: 'minusInCircle', + onClick: () => actions?.addFilter && actions.addFilter(field, value, '-'), + display: true, + }, + { + id: 'filterForFieldPresentAction', + tooltipContent: flyoutHoverActionFilterForFieldPresentText, + iconType: 'filter', + onClick: () => actions?.addFilter && actions.addFilter('_exists_', field, '+'), + display: true, + }, + { + id: 'toggleColumnAction', + tooltipContent: flyoutHoverActionToggleColumnText, + iconType: 'listAdd', + onClick: () => { + if (actions) { + if (columnAdded) { + actions?.removeColumn?.(field); + } else { + actions?.addColumn?.(field); + } + setColumnAdded(!columnAdded); + } + }, + display: true, + }, + { + id: 'copyToClipboardAction', + tooltipContent: flyoutHoverActionCopyToClipboardText, + iconType: 'copyClipboard', + onClick: () => copyToClipboard(value as string), + display: true, + }, + ], + [filterForText, filterOutText, actions, field, value, columnAdded] + ); + return formattedValue ? ( + + + + {label} + + + + + + + ) : null; +} diff --git a/x-pack/plugins/log_explorer/public/components/flyout_detail/sub_components/highlight_section.tsx b/x-pack/plugins/log_explorer/public/components/flyout_detail/sub_components/highlight_section.tsx new file mode 100644 index 0000000000000..0b598339ed29b --- /dev/null +++ b/x-pack/plugins/log_explorer/public/components/flyout_detail/sub_components/highlight_section.tsx @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { + EuiAccordion, + EuiFlexGrid, + EuiHorizontalRule, + EuiTitle, + EuiFlexItem, + useGeneratedHtmlId, +} from '@elastic/eui'; + +interface HighlightSectionProps { + title: string; + children: React.ReactNode; + showBottomRule?: boolean; + columns: 1 | 2 | 3; +} + +export function HighlightSection({ + title, + children, + showBottomRule = true, + columns, +}: HighlightSectionProps) { + const validChildren = React.Children.toArray(children).filter(Boolean); + const shouldRenderSection = validChildren.length > 0; + const accordionTitle = ( + +

{title}

+
+ ); + + const flexChildren = validChildren.map((child, idx) => ( + {child} + )); + + const accordionId = useGeneratedHtmlId({ + prefix: title, + }); + + return shouldRenderSection ? ( + <> + + {flexChildren} + + {showBottomRule && } + + ) : null; +} diff --git a/x-pack/plugins/log_explorer/public/components/flyout_detail/sub_components/hover_action.tsx b/x-pack/plugins/log_explorer/public/components/flyout_detail/sub_components/hover_action.tsx new file mode 100644 index 0000000000000..5ed25be2b36d9 --- /dev/null +++ b/x-pack/plugins/log_explorer/public/components/flyout_detail/sub_components/hover_action.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 React from 'react'; + +import { + EuiFlexGroup, + EuiToolTip, + EuiButtonIcon, + useEuiTheme, + EuiTextTruncate, + EuiText, +} from '@elastic/eui'; +import type { IconType } from '@elastic/eui'; + +export interface HoverActionType { + id: string; + tooltipContent: string; + iconType: IconType; + onClick: () => void; + display: boolean; +} + +interface HoverActionProps { + displayText: string; + actions: HoverActionType[]; + width: number; +} + +export const HoverAction = ({ displayText, actions, width }: HoverActionProps) => { + const { euiTheme } = useEuiTheme(); + + return ( + + + {(truncatedText: string) => ( + + )} + + + {actions.map((action) => ( + + action.onClick()} + /> + + ))} + + + ); +}; diff --git a/x-pack/plugins/log_explorer/public/components/flyout_detail/translations.ts b/x-pack/plugins/log_explorer/public/components/flyout_detail/translations.ts index fcb42cf79a5dd..9c27e333a4bdc 100644 --- a/x-pack/plugins/log_explorer/public/components/flyout_detail/translations.ts +++ b/x-pack/plugins/log_explorer/public/components/flyout_detail/translations.ts @@ -10,3 +10,151 @@ import { i18n } from '@kbn/i18n'; export const flyoutMessageLabel = i18n.translate('xpack.logExplorer.flyoutDetail.label.message', { defaultMessage: 'Message', }); + +export const flyoutServiceLabel = i18n.translate('xpack.logExplorer.flyoutDetail.label.service', { + defaultMessage: 'Service', +}); + +export const flyoutTraceLabel = i18n.translate('xpack.logExplorer.flyoutDetail.label.trace', { + defaultMessage: 'Trace', +}); + +export const flyoutHostNameLabel = i18n.translate('xpack.logExplorer.flyoutDetail.label.hostName', { + defaultMessage: 'Host name', +}); + +export const serviceAccordionTitle = i18n.translate( + 'xpack.logExplorer.flyoutDetail.accordion.title.service', + { + defaultMessage: 'Service', + } +); + +export const infraAccordionTitle = i18n.translate( + 'xpack.logExplorer.flyoutDetail.accordion.title.infrastructure', + { + defaultMessage: 'Infrastructure', + } +); + +export const cloudAccordionTitle = i18n.translate( + 'xpack.logExplorer.flyoutDetail.accordion.title.cloud', + { + defaultMessage: 'Cloud', + } +); + +export const otherAccordionTitle = i18n.translate( + 'xpack.logExplorer.flyoutDetail.accordion.title.other', + { + defaultMessage: 'Other', + } +); + +export const flyoutOrchestratorClusterNameLabel = i18n.translate( + 'xpack.logExplorer.flyoutDetail.label.orchestratorClusterName', + { + defaultMessage: 'Orchestrator cluster Name', + } +); + +export const flyoutOrchestratorResourceIdLabel = i18n.translate( + 'xpack.logExplorer.flyoutDetail.label.orchestratorResourceId', + { + defaultMessage: 'Orchestrator resource ID', + } +); + +export const flyoutCloudProviderLabel = i18n.translate( + 'xpack.logExplorer.flyoutDetail.label.cloudProvider', + { + defaultMessage: 'Cloud provider', + } +); + +export const flyoutCloudRegionLabel = i18n.translate( + 'xpack.logExplorer.flyoutDetail.label.cloudRegion', + { + defaultMessage: 'Cloud region', + } +); + +export const flyoutCloudAvailabilityZoneLabel = i18n.translate( + 'xpack.logExplorer.flyoutDetail.label.cloudAvailabilityZone', + { + defaultMessage: 'Cloud availability zone', + } +); + +export const flyoutCloudProjectIdLabel = i18n.translate( + 'xpack.logExplorer.flyoutDetail.label.cloudProjectId', + { + defaultMessage: 'Cloud project ID', + } +); + +export const flyoutCloudInstanceIdLabel = i18n.translate( + 'xpack.logExplorer.flyoutDetail.label.cloudInstanceId', + { + defaultMessage: 'Cloud instance ID', + } +); + +export const flyoutLogPathFileLabel = i18n.translate( + 'xpack.logExplorer.flyoutDetail.label.logPathFile', + { + defaultMessage: 'Log path file', + } +); + +export const flyoutNamespaceLabel = i18n.translate( + 'xpack.logExplorer.flyoutDetail.label.namespace', + { + defaultMessage: 'Namespace', + } +); + +export const flyoutDatasetLabel = i18n.translate('xpack.logExplorer.flyoutDetail.label.dataset', { + defaultMessage: 'Dataset', +}); + +export const flyoutShipperLabel = i18n.translate('xpack.logExplorer.flyoutDetail.label.shipper', { + defaultMessage: 'Shipper', +}); + +export const flyoutHoverActionFilterForText = (text: unknown) => + i18n.translate('xpack.logExplorer.flyoutDetail.value.hover.filterFor', { + defaultMessage: 'Filter for this {value}', + values: { + value: text as string, + }, + }); + +export const flyoutHoverActionFilterOutText = (text: unknown) => + i18n.translate('xpack.logExplorer.flyoutDetail.value.hover.filterOut', { + defaultMessage: 'Filter out this {value}', + values: { + value: text as string, + }, + }); + +export const flyoutHoverActionFilterForFieldPresentText = i18n.translate( + 'xpack.logExplorer.flyoutDetail.value.hover.filterForFieldPresent', + { + defaultMessage: 'Filter for field present', + } +); + +export const flyoutHoverActionToggleColumnText = i18n.translate( + 'xpack.logExplorer.flyoutDetail.value.hover.toggleColumn', + { + defaultMessage: 'Toggle column in table', + } +); + +export const flyoutHoverActionCopyToClipboardText = i18n.translate( + 'xpack.logExplorer.flyoutDetail.value.hover.copyToClipboard', + { + defaultMessage: 'Copy to clipboard', + } +); diff --git a/x-pack/plugins/log_explorer/public/components/flyout_detail/types.ts b/x-pack/plugins/log_explorer/public/components/flyout_detail/types.ts index cf8cfb8170e21..28110024108da 100644 --- a/x-pack/plugins/log_explorer/public/components/flyout_detail/types.ts +++ b/x-pack/plugins/log_explorer/public/components/flyout_detail/types.ts @@ -5,7 +5,6 @@ * 2.0. */ -import type { EuiIconType } from '@elastic/eui/src/components/icon/icon'; import type { DataView } from '@kbn/data-views-plugin/common'; import type { FlyoutContentProps } from '@kbn/discover-plugin/public'; import type { DataTableRecord } from '@kbn/discover-utils/types'; @@ -19,6 +18,21 @@ export interface LogDocument extends DataTableRecord { '@timestamp': string; 'log.level'?: string; message?: string; + + 'host.name'?: string; + 'service.name'?: string; + 'trace.id'?: string; + 'agent.name'?: string; + 'orchestrator.cluster.name'?: string; + 'orchestrator.resource.id'?: string; + 'cloud.provider'?: string; + 'cloud.region'?: string; + 'cloud.availability_zone'?: string; + 'cloud.project.id'?: string; + 'cloud.instance.id'?: string; + 'log.file.path'?: string; + 'data_stream.namespace': string; + 'data_stream.dataset': string; }; } @@ -26,10 +40,19 @@ export interface FlyoutDoc { '@timestamp': string; 'log.level'?: string; message?: string; -} -export interface FlyoutHighlightField { - label: string; - value: string; - iconType?: EuiIconType; + 'host.name'?: string; + 'service.name'?: string; + 'trace.id'?: string; + 'agent.name'?: string; + 'orchestrator.cluster.name'?: string; + 'orchestrator.resource.id'?: string; + 'cloud.provider'?: string; + 'cloud.region'?: string; + 'cloud.availability_zone'?: string; + 'cloud.project.id'?: string; + 'cloud.instance.id'?: string; + 'log.file.path'?: string; + 'data_stream.namespace': string; + 'data_stream.dataset': string; } diff --git a/x-pack/plugins/log_explorer/public/components/flyout_detail/use_doc_detail.ts b/x-pack/plugins/log_explorer/public/components/flyout_detail/use_doc_detail.ts index 32e4bcd966745..938855f00f5a3 100644 --- a/x-pack/plugins/log_explorer/public/components/flyout_detail/use_doc_detail.ts +++ b/x-pack/plugins/log_explorer/public/components/flyout_detail/use_doc_detail.ts @@ -5,7 +5,8 @@ * 2.0. */ import { formatFieldValue } from '@kbn/discover-utils'; -import { LOG_LEVEL_FIELD, MESSAGE_FIELD, TIMESTAMP_FIELD } from '../../../common/constants'; +import he from 'he'; +import * as constants from '../../../common/constants'; import { useKibanaContextForPlugin } from '../../utils/use_kibana'; import { FlyoutDoc, FlyoutProps, LogDocument } from './types'; @@ -30,21 +31,59 @@ export function useDocDetail( ); }; - const level = formatField(LOG_LEVEL_FIELD)?.toLowerCase(); - const timestamp = formatField(TIMESTAMP_FIELD); - const message = formatField(MESSAGE_FIELD); + // Flyout Headers + const level = formatField(constants.LOG_LEVEL_FIELD)?.toLowerCase(); + const timestamp = formatField(constants.TIMESTAMP_FIELD); + const formattedMessage = formatField(constants.MESSAGE_FIELD); + const message = formattedMessage ? he.decode(formattedMessage) : undefined; + + // Service Highlights + const serviceName = formatField(constants.SERVICE_NAME_FIELD); + const traceId = formatField(constants.TRACE_ID_FIELD); + + // Infrastructure Highlights + const hostname = formatField(constants.HOST_NAME_FIELD); + const orchestratorClusterName = formatField(constants.ORCHESTRATOR_CLUSTER_NAME_FIELD); + const orchestratorResourceId = formatField(constants.ORCHESTRATOR_RESOURCE_ID_FIELD); + + // Cloud Highlights + const cloudProvider = formatField(constants.CLOUD_PROVIDER_FIELD); + const cloudRegion = formatField(constants.CLOUD_REGION_FIELD); + const cloudAz = formatField(constants.CLOUD_AVAILABILITY_ZONE_FIELD); + const cloudProjectId = formatField(constants.CLOUD_PROJECT_ID_FIELD); + const cloudInstanceId = formatField(constants.CLOUD_INSTANCE_ID_FIELD); + + // Other Highlights + const logFilePath = formatField(constants.LOG_FILE_PATH_FIELD); + const namespace = formatField(constants.DATASTREAM_NAMESPACE_FIELD); + const dataset = formatField(constants.DATASTREAM_DATASET_FIELD); + const agentName = formatField(constants.AGENT_NAME_FIELD); return { - [LOG_LEVEL_FIELD]: level, - [TIMESTAMP_FIELD]: timestamp, - [MESSAGE_FIELD]: message, + [constants.LOG_LEVEL_FIELD]: level, + [constants.TIMESTAMP_FIELD]: timestamp, + [constants.MESSAGE_FIELD]: message, + [constants.SERVICE_NAME_FIELD]: serviceName, + [constants.TRACE_ID_FIELD]: traceId, + [constants.HOST_NAME_FIELD]: hostname, + [constants.ORCHESTRATOR_CLUSTER_NAME_FIELD]: orchestratorClusterName, + [constants.ORCHESTRATOR_RESOURCE_ID_FIELD]: orchestratorResourceId, + [constants.CLOUD_PROVIDER_FIELD]: cloudProvider, + [constants.CLOUD_REGION_FIELD]: cloudRegion, + [constants.CLOUD_AVAILABILITY_ZONE_FIELD]: cloudAz, + [constants.CLOUD_PROJECT_ID_FIELD]: cloudProjectId, + [constants.CLOUD_INSTANCE_ID_FIELD]: cloudInstanceId, + [constants.LOG_FILE_PATH_FIELD]: logFilePath, + [constants.DATASTREAM_NAMESPACE_FIELD]: namespace, + [constants.DATASTREAM_DATASET_FIELD]: dataset, + [constants.AGENT_NAME_FIELD]: agentName, }; } -export const getDocDetailRenderFlags = (doc: FlyoutDoc) => { - const hasTimestamp = Boolean(doc['@timestamp']); - const hasLogLevel = Boolean(doc['log.level']); - const hasMessage = Boolean(doc.message); +export const getDocDetailHeaderRenderFlags = (doc: FlyoutDoc) => { + const hasTimestamp = Boolean(doc[constants.TIMESTAMP_FIELD]); + const hasLogLevel = Boolean(doc[constants.LOG_LEVEL_FIELD]); + const hasMessage = Boolean(doc[constants.MESSAGE_FIELD]); const hasBadges = hasTimestamp || hasLogLevel; diff --git a/x-pack/plugins/log_explorer/public/hooks/use_discover_action.ts b/x-pack/plugins/log_explorer/public/hooks/use_discover_action.ts new file mode 100644 index 0000000000000..ef1a1ae715ca6 --- /dev/null +++ b/x-pack/plugins/log_explorer/public/hooks/use_discover_action.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import createContainer from 'constate'; +import { FlyoutContentActions } from '@kbn/discover-plugin/public'; + +interface UseFlyoutActionsDeps { + value: FlyoutContentActions; +} + +const useDiscoverActions = ({ value }: UseFlyoutActionsDeps) => value; + +export const [DiscoverActionsProvider, useDiscoverActionsContext] = + createContainer(useDiscoverActions); diff --git a/x-pack/plugins/log_explorer/public/hooks/use_flyouot_column_width.tsx b/x-pack/plugins/log_explorer/public/hooks/use_flyouot_column_width.tsx new file mode 100644 index 0000000000000..53e626223b910 --- /dev/null +++ b/x-pack/plugins/log_explorer/public/hooks/use_flyouot_column_width.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 { useEuiTheme } from '@elastic/eui'; + +interface FlyoutColumnWidth { + columns: 1 | 2 | 3; + fieldWidth: number; +} + +export const useFlyoutColumnWidth = (width: number): FlyoutColumnWidth => { + const { euiTheme } = useEuiTheme(); + + const numberOfColumns = width > euiTheme.breakpoint.m ? 3 : width > euiTheme.breakpoint.s ? 2 : 1; + const widthFactor = numberOfColumns === 3 ? 2.5 : 2.2; + const fieldWidth = width / (numberOfColumns * widthFactor); + + return { + columns: numberOfColumns, + fieldWidth, + }; +}; diff --git a/x-pack/test/functional/apps/observability_log_explorer/flyout.ts b/x-pack/test/functional/apps/observability_log_explorer/flyout.ts index f8277db16e8db..b2b71c817255a 100644 --- a/x-pack/test/functional/apps/observability_log_explorer/flyout.ts +++ b/x-pack/test/functional/apps/observability_log_explorer/flyout.ts @@ -54,9 +54,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); - after('clean up archives', async () => { + after('clean up DataStream', async () => { if (cleanupDataStreamSetup) { - cleanupDataStreamSetup(); + await cleanupDataStreamSetup(); } }); diff --git a/x-pack/test/functional/apps/observability_log_explorer/flyout_highlights.ts b/x-pack/test/functional/apps/observability_log_explorer/flyout_highlights.ts new file mode 100644 index 0000000000000..3b71935cd6fbc --- /dev/null +++ b/x-pack/test/functional/apps/observability_log_explorer/flyout_highlights.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { FtrProviderContext } from '../../ftr_provider_context'; + +const DATASET_NAME = 'flyout'; +const NAMESPACE = 'default'; +const DATA_STREAM_NAME = `logs-${DATASET_NAME}-${NAMESPACE}`; +const NOW = Date.now(); + +const sharedDoc = { + time: NOW + 1000, + logFilepath: '/flyout.log', + serviceName: 'frontend-node', + datasetName: DATASET_NAME, + namespace: NAMESPACE, + message: 'full document', + logLevel: 'info', + traceId: 'abcdef', + hostName: 'gke-edge-oblt-pool', + orchestratorClusterId: 'my-cluster-id', + orchestratorClusterName: 'my-cluster-id', + orchestratorResourceId: 'orchestratorResourceId', + cloudProvider: 'gcp', + cloudRegion: 'us-central-1', + cloudAz: 'us-central-1a', + cloudProjectId: 'elastic-project', + cloudInstanceId: 'BgfderflkjTheUiGuy', + agentName: 'node', +}; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const dataGrid = getService('dataGrid'); + const testSubjects = getService('testSubjects'); + const PageObjects = getPageObjects(['observabilityLogExplorer']); + + describe('Flyout highlight customization', () => { + let cleanupDataStreamSetup: () => Promise; + + describe('Service container', () => { + const { serviceName, traceId, ...rest } = sharedDoc; + const docWithoutServiceName = { ...rest, traceId, time: NOW - 1000 }; + const docWithoutTraceId = { ...rest, serviceName, time: NOW - 2000 }; + const docWithoutServiceContainer = { ...rest, time: NOW - 4000 }; + + const docs = [ + sharedDoc, + docWithoutServiceName, + docWithoutTraceId, + docWithoutServiceContainer, + ]; + before('setup DataStream', async () => { + cleanupDataStreamSetup = await PageObjects.observabilityLogExplorer.setupDataStream( + DATASET_NAME, + NAMESPACE + ); + await PageObjects.observabilityLogExplorer.ingestLogEntries(DATA_STREAM_NAME, docs); + }); + + after('clean up DataStream', async () => { + if (cleanupDataStreamSetup) { + await cleanupDataStreamSetup(); + } + }); + + beforeEach(async () => { + await PageObjects.observabilityLogExplorer.navigateTo({ + from: new Date(NOW - 60_000).toISOString(), + to: new Date(NOW + 60_000).toISOString(), + }); + }); + + it('should load the service container with all fields', async () => { + await dataGrid.clickRowToggle(); + await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionService'); + await testSubjects.existOrFail('logExplorerFlyoutService'); + await testSubjects.existOrFail('logExplorerFlyoutTrace'); + await dataGrid.closeFlyout(); + }); + + it('should load the service container even when 1 field is missing', async () => { + await dataGrid.clickRowToggle({ rowIndex: 1 }); + await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionService'); + await testSubjects.missingOrFail('logExplorerFlyoutService'); + await testSubjects.existOrFail('logExplorerFlyoutTrace'); + await dataGrid.closeFlyout(); + }); + + it('should not load the service container if all fields are missing', async () => { + await dataGrid.clickRowToggle({ rowIndex: 3 }); + await testSubjects.missingOrFail('logExplorerFlyoutHighlightSectionService'); + await testSubjects.missingOrFail('logExplorerFlyoutService'); + await testSubjects.missingOrFail('logExplorerFlyoutTrace'); + await dataGrid.closeFlyout(); + }); + }); + + describe('Infrastructure container', () => { + const { hostName, orchestratorClusterName, orchestratorResourceId, ...rest } = sharedDoc; + const docWithoutHostName = { + ...rest, + orchestratorClusterName, + orchestratorResourceId, + time: NOW - 1000, + }; + const docWithoutInfrastructureContainer = { ...rest, time: NOW - 2000 }; + + const docs = [sharedDoc, docWithoutHostName, docWithoutInfrastructureContainer]; + before('setup DataStream', async () => { + cleanupDataStreamSetup = await PageObjects.observabilityLogExplorer.setupDataStream( + DATASET_NAME, + NAMESPACE + ); + await PageObjects.observabilityLogExplorer.ingestLogEntries(DATA_STREAM_NAME, docs); + }); + + after('clean up DataStream', async () => { + if (cleanupDataStreamSetup) { + await cleanupDataStreamSetup(); + } + }); + + beforeEach(async () => { + await PageObjects.observabilityLogExplorer.navigateTo({ + from: new Date(NOW - 60_000).toISOString(), + to: new Date(NOW + 60_000).toISOString(), + }); + }); + + it('should load the infrastructure container with all fields', async () => { + await dataGrid.clickRowToggle(); + await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionInfrastructure'); + await testSubjects.existOrFail('logExplorerFlyoutHostName'); + await testSubjects.existOrFail('logExplorerFlyoutClusterName'); + await testSubjects.existOrFail('logExplorerFlyoutResourceId'); + await dataGrid.closeFlyout(); + }); + + it('should load the infrastructure container even when 1 field is missing', async () => { + await dataGrid.clickRowToggle({ rowIndex: 1 }); + await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionInfrastructure'); + await testSubjects.missingOrFail('logExplorerFlyoutHostName'); + await testSubjects.existOrFail('logExplorerFlyoutClusterName'); + await testSubjects.existOrFail('logExplorerFlyoutResourceId'); + await dataGrid.closeFlyout(); + }); + + it('should not load the infrastructure container if all fields are missing', async () => { + await dataGrid.clickRowToggle({ rowIndex: 2 }); + await testSubjects.missingOrFail('logExplorerFlyoutHighlightSectionInfrastructure'); + await testSubjects.missingOrFail('logExplorerFlyoutHostName'); + await testSubjects.missingOrFail('logExplorerFlyoutClusterName'); + await testSubjects.missingOrFail('logExplorerFlyoutResourceId'); + await dataGrid.closeFlyout(); + }); + }); + + describe('Cloud container', () => { + const { cloudProvider, cloudInstanceId, cloudProjectId, cloudRegion, cloudAz, ...rest } = + sharedDoc; + const docWithoutCloudProviderAndInstanceId = { + ...rest, + cloudProjectId, + cloudRegion, + cloudAz, + time: NOW - 1000, + }; + const docWithoutCloudContainer = { ...rest, time: NOW - 2000 }; + + const docs = [sharedDoc, docWithoutCloudProviderAndInstanceId, docWithoutCloudContainer]; + before('setup DataStream', async () => { + cleanupDataStreamSetup = await PageObjects.observabilityLogExplorer.setupDataStream( + DATASET_NAME, + NAMESPACE + ); + await PageObjects.observabilityLogExplorer.ingestLogEntries(DATA_STREAM_NAME, docs); + }); + + after('clean up DataStream', async () => { + if (cleanupDataStreamSetup) { + await cleanupDataStreamSetup(); + } + }); + + beforeEach(async () => { + await PageObjects.observabilityLogExplorer.navigateTo({ + from: new Date(NOW - 60_000).toISOString(), + to: new Date(NOW + 60_000).toISOString(), + }); + }); + + it('should load the cloud container with all fields', async () => { + await dataGrid.clickRowToggle(); + await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionCloud'); + await testSubjects.existOrFail('logExplorerFlyoutCloudProvider'); + await testSubjects.existOrFail('logExplorerFlyoutCloudRegion'); + await testSubjects.existOrFail('logExplorerFlyoutCloudAz'); + await testSubjects.existOrFail('logExplorerFlyoutCloudProjectId'); + await testSubjects.existOrFail('logExplorerFlyoutCloudInstanceId'); + await dataGrid.closeFlyout(); + }); + + it('should load the cloud container even when some fields are missing', async () => { + await dataGrid.clickRowToggle({ rowIndex: 1 }); + await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionCloud'); + + await testSubjects.missingOrFail('logExplorerFlyoutCloudProvider'); + await testSubjects.missingOrFail('logExplorerFlyoutCloudInstanceId'); + + await testSubjects.existOrFail('logExplorerFlyoutCloudRegion'); + await testSubjects.existOrFail('logExplorerFlyoutCloudAz'); + await testSubjects.existOrFail('logExplorerFlyoutCloudProjectId'); + await dataGrid.closeFlyout(); + }); + + it('should not load the cloud container if all fields are missing', async () => { + await dataGrid.clickRowToggle({ rowIndex: 2 }); + await testSubjects.missingOrFail('logExplorerFlyoutHighlightSectionCloud'); + await testSubjects.missingOrFail('logExplorerFlyoutCloudProvider'); + await testSubjects.missingOrFail('logExplorerFlyoutCloudRegion'); + await testSubjects.missingOrFail('logExplorerFlyoutCloudAz'); + await testSubjects.missingOrFail('logExplorerFlyoutCloudProjectId'); + await testSubjects.missingOrFail('logExplorerFlyoutCloudInstanceId'); + await dataGrid.closeFlyout(); + }); + }); + + describe('Other container', () => { + const { logFilepath, agentName, ...rest } = sharedDoc; + const docWithoutLogPathAndAgentName = { + ...rest, + time: NOW - 1000, + }; + + const docs = [sharedDoc, docWithoutLogPathAndAgentName]; + before('setup DataStream', async () => { + cleanupDataStreamSetup = await PageObjects.observabilityLogExplorer.setupDataStream( + DATASET_NAME, + NAMESPACE + ); + await PageObjects.observabilityLogExplorer.ingestLogEntries(DATA_STREAM_NAME, docs); + }); + + after('clean up DataStream', async () => { + if (cleanupDataStreamSetup) { + await cleanupDataStreamSetup(); + } + }); + + beforeEach(async () => { + await PageObjects.observabilityLogExplorer.navigateTo({ + from: new Date(NOW - 60_000).toISOString(), + to: new Date(NOW + 60_000).toISOString(), + }); + }); + + it('should load the other container with all fields', async () => { + await dataGrid.clickRowToggle(); + await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionOther'); + await testSubjects.existOrFail('logExplorerFlyoutLogPathFile'); + await testSubjects.existOrFail('logExplorerFlyoutNamespace'); + await testSubjects.existOrFail('logExplorerFlyoutDataset'); + await testSubjects.existOrFail('logExplorerFlyoutLogShipper'); + await dataGrid.closeFlyout(); + }); + + it('should load the other container even when some fields are missing', async () => { + await dataGrid.clickRowToggle({ rowIndex: 1 }); + await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionOther'); + + await testSubjects.missingOrFail('logExplorerFlyoutLogPathFile'); + await testSubjects.missingOrFail('logExplorerFlyoutLogShipper'); + + await testSubjects.existOrFail('logExplorerFlyoutNamespace'); + await testSubjects.existOrFail('logExplorerFlyoutDataset'); + await dataGrid.closeFlyout(); + }); + }); + }); +} diff --git a/x-pack/test/functional/apps/observability_log_explorer/index.ts b/x-pack/test/functional/apps/observability_log_explorer/index.ts index 948910dab6ab4..7a767baa887de 100644 --- a/x-pack/test/functional/apps/observability_log_explorer/index.ts +++ b/x-pack/test/functional/apps/observability_log_explorer/index.ts @@ -16,5 +16,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./filter_controls')); loadTestFile(require.resolve('./flyout')); loadTestFile(require.resolve('./header_menu')); + loadTestFile(require.resolve('./flyout_highlights.ts')); }); } diff --git a/x-pack/test/functional/page_objects/observability_log_explorer.ts b/x-pack/test/functional/page_objects/observability_log_explorer.ts index 2978e3205f464..0b3266465c32e 100644 --- a/x-pack/test/functional/page_objects/observability_log_explorer.ts +++ b/x-pack/test/functional/page_objects/observability_log_explorer.ts @@ -406,12 +406,24 @@ export function ObservabilityLogExplorerPageObject({ interface MockLogDoc { time: number; - logFilepath: string; + logFilepath?: string; serviceName?: string; namespace: string; datasetName: string; message?: string; logLevel?: string; + traceId?: string; + hostName?: string; + orchestratorClusterId?: string; + orchestratorClusterName?: string; + orchestratorResourceId?: string; + cloudProvider?: string; + cloudRegion?: string; + cloudAz?: string; + cloudProjectId?: string; + cloudInstanceId?: string; + agentName?: string; + [key: string]: unknown; } @@ -423,6 +435,17 @@ export function createLogDoc({ datasetName, message, logLevel, + traceId, + hostName, + orchestratorClusterId, + orchestratorClusterName, + orchestratorResourceId, + cloudProvider, + cloudRegion, + cloudAz, + cloudProjectId, + cloudInstanceId, + agentName, ...extraFields }: MockLogDoc) { return { @@ -452,6 +475,17 @@ export function createLogDoc({ dataset: datasetName, }, ...(logLevel && { 'log.level': logLevel }), + ...(traceId && { 'trace.id': traceId }), + ...(hostName && { 'host.name': hostName }), + ...(orchestratorClusterId && { 'orchestrator.cluster.id': orchestratorClusterId }), + ...(orchestratorClusterName && { 'orchestrator.cluster.name': orchestratorClusterName }), + ...(orchestratorResourceId && { 'orchestrator.resource.id': orchestratorResourceId }), + ...(cloudProvider && { 'cloud.provider': cloudProvider }), + ...(cloudRegion && { 'cloud.region': cloudRegion }), + ...(cloudAz && { 'cloud.availability_zone': cloudAz }), + ...(cloudProjectId && { 'cloud.project.id': cloudProjectId }), + ...(cloudInstanceId && { 'cloud.instance.id': cloudInstanceId }), + ...(agentName && { 'agent.name': agentName }), ...extraFields, }; } diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/flyout_highlights.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/flyout_highlights.ts new file mode 100644 index 0000000000000..98a3914fed4e8 --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/flyout_highlights.ts @@ -0,0 +1,291 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { FtrProviderContext } from '../../../ftr_provider_context'; + +const DATASET_NAME = 'flyout'; +const NAMESPACE = 'default'; +const DATA_STREAM_NAME = `logs-${DATASET_NAME}-${NAMESPACE}`; +const NOW = Date.now(); + +const sharedDoc = { + time: NOW + 1000, + logFilepath: '/flyout.log', + serviceName: 'frontend-node', + datasetName: DATASET_NAME, + namespace: NAMESPACE, + message: 'full document', + logLevel: 'info', + traceId: 'abcdef', + hostName: 'gke-edge-oblt-pool', + orchestratorClusterId: 'my-cluster-id', + orchestratorClusterName: 'my-cluster-id', + orchestratorResourceId: 'orchestratorResourceId', + cloudProvider: 'gcp', + cloudRegion: 'us-central-1', + cloudAz: 'us-central-1a', + cloudProjectId: 'elastic-project', + cloudInstanceId: 'BgfderflkjTheUiGuy', + agentName: 'node', +}; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const dataGrid = getService('dataGrid'); + const testSubjects = getService('testSubjects'); + const PageObjects = getPageObjects(['observabilityLogExplorer', 'svlCommonPage']); + + describe('Flyout highlight customization', () => { + let cleanupDataStreamSetup: () => Promise; + + describe('Service container', () => { + const { serviceName, traceId, ...rest } = sharedDoc; + const docWithoutServiceName = { ...rest, traceId, time: NOW - 1000 }; + const docWithoutTraceId = { ...rest, serviceName, time: NOW - 2000 }; + const docWithoutServiceContainer = { ...rest, time: NOW - 4000 }; + + const docs = [ + sharedDoc, + docWithoutServiceName, + docWithoutTraceId, + docWithoutServiceContainer, + ]; + before('setup DataStream', async () => { + cleanupDataStreamSetup = await PageObjects.observabilityLogExplorer.setupDataStream( + DATASET_NAME, + NAMESPACE + ); + await PageObjects.observabilityLogExplorer.ingestLogEntries(DATA_STREAM_NAME, docs); + await PageObjects.svlCommonPage.login(); + }); + + after('clean up DataStream', async () => { + await PageObjects.svlCommonPage.forceLogout(); + if (cleanupDataStreamSetup) { + await cleanupDataStreamSetup(); + } + }); + + beforeEach(async () => { + await PageObjects.observabilityLogExplorer.navigateTo({ + from: new Date(NOW - 60_000).toISOString(), + to: new Date(NOW + 60_000).toISOString(), + }); + }); + + it('should load the service container with all fields', async () => { + await dataGrid.clickRowToggle(); + await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionService'); + await testSubjects.existOrFail('logExplorerFlyoutService'); + await testSubjects.existOrFail('logExplorerFlyoutTrace'); + await dataGrid.closeFlyout(); + }); + + it('should load the service container even when 1 field is missing', async () => { + await dataGrid.clickRowToggle({ rowIndex: 1 }); + await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionService'); + await testSubjects.missingOrFail('logExplorerFlyoutService'); + await testSubjects.existOrFail('logExplorerFlyoutTrace'); + await dataGrid.closeFlyout(); + }); + + it('should not load the service container if all fields are missing', async () => { + await dataGrid.clickRowToggle({ rowIndex: 3 }); + await testSubjects.missingOrFail('logExplorerFlyoutHighlightSectionService'); + await testSubjects.missingOrFail('logExplorerFlyoutService'); + await testSubjects.missingOrFail('logExplorerFlyoutTrace'); + await dataGrid.closeFlyout(); + }); + }); + + describe('Infrastructure container', () => { + const { hostName, orchestratorClusterName, orchestratorResourceId, ...rest } = sharedDoc; + const docWithoutHostName = { + ...rest, + orchestratorClusterName, + orchestratorResourceId, + time: NOW - 1000, + }; + const docWithoutInfrastructureContainer = { ...rest, time: NOW - 2000 }; + + const docs = [sharedDoc, docWithoutHostName, docWithoutInfrastructureContainer]; + before('setup DataStream', async () => { + cleanupDataStreamSetup = await PageObjects.observabilityLogExplorer.setupDataStream( + DATASET_NAME, + NAMESPACE + ); + await PageObjects.observabilityLogExplorer.ingestLogEntries(DATA_STREAM_NAME, docs); + await PageObjects.svlCommonPage.login(); + }); + + after('clean up DataStream', async () => { + await PageObjects.svlCommonPage.forceLogout(); + if (cleanupDataStreamSetup) { + await cleanupDataStreamSetup(); + } + }); + + beforeEach(async () => { + await PageObjects.observabilityLogExplorer.navigateTo({ + from: new Date(NOW - 60_000).toISOString(), + to: new Date(NOW + 60_000).toISOString(), + }); + }); + + it('should load the infrastructure container with all fields', async () => { + await dataGrid.clickRowToggle(); + await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionInfrastructure'); + await testSubjects.existOrFail('logExplorerFlyoutHostName'); + await testSubjects.existOrFail('logExplorerFlyoutClusterName'); + await testSubjects.existOrFail('logExplorerFlyoutResourceId'); + await dataGrid.closeFlyout(); + }); + + it('should load the infrastructure container even when 1 field is missing', async () => { + await dataGrid.clickRowToggle({ rowIndex: 1 }); + await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionInfrastructure'); + await testSubjects.missingOrFail('logExplorerFlyoutHostName'); + await testSubjects.existOrFail('logExplorerFlyoutClusterName'); + await testSubjects.existOrFail('logExplorerFlyoutResourceId'); + await dataGrid.closeFlyout(); + }); + + it('should not load the infrastructure container if all fields are missing', async () => { + await dataGrid.clickRowToggle({ rowIndex: 2 }); + await testSubjects.missingOrFail('logExplorerFlyoutHighlightSectionInfrastructure'); + await testSubjects.missingOrFail('logExplorerFlyoutHostName'); + await testSubjects.missingOrFail('logExplorerFlyoutClusterName'); + await testSubjects.missingOrFail('logExplorerFlyoutResourceId'); + await dataGrid.closeFlyout(); + }); + }); + + describe('Cloud container', () => { + const { cloudProvider, cloudInstanceId, cloudProjectId, cloudRegion, cloudAz, ...rest } = + sharedDoc; + const docWithoutCloudProviderAndInstanceId = { + ...rest, + cloudProjectId, + cloudRegion, + cloudAz, + time: NOW - 1000, + }; + const docWithoutCloudContainer = { ...rest, time: NOW - 2000 }; + + const docs = [sharedDoc, docWithoutCloudProviderAndInstanceId, docWithoutCloudContainer]; + before('setup DataStream', async () => { + cleanupDataStreamSetup = await PageObjects.observabilityLogExplorer.setupDataStream( + DATASET_NAME, + NAMESPACE + ); + await PageObjects.observabilityLogExplorer.ingestLogEntries(DATA_STREAM_NAME, docs); + await PageObjects.svlCommonPage.login(); + }); + + after('clean up DataStream', async () => { + await PageObjects.svlCommonPage.forceLogout(); + if (cleanupDataStreamSetup) { + await cleanupDataStreamSetup(); + } + }); + + beforeEach(async () => { + await PageObjects.observabilityLogExplorer.navigateTo({ + from: new Date(NOW - 60_000).toISOString(), + to: new Date(NOW + 60_000).toISOString(), + }); + }); + + it('should load the cloud container with all fields', async () => { + await dataGrid.clickRowToggle(); + await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionCloud'); + await testSubjects.existOrFail('logExplorerFlyoutCloudProvider'); + await testSubjects.existOrFail('logExplorerFlyoutCloudRegion'); + await testSubjects.existOrFail('logExplorerFlyoutCloudAz'); + await testSubjects.existOrFail('logExplorerFlyoutCloudProjectId'); + await testSubjects.existOrFail('logExplorerFlyoutCloudInstanceId'); + await dataGrid.closeFlyout(); + }); + + it('should load the cloud container even when some fields are missing', async () => { + await dataGrid.clickRowToggle({ rowIndex: 1 }); + await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionCloud'); + + await testSubjects.missingOrFail('logExplorerFlyoutCloudProvider'); + await testSubjects.missingOrFail('logExplorerFlyoutCloudInstanceId'); + + await testSubjects.existOrFail('logExplorerFlyoutCloudRegion'); + await testSubjects.existOrFail('logExplorerFlyoutCloudAz'); + await testSubjects.existOrFail('logExplorerFlyoutCloudProjectId'); + await dataGrid.closeFlyout(); + }); + + it('should not load the cloud container if all fields are missing', async () => { + await dataGrid.clickRowToggle({ rowIndex: 2 }); + await testSubjects.missingOrFail('logExplorerFlyoutHighlightSectionCloud'); + await testSubjects.missingOrFail('logExplorerFlyoutCloudProvider'); + await testSubjects.missingOrFail('logExplorerFlyoutCloudRegion'); + await testSubjects.missingOrFail('logExplorerFlyoutCloudAz'); + await testSubjects.missingOrFail('logExplorerFlyoutCloudProjectId'); + await testSubjects.missingOrFail('logExplorerFlyoutCloudInstanceId'); + await dataGrid.closeFlyout(); + }); + }); + + describe('Other container', () => { + const { logFilepath, agentName, ...rest } = sharedDoc; + const docWithoutLogPathAndAgentName = { + ...rest, + time: NOW - 1000, + }; + + const docs = [sharedDoc, docWithoutLogPathAndAgentName]; + before('setup DataStream', async () => { + cleanupDataStreamSetup = await PageObjects.observabilityLogExplorer.setupDataStream( + DATASET_NAME, + NAMESPACE + ); + await PageObjects.observabilityLogExplorer.ingestLogEntries(DATA_STREAM_NAME, docs); + await PageObjects.svlCommonPage.login(); + }); + + after('clean up DataStream', async () => { + await PageObjects.svlCommonPage.forceLogout(); + if (cleanupDataStreamSetup) { + await cleanupDataStreamSetup(); + } + }); + + beforeEach(async () => { + await PageObjects.observabilityLogExplorer.navigateTo({ + from: new Date(NOW - 60_000).toISOString(), + to: new Date(NOW + 60_000).toISOString(), + }); + }); + + it('should load the other container with all fields', async () => { + await dataGrid.clickRowToggle(); + await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionOther'); + await testSubjects.existOrFail('logExplorerFlyoutLogPathFile'); + await testSubjects.existOrFail('logExplorerFlyoutNamespace'); + await testSubjects.existOrFail('logExplorerFlyoutDataset'); + await testSubjects.existOrFail('logExplorerFlyoutLogShipper'); + await dataGrid.closeFlyout(); + }); + + it('should load the other container even when some fields are missing', async () => { + await dataGrid.clickRowToggle({ rowIndex: 1 }); + await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionOther'); + + await testSubjects.missingOrFail('logExplorerFlyoutLogPathFile'); + await testSubjects.missingOrFail('logExplorerFlyoutLogShipper'); + + await testSubjects.existOrFail('logExplorerFlyoutNamespace'); + await testSubjects.existOrFail('logExplorerFlyoutDataset'); + await dataGrid.closeFlyout(); + }); + }); + }); +} diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/index.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/index.ts index 6e0b650869680..9f3d9b7e39c86 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/index.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/index.ts @@ -16,5 +16,7 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./filter_controls')); loadTestFile(require.resolve('./flyout')); loadTestFile(require.resolve('./header_menu')); + loadTestFile(require.resolve('./header_menu')); + loadTestFile(require.resolve('./flyout_highlights.ts')); }); } From 682600f01c5d7f7f7be5846e6f3906583544bfeb Mon Sep 17 00:00:00 2001 From: Konrad Szwarc Date: Fri, 10 Nov 2023 19:56:42 +0100 Subject: [PATCH 10/12] [EDR Workflows] Protection updates latest date is capped at yesterday (#170932) https://github.com/elastic/kibana/issues/170847 With this PR latest selectable date is set to yesterday. Changes: 1. Datepicker start date is set to `today - 1 day` 2. Api adjusted to accept dates starting at `today - 1 day` 3. Tests aligned. https://github.com/elastic/kibana/assets/29123534/ae2e8ac8-9d35-4cee-a47b-af39fa13485a --- .../cypress/e2e/policy/policy_details.cy.ts | 10 +++++----- .../protection_updates_layout.tsx | 18 ++++++++++-------- .../fleet_integration.test.ts | 14 ++++++++++++-- .../validate_endpoint_package_policy.ts | 7 +++++-- 4 files changed, 32 insertions(+), 17 deletions(-) diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/policy/policy_details.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/policy/policy_details.cy.ts index 3cfa2a5c9287d..ae79f3d47b4a5 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/policy/policy_details.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/policy/policy_details.cy.ts @@ -39,8 +39,8 @@ describe( describe('Renders and saves protection updates', () => { let indexedPolicy: IndexedFleetEndpointPolicyResponse; let policy: PolicyData; - const today = moment.utc(); - const formattedToday = today.format('MMMM DD, YYYY'); + const defaultDate = moment.utc().subtract(1, 'days'); + const formattedDefaultDate = defaultDate.format('MMMM DD, YYYY'); beforeEach(() => { login(); @@ -73,7 +73,7 @@ describe( cy.getByTestSubj('protection-updates-deployed-version').contains('latest'); cy.getByTestSubj('protection-updates-manifest-name-version-to-deploy-title'); cy.getByTestSubj('protection-updates-version-to-deploy-picker').within(() => { - cy.get('input').should('have.value', formattedToday); + cy.get('input').should('have.value', formattedDefaultDate); }); cy.getByTestSubj('protection-updates-manifest-name-note-title'); cy.getByTestSubj('protection-updates-manifest-note'); @@ -91,7 +91,7 @@ describe( cy.getByTestSubj('protectionUpdatesSaveButton').click(); cy.wait('@policy').then(({ request, response }) => { expect(request.body.inputs[0].config.policy.value.global_manifest_version).to.equal( - today.format('YYYY-MM-DD') + defaultDate.format('YYYY-MM-DD') ); expect(response?.statusCode).to.equal(200); }); @@ -102,7 +102,7 @@ describe( }); cy.getByTestSubj('protectionUpdatesSuccessfulMessage'); - cy.getByTestSubj('protection-updates-deployed-version').contains(formattedToday); + cy.getByTestSubj('protection-updates-deployed-version').contains(formattedDefaultDate); cy.getByTestSubj('protection-updates-manifest-note').contains(testNote); cy.getByTestSubj('protectionUpdatesSaveButton').should('be.disabled'); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/protection_updates/protection_updates_layout.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/protection_updates/protection_updates_layout.tsx index cf7012f901974..dc260fdb6449d 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/protection_updates/protection_updates_layout.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/protection_updates/protection_updates_layout.tsx @@ -73,7 +73,9 @@ export const ProtectionUpdatesLayout = React.memo( const [manifestVersion, setManifestVersion] = useState(deployedVersion); const today = moment.utc(); - const [selectedDate, setSelectedDate] = useState(today); + const defaultDate = today.clone().subtract(1, 'days'); + + const [selectedDate, setSelectedDate] = useState(defaultDate); const { data: fetchedNote, isLoading: getNoteInProgress } = useGetProtectionUpdatesNote({ packagePolicyId: _policy.id, @@ -181,24 +183,24 @@ export const ProtectionUpdatesLayout = React.memo( if (checked && !automaticUpdatesEnabled) { setManifestVersion('latest'); // Clear selected date on user enabling automatic updates - if (selectedDate !== today) { - setSelectedDate(today); + if (selectedDate !== defaultDate) { + setSelectedDate(defaultDate); } } else { setManifestVersion(selectedDate.format(internalDateFormat)); } }, - [automaticUpdatesEnabled, selectedDate, today] + [automaticUpdatesEnabled, selectedDate, defaultDate] ); const updateDatepickerSelectedDate = useCallback( (date: Moment | null) => { - if (date?.isAfter(cutoffDate) && date?.isSameOrBefore(today)) { - setSelectedDate(date || today); + if (date?.isAfter(cutoffDate) && date?.isSameOrBefore(defaultDate)) { + setSelectedDate(date || defaultDate); setManifestVersion(date?.format(internalDateFormat) || 'latest'); } }, - [cutoffDate, today] + [cutoffDate, defaultDate] ); const renderVersionToDeployPicker = () => { @@ -224,7 +226,7 @@ export const ProtectionUpdatesLayout = React.memo( popoverPlacement={'downCenter'} dateFormat={displayDateFormat} selected={selectedDate} - maxDate={today} + maxDate={defaultDate} minDate={cutoffDate} onChange={updateDatepickerSelectedDate} /> diff --git a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts index 3d586d1dfab1a..7c861bf87f18f 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts @@ -441,6 +441,8 @@ describe('ingest_integration tests ', () => { licenseEmitter.next(Enterprise); // set license level to enterprise }); + const validDateYesterday = moment.utc().subtract(1, 'day'); + it.each([ { date: 'invalid', @@ -457,13 +459,21 @@ describe('ingest_integration tests ', () => { }, { date: '2100-10-01', - message: 'Global manifest version cannot be in the future. UTC time.', + message: `Global manifest version cannot be in the future. Latest selectable date is ${validDateYesterday.format( + 'MMMM DD, YYYY' + )} UTC time.`, + }, + { + date: validDateYesterday.clone().add(1, 'day').format('YYYY-MM-DD'), + message: `Global manifest version cannot be in the future. Latest selectable date is ${validDateYesterday.format( + 'MMMM DD, YYYY' + )} UTC time.`, }, { date: 'latest', }, { - date: moment.utc().subtract(1, 'day').format('YYYY-MM-DD'), // Correct date + date: validDateYesterday.format('YYYY-MM-DD'), // Correct date }, ])( 'should return bad request for invalid endpoint package policy global manifest values', diff --git a/x-pack/plugins/security_solution/server/fleet_integration/handlers/validate_endpoint_package_policy.ts b/x-pack/plugins/security_solution/server/fleet_integration/handlers/validate_endpoint_package_policy.ts index d8f1a8afecd79..f63d07d890c2d 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/handlers/validate_endpoint_package_policy.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/handlers/validate_endpoint_package_policy.ts @@ -29,9 +29,12 @@ export const validateEndpointPackagePolicy = (inputs: NewPackagePolicyInput[]) = 'Global manifest version is too far in the past. Please use either "latest" or a date within the last 18 months. The earliest valid date is October 1, 2023, in UTC time.' ); } - if (parsedDate.isAfter(moment.utc())) { + const minAllowedDate = moment.utc().subtract(1, 'day'); + if (parsedDate.isAfter(minAllowedDate)) { throw createManifestVersionError( - 'Global manifest version cannot be in the future. UTC time.' + `Global manifest version cannot be in the future. Latest selectable date is ${minAllowedDate.format( + 'MMMM DD, YYYY' + )} UTC time.` ); } } From 4b55f11f450b684a6916dd01bfa39bcd762ce5dd Mon Sep 17 00:00:00 2001 From: Hannah Mudge Date: Fri, 10 Nov 2023 13:07:13 -0700 Subject: [PATCH 11/12] [Dashboard] Rename Dashboard to Dashboards (#170992) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes https://github.com/elastic/kibana/issues/163152 ## Summary This PR renames the "Dashboard" app to "Dashboards" in both the side navigation and the breadcrumbs, like so: | Before | After | |--------|--------| | Screenshot 2023-11-10 at 10 53 37 AM | Screenshot 2023-11-10 at 10 54 12 AM | | Screenshot 2023-11-10 at 10 57 51 AM | Screenshot 2023-11-10 at 10 57 14 AM | This is more consistent with Visualizations, Maps, etc., the dashboard listing page, and dashboard URLs. ### Checklist - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios ### 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) --- .../public/dashboard_app/_dashboard_app_strings.ts | 2 +- src/plugins/dashboard/public/plugin.tsx | 2 +- x-pack/plugins/translations/translations/fr-FR.json | 1 - x-pack/plugins/translations/translations/ja-JP.json | 1 - x-pack/plugins/translations/translations/zh-CN.json | 1 - .../dashboard/group1/feature_controls/dashboard_security.ts | 6 +++--- .../dashboard/group1/feature_controls/dashboard_spaces.ts | 2 +- .../apps/management/feature_controls/management_security.ts | 2 +- 8 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/plugins/dashboard/public/dashboard_app/_dashboard_app_strings.ts b/src/plugins/dashboard/public/dashboard_app/_dashboard_app_strings.ts index 6fefffcc1d668..8e2f7b07d195a 100644 --- a/src/plugins/dashboard/public/dashboard_app/_dashboard_app_strings.ts +++ b/src/plugins/dashboard/public/dashboard_app/_dashboard_app_strings.ts @@ -152,7 +152,7 @@ export const shareModalStrings = { */ export const getDashboardBreadcrumb = () => i18n.translate('dashboard.dashboardAppBreadcrumbsTitle', { - defaultMessage: 'Dashboard', + defaultMessage: 'Dashboards', }); export const topNavStrings = { diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index 4d46b837da5ca..98af5088967f2 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -229,7 +229,7 @@ export class DashboardPlugin const app: App = { id: DASHBOARD_APP_ID, - title: 'Dashboard', + title: 'Dashboards', order: 2500, euiIconType: 'logoKibana', defaultPath: `#${LANDING_PAGE_PATH}`, diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 8edd2b48ce470..e40475d4aae3d 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -1204,7 +1204,6 @@ "dashboard.createConfirmModal.continueButtonLabel": "Poursuivre les modifications", "dashboard.createConfirmModal.unsavedChangesSubtitle": "Poursuivez les modifications ou utilisez un tableau de bord vierge.", "dashboard.createConfirmModal.unsavedChangesTitle": "Nouveau tableau de bord déjà en cours", - "dashboard.dashboardAppBreadcrumbsTitle": "Tableau de bord", "dashboard.dashboardPageTitle": "Tableaux de bord", "dashboard.dashboardWasSavedSuccessMessage": "Le tableau de bord \"{dashTitle}\" a été enregistré.", "dashboard.deleteError.toastDescription": "Erreur rencontrée lors de la suppression du tableau de bord", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 0451262451432..8f58cdabe7785 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -1218,7 +1218,6 @@ "dashboard.createConfirmModal.continueButtonLabel": "編集を続行", "dashboard.createConfirmModal.unsavedChangesSubtitle": "編集を続行するか、空のダッシュボードで始めてください。", "dashboard.createConfirmModal.unsavedChangesTitle": "新しいダッシュボードはすでに実行中です", - "dashboard.dashboardAppBreadcrumbsTitle": "ダッシュボード", "dashboard.dashboardPageTitle": "ダッシュボード", "dashboard.dashboardWasSavedSuccessMessage": "ダッシュボード「{dashTitle}」が保存されました。", "dashboard.deleteError.toastDescription": "ダッシュボードの削除中にエラーが発生しました", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 90a4ae742e05d..62084ac55eed6 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -1218,7 +1218,6 @@ "dashboard.createConfirmModal.continueButtonLabel": "继续编辑", "dashboard.createConfirmModal.unsavedChangesSubtitle": "继续编辑或使用空白仪表板从头开始。", "dashboard.createConfirmModal.unsavedChangesTitle": "新仪表板已在创建中", - "dashboard.dashboardAppBreadcrumbsTitle": "仪表板", "dashboard.dashboardPageTitle": "仪表板", "dashboard.dashboardWasSavedSuccessMessage": "仪表板“{dashTitle}”已保存", "dashboard.deleteError.toastDescription": "删除仪表板时发生错误", diff --git a/x-pack/test/functional/apps/dashboard/group1/feature_controls/dashboard_security.ts b/x-pack/test/functional/apps/dashboard/group1/feature_controls/dashboard_security.ts index 584822a31161b..dc1cf432f8ca6 100644 --- a/x-pack/test/functional/apps/dashboard/group1/feature_controls/dashboard_security.ts +++ b/x-pack/test/functional/apps/dashboard/group1/feature_controls/dashboard_security.ts @@ -96,7 +96,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('only shows the dashboard navlink', async () => { const navLinks = await appsMenu.readLinks(); - expect(navLinks.map((link) => link.text)).to.eql(['Dashboard', 'Stack Management']); + expect(navLinks.map((link) => link.text)).to.eql(['Dashboards', 'Stack Management']); }); it(`landing page shows "Create new Dashboard" button`, async () => { @@ -286,7 +286,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows dashboard navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Dashboard']); + expect(navLinks).to.eql(['Dashboards']); }); it(`landing page doesn't show "Create new Dashboard" button`, async () => { @@ -398,7 +398,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows dashboard navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Dashboard']); + expect(navLinks).to.eql(['Dashboards']); }); it(`landing page doesn't show "Create new Dashboard" button`, async () => { diff --git a/x-pack/test/functional/apps/dashboard/group1/feature_controls/dashboard_spaces.ts b/x-pack/test/functional/apps/dashboard/group1/feature_controls/dashboard_spaces.ts index ef8c83ec667ba..2f04d12bdac6d 100644 --- a/x-pack/test/functional/apps/dashboard/group1/feature_controls/dashboard_spaces.ts +++ b/x-pack/test/functional/apps/dashboard/group1/feature_controls/dashboard_spaces.ts @@ -45,7 +45,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { basePath: '/s/custom_space', }); const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.contain('Dashboard'); + expect(navLinks).to.contain('Dashboards'); }); it(`landing page shows "Create new Dashboard" button`, async () => { diff --git a/x-pack/test/functional/apps/management/feature_controls/management_security.ts b/x-pack/test/functional/apps/management/feature_controls/management_security.ts index d88e2fd8ebe7a..3706649418346 100644 --- a/x-pack/test/functional/apps/management/feature_controls/management_security.ts +++ b/x-pack/test/functional/apps/management/feature_controls/management_security.ts @@ -36,7 +36,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('should not show the Stack Management nav link', async () => { const links = await appsMenu.readLinks(); - expect(links.map((link) => link.text)).to.eql(['Dashboard']); + expect(links.map((link) => link.text)).to.eql(['Dashboards']); }); it('should render the "application not found" view when navigating to management directly', async () => { From b84881a29b37c8ffb68a53e6e2b6a8cf89c7001f Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Fri, 10 Nov 2023 13:36:40 -0700 Subject: [PATCH 12/12] [data.search.bsearch] Forward request abortSignal to search strategy (#170041) ## Summary Attempt to resurrect #169041. Previously flaky tests: * group1: https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/3797 * group2: https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/3798 * group3: https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/3799 * group4: https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/3800 ### 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: Stratoula Kalafateli Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../search/strategies/es_search/types.ts | 3 +- .../data/server/search/routes/bsearch.ts | 4 +- .../data/server/search/search_service.ts | 14 +-- .../eql_search/eql_search_strategy.test.ts | 33 +++++++ .../eql_search/eql_search_strategy.ts | 20 +++- .../ese_search/ese_search_strategy.test.ts | 94 +++++++++++++++++++ .../ese_search/ese_search_strategy.ts | 38 +++++--- .../search/strategies/ese_search/types.ts | 16 +++- .../sql_search/sql_search_strategy.test.ts | 27 ++++++ .../sql_search/sql_search_strategy.ts | 25 +++-- .../api_integration/apis/search/search.ts | 65 +++++++++++++ 11 files changed, 302 insertions(+), 37 deletions(-) diff --git a/src/plugins/data/common/search/strategies/es_search/types.ts b/src/plugins/data/common/search/strategies/es_search/types.ts index 73bf7961fea9b..f8c3b73d995a9 100644 --- a/src/plugins/data/common/search/strategies/es_search/types.ts +++ b/src/plugins/data/common/search/strategies/es_search/types.ts @@ -15,7 +15,8 @@ export type ISearchRequestParams = { trackTotalHits?: boolean; } & estypes.SearchRequest; -export interface IEsSearchRequest extends IKibanaSearchRequest { +export interface IEsSearchRequest + extends IKibanaSearchRequest { indexType?: string; } diff --git a/src/plugins/data/server/search/routes/bsearch.ts b/src/plugins/data/server/search/routes/bsearch.ts index 8b65c5d8eb1dc..ee19c8cba30a3 100644 --- a/src/plugins/data/server/search/routes/bsearch.ts +++ b/src/plugins/data/server/search/routes/bsearch.ts @@ -11,6 +11,7 @@ import { catchError } from 'rxjs/operators'; import { BfetchServerSetup } from '@kbn/bfetch-plugin/server'; import type { ExecutionContextSetup } from '@kbn/core/server'; import apm from 'elastic-apm-node'; +import { getRequestAbortedSignal } from '../..'; import { IKibanaSearchRequest, IKibanaSearchResponse, @@ -28,6 +29,7 @@ export function registerBsearchRoute( IKibanaSearchResponse >('/internal/bsearch', (request) => { const search = getScoped(request); + const abortSignal = getRequestAbortedSignal(request.events.aborted$); return { /** * @param requestOptions @@ -39,7 +41,7 @@ export function registerBsearchRoute( apm.addLabels(executionContextService.getAsLabels()); return firstValueFrom( - search.search(requestData, restOptions).pipe( + search.search(requestData, { ...restOptions, abortSignal }).pipe( catchError((err) => { // Re-throw as object, to get attributes passed to the client // eslint-disable-next-line no-throw-literal diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index 71a335ce51592..188f853e6a2ce 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -437,11 +437,7 @@ export class SearchService implements Plugin { } }; - private cancel = async ( - deps: SearchStrategyDependencies, - id: string, - options: ISearchOptions = {} - ) => { + private cancel = (deps: SearchStrategyDependencies, id: string, options: ISearchOptions = {}) => { const strategy = this.getSearchStrategy(options.strategy); if (!strategy.cancel) { throw new KbnServerError( @@ -468,14 +464,18 @@ export class SearchService implements Plugin { private cancelSessionSearches = async (deps: SearchStrategyDependencies, sessionId: string) => { const searchIdMapping = await deps.searchSessionsClient.getSearchIdMapping(sessionId); await Promise.allSettled( - Array.from(searchIdMapping).map(([searchId, strategyName]) => { + Array.from(searchIdMapping).map(async ([searchId, strategyName]) => { const searchOptions = { sessionId, strategy: strategyName, isStored: true, }; - return this.cancel(deps, searchId, searchOptions); + try { + await this.cancel(deps, searchId, searchOptions); + } catch (e) { + this.logger.error(`cancelSessionSearches error: ${e.message}`); + } }) ); }; diff --git a/src/plugins/data/server/search/strategies/eql_search/eql_search_strategy.test.ts b/src/plugins/data/server/search/strategies/eql_search/eql_search_strategy.test.ts index 6d61f62cc79ab..475c43a5daed6 100644 --- a/src/plugins/data/server/search/strategies/eql_search/eql_search_strategy.test.ts +++ b/src/plugins/data/server/search/strategies/eql_search/eql_search_strategy.test.ts @@ -15,6 +15,7 @@ import { getMockSearchConfig } from '../../../../config.mock'; const getMockEqlResponse = () => ({ body: { + id: 'my-search-id', is_partial: false, is_running: false, took: 162, @@ -54,6 +55,7 @@ describe('EQL search strategy', () => { describe('search()', () => { let mockEqlSearch: jest.Mock; let mockEqlGet: jest.Mock; + let mockEqlDelete: jest.Mock; let mockDeps: SearchStrategyDependencies; let params: Required['params']; let options: Required['options']; @@ -61,6 +63,8 @@ describe('EQL search strategy', () => { beforeEach(() => { mockEqlSearch = jest.fn().mockResolvedValueOnce(getMockEqlResponse()); mockEqlGet = jest.fn().mockResolvedValueOnce(getMockEqlResponse()); + mockEqlDelete = jest.fn(); + mockDeps = { uiSettingsClient: { get: jest.fn(), @@ -70,6 +74,7 @@ describe('EQL search strategy', () => { eql: { get: mockEqlGet, search: mockEqlSearch, + delete: mockEqlDelete, }, }, }, @@ -124,6 +129,34 @@ describe('EQL search strategy', () => { }); }); + it('should delete when aborted', async () => { + const response = getMockEqlResponse(); + mockEqlSearch.mockReset().mockResolvedValueOnce({ + ...response, + body: { + ...response.body, + is_running: true, + }, + }); + const eqlSearch = await eqlSearchStrategyProvider(mockSearchConfig, mockLogger); + const abortController = new AbortController(); + const abortSignal = abortController.signal; + + // Abort after an incomplete first response is returned + setTimeout(() => abortController.abort(), 100); + + let err: any; + try { + await eqlSearch.search({ options, params }, { abortSignal }, mockDeps).toPromise(); + } catch (e) { + err = e; + } + + expect(mockEqlSearch).toBeCalled(); + expect(err).not.toBeUndefined(); + expect(mockEqlDelete).toBeCalled(); + }); + describe('arguments', () => { it('sends along async search options', async () => { const eqlSearch = await eqlSearchStrategyProvider(mockSearchConfig, mockLogger); diff --git a/src/plugins/data/server/search/strategies/eql_search/eql_search_strategy.ts b/src/plugins/data/server/search/strategies/eql_search/eql_search_strategy.ts index 9dd24e6791719..00b8cfdeb52e5 100644 --- a/src/plugins/data/server/search/strategies/eql_search/eql_search_strategy.ts +++ b/src/plugins/data/server/search/strategies/eql_search/eql_search_strategy.ts @@ -9,6 +9,7 @@ import type { TransportResult } from '@elastic/elasticsearch'; import { tap } from 'rxjs/operators'; import type { IScopedClusterClient, Logger } from '@kbn/core/server'; +import { getKbnServerError } from '@kbn/kibana-utils-plugin/server'; import { SearchConfigSchema } from '../../../../config'; import { EqlSearchStrategyRequest, @@ -27,15 +28,19 @@ export const eqlSearchStrategyProvider = ( searchConfig: SearchConfigSchema, logger: Logger ): ISearchStrategy => { - async function cancelAsyncSearch(id: string, esClient: IScopedClusterClient) { + function cancelAsyncSearch(id: string, esClient: IScopedClusterClient) { const client = esClient.asCurrentUser.eql; - await client.delete({ id }); + return client.delete({ id }); } return { cancel: async (id, options, { esClient }) => { logger.debug(`_eql/delete ${id}`); - await cancelAsyncSearch(id, esClient); + try { + await cancelAsyncSearch(id, esClient); + } catch (e) { + throw getKbnServerError(e); + } }, search: ({ id, ...request }, options: IAsyncSearchOptions, { esClient, uiSettingsClient }) => { @@ -85,8 +90,15 @@ export const eqlSearchStrategyProvider = ( }; const cancel = async () => { - if (id) { + if (!id) return; + try { await cancelAsyncSearch(id, esClient); + } catch (e) { + // A 404 means either this search request does not exist, or that it is already cancelled + if (e.meta?.statusCode === 404) return; + + // Log all other (unexpected) error messages + logger.error(`cancelEqlSearch error: ${e.message}`); } }; diff --git a/src/plugins/data/server/search/strategies/ese_search/ese_search_strategy.test.ts b/src/plugins/data/server/search/strategies/ese_search/ese_search_strategy.test.ts index 3b2c5e8e0e5c8..33987c09d88dd 100644 --- a/src/plugins/data/server/search/strategies/ese_search/ese_search_strategy.test.ts +++ b/src/plugins/data/server/search/strategies/ese_search/ese_search_strategy.test.ts @@ -136,6 +136,30 @@ describe('ES search strategy', () => { expect(request).toHaveProperty('keep_alive', '60000ms'); }); + it('allows overriding keep_alive and wait_for_completion_timeout', async () => { + mockGetCaller.mockResolvedValueOnce(mockAsyncResponse); + + const params = { + index: 'logstash-*', + body: { query: {} }, + wait_for_completion_timeout: '10s', + keep_alive: '5m', + }; + const esSearch = await enhancedEsSearchStrategyProvider( + mockLegacyConfig$, + mockSearchConfig, + mockLogger + ); + + await esSearch.search({ id: 'foo', params }, {}, mockDeps).toPromise(); + + expect(mockGetCaller).toBeCalled(); + const request = mockGetCaller.mock.calls[0][0]; + expect(request.id).toEqual('foo'); + expect(request).toHaveProperty('wait_for_completion_timeout', '10s'); + expect(request).toHaveProperty('keep_alive', '5m'); + }); + it('sets transport options on POST requests', async () => { const transportOptions = { maxRetries: 1 }; mockSubmitCaller.mockResolvedValueOnce(mockAsyncResponse); @@ -260,6 +284,38 @@ describe('ES search strategy', () => { expect(mockApiCaller).toBeCalledTimes(0); }); + + it('should delete when aborted', async () => { + mockSubmitCaller.mockResolvedValueOnce({ + ...mockAsyncResponse, + body: { + ...mockAsyncResponse.body, + is_running: true, + }, + }); + + const params = { index: 'logstash-*', body: { query: {} } }; + const esSearch = await enhancedEsSearchStrategyProvider( + mockLegacyConfig$, + mockSearchConfig, + mockLogger + ); + const abortController = new AbortController(); + const abortSignal = abortController.signal; + + // Abort after an incomplete first response is returned + setTimeout(() => abortController.abort(), 100); + + let err: KbnServerError | undefined; + try { + await esSearch.search({ params }, { abortSignal }, mockDeps).toPromise(); + } catch (e) { + err = e; + } + expect(mockSubmitCaller).toBeCalled(); + expect(err).not.toBeUndefined(); + expect(mockDeleteCaller).toBeCalled(); + }); }); describe('with sessionId', () => { @@ -367,6 +423,44 @@ describe('ES search strategy', () => { expect(request).toHaveProperty('wait_for_completion_timeout'); expect(request).not.toHaveProperty('keep_alive'); }); + + it('should not delete a saved session when aborted', async () => { + mockSubmitCaller.mockResolvedValueOnce({ + ...mockAsyncResponse, + body: { + ...mockAsyncResponse.body, + is_running: true, + }, + }); + + const params = { index: 'logstash-*', body: { query: {} } }; + const esSearch = await enhancedEsSearchStrategyProvider( + mockLegacyConfig$, + mockSearchConfig, + mockLogger + ); + const abortController = new AbortController(); + const abortSignal = abortController.signal; + + // Abort after an incomplete first response is returned + setTimeout(() => abortController.abort(), 100); + + let err: KbnServerError | undefined; + try { + await esSearch + .search( + { params }, + { abortSignal, sessionId: '1', isSearchStored: true, isStored: true }, + mockDeps + ) + .toPromise(); + } catch (e) { + err = e; + } + expect(mockSubmitCaller).toBeCalled(); + expect(err).not.toBeUndefined(); + expect(mockDeleteCaller).not.toBeCalled(); + }); }); it('throws normalized error if ResponseError is thrown', async () => { diff --git a/src/plugins/data/server/search/strategies/ese_search/ese_search_strategy.ts b/src/plugins/data/server/search/strategies/ese_search/ese_search_strategy.ts index 89699d7d58611..174f9924f1cc7 100644 --- a/src/plugins/data/server/search/strategies/ese_search/ese_search_strategy.ts +++ b/src/plugins/data/server/search/strategies/ese_search/ese_search_strategy.ts @@ -12,6 +12,7 @@ import { catchError, tap } from 'rxjs/operators'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { firstValueFrom, from } from 'rxjs'; import { getKbnServerError } from '@kbn/kibana-utils-plugin/server'; +import { IAsyncSearchRequestParams } from '../..'; import { getKbnSearchError, KbnSearchError } from '../../report_search_error'; import type { ISearchStrategy, SearchStrategyDependencies } from '../../types'; import type { @@ -43,18 +44,14 @@ export const enhancedEsSearchStrategyProvider = ( logger: Logger, usage?: SearchUsage, useInternalUser: boolean = false -): ISearchStrategy => { - async function cancelAsyncSearch(id: string, esClient: IScopedClusterClient) { - try { - const client = useInternalUser ? esClient.asInternalUser : esClient.asCurrentUser; - await client.asyncSearch.delete({ id }); - } catch (e) { - throw getKbnServerError(e); - } +): ISearchStrategy> => { + function cancelAsyncSearch(id: string, esClient: IScopedClusterClient) { + const client = useInternalUser ? esClient.asInternalUser : esClient.asCurrentUser; + return client.asyncSearch.delete({ id }); } function asyncSearch( - { id, ...request }: IEsSearchRequest, + { id, ...request }: IEsSearchRequest, options: IAsyncSearchOptions, { esClient, uiSettingsClient }: SearchStrategyDependencies ) { @@ -62,7 +59,13 @@ export const enhancedEsSearchStrategyProvider = ( const search = async () => { const params = id - ? getDefaultAsyncGetParams(searchConfig, options) + ? { + ...getDefaultAsyncGetParams(searchConfig, options), + ...(request.params?.keep_alive ? { keep_alive: request.params.keep_alive } : {}), + ...(request.params?.wait_for_completion_timeout + ? { wait_for_completion_timeout: request.params.wait_for_completion_timeout } + : {}), + } : { ...(await getDefaultAsyncSubmitParams(uiSettingsClient, searchConfig, options)), ...request.params, @@ -89,8 +92,15 @@ export const enhancedEsSearchStrategyProvider = ( }; const cancel = async () => { - if (id) { + if (!id || options.isStored) return; + try { await cancelAsyncSearch(id, esClient); + } catch (e) { + // A 404 means either this search request does not exist, or that it is already cancelled + if (e.meta?.statusCode === 404) return; + + // Log all other (unexpected) error messages + logger.error(`cancelAsyncSearch error: ${e.message}`); } }; @@ -179,7 +189,11 @@ export const enhancedEsSearchStrategyProvider = ( */ cancel: async (id, options, { esClient }) => { logger.debug(`cancel ${id}`); - await cancelAsyncSearch(id, esClient); + try { + await cancelAsyncSearch(id, esClient); + } catch (e) { + throw getKbnServerError(e); + } }, /** * diff --git a/src/plugins/data/server/search/strategies/ese_search/types.ts b/src/plugins/data/server/search/strategies/ese_search/types.ts index 4116aa4380339..5ff324e1c2e4f 100644 --- a/src/plugins/data/server/search/strategies/ese_search/types.ts +++ b/src/plugins/data/server/search/strategies/ese_search/types.ts @@ -6,11 +6,21 @@ * Side Public License, v 1. */ -import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import type { + AsyncSearchGetRequest, + SearchResponse, + ShardStatistics, +} from '@elastic/elasticsearch/lib/api/types'; +import { ISearchRequestParams } from '../../../../common'; + +export interface IAsyncSearchRequestParams extends ISearchRequestParams { + keep_alive?: AsyncSearchGetRequest['keep_alive']; + wait_for_completion_timeout?: AsyncSearchGetRequest['wait_for_completion_timeout']; +} export interface AsyncSearchResponse { id?: string; - response: estypes.SearchResponse; + response: SearchResponse; start_time_in_millis: number; expiration_time_in_millis: number; is_partial: boolean; @@ -18,5 +28,5 @@ export interface AsyncSearchResponse { } export interface AsyncSearchStatusResponse extends Omit { completion_status: number; - _shards: estypes.ShardStatistics; + _shards: ShardStatistics; } diff --git a/src/plugins/data/server/search/strategies/sql_search/sql_search_strategy.test.ts b/src/plugins/data/server/search/strategies/sql_search/sql_search_strategy.test.ts index 700c658de10c0..36fb43a34894f 100644 --- a/src/plugins/data/server/search/strategies/sql_search/sql_search_strategy.test.ts +++ b/src/plugins/data/server/search/strategies/sql_search/sql_search_strategy.test.ts @@ -124,6 +124,33 @@ describe('SQL search strategy', () => { signal: undefined, }); }); + + it('should delete when aborted', async () => { + mockSqlQuery.mockResolvedValueOnce({ + ...mockSqlResponse, + body: { + ...mockSqlResponse.body, + is_running: true, + }, + }); + const esSearch = await sqlSearchStrategyProvider(mockSearchConfig, mockLogger); + const abortController = new AbortController(); + const abortSignal = abortController.signal; + + // Abort after an incomplete first response is returned + setTimeout(() => abortController.abort(), 100); + + let err: any; + try { + await esSearch.search({ params: {} }, { abortSignal }, mockDeps).toPromise(); + } catch (e) { + err = e; + } + + expect(mockSqlQuery).toBeCalled(); + expect(err).not.toBeUndefined(); + expect(mockSqlDelete).toBeCalled(); + }); }); // skip until full search session support https://github.com/elastic/kibana/issues/127880 diff --git a/src/plugins/data/server/search/strategies/sql_search/sql_search_strategy.ts b/src/plugins/data/server/search/strategies/sql_search/sql_search_strategy.ts index 87b29f5438efb..9e04675d12247 100644 --- a/src/plugins/data/server/search/strategies/sql_search/sql_search_strategy.ts +++ b/src/plugins/data/server/search/strategies/sql_search/sql_search_strategy.ts @@ -29,13 +29,9 @@ export const sqlSearchStrategyProvider = ( logger: Logger, useInternalUser: boolean = false ): ISearchStrategy => { - async function cancelAsyncSearch(id: string, esClient: IScopedClusterClient) { - try { - const client = useInternalUser ? esClient.asInternalUser : esClient.asCurrentUser; - await client.sql.deleteAsync({ id }); - } catch (e) { - throw getKbnServerError(e); - } + function cancelAsyncSearch(id: string, esClient: IScopedClusterClient) { + const client = useInternalUser ? esClient.asInternalUser : esClient.asCurrentUser; + return client.sql.deleteAsync({ id }); } function asyncSearch( @@ -92,8 +88,15 @@ export const sqlSearchStrategyProvider = ( }; const cancel = async () => { - if (id) { + if (!id) return; + try { await cancelAsyncSearch(id, esClient); + } catch (e) { + // A 404 means either this search request does not exist, or that it is already cancelled + if (e.meta?.statusCode === 404) return; + + // Log all other (unexpected) error messages + logger.error(`cancelSqlSearch error: ${e.message}`); } }; @@ -130,7 +133,11 @@ export const sqlSearchStrategyProvider = ( */ cancel: async (id, options, { esClient }) => { logger.debug(`sql search: cancel async_search_id=${id}`); - await cancelAsyncSearch(id, esClient); + try { + await cancelAsyncSearch(id, esClient); + } catch (e) { + throw getKbnServerError(e); + } }, /** * diff --git a/x-pack/test/api_integration/apis/search/search.ts b/x-pack/test/api_integration/apis/search/search.ts index 391923601d7c5..15c774eef34ef 100644 --- a/x-pack/test/api_integration/apis/search/search.ts +++ b/x-pack/test/api_integration/apis/search/search.ts @@ -186,6 +186,71 @@ export default function ({ getService }: FtrProviderContext) { expect(resp2.body.isRunning).to.be(true); }); + it('should cancel an async search without server crash', async function () { + await markRequiresShardDelayAgg(this); + + const resp = await supertest + .post(`/internal/search/ese`) + .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set('kbn-xsrf', 'foo') + .send({ + params: { + body: { + query: { + match_all: {}, + }, + ...shardDelayAgg('10s'), + }, + wait_for_completion_timeout: '1ms', + }, + }) + .expect(200); + + const { id } = resp.body; + expect(id).not.to.be(undefined); + expect(resp.body.isPartial).to.be(true); + expect(resp.body.isRunning).to.be(true); + + // Send a follow-up request that waits up to 10s for completion + const req = supertest + .post(`/internal/search/ese/${id}`) + .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set('kbn-xsrf', 'foo') + .send({ params: { wait_for_completion_timeout: '10s' } }) + .expect(200); + + // After 2s, abort and send the cancellation (to result in a race towards cancellation) + // This should be swallowed and not kill the Kibana server + await new Promise((resolve) => + setTimeout(() => { + req.abort(); + resolve(null); + }, 2000) + ); + await supertest + .delete(`/internal/search/ese/${id}`) + .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set('kbn-xsrf', 'foo') + .expect(200); + + let err: Error | undefined; + try { + await req; + } catch (e) { + err = e; + } + + expect(err).not.to.be(undefined); + + // Ensure the search was succesfully cancelled + await supertest + .post(`/internal/search/ese/${id}`) + .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set('kbn-xsrf', 'foo') + .send({}) + .expect(404); + }); + it('should fail without kbn-xref header', async () => { const resp = await supertest .post(`/internal/search/ese`)