diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4634f8d70462f..8662c30e29f89 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -467,6 +467,7 @@ packages/kbn-rrule @elastic/response-ops packages/kbn-rule-data-utils @elastic/security-detections-response @elastic/response-ops @elastic/obs-ux-management-team packages/kbn-safer-lodash-set @elastic/kibana-security packages/kbn-saved-objects-settings @elastic/appex-sharedux +packages/kbn-saved-search-component @elastic/obs-ux-logs-team packages/kbn-scout @elastic/appex-qa packages/kbn-screenshotting-server @elastic/appex-sharedux packages/kbn-search-api-keys-components @elastic/search-kibana diff --git a/package.json b/package.json index c67ed129058a7..873dbff00d258 100644 --- a/package.json +++ b/package.json @@ -785,6 +785,7 @@ "@kbn/saved-objects-settings": "link:packages/kbn-saved-objects-settings", "@kbn/saved-objects-tagging-oss-plugin": "link:src/plugins/saved_objects_tagging_oss", "@kbn/saved-objects-tagging-plugin": "link:x-pack/plugins/saved_objects_tagging", + "@kbn/saved-search-component": "link:packages/kbn-saved-search-component", "@kbn/saved-search-plugin": "link:src/plugins/saved_search", "@kbn/screenshot-mode-example-plugin": "link:examples/screenshot_mode_example", "@kbn/screenshot-mode-plugin": "link:src/plugins/screenshot_mode", diff --git a/packages/kbn-saved-search-component/README.md b/packages/kbn-saved-search-component/README.md new file mode 100644 index 0000000000000..296ddb9079bcf --- /dev/null +++ b/packages/kbn-saved-search-component/README.md @@ -0,0 +1,26 @@ +# @kbn/saved-search-component + +A component wrapper around Discover's Saved Search embeddable. This can be used in solutions without being within a Dasboard context. + +This can be used to render a context-aware (logs etc) "document table". + +In the past you may have used the Log Stream Component to achieve this, this component supersedes that. + +## Basic usage + +``` +import { LazySavedSearchComponent } from '@kbn/saved-search-component'; + + +``` \ No newline at end of file diff --git a/packages/kbn-saved-search-component/index.ts b/packages/kbn-saved-search-component/index.ts new file mode 100644 index 0000000000000..80617ebfa46ee --- /dev/null +++ b/packages/kbn-saved-search-component/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", 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 type { SavedSearchComponentDependencies, SavedSearchComponentProps } from './src/types'; + +export const LazySavedSearchComponent = dynamic(() => + import('./src/components/saved_search').then((mod) => ({ + default: mod.SavedSearchComponent, + })) +); diff --git a/packages/kbn-saved-search-component/jest.config.js b/packages/kbn-saved-search-component/jest.config.js new file mode 100644 index 0000000000000..dedff3f69781d --- /dev/null +++ b/packages/kbn-saved-search-component/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-saved-search-component'], +}; diff --git a/packages/kbn-saved-search-component/kibana.jsonc b/packages/kbn-saved-search-component/kibana.jsonc new file mode 100644 index 0000000000000..d0de843443d12 --- /dev/null +++ b/packages/kbn-saved-search-component/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-browser", + "id": "@kbn/saved-search-component", + "owner": "@elastic/obs-ux-logs-team" +} diff --git a/packages/kbn-saved-search-component/package.json b/packages/kbn-saved-search-component/package.json new file mode 100644 index 0000000000000..917956fd69ba0 --- /dev/null +++ b/packages/kbn-saved-search-component/package.json @@ -0,0 +1,7 @@ +{ + "name": "@kbn/saved-search-component", + "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/packages/kbn-saved-search-component/src/components/error.tsx b/packages/kbn-saved-search-component/src/components/error.tsx new file mode 100644 index 0000000000000..26f898ffe4358 --- /dev/null +++ b/packages/kbn-saved-search-component/src/components/error.tsx @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", 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 { EuiCodeBlock, EuiEmptyPrompt } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; + +export interface SavedSearchComponentErrorContentProps { + error?: Error; +} + +export const SavedSearchComponentErrorContent: React.FC = ({ + error, +}) => { + return ( + {SavedSearchComponentErrorTitle}} + body={ + +

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

+
+ } + layout="vertical" + /> + ); +}; + +const SavedSearchComponentErrorTitle = i18n.translate('savedSearchComponent.errorTitle', { + defaultMessage: 'Error', +}); + +const unknownErrorDescription = i18n.translate('savedSearchComponent.unknownErrorDescription', { + defaultMessage: 'An unspecified error occurred.', +}); diff --git a/packages/kbn-saved-search-component/src/components/saved_search.tsx b/packages/kbn-saved-search-component/src/components/saved_search.tsx new file mode 100644 index 0000000000000..f5cf7cc2a59ab --- /dev/null +++ b/packages/kbn-saved-search-component/src/components/saved_search.tsx @@ -0,0 +1,214 @@ +/* + * Copyright 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, { useEffect, useMemo, useRef, useState } from 'react'; +import { ReactEmbeddableRenderer } from '@kbn/embeddable-plugin/public'; +import { SEARCH_EMBEDDABLE_TYPE } from '@kbn/discover-utils'; +import type { + SearchEmbeddableSerializedState, + SearchEmbeddableRuntimeState, + SearchEmbeddableApi, +} from '@kbn/discover-plugin/public'; +import { SerializedPanelState } from '@kbn/presentation-containers'; +import { css } from '@emotion/react'; +import { SavedSearchComponentProps } from '../types'; +import { SavedSearchComponentErrorContent } from './error'; + +const TIMESTAMP_FIELD = '@timestamp'; + +export const SavedSearchComponent: React.FC = (props) => { + // Creates our *initial* search source and set of attributes. + // Future changes to these properties will be facilitated by the Parent API from the embeddable. + const [initialSerializedState, setInitialSerializedState] = + useState>(); + + const [error, setError] = useState(); + + const { + dependencies: { dataViews, searchSource: searchSourceService }, + timeRange, + query, + filters, + index, + timestampField, + height, + } = props; + + const { + enableDocumentViewer: documentViewerEnabled = true, + enableFilters: filtersEnabled = true, + } = props.displayOptions ?? {}; + + useEffect(() => { + // Ensure we get a stabilised set of initial state incase dependencies change, as + // the data view creation process is async. + const abortController = new AbortController(); + + async function createInitialSerializedState() { + try { + // Ad-hoc data view + const dataView = await dataViews.create({ + title: index, + timeFieldName: timestampField ?? TIMESTAMP_FIELD, + }); + if (!abortController.signal.aborted) { + // Search source + const searchSource = searchSourceService.createEmpty(); + searchSource.setField('index', dataView); + searchSource.setField('query', query); + searchSource.setField('filter', filters); + const { searchSourceJSON, references } = searchSource.serialize(); + // By-value saved object structure + const attributes = { + kibanaSavedObjectMeta: { + searchSourceJSON, + }, + }; + setInitialSerializedState({ + rawState: { + attributes: { ...attributes, references }, + timeRange, + nonPersistedDisplayOptions: { + enableDocumentViewer: documentViewerEnabled, + enableFilters: filtersEnabled, + }, + } as SearchEmbeddableSerializedState, + references, + }); + } + } catch (e) { + setError(e); + } + } + + createInitialSerializedState(); + + return () => { + abortController.abort(); + }; + }, [ + dataViews, + documentViewerEnabled, + filters, + filtersEnabled, + index, + query, + searchSourceService, + timeRange, + timestampField, + ]); + + if (error) { + return ; + } + + return initialSerializedState ? ( +
[data-test-subj='embeddedSavedSearchDocTable'] { + height: 100%; + } + `} + > + +
+ ) : null; +}; + +const SavedSearchComponentTable: React.FC< + SavedSearchComponentProps & { + initialSerializedState: SerializedPanelState; + } +> = (props) => { + const { + dependencies: { dataViews }, + initialSerializedState, + filters, + query, + timeRange, + timestampField, + index, + } = props; + const embeddableApi = useRef(undefined); + + const parentApi = useMemo(() => { + return { + getSerializedStateForChild: () => { + return initialSerializedState; + }, + }; + }, [initialSerializedState]); + + useEffect( + function syncIndex() { + if (!embeddableApi.current) return; + + const abortController = new AbortController(); + + async function updateDataView() { + // Ad-hoc data view + const dataView = await dataViews.create({ + title: index, + timeFieldName: timestampField ?? TIMESTAMP_FIELD, + }); + if (!abortController.signal.aborted) { + embeddableApi.current?.setDataViews([dataView]); + } + } + + updateDataView(); + + return () => { + abortController.abort(); + }; + }, + [dataViews, index, timestampField] + ); + + useEffect( + function syncFilters() { + if (!embeddableApi.current) return; + embeddableApi.current.setFilters(filters); + }, + [filters] + ); + + useEffect( + function syncQuery() { + if (!embeddableApi.current) return; + embeddableApi.current.setQuery(query); + }, + [query] + ); + + useEffect( + function syncTimeRange() { + if (!embeddableApi.current) return; + embeddableApi.current.setTimeRange(timeRange); + }, + [timeRange] + ); + + return ( + + maybeId={undefined} + type={SEARCH_EMBEDDABLE_TYPE} + getParentApi={() => parentApi} + onApiAvailable={(api) => { + embeddableApi.current = api; + }} + hidePanelChrome + /> + ); +}; diff --git a/packages/kbn-saved-search-component/src/types.ts b/packages/kbn-saved-search-component/src/types.ts new file mode 100644 index 0000000000000..23823506a08e2 --- /dev/null +++ b/packages/kbn-saved-search-component/src/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", 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 { EmbeddableStart } from '@kbn/embeddable-plugin/public'; +import { Filter, Query, TimeRange } from '@kbn/es-query'; +import { DataViewsContract, ISearchStartSearchSource } from '@kbn/data-plugin/public'; +import type { NonPersistedDisplayOptions } from '@kbn/discover-plugin/public'; +import { CSSProperties } from 'react'; + +export interface SavedSearchComponentDependencies { + embeddable: EmbeddableStart; + searchSource: ISearchStartSearchSource; + dataViews: DataViewsContract; +} + +export interface SavedSearchComponentProps { + dependencies: SavedSearchComponentDependencies; + index: string; + timeRange?: TimeRange; + query?: Query; + filters?: Filter[]; + timestampField?: string; + height?: CSSProperties['height']; + displayOptions?: NonPersistedDisplayOptions; +} diff --git a/packages/kbn-saved-search-component/tsconfig.json b/packages/kbn-saved-search-component/tsconfig.json new file mode 100644 index 0000000000000..299d561ae4d7b --- /dev/null +++ b/packages/kbn-saved-search-component/tsconfig.json @@ -0,0 +1,29 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node", + "react", + "@emotion/react/types/css-prop", + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx", + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/embeddable-plugin", + "@kbn/shared-ux-utility", + "@kbn/discover-utils", + "@kbn/es-query", + "@kbn/data-plugin", + "@kbn/discover-plugin", + "@kbn/presentation-containers", + "@kbn/i18n", + ] +} diff --git a/packages/presentation/presentation_publishing/index.ts b/packages/presentation/presentation_publishing/index.ts index d3b9c44f6fd36..b290d49a8434f 100644 --- a/packages/presentation/presentation_publishing/index.ts +++ b/packages/presentation/presentation_publishing/index.ts @@ -97,7 +97,11 @@ export { apiPublishesDataLoading, type PublishesDataLoading, } from './interfaces/publishes_data_loading'; -export { apiPublishesDataViews, type PublishesDataViews } from './interfaces/publishes_data_views'; +export { + apiPublishesDataViews, + type PublishesDataViews, + type PublishesWritableDataViews, +} from './interfaces/publishes_data_views'; export { apiPublishesDisabledActionIds, type PublishesDisabledActionIds, diff --git a/packages/presentation/presentation_publishing/interfaces/publishes_data_views.ts b/packages/presentation/presentation_publishing/interfaces/publishes_data_views.ts index d160cdb6e4195..50649b1764c32 100644 --- a/packages/presentation/presentation_publishing/interfaces/publishes_data_views.ts +++ b/packages/presentation/presentation_publishing/interfaces/publishes_data_views.ts @@ -14,6 +14,10 @@ export interface PublishesDataViews { dataViews: PublishingSubject; } +export type PublishesWritableDataViews = PublishesDataViews & { + setDataViews: (dataViews: DataView[]) => void; +}; + export const apiPublishesDataViews = ( unknownApi: null | unknown ): unknownApi is PublishesDataViews => { diff --git a/src/plugins/discover/public/embeddable/components/saved_search_grid.tsx b/src/plugins/discover/public/embeddable/components/saved_search_grid.tsx index f6c77dc6cddf5..0a2919fe1540c 100644 --- a/src/plugins/discover/public/embeddable/components/saved_search_grid.tsx +++ b/src/plugins/discover/public/embeddable/components/saved_search_grid.tsx @@ -36,12 +36,13 @@ interface DiscoverGridEmbeddableProps extends Omit void; onRemoveColumn: (column: string) => void; savedSearchId?: string; + enableDocumentViewer: boolean; } export const DiscoverGridMemoized = React.memo(DiscoverGrid); export function DiscoverGridEmbeddable(props: DiscoverGridEmbeddableProps) { - const { interceptedWarnings, ...gridProps } = props; + const { interceptedWarnings, enableDocumentViewer, ...gridProps } = props; const [expandedDoc, setExpandedDoc] = useState(undefined); @@ -131,7 +132,7 @@ export function DiscoverGridEmbeddable(props: DiscoverGridEmbeddableProps) { expandedDoc={expandedDoc} showMultiFields={props.services.uiSettings.get(SHOW_MULTIFIELDS)} maxDocFieldsDisplayed={props.services.uiSettings.get(MAX_DOC_FIELDS_DISPLAYED)} - renderDocumentView={renderDocumentView} + renderDocumentView={enableDocumentViewer ? renderDocumentView : undefined} renderCustomToolbar={renderCustomToolbarWithElements} externalCustomRenderers={cellRenderers} enableComparisonMode diff --git a/src/plugins/discover/public/embeddable/components/search_embeddable_grid_component.tsx b/src/plugins/discover/public/embeddable/components/search_embeddable_grid_component.tsx index 44d3c1685cbfe..9c7a54054d265 100644 --- a/src/plugins/discover/public/embeddable/components/search_embeddable_grid_component.tsx +++ b/src/plugins/discover/public/embeddable/components/search_embeddable_grid_component.tsx @@ -49,6 +49,7 @@ interface SavedSearchEmbeddableComponentProps { }; dataView: DataView; onAddFilter?: DocViewFilterFn; + enableDocumentViewer: boolean; stateManager: SearchEmbeddableStateManager; } @@ -59,6 +60,7 @@ export function SearchEmbeddableGridComponent({ api, dataView, onAddFilter, + enableDocumentViewer, stateManager, }: SavedSearchEmbeddableComponentProps) { const discoverServices = useDiscoverServices(); @@ -272,6 +274,7 @@ export function SearchEmbeddableGridComponent({ services={discoverServices} showTimeCol={!discoverServices.uiSettings.get(DOC_HIDE_TIME_COLUMN_SETTING, false)} dataGridDensityState={savedSearch.density} + enableDocumentViewer={enableDocumentViewer} /> ); } diff --git a/src/plugins/discover/public/embeddable/get_search_embeddable_factory.tsx b/src/plugins/discover/public/embeddable/get_search_embeddable_factory.tsx index 37213b17c377d..4117a36a4e048 100644 --- a/src/plugins/discover/public/embeddable/get_search_embeddable_factory.tsx +++ b/src/plugins/discover/public/embeddable/get_search_embeddable_factory.tsx @@ -37,6 +37,7 @@ import { initializeEditApi } from './initialize_edit_api'; import { initializeFetch, isEsqlMode } from './initialize_fetch'; import { initializeSearchEmbeddableApi } from './initialize_search_embeddable_api'; import { + NonPersistedDisplayOptions, SearchEmbeddableApi, SearchEmbeddableRuntimeState, SearchEmbeddableSerializedState, @@ -84,6 +85,11 @@ export const getSearchEmbeddableFactory = ({ initialState?.savedObjectDescription ); + /** By-value SavedSearchComponent package (non-dashboard contexts) state, to adhere to the comparator contract of an embeddable. */ + const nonPersistedDisplayOptions$ = new BehaviorSubject< + NonPersistedDisplayOptions | undefined + >(initialState?.nonPersistedDisplayOptions); + /** All other state */ const blockingError$ = new BehaviorSubject(undefined); const dataLoading$ = new BehaviorSubject(true); @@ -201,6 +207,10 @@ export const getSearchEmbeddableFactory = ({ defaultPanelDescription$, (value) => defaultPanelDescription$.next(value), ], + nonPersistedDisplayOptions: [ + nonPersistedDisplayOptions$, + (value) => nonPersistedDisplayOptions$.next(value), + ], } ); @@ -304,7 +314,18 @@ export const getSearchEmbeddableFactory = ({ diff --git a/src/plugins/discover/public/embeddable/initialize_search_embeddable_api.tsx b/src/plugins/discover/public/embeddable/initialize_search_embeddable_api.tsx index e824fb6fdc021..650d2e95852bb 100644 --- a/src/plugins/discover/public/embeddable/initialize_search_embeddable_api.tsx +++ b/src/plugins/discover/public/embeddable/initialize_search_embeddable_api.tsx @@ -15,8 +15,8 @@ import { ISearchSource, SerializedSearchSourceFields } from '@kbn/data-plugin/co import { DataView } from '@kbn/data-views-plugin/common'; import { DataTableRecord } from '@kbn/discover-utils/types'; import type { - PublishesDataViews, - PublishesUnifiedSearch, + PublishesWritableUnifiedSearch, + PublishesWritableDataViews, StateComparators, } from '@kbn/presentation-publishing'; import { DiscoverGridSettings, SavedSearch } from '@kbn/saved-search-plugin/common'; @@ -71,7 +71,7 @@ export const initializeSearchEmbeddableApi = async ( discoverServices: DiscoverServices; } ): Promise<{ - api: PublishesSavedSearch & PublishesDataViews & Partial; + api: PublishesSavedSearch & PublishesWritableDataViews & Partial; stateManager: SearchEmbeddableStateManager; comparators: StateComparators; cleanup: () => void; @@ -144,6 +144,25 @@ export const initializeSearchEmbeddableApi = async ( pick(stateManager, EDITABLE_SAVED_SEARCH_KEYS) ); + /** APIs for updating search source properties */ + const setDataViews = (nextDataViews: DataView[]) => { + searchSource.setField('index', nextDataViews[0]); + dataViews.next(nextDataViews); + searchSource$.next(searchSource); + }; + + const setFilters = (filters: Filter[] | undefined) => { + searchSource.setField('filter', filters); + filters$.next(filters); + searchSource$.next(searchSource); + }; + + const setQuery = (query: Query | AggregateQuery | undefined) => { + searchSource.setField('query', query); + query$.next(query); + searchSource$.next(searchSource); + }; + /** Keep the saved search in sync with any state changes */ const syncSavedSearch = combineLatest([onAnyStateChange, searchSource$]) .pipe( @@ -163,10 +182,13 @@ export const initializeSearchEmbeddableApi = async ( syncSavedSearch.unsubscribe(); }, api: { + setDataViews, dataViews, savedSearch$, filters$, + setFilters, query$, + setQuery, }, stateManager, comparators: { diff --git a/src/plugins/discover/public/embeddable/types.ts b/src/plugins/discover/public/embeddable/types.ts index 1b7ab4a96c2de..64cf5a390da3a 100644 --- a/src/plugins/discover/public/embeddable/types.ts +++ b/src/plugins/discover/public/embeddable/types.ts @@ -15,10 +15,9 @@ import { HasInPlaceLibraryTransforms, PublishesBlockingError, PublishesDataLoading, - PublishesDataViews, PublishesSavedObjectId, - PublishesUnifiedSearch, PublishesWritablePanelTitle, + PublishesWritableUnifiedSearch, PublishingSubject, SerializedTimeRange, SerializedTitles, @@ -30,6 +29,7 @@ import { } from '@kbn/saved-search-plugin/common/types'; import { DataTableColumnsMeta } from '@kbn/unified-data-table'; import { BehaviorSubject } from 'rxjs'; +import { PublishesWritableDataViews } from '@kbn/presentation-publishing/interfaces/publishes_data_views'; import { EDITABLE_SAVED_SEARCH_KEYS } from './constants'; export type SearchEmbeddableState = Pick< @@ -59,6 +59,13 @@ export type SearchEmbeddableSerializedAttributes = Omit< > & Pick; +// These are options that are not persisted in the saved object, but can be used by solutions +// when utilising the SavedSearchComponent package outside of dashboard contexts. +export interface NonPersistedDisplayOptions { + enableDocumentViewer?: boolean; + enableFilters?: boolean; +} + export type SearchEmbeddableSerializedState = SerializedTitles & SerializedTimeRange & Partial> & { @@ -66,6 +73,7 @@ export type SearchEmbeddableSerializedState = SerializedTitles & attributes?: SavedSearchAttributes & { references: SavedSearch['references'] }; // by reference savedObjectId?: string; + nonPersistedDisplayOptions?: NonPersistedDisplayOptions; }; export type SearchEmbeddableRuntimeState = SearchEmbeddableSerializedAttributes & @@ -74,20 +82,20 @@ export type SearchEmbeddableRuntimeState = SearchEmbeddableSerializedAttributes savedObjectTitle?: string; savedObjectId?: string; savedObjectDescription?: string; + nonPersistedDisplayOptions?: NonPersistedDisplayOptions; }; export type SearchEmbeddableApi = DefaultEmbeddableApi< SearchEmbeddableSerializedState, SearchEmbeddableRuntimeState > & - PublishesDataViews & PublishesSavedObjectId & PublishesDataLoading & PublishesBlockingError & PublishesWritablePanelTitle & PublishesSavedSearch & - PublishesDataViews & - PublishesUnifiedSearch & + PublishesWritableDataViews & + PublishesWritableUnifiedSearch & HasInPlaceLibraryTransforms & HasTimeRange & Partial; diff --git a/src/plugins/discover/public/embeddable/utils/serialization_utils.ts b/src/plugins/discover/public/embeddable/utils/serialization_utils.ts index 191a37fe3326e..f193d52054a3c 100644 --- a/src/plugins/discover/public/embeddable/utils/serialization_utils.ts +++ b/src/plugins/discover/public/embeddable/utils/serialization_utils.ts @@ -67,6 +67,7 @@ export const deserializeState = async ({ return { ...savedSearch, ...panelState, + nonPersistedDisplayOptions: serializedState.rawState.nonPersistedDisplayOptions, }; } }; diff --git a/src/plugins/discover/public/index.ts b/src/plugins/discover/public/index.ts index 5555b6a787987..b54665bdc50a0 100644 --- a/src/plugins/discover/public/index.ts +++ b/src/plugins/discover/public/index.ts @@ -35,6 +35,9 @@ export { type PublishesSavedSearch, type HasTimeRange, type SearchEmbeddableSerializedState, + type SearchEmbeddableRuntimeState, + type SearchEmbeddableApi, + type NonPersistedDisplayOptions, } from './embeddable'; export { loadSharingDataHelpers } from './utils'; export { LogsExplorerTabs, type LogsExplorerTabsProps } from './components/logs_explorer_tabs'; diff --git a/tsconfig.base.json b/tsconfig.base.json index 7e0b12009f4a5..ed8b402e2d179 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1534,6 +1534,8 @@ "@kbn/saved-objects-tagging-oss-plugin/*": ["src/plugins/saved_objects_tagging_oss/*"], "@kbn/saved-objects-tagging-plugin": ["x-pack/plugins/saved_objects_tagging"], "@kbn/saved-objects-tagging-plugin/*": ["x-pack/plugins/saved_objects_tagging/*"], + "@kbn/saved-search-component": ["packages/kbn-saved-search-component"], + "@kbn/saved-search-component/*": ["packages/kbn-saved-search-component/*"], "@kbn/saved-search-plugin": ["src/plugins/saved_search"], "@kbn/saved-search-plugin/*": ["src/plugins/saved_search/*"], "@kbn/scout": ["packages/kbn-scout"], 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 c2b1a0989c2ec..fdb059d971943 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 @@ -76,7 +76,6 @@ export const LogCategoriesResultContent: React.FC void; logCategory: LogCategory; - categoryDetailsServiceState: StateFrom; dependencies: LogCategoriesFlyoutDependencies; logsSource: ResolvedIndexNameLogsSourceConfiguration; documentFilters?: QueryDslQueryContainer[]; @@ -51,7 +55,6 @@ interface LogCategoryDetailsFlyoutProps { export const LogCategoryDetailsFlyout: React.FC = ({ onCloseFlyout, logCategory, - categoryDetailsServiceState, dependencies, logsSource, documentFilters, @@ -61,11 +64,19 @@ export const LogCategoryDetailsFlyout: React.FC = prefix: 'flyoutTitle', }); + const categoryFilter = useMemo(() => { + return createCategoryQuery(logsSource.messageField)(logCategory.terms); + }, [logCategory.terms, logsSource.messageField]); + + const documentAndCategoryFilters = useMemo(() => { + return [...(documentFilters ?? []), categoryFilter]; + }, [categoryFilter, documentFilters]); + const linkFilters = useMemo(() => { return [ ...(documentFilters ? documentFilters.map((filter) => ({ filter })) : []), { - filter: createCategoryQuery(logsSource.messageField)(logCategory.terms), + filter: categoryFilter, meta: { name: i18n.translate( 'xpack.observabilityLogsOverview.logCategoryDetailsFlyout.discoverLinkFilterName', @@ -79,7 +90,20 @@ export const LogCategoryDetailsFlyout: React.FC = }, }, ]; - }, [documentFilters, logCategory.terms, logsSource.messageField]); + }, [categoryFilter, documentFilters, logCategory.terms]); + + const filters = useMemo(() => { + return documentAndCategoryFilters.map((filter) => + buildCustomFilter( + logsSource.indexName, + filter, + false, + false, + 'Document filters', + FilterStateStore.APP_STATE + ) + ); + }, [documentAndCategoryFilters, logsSource.indexName]); return ( onCloseFlyout()} aria-labelledby={flyoutTitleId}> @@ -107,32 +131,12 @@ export const LogCategoryDetailsFlyout: React.FC = - - {categoryDetailsServiceState.matches({ hasCategory: 'fetchingDocuments' }) ? ( - - ) : categoryDetailsServiceState.matches({ hasCategory: 'error' }) ? ( - - ) : ( - - )} + + ); 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 index 6b43fa86fe49e..0101a2496415f 100644 --- 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 @@ -5,147 +5,45 @@ * 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'; +import React from 'react'; +import { LazySavedSearchComponent } from '@kbn/saved-search-component'; +import { EmbeddableStart } from '@kbn/embeddable-plugin/public'; +import { DataViewsContract } from '@kbn/data-views-plugin/public'; +import { ISearchStartSearchSource } from '@kbn/data-plugin/common'; +import { Filter } from '@kbn/es-query'; +import { ResolvedIndexNameLogsSourceConfiguration } from '../../utils/logs_source'; export interface LogCategoryDocumentExamplesTableDependencies { - core: CoreStart; - uiSettings: SettingsStart; - fieldFormats: FieldFormatsStart; - share: SharePluginStart; + embeddable: EmbeddableStart; + dataViews: DataViewsContract; + searchSource: ISearchStartSearchSource; } export interface LogCategoryDocumentExamplesTableProps { dependencies: LogCategoryDocumentExamplesTableDependencies; - categoryDocuments: LogCategoryDocument[]; logsSource: ResolvedIndexNameLogsSourceConfiguration; + filters: Filter[]; } -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, + filters, 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/services/category_details_service/category_details_service.ts b/x-pack/packages/observability/logs_overview/src/services/category_details_service/category_details_service.ts index 958f717548600..40dc0d1eca122 100644 --- 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 @@ -7,40 +7,24 @@ 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'; +import { CategoryDetailsServiceDependencies, LogCategoryDetailsParams } from './types'; export const categoryDetailsService = setup({ types: { input: {} as LogCategoryDetailsParams, - output: {} as { - categoryDocuments: LogCategoryDocument[] | null; - }, + output: {} as {}, 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), + events: {} as { + type: 'setExpandedCategory'; + rowIndex: number | null; + category: LogCategory | null; + }, }, + actors: {}, actions: { storeCategory: assign( ({ context, event }, params: { category: LogCategory | null; rowIndex: number | null }) => ({ @@ -48,22 +32,10 @@ export const categoryDetailsService = setup({ 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 */ @@ -71,7 +43,6 @@ export const categoryDetailsService = setup({ context: ({ input }) => ({ expandedCategory: null, expandedRowIndex: null, - categoryDocuments: [], parameters: input, }), initial: 'idle', @@ -79,38 +50,6 @@ export const categoryDetailsService = setup({ 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', @@ -119,73 +58,13 @@ export const categoryDetailsService = setup({ ], }, }, - 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, - }), + output: ({ context }) => ({}), }); 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 deleted file mode 100644 index b513fa79fc686..0000000000000 --- a/x-pack/packages/observability/logs_overview/src/services/category_details_service/category_documents.ts +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { 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/queries.ts b/x-pack/packages/observability/logs_overview/src/services/category_details_service/queries.ts deleted file mode 100644 index cd1053077c334..0000000000000 --- a/x-pack/packages/observability/logs_overview/src/services/category_details_service/queries.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { 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 index 72369275578e3..9c3327632055a 100644 --- 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 @@ -8,11 +8,6 @@ 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[]; @@ -27,5 +22,3 @@ export interface LogCategoryDetailsParams { export interface CategoryDetailsServiceDependencies { search: ISearchGeneric; } - -export type LogCategoryDocumentsParams = LogCategoryDetailsParams & { categoryTerms: string }; diff --git a/x-pack/packages/observability/logs_overview/tsconfig.json b/x-pack/packages/observability/logs_overview/tsconfig.json index 29595ce0162fe..610ef8cc0126e 100644 --- a/x-pack/packages/observability/logs_overview/tsconfig.json +++ b/x-pack/packages/observability/logs_overview/tsconfig.json @@ -34,12 +34,9 @@ "@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", + "@kbn/embeddable-plugin", + "@kbn/data-plugin", + "@kbn/saved-search-component", ] } diff --git a/x-pack/plugins/observability_solution/apm/kibana.jsonc b/x-pack/plugins/observability_solution/apm/kibana.jsonc index 656f898f24064..bcb0801fc6394 100644 --- a/x-pack/plugins/observability_solution/apm/kibana.jsonc +++ b/x-pack/plugins/observability_solution/apm/kibana.jsonc @@ -37,7 +37,8 @@ "lens", "maps", "uiActions", - "logsDataAccess" + "logsDataAccess", + "savedSearch", ], "optionalPlugins": [ "actions", diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/service_logs/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/service_logs/index.tsx index a1dadbf186b91..35f502642518f 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/service_logs/index.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/service_logs/index.tsx @@ -6,9 +6,9 @@ */ import React, { useMemo } from 'react'; -import moment from 'moment'; import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; -import { LogStream } from '@kbn/logs-shared-plugin/public'; +import { LazySavedSearchComponent } from '@kbn/saved-search-component'; +import useAsync from 'react-use/lib/useAsync'; import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; import { CONTAINER_ID, SERVICE_ENVIRONMENT, SERVICE_NAME } from '../../../../common/es_fields/apm'; import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; @@ -35,6 +35,19 @@ export function ServiceLogs() { } export function ClassicServiceLogsStream() { + const { + services: { + logsDataAccess: { + services: { logSourcesService }, + }, + embeddable, + dataViews, + data: { + search: { searchSource }, + }, + }, + } = useKibana(); + const { serviceName } = useApmServiceContext(); const { @@ -62,17 +75,31 @@ export function ClassicServiceLogsStream() { [environment, kuery, serviceName, start, end] ); - return ( - ({ from: start, to: end }), [start, end]); + + const query = useMemo( + () => ({ + language: 'kuery', + query: getInfrastructureKQLFilter({ data, serviceName, environment }), + }), + [data, serviceName, environment] + ); + + return logSources.value ? ( + - ); + ) : null; } export function ServiceLogsOverview() { diff --git a/x-pack/plugins/observability_solution/apm/public/plugin.ts b/x-pack/plugins/observability_solution/apm/public/plugin.ts index f7246115e1e0e..532c0498f7a56 100644 --- a/x-pack/plugins/observability_solution/apm/public/plugin.ts +++ b/x-pack/plugins/observability_solution/apm/public/plugin.ts @@ -70,6 +70,8 @@ import { map } from 'rxjs'; import type { CloudSetup } from '@kbn/cloud-plugin/public'; import type { ServerlessPluginStart } from '@kbn/serverless/public'; import { LogsSharedClientStartExports } from '@kbn/logs-shared-plugin/public'; +import { LogsDataAccessPluginStart } from '@kbn/logs-data-access-plugin/public'; +import { SavedSearchPublicPluginStart } from '@kbn/saved-search-plugin/public'; import type { ConfigSchema } from '.'; import { registerApmRuleTypes } from './components/alerting/rule_types/register_apm_rule_types'; import { registerEmbeddables } from './embeddable/register_embeddables'; @@ -143,6 +145,8 @@ export interface ApmPluginStartDeps { metricsDataAccess: MetricsDataPluginStart; uiSettings: IUiSettingsClient; logsShared: LogsSharedClientStartExports; + logsDataAccess: LogsDataAccessPluginStart; + savedSearch: SavedSearchPublicPluginStart; } const applicationsTitle = i18n.translate('xpack.apm.navigation.rootTitle', { diff --git a/x-pack/plugins/observability_solution/apm/tsconfig.json b/x-pack/plugins/observability_solution/apm/tsconfig.json index d3de1633dcad7..b2fda13c3f76f 100644 --- a/x-pack/plugins/observability_solution/apm/tsconfig.json +++ b/x-pack/plugins/observability_solution/apm/tsconfig.json @@ -127,6 +127,8 @@ "@kbn/router-utils", "@kbn/react-hooks", "@kbn/alerting-comparators", + "@kbn/saved-search-component", + "@kbn/saved-search-plugin", ], "exclude": ["target/**/*"] } diff --git a/x-pack/plugins/observability_solution/logs_data_access/public/services/log_sources_service/index.ts b/x-pack/plugins/observability_solution/logs_data_access/public/services/log_sources_service/index.ts index f329907f145ef..e44b9cadcae08 100644 --- a/x-pack/plugins/observability_solution/logs_data_access/public/services/log_sources_service/index.ts +++ b/x-pack/plugins/observability_solution/logs_data_access/public/services/log_sources_service/index.ts @@ -12,23 +12,32 @@ import { RegisterServicesParams } from '../register_services'; export function createLogSourcesService(params: RegisterServicesParams): LogSourcesService { const { uiSettings } = params.deps; - return { - async getLogSources() { - const logSources = uiSettings.get(OBSERVABILITY_LOGS_DATA_ACCESS_LOG_SOURCES_ID); - return logSources.map((logSource) => ({ - indexPattern: logSource, - })); - }, - async getFlattenedLogSources() { - const logSources = await this.getLogSources(); - return flattenLogSources(logSources); - }, - async setLogSources(sources: LogSource[]) { - await uiSettings.set( - OBSERVABILITY_LOGS_DATA_ACCESS_LOG_SOURCES_ID, - sources.map((source) => source.indexPattern) - ); - return; - }, + + const getLogSources = async () => { + const logSources = uiSettings.get(OBSERVABILITY_LOGS_DATA_ACCESS_LOG_SOURCES_ID); + return logSources.map((logSource) => ({ + indexPattern: logSource, + })); + }; + + const getFlattenedLogSources = async () => { + const logSources = await getLogSources(); + return flattenLogSources(logSources); + }; + + const setLogSources = async (sources: LogSource[]) => { + await uiSettings.set( + OBSERVABILITY_LOGS_DATA_ACCESS_LOG_SOURCES_ID, + sources.map((source) => source.indexPattern) + ); + return; + }; + + const logSourcesService: LogSourcesService = { + getLogSources, + getFlattenedLogSources, + setLogSources, }; + + return logSourcesService; } diff --git a/x-pack/plugins/observability_solution/logs_shared/kibana.jsonc b/x-pack/plugins/observability_solution/logs_shared/kibana.jsonc index f5e9f76c2ace6..69539098f2463 100644 --- a/x-pack/plugins/observability_solution/logs_shared/kibana.jsonc +++ b/x-pack/plugins/observability_solution/logs_shared/kibana.jsonc @@ -18,11 +18,13 @@ "observabilityShared", "share", "usageCollection", + "embeddable", ], "optionalPlugins": [ "observabilityAIAssistant", ], - "requiredBundles": ["kibanaUtils", "kibanaReact", "unifiedDocViewer"], + "requiredBundles": ["kibanaUtils", "kibanaReact"], "extraPublicDirs": ["common"] } } + \ No newline at end of file diff --git a/x-pack/plugins/observability_solution/logs_shared/public/plugin.tsx b/x-pack/plugins/observability_solution/logs_shared/public/plugin.tsx index 0321651607ed1..e6ec419ae8b0f 100644 --- a/x-pack/plugins/observability_solution/logs_shared/public/plugin.tsx +++ b/x-pack/plugins/observability_solution/logs_shared/public/plugin.tsx @@ -61,7 +61,6 @@ export class LogsSharedPlugin implements LogsSharedClientPluginClass { logsDataAccess, observabilityAIAssistant, share, - fieldFormats, } = plugins; const logViews = this.logViews.start({ @@ -72,14 +71,14 @@ export class LogsSharedPlugin implements LogsSharedClientPluginClass { }); const LogsOverview = createLogsOverview({ - core, charts, logsDataAccess, search: data.search.search, + searchSource: data.search.searchSource, uiSettings: settings, share, dataViews, - fieldFormats, + embeddable: plugins.embeddable, }); 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 e2435fa1f4915..90bbd89f2481d 100644 --- a/x-pack/plugins/observability_solution/logs_shared/public/types.ts +++ b/x-pack/plugins/observability_solution/logs_shared/public/types.ts @@ -15,6 +15,8 @@ import type { ObservabilityAIAssistantPublicStart } from '@kbn/observability-ai- 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 { EmbeddableStart } from '@kbn/embeddable-plugin/public'; +import { SavedSearchPublicPluginStart } from '@kbn/saved-search-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'; @@ -46,6 +48,8 @@ export interface LogsSharedClientStartDeps { share: SharePluginStart; uiActions: UiActionsStart; fieldFormats: FieldFormatsStart; + embeddable: EmbeddableStart; + savedSearch: SavedSearchPublicPluginStart; } 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 f171c79afccd0..acaed5073a176 100644 --- a/x-pack/plugins/observability_solution/logs_shared/tsconfig.json +++ b/x-pack/plugins/observability_solution/logs_shared/tsconfig.json @@ -49,5 +49,7 @@ "@kbn/charts-plugin", "@kbn/core-ui-settings-common", "@kbn/field-formats-plugin", + "@kbn/embeddable-plugin", + "@kbn/saved-search-plugin", ] } diff --git a/yarn.lock b/yarn.lock index 866e6552b04f6..9a1a2cbfd544d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6876,6 +6876,10 @@ version "0.0.0" uid "" +"@kbn/saved-search-component@link:packages/kbn-saved-search-component": + version "0.0.0" + uid "" + "@kbn/saved-search-plugin@link:src/plugins/saved_search": version "0.0.0" uid ""