From c4f03aeb64d3bc8ad5595daaaabc4690534e5c3b Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 28 Oct 2024 23:19:04 +1100 Subject: [PATCH] [8.x] [Logs Overview] Add a flyout to show category document examples (#194867) (#197966) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Backport This will backport the following commits from `main` to `8.x`: - [[Logs Overview] Add a flyout to show category document examples (#194867)](https://github.com/elastic/kibana/pull/194867) ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) Co-authored-by: Kerry Gallagher --- .github/CODEOWNERS | 1 + .i18nrc.json | 2 +- package.json | 1 + .../README.md | 3 + .../index.ts | 7 +- .../jest.config.js | 14 + .../kibana.jsonc | 5 + .../package.json | 7 + .../logs/components}/cell_actions_popover.tsx | 64 ++-- .../src/data_types/logs/components/index.ts | 12 + .../log_level_badge_cell.test.tsx | 4 +- .../log_level_badge_cell.tsx | 4 +- .../service_name_badge_with_actions.tsx | 17 +- .../components}/summary_column/content.tsx | 4 +- .../logs/components/summary_column/index.ts | 13 + .../components}/summary_column/resource.tsx | 5 +- .../summary_column/summary_column.test.tsx | 50 +-- .../summary_column/summary_column.tsx | 171 ++++++++++ .../logs/components/summary_column/utils.tsx | 147 +++++++++ .../logs/components/translations.tsx | 72 +++++ .../src/index.ts | 16 + .../tsconfig.json | 37 +++ packages/kbn-discover-utils/index.ts | 4 +- .../logs/components/{index.ts => index.tsx} | 0 .../src/data_types/logs/constants.ts | 70 ++++ .../src/data_types/logs/index.ts | 2 +- .../src/data_types/logs/types.ts | 7 + .../utils/get_available_resource_fields.ts | 4 +- .../src/data_types/logs/utils/index.ts | 1 + packages/kbn-discover-utils/tsconfig.json | 2 +- .../common/data_types/logs/constants.ts | 62 +--- .../data_types/logs/service_name_cell.tsx | 6 +- .../data_types/logs/summary_column/index.tsx | 8 +- .../logs/summary_column/summary_column.tsx | 172 +--------- .../data_types/logs/summary_column/utils.tsx | 126 -------- .../data_types/logs/translations.tsx | 305 ------------------ .../accessors/get_cell_renderers.tsx | 2 +- src/plugins/discover/tsconfig.json | 4 +- src/plugins/unified_doc_viewer/kibana.jsonc | 1 + tsconfig.base.json | 2 + .../discover_link/discover_link.tsx | 16 +- .../log_categories/log_categories.tsx | 49 ++- .../log_categories_control_bar.tsx | 13 +- .../log_categories/log_categories_grid.tsx | 12 + .../log_categories_grid_cell.tsx | 2 +- .../log_categories_grid_control_columns.tsx | 45 +++ .../log_categories_grid_expand_button.tsx | 71 ++++ .../log_categories_grid_pattern_cell.tsx | 37 +-- .../log_categories_result_content.tsx | 38 ++- .../log_category_details_error_content.tsx | 41 +++ .../log_category_details_flyout.tsx | 139 ++++++++ .../log_category_details_loading_content.tsx | 19 ++ .../log_category_document_examples_table.tsx | 151 +++++++++ .../logs_overview/logs_overview.tsx | 10 +- .../shared/log_category_pattern.tsx | 50 +++ .../category_details_service.ts | 191 +++++++++++ .../category_documents.ts | 63 ++++ .../category_details_service/index.ts | 8 + .../category_details_service/queries.ts | 58 ++++ .../category_details_service/types.ts | 31 ++ .../logs_overview/src/utils/log_category.ts | 12 + .../logs_overview/src/utils/logs_source.ts | 53 ++- .../observability/logs_overview/tsconfig.json | 8 +- .../logs_shared/kibana.jsonc | 3 +- .../public/{plugin.ts => plugin.tsx} | 4 + .../logs_shared/public/types.ts | 2 + .../logs_shared/tsconfig.json | 1 + .../translations/translations/fr-FR.json | 35 -- .../translations/translations/ja-JP.json | 35 -- .../translations/translations/zh-CN.json | 35 -- yarn.lock | 4 + 71 files changed, 1766 insertions(+), 904 deletions(-) create mode 100644 packages/kbn-discover-contextual-components/README.md rename src/plugins/discover/common/data_types/logs/display_options.ts => packages/kbn-discover-contextual-components/index.ts (75%) create mode 100644 packages/kbn-discover-contextual-components/jest.config.js create mode 100644 packages/kbn-discover-contextual-components/kibana.jsonc create mode 100644 packages/kbn-discover-contextual-components/package.json rename {src/plugins/discover/public/components/data_types/logs => packages/kbn-discover-contextual-components/src/data_types/logs/components}/cell_actions_popover.tsx (75%) create mode 100644 packages/kbn-discover-contextual-components/src/data_types/logs/components/index.ts rename {src/plugins/discover/public/components/data_types/logs => packages/kbn-discover-contextual-components/src/data_types/logs/components/log_level_badge_cell}/log_level_badge_cell.test.tsx (93%) rename {src/plugins/discover/public/components/data_types/logs => packages/kbn-discover-contextual-components/src/data_types/logs/components/log_level_badge_cell}/log_level_badge_cell.tsx (92%) rename {src/plugins/discover/public/components/data_types/logs => packages/kbn-discover-contextual-components/src/data_types/logs/components}/service_name_badge_with_actions.tsx (80%) rename {src/plugins/discover/public/components/data_types/logs => packages/kbn-discover-contextual-components/src/data_types/logs/components}/summary_column/content.tsx (95%) create mode 100644 packages/kbn-discover-contextual-components/src/data_types/logs/components/summary_column/index.ts rename {src/plugins/discover/public/components/data_types/logs => packages/kbn-discover-contextual-components/src/data_types/logs/components}/summary_column/resource.tsx (89%) rename {src/plugins/discover/public/components/data_types/logs => packages/kbn-discover-contextual-components/src/data_types/logs/components}/summary_column/summary_column.test.tsx (86%) create mode 100644 packages/kbn-discover-contextual-components/src/data_types/logs/components/summary_column/summary_column.tsx create mode 100644 packages/kbn-discover-contextual-components/src/data_types/logs/components/summary_column/utils.tsx create mode 100644 packages/kbn-discover-contextual-components/src/data_types/logs/components/translations.tsx create mode 100644 packages/kbn-discover-contextual-components/src/index.ts create mode 100644 packages/kbn-discover-contextual-components/tsconfig.json rename packages/kbn-discover-utils/src/data_types/logs/components/{index.ts => index.tsx} (100%) create mode 100644 packages/kbn-discover-utils/src/data_types/logs/constants.ts rename {src/plugins/discover/public => packages/kbn-discover-utils/src/data_types/logs}/utils/get_available_resource_fields.ts (87%) delete mode 100644 src/plugins/discover/public/components/data_types/logs/summary_column/utils.tsx delete mode 100644 src/plugins/discover/public/components/data_types/logs/translations.tsx create mode 100644 x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_control_columns.tsx create mode 100644 x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_expand_button.tsx create mode 100644 x-pack/packages/observability/logs_overview/src/components/log_category_details/log_category_details_error_content.tsx create mode 100644 x-pack/packages/observability/logs_overview/src/components/log_category_details/log_category_details_flyout.tsx create mode 100644 x-pack/packages/observability/logs_overview/src/components/log_category_details/log_category_details_loading_content.tsx create mode 100644 x-pack/packages/observability/logs_overview/src/components/log_category_details/log_category_document_examples_table.tsx create mode 100644 x-pack/packages/observability/logs_overview/src/components/shared/log_category_pattern.tsx create mode 100644 x-pack/packages/observability/logs_overview/src/services/category_details_service/category_details_service.ts create mode 100644 x-pack/packages/observability/logs_overview/src/services/category_details_service/category_documents.ts create mode 100644 x-pack/packages/observability/logs_overview/src/services/category_details_service/index.ts create mode 100644 x-pack/packages/observability/logs_overview/src/services/category_details_service/queries.ts create mode 100644 x-pack/packages/observability/logs_overview/src/services/category_details_service/types.ts create mode 100644 x-pack/packages/observability/logs_overview/src/utils/log_category.ts rename x-pack/plugins/observability_solution/logs_shared/public/{plugin.ts => plugin.tsx} (97%) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 1ea2730e6c22d..ddc5535d29008 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -385,6 +385,7 @@ packages/kbn-dev-proc-runner @elastic/kibana-operations src/plugins/dev_tools @elastic/kibana-management packages/kbn-dev-utils @elastic/kibana-operations examples/developer_examples @elastic/appex-sharedux +packages/kbn-discover-contextual-components @elastic/obs-ux-logs-team @elastic/kibana-data-discovery examples/discover_customization_examples @elastic/kibana-data-discovery x-pack/plugins/discover_enhanced @elastic/kibana-data-discovery src/plugins/discover @elastic/kibana-data-discovery diff --git a/.i18nrc.json b/.i18nrc.json index 036be597ac969..5c7642e6283eb 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -27,7 +27,7 @@ "dataViews": "src/plugins/data_views", "defaultNavigation": "packages/default-nav", "devTools": "src/plugins/dev_tools", - "discover": ["src/plugins/discover", "packages/kbn-discover-utils"], + "discover": ["src/plugins/discover", "packages/kbn-discover-utils", "packages/kbn-discover-contextual-components"], "savedSearch": "src/plugins/saved_search", "embeddableApi": "src/plugins/embeddable", "presentationPanel": "src/plugins/presentation_panel", diff --git a/package.json b/package.json index a5ac1fe7f6d64..d3938c64969fb 100644 --- a/package.json +++ b/package.json @@ -451,6 +451,7 @@ "@kbn/default-nav-ml": "link:packages/default-nav/ml", "@kbn/dev-tools-plugin": "link:src/plugins/dev_tools", "@kbn/developer-examples-plugin": "link:examples/developer_examples", + "@kbn/discover-contextual-components": "link:packages/kbn-discover-contextual-components", "@kbn/discover-customization-examples-plugin": "link:examples/discover_customization_examples", "@kbn/discover-enhanced-plugin": "link:x-pack/plugins/discover_enhanced", "@kbn/discover-plugin": "link:src/plugins/discover", diff --git a/packages/kbn-discover-contextual-components/README.md b/packages/kbn-discover-contextual-components/README.md new file mode 100644 index 0000000000000..ae9e2402c2a69 --- /dev/null +++ b/packages/kbn-discover-contextual-components/README.md @@ -0,0 +1,3 @@ +# @kbn/discover-contextual-components + +Houses contextual (e.g. logs) components that are used by Discover. diff --git a/src/plugins/discover/common/data_types/logs/display_options.ts b/packages/kbn-discover-contextual-components/index.ts similarity index 75% rename from src/plugins/discover/common/data_types/logs/display_options.ts rename to packages/kbn-discover-contextual-components/index.ts index 05803ba0bde7f..55b900ad5137a 100644 --- a/src/plugins/discover/common/data_types/logs/display_options.ts +++ b/packages/kbn-discover-contextual-components/index.ts @@ -7,9 +7,4 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -export interface SmartFieldGridColumnOptions { - type: 'smart-field'; - smartField: 'content' | 'resource'; - fallbackFields: string[]; - width?: number; -} +export * from './src'; diff --git a/packages/kbn-discover-contextual-components/jest.config.js b/packages/kbn-discover-contextual-components/jest.config.js new file mode 100644 index 0000000000000..bacfd33649ce4 --- /dev/null +++ b/packages/kbn-discover-contextual-components/jest.config.js @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-discover-contextual-components'], +}; diff --git a/packages/kbn-discover-contextual-components/kibana.jsonc b/packages/kbn-discover-contextual-components/kibana.jsonc new file mode 100644 index 0000000000000..cfb9b1d5431ef --- /dev/null +++ b/packages/kbn-discover-contextual-components/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-browser", + "id": "@kbn/discover-contextual-components", + "owner": ["@elastic/obs-ux-logs-team", "@elastic/kibana-data-discovery"] +} diff --git a/packages/kbn-discover-contextual-components/package.json b/packages/kbn-discover-contextual-components/package.json new file mode 100644 index 0000000000000..4a63d975cda42 --- /dev/null +++ b/packages/kbn-discover-contextual-components/package.json @@ -0,0 +1,7 @@ +{ + "name": "@kbn/discover-contextual-components", + "private": true, + "version": "1.0.0", + "license": "Elastic License 2.0 OR AGPL-3.0-only OR SSPL-1.0", + "sideEffects": false +} \ No newline at end of file diff --git a/src/plugins/discover/public/components/data_types/logs/cell_actions_popover.tsx b/packages/kbn-discover-contextual-components/src/data_types/logs/components/cell_actions_popover.tsx similarity index 75% rename from src/plugins/discover/public/components/data_types/logs/cell_actions_popover.tsx rename to packages/kbn-discover-contextual-components/src/data_types/logs/components/cell_actions_popover.tsx index 7b9d68e8f3dd7..96651cf26189b 100644 --- a/src/plugins/discover/public/components/data_types/logs/cell_actions_popover.tsx +++ b/packages/kbn-discover-contextual-components/src/data_types/logs/components/cell_actions_popover.tsx @@ -24,7 +24,9 @@ import { import { css } from '@emotion/react'; import { useBoolean } from '@kbn/react-hooks'; import { euiThemeVars } from '@kbn/ui-theme'; -import { DocViewFilterFn } from '@kbn/unified-doc-viewer/types'; +import type { DocViewFilterFn } from '@kbn/unified-doc-viewer/types'; +import type { SharePluginStart } from '@kbn/share-plugin/public'; +import type { CoreStart } from '@kbn/core-lifecycle-browser'; import { actionFilterForText, actionFilterOutText, @@ -109,30 +111,32 @@ export function CellActionsPopover({ /> - - - - {filterForText} - - - {filterOutText} - - - + {onFilter ? ( + + + + {filterForText} + + + {filterOutText} + + + + ) : null} {(copy) => ( @@ -158,13 +162,21 @@ export interface FieldBadgeWithActionsProps icon?: EuiBadgeProps['iconType']; } +interface FieldBadgeWithActionsDependencies { + core?: CoreStart; + share?: SharePluginStart; +} + +export type FieldBadgeWithActionsPropsAndDependencies = FieldBadgeWithActionsProps & + FieldBadgeWithActionsDependencies; + export function FieldBadgeWithActions({ icon, onFilter, property, renderValue, value, -}: FieldBadgeWithActionsProps) { +}: FieldBadgeWithActionsPropsAndDependencies) { return ( { const LogLevelBadgeCell = getLogLevelBadgeCell(logLevelField); diff --git a/src/plugins/discover/public/components/data_types/logs/log_level_badge_cell.tsx b/packages/kbn-discover-contextual-components/src/data_types/logs/components/log_level_badge_cell/log_level_badge_cell.tsx similarity index 92% rename from src/plugins/discover/public/components/data_types/logs/log_level_badge_cell.tsx rename to packages/kbn-discover-contextual-components/src/data_types/logs/components/log_level_badge_cell/log_level_badge_cell.tsx index bff3bdddee026..4223f1e0de5c1 100644 --- a/src/plugins/discover/public/components/data_types/logs/log_level_badge_cell.tsx +++ b/packages/kbn-discover-contextual-components/src/data_types/logs/components/log_level_badge_cell/log_level_badge_cell.tsx @@ -9,8 +9,8 @@ import type { CSSObject } from '@emotion/react'; import React from 'react'; +import type { DataGridCellValueElementProps } from '@kbn/unified-data-table/src/types'; import { LogLevelBadge } from '@kbn/discover-utils'; -import type { DataGridCellValueElementProps } from '@kbn/unified-data-table'; const dataTestSubj = 'logLevelBadgeCell'; const badgeCss: CSSObject = { marginTop: '-4px' }; @@ -32,3 +32,5 @@ export const getLogLevelBadgeCell = /> ); }; + +export type LogLevelBadgeCell = ReturnType; diff --git a/src/plugins/discover/public/components/data_types/logs/service_name_badge_with_actions.tsx b/packages/kbn-discover-contextual-components/src/data_types/logs/components/service_name_badge_with_actions.tsx similarity index 80% rename from src/plugins/discover/public/components/data_types/logs/service_name_badge_with_actions.tsx rename to packages/kbn-discover-contextual-components/src/data_types/logs/components/service_name_badge_with_actions.tsx index 581c889b8e98e..7916b1144d851 100644 --- a/src/plugins/discover/public/components/data_types/logs/service_name_badge_with_actions.tsx +++ b/packages/kbn-discover-contextual-components/src/data_types/logs/components/service_name_badge_with_actions.tsx @@ -11,17 +11,20 @@ import React from 'react'; import { getRouterLinkProps } from '@kbn/router-utils'; import { EuiLink } from '@elastic/eui'; import { OBSERVABILITY_ENTITY_CENTRIC_EXPERIENCE } from '@kbn/management-settings-ids'; -import { SharePublicStart } from '@kbn/share-plugin/public/plugin'; -import { useDiscoverServices } from '../../../hooks/use_discover_services'; -import { FieldBadgeWithActions, FieldBadgeWithActionsProps } from './cell_actions_popover'; +import type { SharePublicStart } from '@kbn/share-plugin/public/plugin'; +import { + FieldBadgeWithActions, + FieldBadgeWithActionsProps, + FieldBadgeWithActionsPropsAndDependencies, +} from './cell_actions_popover'; const SERVICE_ENTITY_LOCATOR = 'SERVICE_ENTITY_LOCATOR'; -export function ServiceNameBadgeWithActions(props: FieldBadgeWithActionsProps) { - const { share, core } = useDiscoverServices(); - const canViewApm = core.application.capabilities.apm?.show || false; +export function ServiceNameBadgeWithActions(props: FieldBadgeWithActionsPropsAndDependencies) { + const { share, core } = props; + const canViewApm = core?.application.capabilities.apm?.show || false; const isEntityCentricExperienceSettingEnabled = canViewApm - ? core.uiSettings.get(OBSERVABILITY_ENTITY_CENTRIC_EXPERIENCE) + ? core?.uiSettings.get(OBSERVABILITY_ENTITY_CENTRIC_EXPERIENCE) : false; const derivedPropsForEntityExperience = isEntityCentricExperienceSettingEnabled diff --git a/src/plugins/discover/public/components/data_types/logs/summary_column/content.tsx b/packages/kbn-discover-contextual-components/src/data_types/logs/components/summary_column/content.tsx similarity index 95% rename from src/plugins/discover/public/components/data_types/logs/summary_column/content.tsx rename to packages/kbn-discover-contextual-components/src/data_types/logs/components/summary_column/content.tsx index 0da98cbf7145e..cc576efff17db 100644 --- a/src/plugins/discover/public/components/data_types/logs/summary_column/content.tsx +++ b/packages/kbn-discover-contextual-components/src/data_types/logs/components/summary_column/content.tsx @@ -14,7 +14,7 @@ import { getLogDocumentOverview, getMessageFieldWithFallbacks, } from '@kbn/discover-utils'; -import * as constants from '../../../../../common/data_types/logs/constants'; +import { MESSAGE_FIELD } from '@kbn/discover-utils'; import { formatJsonDocumentForContent } from './utils'; interface ContentProps extends DataGridCellValueElementProps { @@ -32,7 +32,7 @@ const LogMessage = ({ value: string; className: string; }) => { - const shouldRenderFieldName = field !== constants.MESSAGE_FIELD; + const shouldRenderFieldName = field !== MESSAGE_FIELD; if (shouldRenderFieldName) { return ( diff --git a/packages/kbn-discover-contextual-components/src/data_types/logs/components/summary_column/index.ts b/packages/kbn-discover-contextual-components/src/data_types/logs/components/summary_column/index.ts new file mode 100644 index 0000000000000..006ec34d0a475 --- /dev/null +++ b/packages/kbn-discover-contextual-components/src/data_types/logs/components/summary_column/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +export * from './content'; +export * from './resource'; +export * from './summary_column'; +export * from './utils'; diff --git a/src/plugins/discover/public/components/data_types/logs/summary_column/resource.tsx b/packages/kbn-discover-contextual-components/src/data_types/logs/components/summary_column/resource.tsx similarity index 89% rename from src/plugins/discover/public/components/data_types/logs/summary_column/resource.tsx rename to packages/kbn-discover-contextual-components/src/data_types/logs/components/summary_column/resource.tsx index a7955fadde622..5ea7ddda7a6b7 100644 --- a/src/plugins/discover/public/components/data_types/logs/summary_column/resource.tsx +++ b/packages/kbn-discover-contextual-components/src/data_types/logs/components/summary_column/resource.tsx @@ -8,8 +8,8 @@ */ import React from 'react'; -import { EuiBadge, EuiFlexGroup } from '@elastic/eui'; -import { DocViewFilterFn } from '@kbn/unified-doc-viewer/types'; +import { CommonProps, EuiBadge, EuiFlexGroup } from '@elastic/eui'; +import type { DocViewFilterFn } from '@kbn/unified-doc-viewer/types'; import { ResourceFieldDescriptor } from './utils'; const MAX_LIMITED_FIELDS_VISIBLE = 3; @@ -19,6 +19,7 @@ interface ResourceProps { /* When true, the column will render a predefined number of resources and indicates with a badge how many more we have */ limited?: boolean; onFilter?: DocViewFilterFn; + css?: CommonProps['css']; } export const Resource = ({ fields, limited = false, onFilter, ...props }: ResourceProps) => { diff --git a/src/plugins/discover/public/components/data_types/logs/summary_column/summary_column.test.tsx b/packages/kbn-discover-contextual-components/src/data_types/logs/components/summary_column/summary_column.test.tsx similarity index 86% rename from src/plugins/discover/public/components/data_types/logs/summary_column/summary_column.test.tsx rename to packages/kbn-discover-contextual-components/src/data_types/logs/components/summary_column/summary_column.test.tsx index b8eeea613c9c6..6b337167279e3 100644 --- a/src/plugins/discover/public/components/data_types/logs/summary_column/summary_column.test.tsx +++ b/packages/kbn-discover-contextual-components/src/data_types/logs/components/summary_column/summary_column.test.tsx @@ -8,41 +8,41 @@ */ import React from 'react'; -import { buildDataTableRecord, DataTableRecord } from '@kbn/discover-utils'; -import { dataViewMock } from '@kbn/discover-utils/src/__mocks__'; import { fieldFormatsMock } from '@kbn/field-formats-plugin/common/mocks'; import { render, screen } from '@testing-library/react'; import SummaryColumn, { SummaryColumnFactoryDeps, SummaryColumnProps } from './summary_column'; import { DataGridDensity, ROWS_HEIGHT_OPTIONS } from '@kbn/unified-data-table'; -import * as constants from '../../../../../common/data_types/logs/constants'; -import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; -import { discoverServiceMock } from '../../../../__mocks__/services'; +import * as constants from '@kbn/discover-utils/src/data_types/logs/constants'; +import { sharePluginMock } from '@kbn/share-plugin/public/mocks'; +import { coreMock as corePluginMock } from '@kbn/core/public/mocks'; +import { DataTableRecord, buildDataTableRecord } from '@kbn/discover-utils'; +import { dataViewMock } from '@kbn/discover-utils/src/__mocks__/data_view'; const renderSummary = ( record: DataTableRecord, opts: Partial = {} ) => { render( - - {}} - closePopover={() => {}} - density={DataGridDensity.COMPACT} - rowHeight={ROWS_HEIGHT_OPTIONS.single} - onFilter={jest.fn()} - shouldShowFieldHandler={() => true} - {...opts} - /> - + {}} + closePopover={() => {}} + density={DataGridDensity.COMPACT} + rowHeight={ROWS_HEIGHT_OPTIONS.single} + onFilter={jest.fn()} + shouldShowFieldHandler={() => true} + core={corePluginMock.createStart()} + share={sharePluginMock.createStartContract()} + {...opts} + /> ); }; diff --git a/packages/kbn-discover-contextual-components/src/data_types/logs/components/summary_column/summary_column.tsx b/packages/kbn-discover-contextual-components/src/data_types/logs/components/summary_column/summary_column.tsx new file mode 100644 index 0000000000000..98f772fcf41d1 --- /dev/null +++ b/packages/kbn-discover-contextual-components/src/data_types/logs/components/summary_column/summary_column.tsx @@ -0,0 +1,171 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { DataGridDensity, type DataGridCellValueElementProps } from '@kbn/unified-data-table'; +import React from 'react'; +import { EuiButtonIcon, EuiCodeBlock, EuiFlexGroup, EuiText, EuiTitle } from '@elastic/eui'; +import { JsonCodeEditor } from '@kbn/unified-doc-viewer-plugin/public'; +import { DocViewFilterFn } from '@kbn/unified-doc-viewer/types'; +import type { CoreStart } from '@kbn/core-lifecycle-browser'; +import type { SharePluginStart } from '@kbn/share-plugin/public'; +import { + ShouldShowFieldInTableHandler, + getLogDocumentOverview, + getMessageFieldWithFallbacks, +} from '@kbn/discover-utils'; +import { ROWS_HEIGHT_OPTIONS } from '@kbn/unified-data-table'; +import { Resource } from './resource'; +import { Content } from './content'; +import { createResourceFields, formatJsonDocumentForContent } from './utils'; +import { + closeCellActionPopoverText, + contentLabel, + jsonLabel, + resourceLabel, +} from '../translations'; + +export interface SummaryColumnFactoryDeps { + density: DataGridDensity | undefined; + rowHeight: number | undefined; + shouldShowFieldHandler: ShouldShowFieldInTableHandler; + onFilter?: DocViewFilterFn; + core: CoreStart; + share?: SharePluginStart; +} + +export type SummaryColumnProps = DataGridCellValueElementProps; +export type AllSummaryColumnProps = SummaryColumnProps & SummaryColumnFactoryDeps; + +export const SummaryColumn = (props: AllSummaryColumnProps) => { + const { isDetails } = props; + + if (isDetails) { + return ; + } + + return ; +}; + +// eslint-disable-next-line import/no-default-export +export default SummaryColumn; + +const SummaryCell = ({ + density: maybeNullishDensity, + rowHeight: maybeNullishRowHeight, + ...props +}: AllSummaryColumnProps) => { + const { onFilter, row, share, core } = props; + + const density = maybeNullishDensity ?? DataGridDensity.COMPACT; + const isCompressed = density === DataGridDensity.COMPACT; + + const rowHeight = maybeNullishRowHeight ?? ROWS_HEIGHT_OPTIONS.single; + const isSingleLine = rowHeight === ROWS_HEIGHT_OPTIONS.single || rowHeight === 1; + + const resourceFields = createResourceFields(row, core, share); + const shouldRenderResource = resourceFields.length > 0; + + return isSingleLine ? ( + + {shouldRenderResource && ( + + )} + + + ) : ( + <> + {shouldRenderResource && ( + + )} + + + ); +}; + +const SummaryCellPopover = (props: AllSummaryColumnProps) => { + const { row, dataView, fieldFormats, onFilter, closePopover, share, core } = props; + + const resourceFields = createResourceFields(row, core, share); + const shouldRenderResource = resourceFields.length > 0; + + const documentOverview = getLogDocumentOverview(row, { dataView, fieldFormats }); + const { field, value } = getMessageFieldWithFallbacks(documentOverview); + const shouldRenderContent = Boolean(field && value); + + const shouldRenderSource = !shouldRenderContent; + + return ( + + + {shouldRenderResource && ( + + + {resourceLabel} + + + + )} + + + {contentLabel} + + {shouldRenderContent && ( + + + {field} + + + {value} + + + )} + {shouldRenderSource && ( + + + {jsonLabel} + + + + )} + + + ); +}; + +const singleLineResourceCss = { + flexGrow: 0, + lineHeight: 'normal', + marginTop: -1, +}; + +const multiLineResourceCss = { display: 'inline-flex' }; diff --git a/packages/kbn-discover-contextual-components/src/data_types/logs/components/summary_column/utils.tsx b/packages/kbn-discover-contextual-components/src/data_types/logs/components/summary_column/utils.tsx new file mode 100644 index 0000000000000..7dacc3393763e --- /dev/null +++ b/packages/kbn-discover-contextual-components/src/data_types/logs/components/summary_column/utils.tsx @@ -0,0 +1,147 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { dynamic } from '@kbn/shared-ux-utility'; +import React from 'react'; +import { css } from '@emotion/react'; +import { AgentName } from '@kbn/elastic-agent-utils'; +import { euiThemeVars } from '@kbn/ui-theme'; +import type { SharePluginStart } from '@kbn/share-plugin/public'; +import type { CoreStart } from '@kbn/core-lifecycle-browser'; +import { + AGENT_NAME_FIELD, + CLOUD_INSTANCE_ID_FIELD, + CONTAINER_ID_FIELD, + CONTAINER_NAME_FIELD, + FILTER_OUT_FIELDS_PREFIXES_FOR_CONTENT, + HOST_NAME_FIELD, + ORCHESTRATOR_CLUSTER_NAME_FIELD, + ORCHESTRATOR_NAMESPACE_FIELD, + ORCHESTRATOR_RESOURCE_ID_FIELD, + SERVICE_NAME_FIELD, +} from '@kbn/discover-utils'; +import { DataTableRecord, getFieldValue } from '@kbn/discover-utils'; +import { LogDocument, ResourceFields, getAvailableResourceFields } from '@kbn/discover-utils/src'; +import { FieldBadgeWithActions, FieldBadgeWithActionsProps } from '../cell_actions_popover'; +import { ServiceNameBadgeWithActions } from '../service_name_badge_with_actions'; +/** + * getUnformattedResourceFields definitions + */ +export const getUnformattedResourceFields = (doc: LogDocument): ResourceFields => { + const serviceName = getFieldValue(doc, SERVICE_NAME_FIELD); + const hostName = getFieldValue(doc, HOST_NAME_FIELD); + const agentName = getFieldValue(doc, AGENT_NAME_FIELD); + const orchestratorClusterName = getFieldValue(doc, ORCHESTRATOR_CLUSTER_NAME_FIELD); + const orchestratorResourceId = getFieldValue(doc, ORCHESTRATOR_RESOURCE_ID_FIELD); + const orchestratorNamespace = getFieldValue(doc, ORCHESTRATOR_NAMESPACE_FIELD); + const containerName = getFieldValue(doc, CONTAINER_NAME_FIELD); + const containerId = getFieldValue(doc, CONTAINER_ID_FIELD); + const cloudInstanceId = getFieldValue(doc, CLOUD_INSTANCE_ID_FIELD); + + return { + [SERVICE_NAME_FIELD]: serviceName, + [HOST_NAME_FIELD]: hostName, + [AGENT_NAME_FIELD]: agentName, + [ORCHESTRATOR_CLUSTER_NAME_FIELD]: orchestratorClusterName, + [ORCHESTRATOR_RESOURCE_ID_FIELD]: orchestratorResourceId, + [ORCHESTRATOR_NAMESPACE_FIELD]: orchestratorNamespace, + [CONTAINER_NAME_FIELD]: containerName, + [CONTAINER_ID_FIELD]: containerId, + [CLOUD_INSTANCE_ID_FIELD]: cloudInstanceId, + }; +}; + +/** + * createResourceFields definitions + */ +const AgentIcon = dynamic(() => import('@kbn/custom-icons/src/components/agent_icon')); + +const resourceCustomComponentsMap: Partial< + Record> +> = { + [SERVICE_NAME_FIELD]: ServiceNameBadgeWithActions, +}; + +export interface ResourceFieldDescriptor { + ResourceBadge: React.ComponentType; + Icon?: () => JSX.Element; + name: keyof ResourceFields; + value: string; +} + +export const createResourceFields = ( + row: DataTableRecord, + core: CoreStart, + share?: SharePluginStart +): ResourceFieldDescriptor[] => { + const resourceDoc = getUnformattedResourceFields(row as LogDocument); + + const availableResourceFields = getAvailableResourceFields(resourceDoc); + + const resourceFields = availableResourceFields.map((name) => { + const ResourceBadgeComponent = resourceCustomComponentsMap[name] ?? FieldBadgeWithActions; + const resourceBadgeComponentWithDependencies = (props: FieldBadgeWithActionsProps) => ( + + ); + return { + name, + value: resourceDoc[name] as string, + ResourceBadge: resourceBadgeComponentWithDependencies, + ...(name === SERVICE_NAME_FIELD && { + Icon: () => ( + + ), + }), + }; + }); + + return resourceFields; +}; + +/** + * formatJsonDocumentForContent definitions + */ +export const formatJsonDocumentForContent = (row: DataTableRecord) => { + const flattenedResult: DataTableRecord['flattened'] = {}; + const rawFieldResult: DataTableRecord['raw']['fields'] = {}; + const { raw, flattened } = row; + const { fields } = raw; + + // We need 2 loops here for flattened and raw.fields. Flattened contains all fields, + // whereas raw.fields only contains certain fields excluding _ignored + for (const fieldName in flattened) { + if (isFieldAllowed(fieldName) && flattened[fieldName]) { + flattenedResult[fieldName] = flattened[fieldName]; + } + } + + for (const fieldName in fields) { + if (isFieldAllowed(fieldName) && fields[fieldName]) { + rawFieldResult[fieldName] = fields[fieldName]; + } + } + + return { + ...row, + flattened: flattenedResult, + raw: { + ...raw, + fields: rawFieldResult, + }, + }; +}; + +const isFieldAllowed = (field: string) => + !FILTER_OUT_FIELDS_PREFIXES_FOR_CONTENT.some((prefix) => field.startsWith(prefix)); diff --git a/packages/kbn-discover-contextual-components/src/data_types/logs/components/translations.tsx b/packages/kbn-discover-contextual-components/src/data_types/logs/components/translations.tsx new file mode 100644 index 0000000000000..52e083f8b86b8 --- /dev/null +++ b/packages/kbn-discover-contextual-components/src/data_types/logs/components/translations.tsx @@ -0,0 +1,72 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { i18n } from '@kbn/i18n'; + +export const jsonLabel = i18n.translate('discover.logs.dataTable.header.popover.json', { + defaultMessage: 'JSON', +}); + +export const contentLabel = i18n.translate('discover.logs.dataTable.header.popover.content', { + defaultMessage: 'Content', +}); + +export const resourceLabel = i18n.translate('discover.logs.dataTable.header.popover.resource', { + defaultMessage: 'Resource', +}); + +export const actionFilterForText = (text: string) => + i18n.translate('discover.logs.flyoutDetail.value.hover.filterFor', { + defaultMessage: 'Filter for this {value}', + values: { + value: text, + }, + }); + +export const actionFilterOutText = (text: string) => + i18n.translate('discover.logs.flyoutDetail.value.hover.filterOut', { + defaultMessage: 'Filter out this {value}', + values: { + value: text, + }, + }); + +export const filterOutText = i18n.translate('discover.logs.popoverAction.filterOut', { + defaultMessage: 'Filter out', +}); + +export const filterForText = i18n.translate('discover.logs.popoverAction.filterFor', { + defaultMessage: 'Filter for', +}); + +export const copyValueText = i18n.translate('discover.logs.popoverAction.copyValue', { + defaultMessage: 'Copy value', +}); + +export const copyValueAriaText = (fieldName: string) => + i18n.translate('discover.logs.popoverAction.copyValueAriaText', { + defaultMessage: 'Copy value of {fieldName}', + values: { + fieldName, + }, + }); + +export const openCellActionPopoverAriaText = i18n.translate( + 'discover.logs.popoverAction.openPopover', + { + defaultMessage: 'Open popover', + } +); + +export const closeCellActionPopoverText = i18n.translate( + 'discover.logs.popoverAction.closePopover', + { + defaultMessage: 'Close popover', + } +); diff --git a/packages/kbn-discover-contextual-components/src/index.ts b/packages/kbn-discover-contextual-components/src/index.ts new file mode 100644 index 0000000000000..52ee5931aa4fc --- /dev/null +++ b/packages/kbn-discover-contextual-components/src/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { dynamic } from '@kbn/shared-ux-utility'; + +export * from './data_types/logs/components'; + +export const LazySummaryColumn = dynamic( + () => import('./data_types/logs/components/summary_column/summary_column') +); diff --git a/packages/kbn-discover-contextual-components/tsconfig.json b/packages/kbn-discover-contextual-components/tsconfig.json new file mode 100644 index 0000000000000..21d65228b9597 --- /dev/null +++ b/packages/kbn-discover-contextual-components/tsconfig.json @@ -0,0 +1,37 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node", + "@testing-library/jest-dom", + "@testing-library/react" + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx", + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/field-formats-plugin", + "@kbn/discover-utils", + "@kbn/router-utils", + "@kbn/management-settings-ids", + "@kbn/share-plugin", + "@kbn/ui-theme", + "@kbn/unified-data-table", + "@kbn/unified-doc-viewer", + "@kbn/react-hooks", + "@kbn/core-lifecycle-browser", + "@kbn/i18n", + "@kbn/unified-doc-viewer-plugin", + "@kbn/core", + "@kbn/shared-ux-utility", + "@kbn/elastic-agent-utils", + "@kbn/custom-icons", + ] +} diff --git a/packages/kbn-discover-utils/index.ts b/packages/kbn-discover-utils/index.ts index ed6d58ca3da8d..7234944783037 100644 --- a/packages/kbn-discover-utils/index.ts +++ b/packages/kbn-discover-utils/index.ts @@ -52,15 +52,17 @@ export { getLogLevelCoalescedValue, getLogLevelCoalescedValueLabel, LogLevelCoalescedValue, - LogLevelBadge, getFieldValue, getVisibleColumns, canPrependTimeFieldColumn, DiscoverFlyouts, dismissAllFlyoutsExceptFor, dismissFlyouts, + LogLevelBadge, } from './src'; export type { LogsContextService } from './src'; export * from './src/types'; + +export * from './src/data_types/logs/constants'; diff --git a/packages/kbn-discover-utils/src/data_types/logs/components/index.ts b/packages/kbn-discover-utils/src/data_types/logs/components/index.tsx similarity index 100% rename from packages/kbn-discover-utils/src/data_types/logs/components/index.ts rename to packages/kbn-discover-utils/src/data_types/logs/components/index.tsx diff --git a/packages/kbn-discover-utils/src/data_types/logs/constants.ts b/packages/kbn-discover-utils/src/data_types/logs/constants.ts new file mode 100644 index 0000000000000..82edebaff0e81 --- /dev/null +++ b/packages/kbn-discover-utils/src/data_types/logs/constants.ts @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { fieldConstants } from '../..'; +import { SmartFieldGridColumnOptions } from './types'; + +export * from '../../field_constants'; + +export const LOGS_EXPLORER_PROFILE_ID = 'logs-explorer'; + +// Virtual column fields +export const CONTENT_FIELD = 'content'; +export const RESOURCE_FIELD = 'resource'; + +// Sizing +export const DATA_GRID_COLUMN_WIDTH_SMALL = 240; +export const DATA_GRID_COLUMN_WIDTH_MEDIUM = 320; +export const ACTIONS_COLUMN_WIDTH = 80; + +export const RESOURCE_FIELD_CONFIGURATION: SmartFieldGridColumnOptions = { + type: 'smart-field', + smartField: RESOURCE_FIELD, + fallbackFields: [fieldConstants.HOST_NAME_FIELD, fieldConstants.SERVICE_NAME_FIELD], + width: DATA_GRID_COLUMN_WIDTH_MEDIUM, +}; + +export const CONTENT_FIELD_CONFIGURATION: SmartFieldGridColumnOptions = { + type: 'smart-field', + smartField: CONTENT_FIELD, + fallbackFields: [fieldConstants.MESSAGE_FIELD], +}; + +export const SMART_FALLBACK_FIELDS = { + [CONTENT_FIELD]: CONTENT_FIELD_CONFIGURATION, + [RESOURCE_FIELD]: RESOURCE_FIELD_CONFIGURATION, +}; + +// UI preferences +export const DEFAULT_COLUMNS = [RESOURCE_FIELD_CONFIGURATION, CONTENT_FIELD_CONFIGURATION]; +export const DEFAULT_ROWS_PER_PAGE = 100; + +// List of prefixes which needs to be filtered out for Display in Content Column +export const FILTER_OUT_FIELDS_PREFIXES_FOR_CONTENT = [ + '_', // Filter fields like '_id', '_score' + '@timestamp', + 'agent.', + 'elastic_agent.', + 'data_stream.', + 'ecs.', + 'host.', + 'container.', + 'cloud.', + 'kubernetes.', + 'orchestrator.', + 'log.', + 'service.', +]; + +export const DEFAULT_ALLOWED_DATA_VIEWS = ['logs', 'auditbeat', 'filebeat', 'winlogbeat']; +export const DEFAULT_ALLOWED_LOGS_DATA_VIEWS = ['logs', 'auditbeat', 'filebeat', 'winlogbeat']; + +export const LOG_LEVEL_FIELDS = ['log.level', 'log_level']; +export const SERVICE_NAME_FIELDS = ['service.name', 'service_name']; +export const AGENT_NAME_FIELD = 'agent.name'; diff --git a/packages/kbn-discover-utils/src/data_types/logs/index.ts b/packages/kbn-discover-utils/src/data_types/logs/index.ts index 7ec996ee31010..30b023b6328bb 100644 --- a/packages/kbn-discover-utils/src/data_types/logs/index.ts +++ b/packages/kbn-discover-utils/src/data_types/logs/index.ts @@ -8,7 +8,7 @@ */ export * from './types'; -export * from './components'; export * from './utils'; export * from './logs_context_service'; +export * from './components'; diff --git a/packages/kbn-discover-utils/src/data_types/logs/types.ts b/packages/kbn-discover-utils/src/data_types/logs/types.ts index 843205d6e8b1e..123ad6c631026 100644 --- a/packages/kbn-discover-utils/src/data_types/logs/types.ts +++ b/packages/kbn-discover-utils/src/data_types/logs/types.ts @@ -86,3 +86,10 @@ export interface StackTraceFields { 'error.exception.stacktrace'?: string; 'error.log.stacktrace'?: string; } + +export interface SmartFieldGridColumnOptions { + type: 'smart-field'; + smartField: 'content' | 'resource'; + fallbackFields: string[]; + width?: number; +} diff --git a/src/plugins/discover/public/utils/get_available_resource_fields.ts b/packages/kbn-discover-utils/src/data_types/logs/utils/get_available_resource_fields.ts similarity index 87% rename from src/plugins/discover/public/utils/get_available_resource_fields.ts rename to packages/kbn-discover-utils/src/data_types/logs/utils/get_available_resource_fields.ts index 588194d2a13ca..e59b7a99c9163 100644 --- a/src/plugins/discover/public/utils/get_available_resource_fields.ts +++ b/packages/kbn-discover-utils/src/data_types/logs/utils/get_available_resource_fields.ts @@ -7,8 +7,8 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { ResourceFields } from '@kbn/discover-utils/src'; -import * as constants from '../../common/data_types/logs/constants'; +import { ResourceFields } from '../../..'; +import * as constants from '../constants'; export const getAvailableResourceFields = (resourceDoc: ResourceFields) => { const resourceFields: Array = [ diff --git a/packages/kbn-discover-utils/src/data_types/logs/utils/index.ts b/packages/kbn-discover-utils/src/data_types/logs/utils/index.ts index 0b266fa5b4935..365365eb7ac13 100644 --- a/packages/kbn-discover-utils/src/data_types/logs/utils/index.ts +++ b/packages/kbn-discover-utils/src/data_types/logs/utils/index.ts @@ -9,3 +9,4 @@ export * from './get_log_level_color'; export * from './get_log_level_coalesed_value'; +export * from './get_available_resource_fields'; diff --git a/packages/kbn-discover-utils/tsconfig.json b/packages/kbn-discover-utils/tsconfig.json index 90235fada49c5..865603e379eca 100644 --- a/packages/kbn-discover-utils/tsconfig.json +++ b/packages/kbn-discover-utils/tsconfig.json @@ -25,9 +25,9 @@ "@kbn/field-types", "@kbn/i18n", "@kbn/core-ui-settings-browser", - "@kbn/ui-theme", "@kbn/expressions-plugin", "@kbn/logs-data-access-plugin", + "@kbn/ui-theme", "@kbn/i18n-react" ] } diff --git a/src/plugins/discover/common/data_types/logs/constants.ts b/src/plugins/discover/common/data_types/logs/constants.ts index 18259dcc56b28..a9ca3697763f9 100644 --- a/src/plugins/discover/common/data_types/logs/constants.ts +++ b/src/plugins/discover/common/data_types/logs/constants.ts @@ -7,64 +7,4 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { fieldConstants } from '@kbn/discover-utils'; -import { SmartFieldGridColumnOptions } from './display_options'; - -export * from '@kbn/discover-utils/src/field_constants'; - -export const LOGS_EXPLORER_PROFILE_ID = 'logs-explorer'; - -// Virtual column fields -export const CONTENT_FIELD = 'content'; -export const RESOURCE_FIELD = 'resource'; - -// Sizing -export const DATA_GRID_COLUMN_WIDTH_SMALL = 240; -export const DATA_GRID_COLUMN_WIDTH_MEDIUM = 320; -export const ACTIONS_COLUMN_WIDTH = 80; - -export const RESOURCE_FIELD_CONFIGURATION: SmartFieldGridColumnOptions = { - type: 'smart-field', - smartField: RESOURCE_FIELD, - fallbackFields: [fieldConstants.HOST_NAME_FIELD, fieldConstants.SERVICE_NAME_FIELD], - width: DATA_GRID_COLUMN_WIDTH_MEDIUM, -}; - -export const CONTENT_FIELD_CONFIGURATION: SmartFieldGridColumnOptions = { - type: 'smart-field', - smartField: CONTENT_FIELD, - fallbackFields: [fieldConstants.MESSAGE_FIELD], -}; - -export const SMART_FALLBACK_FIELDS = { - [CONTENT_FIELD]: CONTENT_FIELD_CONFIGURATION, - [RESOURCE_FIELD]: RESOURCE_FIELD_CONFIGURATION, -}; - -// UI preferences -export const DEFAULT_COLUMNS = [RESOURCE_FIELD_CONFIGURATION, CONTENT_FIELD_CONFIGURATION]; -export const DEFAULT_ROWS_PER_PAGE = 100; - -// List of prefixes which needs to be filtered out for Display in Content Column -export const FILTER_OUT_FIELDS_PREFIXES_FOR_CONTENT = [ - '_', // Filter fields like '_id', '_score' - '@timestamp', - 'agent.', - 'elastic_agent.', - 'data_stream.', - 'ecs.', - 'host.', - 'container.', - 'cloud.', - 'kubernetes.', - 'orchestrator.', - 'log.', - 'service.', -]; - -export const DEFAULT_ALLOWED_DATA_VIEWS = ['logs', 'auditbeat', 'filebeat', 'winlogbeat']; -export const DEFAULT_ALLOWED_LOGS_DATA_VIEWS = ['logs', 'auditbeat', 'filebeat', 'winlogbeat']; - -export const LOG_LEVEL_FIELDS = ['log.level', 'log_level']; -export const SERVICE_NAME_FIELDS = ['service.name', 'service_name']; -export const AGENT_NAME_FIELD = 'agent.name'; +export * from '@kbn/discover-utils/src/data_types/logs/constants'; diff --git a/src/plugins/discover/public/components/data_types/logs/service_name_cell.tsx b/src/plugins/discover/public/components/data_types/logs/service_name_cell.tsx index cd94cd609dc69..3d543f7f0c954 100644 --- a/src/plugins/discover/public/components/data_types/logs/service_name_cell.tsx +++ b/src/plugins/discover/public/components/data_types/logs/service_name_cell.tsx @@ -15,9 +15,10 @@ import type { DataGridCellValueElementProps } from '@kbn/unified-data-table'; import { css } from '@emotion/react'; import { getFieldValue } from '@kbn/discover-utils'; import { euiThemeVars } from '@kbn/ui-theme'; +import { ServiceNameBadgeWithActions } from '@kbn/discover-contextual-components'; +import { useDiscoverServices } from '../../../hooks/use_discover_services'; import { CellRenderersExtensionParams } from '../../../context_awareness'; import { AGENT_NAME_FIELD } from '../../../../common/data_types/logs/constants'; -import { ServiceNameBadgeWithActions } from './service_name_badge_with_actions'; const AgentIcon = dynamic(() => import('@kbn/custom-icons/src/components/agent_icon')); const dataTestSubj = 'serviceNameCell'; @@ -28,6 +29,7 @@ const agentIconStyle = css` export const getServiceNameCell = (serviceNameField: string, { actions }: CellRenderersExtensionParams) => (props: DataGridCellValueElementProps) => { + const { core, share } = useDiscoverServices(); const serviceNameValue = getFieldValue(props.row, serviceNameField) as string; const agentName = getFieldValue(props.row, AGENT_NAME_FIELD) as AgentName; @@ -47,6 +49,8 @@ export const getServiceNameCell = icon={getIcon} value={serviceNameValue} property={serviceNameField} + core={core} + share={share} /> ); }; diff --git a/src/plugins/discover/public/components/data_types/logs/summary_column/index.tsx b/src/plugins/discover/public/components/data_types/logs/summary_column/index.tsx index 20fe4380199f3..dbcef4f558b33 100644 --- a/src/plugins/discover/public/components/data_types/logs/summary_column/index.tsx +++ b/src/plugins/discover/public/components/data_types/logs/summary_column/index.tsx @@ -8,13 +8,11 @@ */ import React from 'react'; -import { dynamic } from '@kbn/shared-ux-utility'; import { getShouldShowFieldHandler } from '@kbn/discover-utils'; import { DataView } from '@kbn/data-views-plugin/common'; +import { SummaryColumnProps } from '@kbn/discover-contextual-components'; import { CellRenderersExtensionParams } from '../../../../context_awareness'; -import type { SummaryColumnProps } from './summary_column'; - -const SummaryColumn = dynamic(() => import('./summary_column')); +import { SummaryColumn } from './summary_column'; export type SummaryColumnGetterDeps = CellRenderersExtensionParams; @@ -22,7 +20,7 @@ export const getSummaryColumn = (params: SummaryColumnGetterDeps) => { const { actions, dataView, density, rowHeight } = params; const shouldShowFieldHandler = createGetShouldShowFieldHandler(dataView); - return (props: SummaryColumnProps) => ( + return (props: Omit) => ( { - const { isDetails } = props; - - if (isDetails) { - return ; - } - - return ; +import { AllSummaryColumnProps } from '@kbn/discover-contextual-components'; +import { useDiscoverServices } from '../../../../hooks/use_discover_services'; + +const LazySummaryColumn = dynamic( + () => + import( + '@kbn/discover-contextual-components/src/data_types/logs/components/summary_column/summary_column' + ) +); + +export const SummaryColumn = (props: Omit) => { + const { share, core } = useDiscoverServices(); + return ; }; - -// eslint-disable-next-line import/no-default-export -export default SummaryColumn; - -const SummaryCell = ({ - density: maybeNullishDensity, - rowHeight: maybeNullishRowHeight, - ...props -}: SummaryColumnProps & SummaryColumnFactoryDeps) => { - const { onFilter, row } = props; - - const density = maybeNullishDensity ?? DataGridDensity.COMPACT; - const isCompressed = density === DataGridDensity.COMPACT; - - const rowHeight = maybeNullishRowHeight ?? ROWS_HEIGHT_OPTIONS.single; - const isSingleLine = rowHeight === ROWS_HEIGHT_OPTIONS.single || rowHeight === 1; - - const resourceFields = createResourceFields(row); - const shouldRenderResource = resourceFields.length > 0; - - return isSingleLine ? ( - - {shouldRenderResource && ( - - )} - - - ) : ( - <> - {shouldRenderResource && ( - - )} - - - ); -}; - -const SummaryCellPopover = (props: SummaryColumnProps & SummaryColumnFactoryDeps) => { - const { row, dataView, fieldFormats, onFilter, closePopover } = props; - - const resourceFields = createResourceFields(row); - const shouldRenderResource = resourceFields.length > 0; - - const documentOverview = getLogDocumentOverview(row, { dataView, fieldFormats }); - const { field, value } = getMessageFieldWithFallbacks(documentOverview); - const shouldRenderContent = Boolean(field && value); - - const shouldRenderSource = !shouldRenderContent; - - return ( - - - {shouldRenderResource && ( - - - {resourceLabel} - - - - )} - - - {contentLabel} - - {shouldRenderContent && ( - - - {field} - - - {value} - - - )} - {shouldRenderSource && ( - - - {jsonLabel} - - - - )} - - - ); -}; - -const singleLineResourceCss = { - flexGrow: 0, - lineHeight: 'normal', - marginTop: -1, -}; - -const multiLineResourceCss = { display: 'inline-flex' }; diff --git a/src/plugins/discover/public/components/data_types/logs/summary_column/utils.tsx b/src/plugins/discover/public/components/data_types/logs/summary_column/utils.tsx deleted file mode 100644 index 470ec8a0f86fa..0000000000000 --- a/src/plugins/discover/public/components/data_types/logs/summary_column/utils.tsx +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { getFieldValue, LogDocument, ResourceFields } from '@kbn/discover-utils/src'; -import { DataTableRecord } from '@kbn/discover-utils'; -import { dynamic } from '@kbn/shared-ux-utility'; -import React from 'react'; -import { css } from '@emotion/react'; -import { AgentName } from '@kbn/elastic-agent-utils'; -import { euiThemeVars } from '@kbn/ui-theme'; -import { getAvailableResourceFields } from '../../../../utils/get_available_resource_fields'; -import * as constants from '../../../../../common/data_types/logs/constants'; -import { ServiceNameBadgeWithActions } from '../service_name_badge_with_actions'; -import { FieldBadgeWithActions, FieldBadgeWithActionsProps } from '../cell_actions_popover'; - -/** - * getUnformattedResourceFields definitions - */ -export const getUnformattedResourceFields = (doc: LogDocument): ResourceFields => { - const serviceName = getFieldValue(doc, constants.SERVICE_NAME_FIELD); - const hostName = getFieldValue(doc, constants.HOST_NAME_FIELD); - const agentName = getFieldValue(doc, constants.AGENT_NAME_FIELD); - const orchestratorClusterName = getFieldValue(doc, constants.ORCHESTRATOR_CLUSTER_NAME_FIELD); - const orchestratorResourceId = getFieldValue(doc, constants.ORCHESTRATOR_RESOURCE_ID_FIELD); - const orchestratorNamespace = getFieldValue(doc, constants.ORCHESTRATOR_NAMESPACE_FIELD); - const containerName = getFieldValue(doc, constants.CONTAINER_NAME_FIELD); - const containerId = getFieldValue(doc, constants.CONTAINER_ID_FIELD); - const cloudInstanceId = getFieldValue(doc, constants.CLOUD_INSTANCE_ID_FIELD); - - return { - [constants.SERVICE_NAME_FIELD]: serviceName, - [constants.HOST_NAME_FIELD]: hostName, - [constants.AGENT_NAME_FIELD]: agentName, - [constants.ORCHESTRATOR_CLUSTER_NAME_FIELD]: orchestratorClusterName, - [constants.ORCHESTRATOR_RESOURCE_ID_FIELD]: orchestratorResourceId, - [constants.ORCHESTRATOR_NAMESPACE_FIELD]: orchestratorNamespace, - [constants.CONTAINER_NAME_FIELD]: containerName, - [constants.CONTAINER_ID_FIELD]: containerId, - [constants.CLOUD_INSTANCE_ID_FIELD]: cloudInstanceId, - }; -}; - -/** - * createResourceFields definitions - */ -const AgentIcon = dynamic(() => import('@kbn/custom-icons/src/components/agent_icon')); - -const resourceCustomComponentsMap: Partial< - Record> -> = { - [constants.SERVICE_NAME_FIELD]: ServiceNameBadgeWithActions, -}; - -export interface ResourceFieldDescriptor { - ResourceBadge: React.ComponentType; - Icon?: () => JSX.Element; - name: keyof ResourceFields; - value: string; -} - -export const createResourceFields = (row: DataTableRecord): ResourceFieldDescriptor[] => { - const resourceDoc = getUnformattedResourceFields(row as LogDocument); - - const availableResourceFields = getAvailableResourceFields(resourceDoc); - - const resourceFields = availableResourceFields.map((name) => ({ - name, - value: resourceDoc[name] as string, - ResourceBadge: resourceCustomComponentsMap[name] ?? FieldBadgeWithActions, - ...(name === constants.SERVICE_NAME_FIELD && { - Icon: () => ( - - ), - }), - })); - - return resourceFields; -}; - -/** - * formatJsonDocumentForContent definitions - */ -export const formatJsonDocumentForContent = (row: DataTableRecord) => { - const flattenedResult: DataTableRecord['flattened'] = {}; - const rawFieldResult: DataTableRecord['raw']['fields'] = {}; - const { raw, flattened } = row; - const { fields } = raw; - - // We need 2 loops here for flattened and raw.fields. Flattened contains all fields, - // whereas raw.fields only contains certain fields excluding _ignored - for (const fieldName in flattened) { - if (isFieldAllowed(fieldName) && flattened[fieldName]) { - flattenedResult[fieldName] = flattened[fieldName]; - } - } - - for (const fieldName in fields) { - if (isFieldAllowed(fieldName) && fields[fieldName]) { - rawFieldResult[fieldName] = fields[fieldName]; - } - } - - return { - ...row, - flattened: flattenedResult, - raw: { - ...raw, - fields: rawFieldResult, - }, - }; -}; - -const isFieldAllowed = (field: string) => - !constants.FILTER_OUT_FIELDS_PREFIXES_FOR_CONTENT.some((prefix) => field.startsWith(prefix)); diff --git a/src/plugins/discover/public/components/data_types/logs/translations.tsx b/src/plugins/discover/public/components/data_types/logs/translations.tsx deleted file mode 100644 index bbc39022bd503..0000000000000 --- a/src/plugins/discover/public/components/data_types/logs/translations.tsx +++ /dev/null @@ -1,305 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import React from 'react'; -import { i18n } from '@kbn/i18n'; -import { EuiCode } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; - -export const flyoutContentLabel = i18n.translate('discover.logs.flyoutDetail.label.message', { - defaultMessage: 'Content breakdown', -}); - -export const jsonLabel = i18n.translate('discover.logs.dataTable.header.popover.json', { - defaultMessage: 'JSON', -}); - -export const contentLabel = i18n.translate('discover.logs.dataTable.header.popover.content', { - defaultMessage: 'Content', -}); - -export const resourceLabel = i18n.translate('discover.logs.dataTable.header.popover.resource', { - defaultMessage: 'Resource', -}); - -export const actionsLabel = i18n.translate('discover.logs.dataTable.header.popover.actions', { - defaultMessage: 'Actions', -}); - -export const actionsLabelLowerCase = i18n.translate( - 'discover.logs.dataTable.header.popover.actions.lowercase', - { - defaultMessage: 'actions', - } -); - -export const flyoutServiceLabel = i18n.translate('discover.logs.flyoutDetail.label.service', { - defaultMessage: 'Service', -}); - -export const flyoutTraceLabel = i18n.translate('discover.logs.flyoutDetail.label.trace', { - defaultMessage: 'Trace', -}); - -export const flyoutHostNameLabel = i18n.translate('discover.logs.flyoutDetail.label.hostName', { - defaultMessage: 'Host name', -}); - -export const serviceInfraAccordionTitle = i18n.translate( - 'discover.logs.flyoutDetail.accordion.title.serviceInfra', - { - defaultMessage: 'Service & Infrastructure', - } -); - -export const cloudAccordionTitle = i18n.translate( - 'discover.logs.flyoutDetail.accordion.title.cloud', - { - defaultMessage: 'Cloud', - } -); - -export const otherAccordionTitle = i18n.translate( - 'discover.logs.flyoutDetail.accordion.title.other', - { - defaultMessage: 'Other', - } -); - -export const flyoutOrchestratorClusterNameLabel = i18n.translate( - 'discover.logs.flyoutDetail.label.orchestratorClusterName', - { - defaultMessage: 'Orchestrator cluster Name', - } -); - -export const flyoutOrchestratorResourceIdLabel = i18n.translate( - 'discover.logs.flyoutDetail.label.orchestratorResourceId', - { - defaultMessage: 'Orchestrator resource ID', - } -); - -export const flyoutCloudProviderLabel = i18n.translate( - 'discover.logs.flyoutDetail.label.cloudProvider', - { - defaultMessage: 'Cloud provider', - } -); - -export const flyoutCloudRegionLabel = i18n.translate( - 'discover.logs.flyoutDetail.label.cloudRegion', - { - defaultMessage: 'Cloud region', - } -); - -export const flyoutCloudAvailabilityZoneLabel = i18n.translate( - 'discover.logs.flyoutDetail.label.cloudAvailabilityZone', - { - defaultMessage: 'Cloud availability zone', - } -); - -export const flyoutCloudProjectIdLabel = i18n.translate( - 'discover.logs.flyoutDetail.label.cloudProjectId', - { - defaultMessage: 'Cloud project ID', - } -); - -export const flyoutCloudInstanceIdLabel = i18n.translate( - 'discover.logs.flyoutDetail.label.cloudInstanceId', - { - defaultMessage: 'Cloud instance ID', - } -); - -export const flyoutLogPathFileLabel = i18n.translate( - 'discover.logs.flyoutDetail.label.logPathFile', - { - defaultMessage: 'Log path file', - } -); - -export const flyoutNamespaceLabel = i18n.translate('discover.logs.flyoutDetail.label.namespace', { - defaultMessage: 'Namespace', -}); - -export const flyoutDatasetLabel = i18n.translate('discover.logs.flyoutDetail.label.dataset', { - defaultMessage: 'Dataset', -}); - -export const flyoutShipperLabel = i18n.translate('discover.logs.flyoutDetail.label.shipper', { - defaultMessage: 'Shipper', -}); - -export const actionFilterForText = (text: string) => - i18n.translate('discover.logs.flyoutDetail.value.hover.filterFor', { - defaultMessage: 'Filter for this {value}', - values: { - value: text, - }, - }); - -export const actionFilterOutText = (text: string) => - i18n.translate('discover.logs.flyoutDetail.value.hover.filterOut', { - defaultMessage: 'Filter out this {value}', - values: { - value: text, - }, - }); - -export const filterOutText = i18n.translate('discover.logs.popoverAction.filterOut', { - defaultMessage: 'Filter out', -}); - -export const filterForText = i18n.translate('discover.logs.popoverAction.filterFor', { - defaultMessage: 'Filter for', -}); - -export const flyoutHoverActionFilterForFieldPresentText = i18n.translate( - 'discover.logs.flyoutDetail.value.hover.filterForFieldPresent', - { - defaultMessage: 'Filter for field present', - } -); - -export const flyoutHoverActionToggleColumnText = i18n.translate( - 'discover.logs.flyoutDetail.value.hover.toggleColumn', - { - defaultMessage: 'Toggle column in table', - } -); - -export const flyoutHoverActionCopyToClipboardText = i18n.translate( - 'discover.logs.flyoutDetail.value.hover.copyToClipboard', - { - defaultMessage: 'Copy to clipboard', - } -); - -export const copyValueText = i18n.translate('discover.logs.popoverAction.copyValue', { - defaultMessage: 'Copy value', -}); - -export const copyValueAriaText = (fieldName: string) => - i18n.translate('discover.logs.popoverAction.copyValueAriaText', { - defaultMessage: 'Copy value of {fieldName}', - values: { - fieldName, - }, - }); - -export const flyoutAccordionShowMoreText = (count: number) => - i18n.translate('discover.logs.flyoutDetail.section.showMore', { - defaultMessage: '+ {hiddenCount} more', - values: { - hiddenCount: count, - }, - }); - -export const openCellActionPopoverAriaText = i18n.translate( - 'discover.logs.popoverAction.openPopover', - { - defaultMessage: 'Open popover', - } -); - -export const closeCellActionPopoverText = i18n.translate( - 'discover.logs.popoverAction.closePopover', - { - defaultMessage: 'Close popover', - } -); - -export const contentHeaderTooltipParagraph1 = ( - log.level, - message: message, - }} - /> -); - -export const contentHeaderTooltipParagraph2 = i18n.translate( - 'discover.logs.dataTable.header.content.tooltip.paragraph2', - { - defaultMessage: 'When the message field is empty, one of the following is displayed:', - } -); - -export const resourceHeaderTooltipParagraph = i18n.translate( - 'discover.logs.dataTable.header.resource.tooltip.paragraph', - { - defaultMessage: "Fields that provide information on the document's source, such as:", - } -); - -export const actionsHeaderTooltipParagraph = i18n.translate( - 'discover.logs.dataTable.header.actions.tooltip.paragraph', - { - defaultMessage: 'Fields that provide actionable information, such as:', - } -); - -export const actionsHeaderTooltipExpandAction = i18n.translate( - 'discover.logs.dataTable.header.actions.tooltip.expand', - { defaultMessage: 'Expand log details' } -); - -export const actionsHeaderTooltipDegradedAction = ( - - _ignored - - ), - }} - /> -); - -export const actionsHeaderTooltipStacktraceAction = i18n.translate( - 'discover.logs.dataTable.header.actions.tooltip.stacktrace', - { defaultMessage: 'Access to available stacktraces based on:' } -); - -export const degradedDocButtonLabelWhenPresent = i18n.translate( - 'discover.logs.dataTable.controlColumn.actions.button.degradedDocPresent', - { - defaultMessage: - "This document couldn't be parsed correctly. Not all fields are properly populated", - } -); - -export const degradedDocButtonLabelWhenNotPresent = i18n.translate( - 'discover.logs.dataTable.controlColumn.actions.button.degradedDocNotPresent', - { - defaultMessage: 'All fields in this document were parsed correctly', - } -); - -export const stacktraceAvailableControlButton = i18n.translate( - 'discover.logs.dataTable.controlColumn.actions.button.stacktrace.available', - { - defaultMessage: 'Stacktraces available', - } -); - -export const stacktraceNotAvailableControlButton = i18n.translate( - 'discover.logs.dataTable.controlColumn.actions.button.stacktrace.notAvailable', - { - defaultMessage: 'Stacktraces not available', - } -); diff --git a/src/plugins/discover/public/context_awareness/profile_providers/common/logs_data_source_profile/accessors/get_cell_renderers.tsx b/src/plugins/discover/public/context_awareness/profile_providers/common/logs_data_source_profile/accessors/get_cell_renderers.tsx index 7e13baf8ddcf9..68349aeefa09a 100644 --- a/src/plugins/discover/public/context_awareness/profile_providers/common/logs_data_source_profile/accessors/get_cell_renderers.tsx +++ b/src/plugins/discover/public/context_awareness/profile_providers/common/logs_data_source_profile/accessors/get_cell_renderers.tsx @@ -8,12 +8,12 @@ */ import { SOURCE_COLUMN } from '@kbn/unified-data-table'; +import { getLogLevelBadgeCell } from '@kbn/discover-contextual-components'; import { getSummaryColumn } from '../../../../../components/data_types/logs/summary_column'; import { LOG_LEVEL_FIELDS, SERVICE_NAME_FIELDS, } from '../../../../../../common/data_types/logs/constants'; -import { getLogLevelBadgeCell } from '../../../../../components/data_types/logs/log_level_badge_cell'; import { getServiceNameCell } from '../../../../../components/data_types/logs/service_name_cell'; import type { DataSourceProfileProvider } from '../../../../profiles'; diff --git a/src/plugins/discover/tsconfig.json b/src/plugins/discover/tsconfig.json index 1f3ed529d804b..197d323d7d221 100644 --- a/src/plugins/discover/tsconfig.json +++ b/src/plugins/discover/tsconfig.json @@ -96,11 +96,9 @@ "@kbn/observability-ai-assistant-plugin", "@kbn/fields-metadata-plugin", "@kbn/security-solution-common", - "@kbn/router-utils", - "@kbn/management-settings-ids", - "@kbn/react-hooks", "@kbn/logs-data-access-plugin", "@kbn/core-lifecycle-browser", + "@kbn/discover-contextual-components", "@kbn/esql-ast" ], "exclude": [ diff --git a/src/plugins/unified_doc_viewer/kibana.jsonc b/src/plugins/unified_doc_viewer/kibana.jsonc index 56ea8951e3a2d..6bd1b738c0ccb 100644 --- a/src/plugins/unified_doc_viewer/kibana.jsonc +++ b/src/plugins/unified_doc_viewer/kibana.jsonc @@ -12,3 +12,4 @@ "optionalPlugins": ["fieldsMetadata"] } } + \ No newline at end of file diff --git a/tsconfig.base.json b/tsconfig.base.json index e2d84fa57311c..2da0c007278e8 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -764,6 +764,8 @@ "@kbn/dev-utils/*": ["packages/kbn-dev-utils/*"], "@kbn/developer-examples-plugin": ["examples/developer_examples"], "@kbn/developer-examples-plugin/*": ["examples/developer_examples/*"], + "@kbn/discover-contextual-components": ["packages/kbn-discover-contextual-components"], + "@kbn/discover-contextual-components/*": ["packages/kbn-discover-contextual-components/*"], "@kbn/discover-customization-examples-plugin": ["examples/discover_customization_examples"], "@kbn/discover-customization-examples-plugin/*": ["examples/discover_customization_examples/*"], "@kbn/discover-enhanced-plugin": ["x-pack/plugins/discover_enhanced"], diff --git a/x-pack/packages/observability/logs_overview/src/components/discover_link/discover_link.tsx b/x-pack/packages/observability/logs_overview/src/components/discover_link/discover_link.tsx index fe108289985a9..676468c2f4a32 100644 --- a/x-pack/packages/observability/logs_overview/src/components/discover_link/discover_link.tsx +++ b/x-pack/packages/observability/logs_overview/src/components/discover_link/discover_link.tsx @@ -13,11 +13,17 @@ import { i18n } from '@kbn/i18n'; import { getRouterLinkProps } from '@kbn/router-utils'; import type { SharePluginStart } from '@kbn/share-plugin/public'; import React, { useCallback, useMemo } from 'react'; -import type { IndexNameLogsSourceConfiguration } from '../../utils/logs_source'; +import type { ResolvedIndexNameLogsSourceConfiguration } from '../../utils/logs_source'; +interface LinkFilter { + filter: QueryDslQueryContainer; + meta?: { + name?: string; + }; +} export interface DiscoverLinkProps { - documentFilters?: QueryDslQueryContainer[]; - logsSource: IndexNameLogsSourceConfiguration; + documentFilters?: LinkFilter[]; + logsSource: ResolvedIndexNameLogsSourceConfiguration; timeRange: { start: string; end: string; @@ -46,10 +52,10 @@ export const DiscoverLink = React.memo( filters: documentFilters?.map((filter) => buildCustomFilter( logsSource.indexName, - filter, + filter.filter, false, false, - categorizedLogsFilterLabel, + filter.meta?.name ?? categorizedLogsFilterLabel, FilterStateStore.APP_STATE ) ), diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories.tsx index 6204667827281..1ce45ca6b3727 100644 --- a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories.tsx +++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories.tsx @@ -14,7 +14,12 @@ import { categorizeLogsService, createCategorizeLogsServiceImplementations, } from '../../services/categorize_logs_service'; -import { IndexNameLogsSourceConfiguration } from '../../utils/logs_source'; +import { + categoryDetailsService, + createCategoryDetailsServiceImplementations, +} from '../../services/category_details_service'; +import { LogCategory } from '../../types'; +import { ResolvedIndexNameLogsSourceConfiguration } from '../../utils/logs_source'; import { LogCategoriesErrorContent } from './log_categories_error_content'; import { LogCategoriesLoadingContent } from './log_categories_loading_content'; import { @@ -25,7 +30,7 @@ import { export interface LogCategoriesProps { dependencies: LogCategoriesDependencies; documentFilters?: QueryDslQueryContainer[]; - logsSource: IndexNameLogsSourceConfiguration; + logsSource: ResolvedIndexNameLogsSourceConfiguration; // The time range could be made optional if we want to support an internal // time range picker timeRange: { @@ -61,12 +66,49 @@ export const LogCategories: React.FC = ({ } ); + const [categoryDetailsServiceState, sendToCategoryDetailsService] = useMachine( + categoryDetailsService.provide( + createCategoryDetailsServiceImplementations({ search: dependencies.search }) + ), + { + inspect: consoleInspector, + input: { + index: logsSource.indexName, + startTimestamp: timeRange.start, + endTimestamp: timeRange.end, + timeField: logsSource.timestampField, + messageField: logsSource.messageField, + additionalFilters: documentFilters, + dataView: logsSource.dataView, + }, + } + ); + const cancelOperation = useCallback(() => { sendToCategorizeLogsService({ type: 'cancel', }); }, [sendToCategorizeLogsService]); + const closeFlyout = useCallback(() => { + sendToCategoryDetailsService({ + type: 'setExpandedCategory', + category: null, + rowIndex: null, + }); + }, [sendToCategoryDetailsService]); + + const openFlyout = useCallback( + (category: LogCategory | null, rowIndex: number | null) => { + sendToCategoryDetailsService({ + type: 'setExpandedCategory', + category, + rowIndex, + }); + }, + [sendToCategoryDetailsService] + ); + if (categorizeLogsServiceState.matches('done')) { return ( = ({ logCategories={categorizeLogsServiceState.context.categories} logsSource={logsSource} timeRange={timeRange} + categoryDetailsServiceState={categoryDetailsServiceState} + onCloseFlyout={closeFlyout} + onOpenFlyout={openFlyout} /> ); } else if (categorizeLogsServiceState.matches('failed')) { diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_control_bar.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_control_bar.tsx index 4538b0ec2fd5d..7c13ac1446320 100644 --- a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_control_bar.tsx +++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_control_bar.tsx @@ -8,13 +8,13 @@ import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import type { SharePluginStart } from '@kbn/share-plugin/public'; -import React from 'react'; -import type { IndexNameLogsSourceConfiguration } from '../../utils/logs_source'; +import React, { useMemo } from 'react'; +import type { ResolvedIndexNameLogsSourceConfiguration } from '../../utils/logs_source'; import { DiscoverLink } from '../discover_link'; export interface LogCategoriesControlBarProps { documentFilters?: QueryDslQueryContainer[]; - logsSource: IndexNameLogsSourceConfiguration; + logsSource: ResolvedIndexNameLogsSourceConfiguration; timeRange: { start: string; end: string; @@ -28,12 +28,17 @@ export interface LogCategoriesControlBarDependencies { export const LogCategoriesControlBar: React.FC = React.memo( ({ dependencies, documentFilters, logsSource, timeRange }) => { + const linkFilters = useMemo( + () => documentFilters?.map((filter) => ({ filter })), + [documentFilters] + ); + return ( diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid.tsx index d9e960685de99..badd316371ec6 100644 --- a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid.tsx +++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid.tsx @@ -25,10 +25,14 @@ import { logCategoriesGridColumns, renderLogCategoriesGridCell, } from './log_categories_grid_cell'; +import { createLogCategoriesGridControlColumns } from './log_categories_grid_control_columns'; export interface LogCategoriesGridProps { dependencies: LogCategoriesGridDependencies; logCategories: LogCategory[]; + expandedRowIndex: number | null; + onOpenFlyout: (category: LogCategory, rowIndex: number) => void; + onCloseFlyout: () => void; } export type LogCategoriesGridDependencies = LogCategoriesGridCellDependencies; @@ -36,6 +40,9 @@ export type LogCategoriesGridDependencies = LogCategoriesGridCellDependencies; export const LogCategoriesGrid: React.FC = ({ dependencies, logCategories, + expandedRowIndex, + onOpenFlyout, + onCloseFlyout, }) => { const [gridState, dispatchGridEvent] = useMachine(gridStateService, { input: { @@ -93,6 +100,11 @@ export const LogCategoriesGrid: React.FC = ({ onSort: (sortingColumns) => dispatchGridEvent({ type: 'changeSortingColumns', sortingColumns }), }} + leadingControlColumns={createLogCategoriesGridControlColumns({ + expandedRowIndex, + onOpenFlyout, + onCloseFlyout, + })} /> ); }; diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_cell.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_cell.tsx index d6ab4969eaf7b..7e40d192df227 100644 --- a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_cell.tsx +++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_cell.tsx @@ -83,7 +83,7 @@ export type LogCategoriesGridColumnId = (typeof logCategoriesGridColumns)[number const cellContextKey = 'cellContext'; -const getCellContext = (cellContext: object): LogCategoriesGridCellContext => +export const getCellContext = (cellContext: object): LogCategoriesGridCellContext => (cellContextKey in cellContext ? cellContext[cellContextKey] : {}) as LogCategoriesGridCellContext; diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_control_columns.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_control_columns.tsx new file mode 100644 index 0000000000000..546754ae1cc5b --- /dev/null +++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_control_columns.tsx @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiScreenReaderOnly } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { LogCategory } from '../../types'; +import { createLogCategoriesGridExpandButton } from './log_categories_grid_expand_button'; + +const DEFAULT_CONTROL_COLUMN_WIDTH = 40; + +interface ControlColumnsProps { + expandedRowIndex: number | null; + onOpenFlyout: (category: LogCategory, rowIndex: number) => void; + onCloseFlyout: () => void; +} + +export const createLogCategoriesGridControlColumns = (props: ControlColumnsProps) => { + const { expandedRowIndex, onOpenFlyout, onCloseFlyout } = props; + + return [ + { + id: 'toggleFlyout', + width: DEFAULT_CONTROL_COLUMN_WIDTH, + headerCellRender: () => ( + + + {i18n.translate('xpack.observabilityLogsOverview.controlColumnHeader', { + defaultMessage: 'Control column', + })} + + + ), + rowCellRender: createLogCategoriesGridExpandButton({ + expandedRowIndex, + onOpenFlyout, + onCloseFlyout, + }), + }, + ]; +}; diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_expand_button.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_expand_button.tsx new file mode 100644 index 0000000000000..34c8e72e2d91a --- /dev/null +++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_expand_button.tsx @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiButtonIcon, EuiToolTip, RenderCellValue } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React, { useCallback } from 'react'; +import { LogCategory } from '../../types'; +import { getCellContext } from './log_categories_grid_cell'; + +interface CreateLogCategoriesGridExpandButtonProps { + expandedRowIndex: number | null; + onOpenFlyout: (category: LogCategory, rowIndex: number) => void; + onCloseFlyout: () => void; +} + +export const createLogCategoriesGridExpandButton = + ({ + expandedRowIndex, + onOpenFlyout, + onCloseFlyout, + }: CreateLogCategoriesGridExpandButtonProps): RenderCellValue => + (props) => { + const { rowIndex } = props; + const { logCategories } = getCellContext(props); + const logCategory = logCategories[rowIndex]; + const isCurrentRowExpanded = expandedRowIndex === rowIndex; + const onClickHandler = useCallback(() => { + if (isCurrentRowExpanded) { + onCloseFlyout(); + } else { + onOpenFlyout(logCategory, rowIndex); + } + }, [isCurrentRowExpanded, logCategory, rowIndex]); + + return ( + + ); + }; + +interface ExpandButtonProps { + isCurrentRowExpanded: boolean; + onClickHandler: () => void; +} + +const ExpandButton: React.FC = ({ isCurrentRowExpanded, onClickHandler }) => { + return ( + + + + ); +}; + +const buttonLabel = i18n.translate( + 'xpack.observabilityLogsOverview.logCategoriesGrid.controlColumns.toggleFlyout', + { + defaultMessage: 'Toggle flyout with details', + } +); diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_pattern_cell.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_pattern_cell.tsx index d507487a99e3c..7507ab5b23f44 100644 --- a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_pattern_cell.tsx +++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_pattern_cell.tsx @@ -5,11 +5,11 @@ * 2.0. */ -import { EuiDataGridColumn, useEuiTheme } from '@elastic/eui'; -import { css } from '@emotion/react'; +import { EuiDataGridColumn } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React, { useMemo } from 'react'; +import React from 'react'; import { LogCategory } from '../../types'; +import { LogCategoryPattern } from '../shared/log_category_pattern'; export const logCategoriesGridPatternColumn = { id: 'pattern' as const, @@ -27,34 +27,5 @@ export interface LogCategoriesGridPatternCellProps { export const LogCategoriesGridPatternCell: React.FC = ({ logCategory, }) => { - const theme = useEuiTheme(); - const { euiTheme } = theme; - const termsList = useMemo(() => logCategory.terms.split(' '), [logCategory.terms]); - - const commonStyle = css` - display: inline-block; - font-family: ${euiTheme.font.familyCode}; - margin-right: ${euiTheme.size.xs}; - `; - - const termStyle = css` - ${commonStyle}; - `; - - const separatorStyle = css` - ${commonStyle}; - color: ${euiTheme.colors.successText}; - `; - - return ( -
-      
*
- {termsList.map((term, index) => ( - -
{term}
-
*
-
- ))} -
- ); + return ; }; diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_result_content.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_result_content.tsx index e16bdda7cb44a..c2b1a0989c2ec 100644 --- a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_result_content.tsx +++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_result_content.tsx @@ -9,8 +9,14 @@ import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/type import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; +import { StateFrom } from 'xstate5'; +import { categoryDetailsService } from '../../services/category_details_service'; import { LogCategory } from '../../types'; -import { IndexNameLogsSourceConfiguration } from '../../utils/logs_source'; +import { ResolvedIndexNameLogsSourceConfiguration } from '../../utils/logs_source'; +import { + LogCategoriesFlyoutDependencies, + LogCategoryDetailsFlyout, +} from '../log_category_details/log_category_details_flyout'; import { LogCategoriesControlBar, LogCategoriesControlBarDependencies, @@ -21,15 +27,19 @@ export interface LogCategoriesResultContentProps { dependencies: LogCategoriesResultContentDependencies; documentFilters?: QueryDslQueryContainer[]; logCategories: LogCategory[]; - logsSource: IndexNameLogsSourceConfiguration; + logsSource: ResolvedIndexNameLogsSourceConfiguration; timeRange: { start: string; end: string; }; + categoryDetailsServiceState: StateFrom; + onCloseFlyout: () => void; + onOpenFlyout: (category: LogCategory, rowIndex: number) => void; } export type LogCategoriesResultContentDependencies = LogCategoriesControlBarDependencies & - LogCategoriesGridDependencies; + LogCategoriesGridDependencies & + LogCategoriesFlyoutDependencies; export const LogCategoriesResultContent: React.FC = ({ dependencies, @@ -37,6 +47,9 @@ export const LogCategoriesResultContent: React.FC { if (logCategories.length === 0) { return ; @@ -52,7 +65,24 @@ export const LogCategoriesResultContent: React.FC
- + + {categoryDetailsServiceState.context.expandedCategory && ( + + )}
); diff --git a/x-pack/packages/observability/logs_overview/src/components/log_category_details/log_category_details_error_content.tsx b/x-pack/packages/observability/logs_overview/src/components/log_category_details/log_category_details_error_content.tsx new file mode 100644 index 0000000000000..509d35b0068e5 --- /dev/null +++ b/x-pack/packages/observability/logs_overview/src/components/log_category_details/log_category_details_error_content.tsx @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiCodeBlock, EuiEmptyPrompt } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; + +export interface LogCategoryDetailsErrorContentProps { + error?: Error; + title: string; +} + +export const LogCategoryDetailsErrorContent: React.FC = ({ + error, + title, +}) => { + return ( + {title}} + body={ + +

{error?.stack ?? error?.toString() ?? unknownErrorDescription}

+
+ } + layout="vertical" + /> + ); +}; + +const unknownErrorDescription = i18n.translate( + 'xpack.observabilityLogsOverview.logCategoryDetails.unknownErrorDescription', + { + defaultMessage: 'An unspecified error occurred.', + } +); diff --git a/x-pack/packages/observability/logs_overview/src/components/log_category_details/log_category_details_flyout.tsx b/x-pack/packages/observability/logs_overview/src/components/log_category_details/log_category_details_flyout.tsx new file mode 100644 index 0000000000000..2f478c771dbfa --- /dev/null +++ b/x-pack/packages/observability/logs_overview/src/components/log_category_details/log_category_details_flyout.tsx @@ -0,0 +1,139 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutHeader, + EuiSpacer, + EuiTitle, + useGeneratedHtmlId, +} from '@elastic/eui'; +import React, { useMemo } from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { StateFrom } from 'xstate5'; +import { i18n } from '@kbn/i18n'; +import { QueryDslQueryContainer } from '@kbn/data-views-plugin/common/types'; +import { LogCategory } from '../../types'; +import { LogCategoryPattern } from '../shared/log_category_pattern'; +import { categoryDetailsService } from '../../services/category_details_service'; +import { + LogCategoryDocumentExamplesTable, + LogCategoryDocumentExamplesTableDependencies, +} from './log_category_document_examples_table'; +import { type ResolvedIndexNameLogsSourceConfiguration } from '../../utils/logs_source'; +import { LogCategoryDetailsLoadingContent } from './log_category_details_loading_content'; +import { LogCategoryDetailsErrorContent } from './log_category_details_error_content'; +import { DiscoverLink } from '../discover_link'; +import { createCategoryQuery } from '../../services/categorize_logs_service/queries'; + +export type LogCategoriesFlyoutDependencies = LogCategoryDocumentExamplesTableDependencies; + +interface LogCategoryDetailsFlyoutProps { + onCloseFlyout: () => void; + logCategory: LogCategory; + categoryDetailsServiceState: StateFrom; + dependencies: LogCategoriesFlyoutDependencies; + logsSource: ResolvedIndexNameLogsSourceConfiguration; + documentFilters?: QueryDslQueryContainer[]; + timeRange: { + start: string; + end: string; + }; +} + +export const LogCategoryDetailsFlyout: React.FC = ({ + onCloseFlyout, + logCategory, + categoryDetailsServiceState, + dependencies, + logsSource, + documentFilters, + timeRange, +}) => { + const flyoutTitleId = useGeneratedHtmlId({ + prefix: 'flyoutTitle', + }); + + const linkFilters = useMemo(() => { + return [ + ...(documentFilters ? documentFilters.map((filter) => ({ filter })) : []), + { + filter: createCategoryQuery(logsSource.messageField)(logCategory.terms), + meta: { + name: i18n.translate( + 'xpack.observabilityLogsOverview.logCategoryDetailsFlyout.discoverLinkFilterName', + { + defaultMessage: 'Category: {terms}', + values: { + terms: logCategory.terms, + }, + } + ), + }, + }, + ]; + }, [documentFilters, logCategory.terms, logsSource.messageField]); + + return ( + onCloseFlyout()} aria-labelledby={flyoutTitleId}> + + + + +

+ +

+
+ + +
+ + + +
+
+ + {categoryDetailsServiceState.matches({ hasCategory: 'fetchingDocuments' }) ? ( + + ) : categoryDetailsServiceState.matches({ hasCategory: 'error' }) ? ( + + ) : ( + + )} + +
+ ); +}; diff --git a/x-pack/packages/observability/logs_overview/src/components/log_category_details/log_category_details_loading_content.tsx b/x-pack/packages/observability/logs_overview/src/components/log_category_details/log_category_details_loading_content.tsx new file mode 100644 index 0000000000000..fd6aa50a38221 --- /dev/null +++ b/x-pack/packages/observability/logs_overview/src/components/log_category_details/log_category_details_loading_content.tsx @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiEmptyPrompt, EuiLoadingSpinner } from '@elastic/eui'; +import React from 'react'; + +interface LogCategoryDetailsLoadingContentProps { + message: string; +} + +export const LogCategoryDetailsLoadingContent: React.FC = ({ + message, +}) => { + return } title={

{message}

} />; +}; diff --git a/x-pack/packages/observability/logs_overview/src/components/log_category_details/log_category_document_examples_table.tsx b/x-pack/packages/observability/logs_overview/src/components/log_category_details/log_category_document_examples_table.tsx new file mode 100644 index 0000000000000..6b43fa86fe49e --- /dev/null +++ b/x-pack/packages/observability/logs_overview/src/components/log_category_details/log_category_document_examples_table.tsx @@ -0,0 +1,151 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiBasicTable, EuiBasicTableColumn, EuiSpacer, EuiText } from '@elastic/eui'; +import React, { useMemo } from 'react'; +import { i18n } from '@kbn/i18n'; +import { DataGridDensity, ROWS_HEIGHT_OPTIONS } from '@kbn/unified-data-table'; +import moment from 'moment'; +import type { SettingsStart } from '@kbn/core-ui-settings-browser'; +import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; +import type { SharePluginStart } from '@kbn/share-plugin/public'; +import { CoreStart } from '@kbn/core-lifecycle-browser'; +import { getLogLevelBadgeCell, LazySummaryColumn } from '@kbn/discover-contextual-components'; +import type { LogCategoryDocument } from '../../services/category_details_service/types'; +import { type ResolvedIndexNameLogsSourceConfiguration } from '../../utils/logs_source'; + +export interface LogCategoryDocumentExamplesTableDependencies { + core: CoreStart; + uiSettings: SettingsStart; + fieldFormats: FieldFormatsStart; + share: SharePluginStart; +} + +export interface LogCategoryDocumentExamplesTableProps { + dependencies: LogCategoryDocumentExamplesTableDependencies; + categoryDocuments: LogCategoryDocument[]; + logsSource: ResolvedIndexNameLogsSourceConfiguration; +} + +const TimestampCell = ({ + dependencies, + timestamp, +}: { + dependencies: LogCategoryDocumentExamplesTableDependencies; + timestamp?: string | number; +}) => { + const dateFormat = useMemo( + () => dependencies.uiSettings.client.get('dateFormat'), + [dependencies.uiSettings.client] + ); + if (!timestamp) return null; + + if (dateFormat) { + return <>{moment(timestamp).format(dateFormat)}; + } else { + return <>{timestamp}; + } +}; + +const LogLevelBadgeCell = getLogLevelBadgeCell('log.level'); + +export const LogCategoryDocumentExamplesTable: React.FC = ({ + categoryDocuments, + dependencies, + logsSource, +}) => { + const columns: Array> = [ + { + field: 'row', + name: 'Timestamp', + width: '25%', + render: (row: any) => { + return ( + + ); + }, + }, + { + field: 'row', + name: 'Log level', + width: '10%', + render: (row: any) => { + return ( + {}} + closePopover={() => {}} + /> + ); + }, + }, + { + field: 'row', + name: 'Summary', + width: '65%', + render: (row: any) => { + return ( + {}} + closePopover={() => {}} + density={DataGridDensity.COMPACT} + rowHeight={ROWS_HEIGHT_OPTIONS.single} + shouldShowFieldHandler={() => false} + core={dependencies.core} + share={dependencies.share} + /> + ); + }, + }, + ]; + return ( + <> + + {i18n.translate( + 'xpack.observabilityLogsOverview.logCategoryDocumentExamplesTable.documentCountText', + { + defaultMessage: 'Displaying the latest {documentsCount} documents.', + values: { + documentsCount: categoryDocuments.length, + }, + } + )} + + + + + ); +}; diff --git a/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview.tsx b/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview.tsx index 988656eb1571e..77535228f7af6 100644 --- a/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview.tsx +++ b/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview.tsx @@ -9,6 +9,7 @@ import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; import { type LogsDataAccessPluginStart } from '@kbn/logs-data-access-plugin/public'; import React from 'react'; import useAsync from 'react-use/lib/useAsync'; +import { DataViewsContract } from '@kbn/data-views-plugin/public'; import { LogsSourceConfiguration, normalizeLogsSource } from '../../utils/logs_source'; import { LogCategories, LogCategoriesDependencies } from '../log_categories'; import { LogsOverviewErrorContent } from './logs_overview_error_content'; @@ -26,6 +27,7 @@ export interface LogsOverviewProps { export type LogsOverviewDependencies = LogCategoriesDependencies & { logsDataAccess: LogsDataAccessPluginStart; + dataViews: DataViewsContract; }; export const LogsOverview: React.FC = React.memo( @@ -36,8 +38,12 @@ export const LogsOverview: React.FC = React.memo( timeRange, }) => { const normalizedLogsSource = useAsync( - () => normalizeLogsSource({ logsDataAccess: dependencies.logsDataAccess })(logsSource), - [dependencies.logsDataAccess, logsSource] + () => + normalizeLogsSource({ + logsDataAccess: dependencies.logsDataAccess, + dataViewsService: dependencies.dataViews, + })(logsSource), + [dependencies.dataViews, dependencies.logsDataAccess, logsSource] ); if (normalizedLogsSource.loading) { diff --git a/x-pack/packages/observability/logs_overview/src/components/shared/log_category_pattern.tsx b/x-pack/packages/observability/logs_overview/src/components/shared/log_category_pattern.tsx new file mode 100644 index 0000000000000..8a8deb5918324 --- /dev/null +++ b/x-pack/packages/observability/logs_overview/src/components/shared/log_category_pattern.tsx @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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'; +import { useMemo } from 'react'; +import { css } from '@emotion/react'; +import React from 'react'; +import { getLogCategoryTerms } from '../../utils/log_category'; +import { LogCategory } from '../../types'; + +interface LogCategoryPatternProps { + logCategory: LogCategory; +} + +export const LogCategoryPattern: React.FC = ({ logCategory }) => { + const theme = useEuiTheme(); + const { euiTheme } = theme; + const termsList = useMemo(() => getLogCategoryTerms(logCategory), [logCategory]); + + const commonStyle = css` + display: inline-block; + font-family: ${euiTheme.font.familyCode}; + margin-right: ${euiTheme.size.xs}; + `; + + const termStyle = css` + ${commonStyle}; + `; + + const separatorStyle = css` + ${commonStyle}; + color: ${euiTheme.colors.successText}; + `; + + return ( +
+      
*
+ {termsList.map((term, index) => ( + +
{term}
+
*
+
+ ))} +
+ ); +}; diff --git a/x-pack/packages/observability/logs_overview/src/services/category_details_service/category_details_service.ts b/x-pack/packages/observability/logs_overview/src/services/category_details_service/category_details_service.ts new file mode 100644 index 0000000000000..958f717548600 --- /dev/null +++ b/x-pack/packages/observability/logs_overview/src/services/category_details_service/category_details_service.ts @@ -0,0 +1,191 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { MachineImplementationsFrom, assign, setup } from 'xstate5'; +import { LogCategory } from '../../types'; +import { getPlaceholderFor } from '../../utils/xstate5_utils'; +import { + CategoryDetailsServiceDependencies, + LogCategoryDocument, + LogCategoryDetailsParams, +} from './types'; +import { getCategoryDocuments } from './category_documents'; + +export const categoryDetailsService = setup({ + types: { + input: {} as LogCategoryDetailsParams, + output: {} as { + categoryDocuments: LogCategoryDocument[] | null; + }, + context: {} as { + parameters: LogCategoryDetailsParams; + error?: Error; + expandedRowIndex: number | null; + expandedCategory: LogCategory | null; + categoryDocuments: LogCategoryDocument[]; + }, + events: {} as + | { + type: 'cancel'; + } + | { + type: 'setExpandedCategory'; + rowIndex: number | null; + category: LogCategory | null; + }, + }, + actors: { + getCategoryDocuments: getPlaceholderFor(getCategoryDocuments), + }, + actions: { + storeCategory: assign( + ({ context, event }, params: { category: LogCategory | null; rowIndex: number | null }) => ({ + expandedCategory: params.category, + expandedRowIndex: params.rowIndex, + }) + ), + storeDocuments: assign( + ({ context, event }, params: { categoryDocuments: LogCategoryDocument[] }) => ({ + categoryDocuments: params.categoryDocuments, + }) + ), + storeError: assign((_, params: { error: unknown }) => ({ + error: params.error instanceof Error ? params.error : new Error(String(params.error)), + })), + }, + guards: { + hasCategory: (_guardArgs, params: { expandedCategory: LogCategory | null }) => + params.expandedCategory !== null, + hasDocumentExamples: ( + _guardArgs, + params: { categoryDocuments: LogCategoryDocument[] | null } + ) => params.categoryDocuments !== null && params.categoryDocuments.length > 0, + }, +}).createMachine({ + /** @xstate-layout N4IgpgJg5mDOIC5QGMCGAXMUD2AnAlgF5gAy2UsAdMtgK4B26+9UAItsrQLZiOwDEEbPTCVmAN2wBrUWkw4CxMhWp1GzNh2690sBBI4Z8wgNoAGALrmLiUAAdssfE2G2QAD0QBmMwA5KACy+AQFmob4AjABMwQBsADQgAJ6IkYEAnJkA7FmxZlERmQGxAL4liXJYeESk5FQ0DEws7Jw8fILCogYy1BhVirUqDerNWm26+vSScsb01iYRNkggDk4u9G6eCD7+QSFhftFxiSkIvgCsWZSxEVlRsbFZ52Zm515lFX0KNcr1ak2aVo6ARCERiKbSWRfapKOqqRoaFraPiTaZGUyWExRJb2RzOWabbx+QLBULhI7FE7eWL+F45GnRPIRZkfECVb6wob-RFjYH8MC4XB4Sh2AA2GAAZnguL15DDBn8EaMgSiDDMMVZLG5VvjXMstjsSftyTFKclEOdzgFKF5zukvA8zBFnl50udWez5b94SNAcjdPw0PRkGBRdZtXj1oTtsS9mTDqaEuaEBF8udKFkIr5fK6olkzOksgEPdCBt6JWB0MgABYaADKqC4YsgAGFS-g4B0wd0oXKBg2m6LW+24OHljqo-rEMzbpQos8-K7fC9CknTrF0rEbbb0oVMoWIgF3eU2e3OVQK1XaywB82IG2+x2BAKhbgReL0FLcDLPf3G3eH36J8x1xNYCSnFNmSuecXhzdJlydTcqQQLJfHSOc0PyLJN3SMxYiPEtH3PShLxret-yHe8RwEIMQzDLVx0jcDQC2GdoIXOCENXZDsyiOcAiiKJ0iiPDLi8V1CKA4jSOvKAACUwC4VBmA0QDvk7UEughHpfxqBSlJUlg1OqUcGNA3UNggrMs347IjzdaIvGQwSvECXI8k3Z43gEiJJI5BUSMrMiWH05T6FU6j+UFYUxUlaVZSksBQsMqBjIIUycRWJi9RY6dIn8KIAjsu1zkc5CAmiG1fBiaIzB8B0QmPT4iICmSNGS8KjMi2jQxArKwJyjw8pswriocqInOTLwIi3ASD1yQpswCd5WXobAIDgNxdPPCMBss3KEAAWjXRBDvTfcLsu9Jlr8r04WGAEkXGeBGL26MBOQzIt2ut4cwmirCt8W6yzhNqbwo4dH0216LOjTMIjnBdYhK1DYgdHjihtZbUIdWIXJuYGflBoLZI6iKoZe8zJwOw9KtGt1kbuTcsmQrwi0oeCQjzZ5blwt1Cek5TKN22GIIKZbAgKC45pyLyeLwtz4Kyabs1QgWAs0kXqaGhBxdcnzpaE2XXmch0MORmaBJeLwjbKMogA */ + id: 'logCategoryDetails', + context: ({ input }) => ({ + expandedCategory: null, + expandedRowIndex: null, + categoryDocuments: [], + parameters: input, + }), + initial: 'idle', + states: { + idle: { + on: { + setExpandedCategory: { + target: 'checkingCategoryState', + actions: [ + { + type: 'storeCategory', + params: ({ event }) => event, + }, + ], + }, + }, + }, + checkingCategoryState: { + always: [ + { + guard: { + type: 'hasCategory', + params: ({ event, context }) => { + return { + expandedCategory: context.expandedCategory, + }; + }, + }, + target: '#hasCategory.fetchingDocuments', + }, + { target: 'idle' }, + ], + }, + hasCategory: { + id: 'hasCategory', + initial: 'fetchingDocuments', + on: { + setExpandedCategory: { + target: 'checkingCategoryState', + actions: [ + { + type: 'storeCategory', + params: ({ event }) => event, + }, + ], + }, + }, + states: { + fetchingDocuments: { + invoke: { + src: 'getCategoryDocuments', + id: 'fetchCategoryDocumentExamples', + input: ({ context }) => ({ + ...context.parameters, + categoryTerms: context.expandedCategory!.terms, + }), + onDone: [ + { + guard: { + type: 'hasDocumentExamples', + params: ({ event }) => { + return event.output; + }, + }, + target: 'hasData', + actions: [ + { + type: 'storeDocuments', + params: ({ event }) => { + return event.output; + }, + }, + ], + }, + { + target: 'noData', + actions: [ + { + type: 'storeDocuments', + params: ({ event }) => { + return { categoryDocuments: [] }; + }, + }, + ], + }, + ], + onError: { + target: 'error', + actions: [ + { + type: 'storeError', + params: ({ event }) => ({ error: event.error }), + }, + ], + }, + }, + }, + hasData: {}, + noData: {}, + error: {}, + }, + }, + }, + output: ({ context }) => ({ + categoryDocuments: context.categoryDocuments, + }), +}); + +export const createCategoryDetailsServiceImplementations = ({ + search, +}: CategoryDetailsServiceDependencies): MachineImplementationsFrom< + typeof categoryDetailsService +> => ({ + actors: { + getCategoryDocuments: getCategoryDocuments({ search }), + }, +}); diff --git a/x-pack/packages/observability/logs_overview/src/services/category_details_service/category_documents.ts b/x-pack/packages/observability/logs_overview/src/services/category_details_service/category_documents.ts new file mode 100644 index 0000000000000..b513fa79fc686 --- /dev/null +++ b/x-pack/packages/observability/logs_overview/src/services/category_details_service/category_documents.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ISearchGeneric } from '@kbn/search-types'; +import { fromPromise } from 'xstate5'; +import { lastValueFrom } from 'rxjs'; +import { flattenHit } from '@kbn/data-service'; +import { LogCategoryDocument, LogCategoryDocumentsParams } from './types'; +import { createGetLogCategoryDocumentsRequestParams } from './queries'; + +export const getCategoryDocuments = ({ search }: { search: ISearchGeneric }) => + fromPromise< + { + categoryDocuments: LogCategoryDocument[]; + }, + LogCategoryDocumentsParams + >( + async ({ + input: { + index, + endTimestamp, + startTimestamp, + timeField, + messageField, + categoryTerms, + additionalFilters = [], + dataView, + }, + signal, + }) => { + const requestParams = createGetLogCategoryDocumentsRequestParams({ + index, + timeField, + messageField, + startTimestamp, + endTimestamp, + additionalFilters, + categoryTerms, + }); + + const { rawResponse } = await lastValueFrom( + search({ params: requestParams }, { abortSignal: signal }) + ); + + const categoryDocuments: LogCategoryDocument[] = + rawResponse.hits?.hits.map((hit) => { + return { + row: { + raw: hit._source, + flattened: flattenHit(hit, dataView), + }, + }; + }) ?? []; + + return { + categoryDocuments, + }; + } + ); diff --git a/x-pack/packages/observability/logs_overview/src/services/category_details_service/index.ts b/x-pack/packages/observability/logs_overview/src/services/category_details_service/index.ts new file mode 100644 index 0000000000000..5df79dbab2cbd --- /dev/null +++ b/x-pack/packages/observability/logs_overview/src/services/category_details_service/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './category_details_service'; diff --git a/x-pack/packages/observability/logs_overview/src/services/category_details_service/queries.ts b/x-pack/packages/observability/logs_overview/src/services/category_details_service/queries.ts new file mode 100644 index 0000000000000..cd1053077c334 --- /dev/null +++ b/x-pack/packages/observability/logs_overview/src/services/category_details_service/queries.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; +import { createCategoryQuery } from '../categorize_logs_service/queries'; + +export const createGetLogCategoryDocumentsRequestParams = ({ + index, + timeField, + messageField, + startTimestamp, + endTimestamp, + additionalFilters = [], + categoryTerms = '', + documentCount = 20, +}: { + startTimestamp: string; + endTimestamp: string; + index: string; + timeField: string; + messageField: string; + additionalFilters?: QueryDslQueryContainer[]; + categoryTerms?: string; + documentCount?: number; +}) => { + return { + index, + size: documentCount, + track_total_hits: false, + sort: [{ [timeField]: { order: 'desc' } }], + query: { + bool: { + filter: [ + { + exists: { + field: messageField, + }, + }, + { + range: { + [timeField]: { + gte: startTimestamp, + lte: endTimestamp, + format: 'strict_date_time', + }, + }, + }, + createCategoryQuery(messageField)(categoryTerms), + ...additionalFilters, + ], + }, + }, + }; +}; diff --git a/x-pack/packages/observability/logs_overview/src/services/category_details_service/types.ts b/x-pack/packages/observability/logs_overview/src/services/category_details_service/types.ts new file mode 100644 index 0000000000000..72369275578e3 --- /dev/null +++ b/x-pack/packages/observability/logs_overview/src/services/category_details_service/types.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; +import { ISearchGeneric } from '@kbn/search-types'; +import { type DataView } from '@kbn/data-views-plugin/common'; +import type { DataTableRecord } from '@kbn/discover-utils'; + +export interface LogCategoryDocument { + row: Pick; +} + +export interface LogCategoryDetailsParams { + additionalFilters: QueryDslQueryContainer[]; + endTimestamp: string; + index: string; + messageField: string; + startTimestamp: string; + timeField: string; + dataView: DataView; +} + +export interface CategoryDetailsServiceDependencies { + search: ISearchGeneric; +} + +export type LogCategoryDocumentsParams = LogCategoryDetailsParams & { categoryTerms: string }; diff --git a/x-pack/packages/observability/logs_overview/src/utils/log_category.ts b/x-pack/packages/observability/logs_overview/src/utils/log_category.ts new file mode 100644 index 0000000000000..3a5e72522d78e --- /dev/null +++ b/x-pack/packages/observability/logs_overview/src/utils/log_category.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { LogCategory } from '../types'; + +export const getLogCategoryTerms = (logCategory: LogCategory) => { + return logCategory.terms.split(' '); +}; diff --git a/x-pack/packages/observability/logs_overview/src/utils/logs_source.ts b/x-pack/packages/observability/logs_overview/src/utils/logs_source.ts index 0c8767c8702d4..15c318766be0e 100644 --- a/x-pack/packages/observability/logs_overview/src/utils/logs_source.ts +++ b/x-pack/packages/observability/logs_overview/src/utils/logs_source.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { type AbstractDataView } from '@kbn/data-views-plugin/common'; +import { type DataViewsContract, type DataView } from '@kbn/data-views-plugin/common'; import { LogsDataAccessPluginStart } from '@kbn/logs-data-access-plugin/public'; export type LogsSourceConfiguration = @@ -28,33 +28,68 @@ export interface IndexNameLogsSourceConfiguration { export interface DataViewLogsSourceConfiguration { type: 'data_view'; - dataView: AbstractDataView; + dataView: DataView; messageField?: string; } +export type ResolvedIndexNameLogsSourceConfiguration = IndexNameLogsSourceConfiguration & { + dataView: DataView; +}; + export const normalizeLogsSource = - ({ logsDataAccess }: { logsDataAccess: LogsDataAccessPluginStart }) => - async (logsSource: LogsSourceConfiguration): Promise => { + ({ + logsDataAccess, + dataViewsService, + }: { + logsDataAccess: LogsDataAccessPluginStart; + dataViewsService: DataViewsContract; + }) => + async ( + logsSource: LogsSourceConfiguration + ): Promise => { switch (logsSource.type) { case 'index_name': - return logsSource; + return { + ...logsSource, + dataView: await getDataViewForLogSource(logsSource, dataViewsService), + }; case 'shared_setting': const logSourcesFromSharedSettings = await logsDataAccess.services.logSourcesService.getLogSources(); - return { - type: 'index_name', + const sharedSettingLogsSource = { + type: 'index_name' as const, indexName: logSourcesFromSharedSettings .map((logSource) => logSource.indexPattern) .join(','), timestampField: logsSource.timestampField ?? '@timestamp', messageField: logsSource.messageField ?? 'message', }; - case 'data_view': return { - type: 'index_name', + ...sharedSettingLogsSource, + dataView: await getDataViewForLogSource(sharedSettingLogsSource, dataViewsService), + }; + case 'data_view': + const dataViewLogsSource = { + type: 'index_name' as const, indexName: logsSource.dataView.getIndexPattern(), timestampField: logsSource.dataView.timeFieldName ?? '@timestamp', messageField: logsSource.messageField ?? 'message', }; + return { + ...dataViewLogsSource, + dataView: logsSource.dataView, + }; } }; + +// Ad-hoc Data View +const getDataViewForLogSource = async ( + logSourceConfiguration: IndexNameLogsSourceConfiguration, + dataViewsService: DataViewsContract +) => { + const dataView = await dataViewsService.create({ + title: logSourceConfiguration.indexName, + timeFieldName: logSourceConfiguration.timestampField, + }); + return dataView; +}; diff --git a/x-pack/packages/observability/logs_overview/tsconfig.json b/x-pack/packages/observability/logs_overview/tsconfig.json index 886062ae8855f..29595ce0162fe 100644 --- a/x-pack/packages/observability/logs_overview/tsconfig.json +++ b/x-pack/packages/observability/logs_overview/tsconfig.json @@ -31,9 +31,15 @@ "@kbn/ml-random-sampler-utils", "@kbn/zod", "@kbn/calculate-auto", - "@kbn/discover-plugin", "@kbn/es-query", "@kbn/router-utils", "@kbn/share-plugin", + "@kbn/field-formats-plugin", + "@kbn/data-service", + "@kbn/discover-utils", + "@kbn/discover-plugin", + "@kbn/unified-data-table", + "@kbn/discover-contextual-components", + "@kbn/core-lifecycle-browser", ] } diff --git a/x-pack/plugins/observability_solution/logs_shared/kibana.jsonc b/x-pack/plugins/observability_solution/logs_shared/kibana.jsonc index 10c8fe32cfe9c..f5e9f76c2ace6 100644 --- a/x-pack/plugins/observability_solution/logs_shared/kibana.jsonc +++ b/x-pack/plugins/observability_solution/logs_shared/kibana.jsonc @@ -11,6 +11,7 @@ "requiredPlugins": [ "charts", "data", + "fieldFormats", "dataViews", "discoverShared", "logsDataAccess", @@ -21,7 +22,7 @@ "optionalPlugins": [ "observabilityAIAssistant", ], - "requiredBundles": ["kibanaUtils", "kibanaReact"], + "requiredBundles": ["kibanaUtils", "kibanaReact", "unifiedDocViewer"], "extraPublicDirs": ["common"] } } diff --git a/x-pack/plugins/observability_solution/logs_shared/public/plugin.ts b/x-pack/plugins/observability_solution/logs_shared/public/plugin.tsx similarity index 97% rename from x-pack/plugins/observability_solution/logs_shared/public/plugin.ts rename to x-pack/plugins/observability_solution/logs_shared/public/plugin.tsx index fc17e9b17cc82..0321651607ed1 100644 --- a/x-pack/plugins/observability_solution/logs_shared/public/plugin.ts +++ b/x-pack/plugins/observability_solution/logs_shared/public/plugin.tsx @@ -61,6 +61,7 @@ export class LogsSharedPlugin implements LogsSharedClientPluginClass { logsDataAccess, observabilityAIAssistant, share, + fieldFormats, } = plugins; const logViews = this.logViews.start({ @@ -71,11 +72,14 @@ export class LogsSharedPlugin implements LogsSharedClientPluginClass { }); const LogsOverview = createLogsOverview({ + core, charts, logsDataAccess, search: data.search.search, uiSettings: settings, share, + dataViews, + fieldFormats, }); if (!observabilityAIAssistant) { diff --git a/x-pack/plugins/observability_solution/logs_shared/public/types.ts b/x-pack/plugins/observability_solution/logs_shared/public/types.ts index 4237c28c621b8..e2435fa1f4915 100644 --- a/x-pack/plugins/observability_solution/logs_shared/public/types.ts +++ b/x-pack/plugins/observability_solution/logs_shared/public/types.ts @@ -14,6 +14,7 @@ import type { LogsDataAccessPluginStart } from '@kbn/logs-data-access-plugin/pub import type { ObservabilityAIAssistantPublicStart } from '@kbn/observability-ai-assistant-plugin/public'; import type { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public'; import type { UiActionsStart } from '@kbn/ui-actions-plugin/public'; +import { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; import type { LogsSharedLocators } from '../common/locators'; import type { LogAIAssistantProps } from './components/log_ai_assistant/log_ai_assistant'; import type { SelfContainedLogsOverview } from './components/logs_overview'; @@ -44,6 +45,7 @@ export interface LogsSharedClientStartDeps { observabilityAIAssistant?: ObservabilityAIAssistantPublicStart; share: SharePluginStart; uiActions: UiActionsStart; + fieldFormats: FieldFormatsStart; } export type LogsSharedClientCoreSetup = CoreSetup< diff --git a/x-pack/plugins/observability_solution/logs_shared/tsconfig.json b/x-pack/plugins/observability_solution/logs_shared/tsconfig.json index 788f55c9b6fc5..f171c79afccd0 100644 --- a/x-pack/plugins/observability_solution/logs_shared/tsconfig.json +++ b/x-pack/plugins/observability_solution/logs_shared/tsconfig.json @@ -48,5 +48,6 @@ "@kbn/observability-logs-overview", "@kbn/charts-plugin", "@kbn/core-ui-settings-common", + "@kbn/field-formats-plugin", ] } diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index f4a38ce6ee929..4c2bb57ded486 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -2480,45 +2480,10 @@ "discover.localMenu.saveTitle": "Enregistrer", "discover.localMenu.shareSearchDescription": "Partager la recherche", "discover.localMenu.shareTitle": "Partager", - "discover.logs.dataTable.controlColumn.actions.button.degradedDoc": "Accès à un document dégradé avec le champ {ignoredProperty}", - "discover.logs.dataTable.controlColumn.actions.button.degradedDocNotPresent": "Tous les champs de ce document ont été analysés correctement", - "discover.logs.dataTable.controlColumn.actions.button.degradedDocPresent": "Ce document n'a pas pu être analysé correctement. Tous les champs n'ont pas été remplis correctement", - "discover.logs.dataTable.controlColumn.actions.button.stacktrace.available": "Traces d'appel disponibles", - "discover.logs.dataTable.controlColumn.actions.button.stacktrace.notAvailable": "Traces d'appel indisponibles", - "discover.logs.dataTable.header.actions.tooltip.expand": "Développer les détails du log", - "discover.logs.dataTable.header.actions.tooltip.paragraph": "Les champs fournissant des informations exploitables, comme :", - "discover.logs.dataTable.header.actions.tooltip.stacktrace": "L'accès aux traces d'appel disponibles est basé sur :", - "discover.logs.dataTable.header.content.tooltip.paragraph1": "Affiche le {logLevel} du document et les champs {message}.", - "discover.logs.dataTable.header.content.tooltip.paragraph2": "Lorsque le champ de message est vide, l'une des informations suivantes s'affiche :", - "discover.logs.dataTable.header.popover.actions": "Actions", - "discover.logs.dataTable.header.popover.actions.lowercase": "actions", "discover.logs.dataTable.header.popover.content": "Contenu", "discover.logs.dataTable.header.popover.resource": "Ressource", - "discover.logs.dataTable.header.resource.tooltip.paragraph": "Les champs fournissant des informations sur la source du document, comme :", - "discover.logs.flyoutDetail.accordion.title.cloud": "Cloud", - "discover.logs.flyoutDetail.accordion.title.other": "Autre", - "discover.logs.flyoutDetail.accordion.title.serviceInfra": "Service et Infrastructure", - "discover.logs.flyoutDetail.label.cloudAvailabilityZone": "Zone de disponibilité du cloud", - "discover.logs.flyoutDetail.label.cloudInstanceId": "ID d'instance du cloud", - "discover.logs.flyoutDetail.label.cloudProjectId": "ID de projet du cloud", - "discover.logs.flyoutDetail.label.cloudProvider": "Fournisseur cloud", - "discover.logs.flyoutDetail.label.cloudRegion": "Région du cloud", - "discover.logs.flyoutDetail.label.dataset": "Ensemble de données", - "discover.logs.flyoutDetail.label.hostName": "Nom d'hôte", - "discover.logs.flyoutDetail.label.logPathFile": "Fichier de chemin d'accès au log", - "discover.logs.flyoutDetail.label.message": "Répartition du contenu", - "discover.logs.flyoutDetail.label.namespace": "Espace de nom", - "discover.logs.flyoutDetail.label.orchestratorClusterName": "Nom de cluster de l'orchestrateur", - "discover.logs.flyoutDetail.label.orchestratorResourceId": "ID de ressource de l'orchestrateur", - "discover.logs.flyoutDetail.label.service": "Service", - "discover.logs.flyoutDetail.label.shipper": "Agent de transfert", - "discover.logs.flyoutDetail.label.trace": "Trace", - "discover.logs.flyoutDetail.section.showMore": "+ {hiddenCount} autres", - "discover.logs.flyoutDetail.value.hover.copyToClipboard": "Copier dans le presse-papiers", "discover.logs.flyoutDetail.value.hover.filterFor": "Filtrer sur cette {value}", - "discover.logs.flyoutDetail.value.hover.filterForFieldPresent": "Filtrer sur le champ", "discover.logs.flyoutDetail.value.hover.filterOut": "Exclure cette {value}", - "discover.logs.flyoutDetail.value.hover.toggleColumn": "Afficher/Masquer la colonne dans le tableau", "discover.logs.popoverAction.closePopover": "Fermer la fenêtre contextuelle", "discover.logs.popoverAction.copyValue": "Copier la valeur", "discover.logs.popoverAction.copyValueAriaText": "Copier la valeur de {fieldName}", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 27b79ebde86f6..e26596932ed93 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -2479,45 +2479,10 @@ "discover.localMenu.saveTitle": "保存", "discover.localMenu.shareSearchDescription": "検索を共有します", "discover.localMenu.shareTitle": "共有", - "discover.logs.dataTable.controlColumn.actions.button.degradedDoc": "{ignoredProperty}フィールドの劣化したドキュメントにアクセス", - "discover.logs.dataTable.controlColumn.actions.button.degradedDocNotPresent": "このドキュメントのすべてのフィールドは正しく解析されました", - "discover.logs.dataTable.controlColumn.actions.button.degradedDocPresent": "このドキュメントを正しく解析できませんでした。一部のフィールドが正しく入力されていません", - "discover.logs.dataTable.controlColumn.actions.button.stacktrace.available": "スタックトレースがあります", - "discover.logs.dataTable.controlColumn.actions.button.stacktrace.notAvailable": "スタックトレースがありません", - "discover.logs.dataTable.header.actions.tooltip.expand": "ログの詳細を展開", - "discover.logs.dataTable.header.actions.tooltip.paragraph": "次のようなアクショナブルな情報を提供するフィールド:", - "discover.logs.dataTable.header.actions.tooltip.stacktrace": "次に基づいて使用可能なスタックトレースにアクセス:", - "discover.logs.dataTable.header.content.tooltip.paragraph1": "ドキュメントの{logLevel}と{message}フィールドを表示します。", - "discover.logs.dataTable.header.content.tooltip.paragraph2": "メッセージフィールドが空のときには、次のいずれかが表示されます。", - "discover.logs.dataTable.header.popover.actions": "アクション", - "discover.logs.dataTable.header.popover.actions.lowercase": "アクション", "discover.logs.dataTable.header.popover.content": "コンテンツ", "discover.logs.dataTable.header.popover.resource": "リソース", - "discover.logs.dataTable.header.resource.tooltip.paragraph": "次のようなドキュメントのソースに関する情報を提供するフィールド:", - "discover.logs.flyoutDetail.accordion.title.cloud": "クラウド", - "discover.logs.flyoutDetail.accordion.title.other": "Other", - "discover.logs.flyoutDetail.accordion.title.serviceInfra": "サービスとインフラストラクチャー", - "discover.logs.flyoutDetail.label.cloudAvailabilityZone": "クラウドアベイラビリティゾーン", - "discover.logs.flyoutDetail.label.cloudInstanceId": "クラウドインスタンスID", - "discover.logs.flyoutDetail.label.cloudProjectId": "クラウドプロジェクトID", - "discover.logs.flyoutDetail.label.cloudProvider": "クラウドプロバイダー", - "discover.logs.flyoutDetail.label.cloudRegion": "クラウドリージョン", - "discover.logs.flyoutDetail.label.dataset": "データセット", - "discover.logs.flyoutDetail.label.hostName": "ホスト名", - "discover.logs.flyoutDetail.label.logPathFile": "ログパスファイル", - "discover.logs.flyoutDetail.label.message": "コンテンツの内訳", - "discover.logs.flyoutDetail.label.namespace": "名前空間", - "discover.logs.flyoutDetail.label.orchestratorClusterName": "オーケストレータークラスター名", - "discover.logs.flyoutDetail.label.orchestratorResourceId": "オーケストレーターリソースID", - "discover.logs.flyoutDetail.label.service": "サービス", - "discover.logs.flyoutDetail.label.shipper": "シッパー", - "discover.logs.flyoutDetail.label.trace": "トレース", - "discover.logs.flyoutDetail.section.showMore": "+ その他{hiddenCount}件", - "discover.logs.flyoutDetail.value.hover.copyToClipboard": "クリップボードにコピー", "discover.logs.flyoutDetail.value.hover.filterFor": "この{value}でフィルターを適用", - "discover.logs.flyoutDetail.value.hover.filterForFieldPresent": "フィールド表示のフィルター", "discover.logs.flyoutDetail.value.hover.filterOut": "この{value}を除外", - "discover.logs.flyoutDetail.value.hover.toggleColumn": "表の列を切り替える", "discover.logs.popoverAction.closePopover": "ポップオーバーを閉じる", "discover.logs.popoverAction.copyValue": "値をコピー", "discover.logs.popoverAction.copyValueAriaText": "{fieldName}の値をコピー", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index ee8601400fef0..bfff698aca31e 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -2481,45 +2481,10 @@ "discover.localMenu.saveTitle": "保存", "discover.localMenu.shareSearchDescription": "共享搜索", "discover.localMenu.shareTitle": "共享", - "discover.logs.dataTable.controlColumn.actions.button.degradedDoc": "包含 {ignoredProperty} 字段的已降级文档的访问权限", - "discover.logs.dataTable.controlColumn.actions.button.degradedDocNotPresent": "此文档中的所有字段均进行了正确解析", - "discover.logs.dataTable.controlColumn.actions.button.degradedDocPresent": "无法正确解析此文档。并非所有字段都进行了正确填充", - "discover.logs.dataTable.controlColumn.actions.button.stacktrace.available": "堆栈跟踪可用", - "discover.logs.dataTable.controlColumn.actions.button.stacktrace.notAvailable": "堆栈跟踪不可用", - "discover.logs.dataTable.header.actions.tooltip.expand": "展开日志详情", - "discover.logs.dataTable.header.actions.tooltip.paragraph": "提供可操作信息的字段,例如:", - "discover.logs.dataTable.header.actions.tooltip.stacktrace": "基于以下项访问可用堆栈跟踪:", - "discover.logs.dataTable.header.content.tooltip.paragraph1": "显示该文档的 {logLevel} 和 {message} 字段。", - "discover.logs.dataTable.header.content.tooltip.paragraph2": "消息字段为空时,将显示以下项之一:", - "discover.logs.dataTable.header.popover.actions": "操作", - "discover.logs.dataTable.header.popover.actions.lowercase": "操作", "discover.logs.dataTable.header.popover.content": "内容", "discover.logs.dataTable.header.popover.resource": "资源", - "discover.logs.dataTable.header.resource.tooltip.paragraph": "提供有关文档来源信息的字段,例如:", - "discover.logs.flyoutDetail.accordion.title.cloud": "云", - "discover.logs.flyoutDetail.accordion.title.other": "其他", - "discover.logs.flyoutDetail.accordion.title.serviceInfra": "服务和基础设施", - "discover.logs.flyoutDetail.label.cloudAvailabilityZone": "云可用区", - "discover.logs.flyoutDetail.label.cloudInstanceId": "云实例 ID", - "discover.logs.flyoutDetail.label.cloudProjectId": "云项目 ID", - "discover.logs.flyoutDetail.label.cloudProvider": "云服务提供商", - "discover.logs.flyoutDetail.label.cloudRegion": "云区域", - "discover.logs.flyoutDetail.label.dataset": "数据集", - "discover.logs.flyoutDetail.label.hostName": "主机名", - "discover.logs.flyoutDetail.label.logPathFile": "日志路径文件", - "discover.logs.flyoutDetail.label.message": "内容细目", - "discover.logs.flyoutDetail.label.namespace": "命名空间", - "discover.logs.flyoutDetail.label.orchestratorClusterName": "Orchestrator 集群名称", - "discover.logs.flyoutDetail.label.orchestratorResourceId": "Orchestrator 资源 ID", - "discover.logs.flyoutDetail.label.service": "服务", - "discover.logs.flyoutDetail.label.shipper": "采集器", - "discover.logs.flyoutDetail.label.trace": "跟踪", - "discover.logs.flyoutDetail.section.showMore": "+ 另外 {hiddenCount} 个", - "discover.logs.flyoutDetail.value.hover.copyToClipboard": "复制到剪贴板", "discover.logs.flyoutDetail.value.hover.filterFor": "筛留此 {value}", - "discover.logs.flyoutDetail.value.hover.filterForFieldPresent": "筛留存在的字段", "discover.logs.flyoutDetail.value.hover.filterOut": "筛除此 {value}", - "discover.logs.flyoutDetail.value.hover.toggleColumn": "在表中切换列", "discover.logs.popoverAction.closePopover": "关闭弹出框", "discover.logs.popoverAction.copyValue": "复制值", "discover.logs.popoverAction.copyValueAriaText": "复制 {fieldName} 的值", diff --git a/yarn.lock b/yarn.lock index 0b67244d4058a..68e3633d66d28 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4788,6 +4788,10 @@ version "0.0.0" uid "" +"@kbn/discover-contextual-components@link:packages/kbn-discover-contextual-components": + version "0.0.0" + uid "" + "@kbn/discover-customization-examples-plugin@link:examples/discover_customization_examples": version "0.0.0" uid ""