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 index e5783b75e198a..9906d65fe22b2 100644 --- a/packages/kbn-saved-search-component/src/components/saved_search.tsx +++ b/packages/kbn-saved-search-component/src/components/saved_search.tsx @@ -17,6 +17,7 @@ import type { } from '@kbn/discover-plugin/public'; import { SerializedPanelState } from '@kbn/presentation-containers'; import { SavedSearchComponentProps } from '../types'; +import { SavedSearchComponentErrorContent } from './error'; const TIMESTAMP_FIELD = '@timestamp'; @@ -26,7 +27,20 @@ export const SavedSearchComponent: React.FC = (props) const [initialSerializedState, setInitialSerializedState] = useState>(); - const { dependencies, timeRange, query, filters, index, timestampField, height } = props; + const [error, setError] = useState(); + + const { + dependencies: { dataViews, searchSource: searchSourceService }, + timeRange, + query, + filters, + index, + timestampField, + height, + } = props; + + const { enableFlyout: flyoutEnabled = true, enableFilters: filtersEnabled = true } = + props.displayOptions ?? {}; useEffect(() => { // Ensure we get a stabilised set of initial state incase dependencies change, as @@ -34,38 +48,39 @@ export const SavedSearchComponent: React.FC = (props) const abortController = new AbortController(); async function createInitialSerializedState() { - const { dataViews, searchSource: searchSourceService } = dependencies; - const { enableFlyout: flyoutEnabled = true, enableFilters: filtersEnabled = true } = - props.displayOptions ?? {}; - // 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: { - enableFlyout: flyoutEnabled, - enableFilters: filtersEnabled, - }, - } as SearchEmbeddableSerializedState, - references, + 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: { + enableFlyout: flyoutEnabled, + enableFilters: filtersEnabled, + }, + } as SearchEmbeddableSerializedState, + references, + }); + } + } catch (e) { + setError(e); } } @@ -74,7 +89,21 @@ export const SavedSearchComponent: React.FC = (props) return () => { abortController.abort(); }; - }, [dependencies, filters, index, props.displayOptions, query, timeRange, timestampField]); + }, [ + dataViews, + filters, + filtersEnabled, + flyoutEnabled, + index, + query, + searchSourceService, + timeRange, + timestampField, + ]); + + if (error) { + return ; + } return initialSerializedState ? (
@@ -84,10 +113,19 @@ export const SavedSearchComponent: React.FC = (props) }; const SavedSearchComponentTable: React.FC< - SavedSearchComponentProps & { initialSerializedState: any } + SavedSearchComponentProps & { + initialSerializedState: SerializedPanelState; + } > = (props) => { - const { dependencies, initialSerializedState, filters, query, timeRange, timestampField, index } = - props; + const { + dependencies: { dataViews }, + initialSerializedState, + filters, + query, + timeRange, + timestampField, + index, + } = props; const embeddableApi = useRef(undefined); const parentApi = useMemo(() => { @@ -104,25 +142,24 @@ const SavedSearchComponentTable: React.FC< const abortController = new AbortController(); - async function updateDataView(indexPattern: string) { - const { dataViews } = dependencies; + 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]); + embeddableApi.current?.setDataViews([dataView]); } } - updateDataView(index); + updateDataView(); return () => { abortController.abort(); }; }, - [dependencies, index, timestampField] + [dataViews, index, timestampField] ); useEffect( diff --git a/packages/kbn-saved-search-component/src/types.ts b/packages/kbn-saved-search-component/src/types.ts index d77304949f893..23823506a08e2 100644 --- a/packages/kbn-saved-search-component/src/types.ts +++ b/packages/kbn-saved-search-component/src/types.ts @@ -11,6 +11,7 @@ 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; @@ -25,6 +26,6 @@ export interface SavedSearchComponentProps { query?: Query; filters?: Filter[]; timestampField?: string; - height?: string | number; + height?: CSSProperties['height']; displayOptions?: NonPersistedDisplayOptions; } 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 c9c7dfe922e02..650d2e95852bb 100644 --- a/src/plugins/discover/public/embeddable/initialize_search_embeddable_api.tsx +++ b/src/plugins/discover/public/embeddable/initialize_search_embeddable_api.tsx @@ -145,7 +145,7 @@ export const initializeSearchEmbeddableApi = async ( ); /** APIs for updating search source properties */ - const setDataViews = async (nextDataViews: DataView[]) => { + const setDataViews = (nextDataViews: DataView[]) => { searchSource.setField('index', nextDataViews[0]); dataViews.next(nextDataViews); searchSource$.next(searchSource); 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 74b6d7f64c5dd..a5fb7961cf84a 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 @@ -41,7 +41,6 @@ export function ClassicServiceLogsStream() { services: { logSourcesService }, }, embeddable, - savedSearch, dataViews, data: { search: { searchSource }, @@ -78,21 +77,22 @@ export function ClassicServiceLogsStream() { const logSources = useAsync(logSourcesService.getFlattenedLogSources); - const dependencies = useMemo(() => { - return { embeddable, savedSearch, searchSource, dataViews }; - }, [dataViews, embeddable, savedSearch, searchSource]); - const timeRange = useMemo(() => ({ from: start, to: end }), [start, end]); + const query = useMemo( + () => ({ + language: 'kuery', + query: getInfrastructureKQLFilter({ data, serviceName, environment }), + }), + [data, serviceName, environment] + ); + return logSources.value ? (