forked from elastic/kibana
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Discover / Logs] Add new "Saved Search component" (elastic#199787)
## Summary Implements elastic/logs-dev#111 (comment). This adds a new "Saved Search component". The component is a wrapper around the current Saved Search Embeddable, but uses `ReactEmbeddableRenderer` directly to render the embeddable outside of Dashboard contexts. It monitors changes to things like `index`, `filters` etc and communicates these changes through the embeddable API. For this PoC two locations were changed to use this component 1) Logs Overview flyout 2) APM Logs tab (when the Logs Overview isn't enabled via advanced settings). The component itself is technically beyond a PoC, and resides in it's own package. ~I'd like to get eyes from the Discover folks etc on the approach, and if we're happy I can fix the remaining known issues (apart from the mixing of columns point as I believe this exists on the roadmap anyway) and we can merge this for the initial two replacement points.~ [Thanks Davis 👌](elastic/logs-dev#111 (comment)). `nonPersistedDisplayOptions` is added to facilitate some configurable options via runtime state, but without the complexity of altering the actual saved search saved object. On the whole I've tried to keep this as clean as possible whilst working within the embeddable framework, outside of a dashboard context. ## Known issues - ~"Flyout on flyout" in the logs overview flyout (e.g. triggering the table's flyout in this context).~ Fixed with `enableFlyout` option. - ~Filter buttons should be disabled via pills (e.g. in Summary column).~ Fixed with `enableFilters` option. - Summary (`_source`) column cannot be used alongside other columns, e.g. log level, so column customisation isn't currently enabled. You'll just get timestamp and summary. This requires changes in the Unified Data Table. **Won't be fixed in this PR** - We are left with this panel button that technically doesn't do anything outside of a dashboard. I don't *think* there's an easy way to disable this. **Won't be fixed in this PR** ![Screenshot 2024-11-20 at 11 50 43](https://github.com/user-attachments/assets/e43a47cd-e36e-4511-ba88-c928a4acd634) ## Followups - ~The Logs Overview details state machine can be cleaned up (it doesn't need to fetch documents etc anymore).~ The state machine no longer fetches it's own documents. Some scaffolding is left in place as it'll be needed for showing category details anyway. ## Example ![Screenshot 2024-11-20 at 12 20 08](https://github.com/user-attachments/assets/3b25d591-e3e2-4e8a-98a8-1bfc849d3bc1) ![Screenshot 2024-11-20 at 12 23 34](https://github.com/user-attachments/assets/a2d28036-98c5-4404-934e-2298cf4a66bf) --------- Co-authored-by: kibanamachine <[email protected]>
- Loading branch information
1 parent
3e0f408
commit f67f8fe
Showing
39 changed files
with
636 additions
and
476 deletions.
There are no files selected for viewing
Validating CODEOWNERS rules …
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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'; | ||
<LazySavedSearchComponent | ||
dependencies={{ | ||
embeddable: dependencies.embeddable, | ||
savedSearch: dependencies.savedSearch, | ||
dataViews: dependencies.dataViews, | ||
searchSource: dependencies.searchSource, | ||
}} | ||
index={anIndexString} | ||
filters={optionalFilters} | ||
query={optionalQuery} | ||
timestampField={optionalTimestampFieldString} | ||
/> | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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, | ||
})) | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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: ['<rootDir>/packages/kbn-saved-search-component'], | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"type": "shared-browser", | ||
"id": "@kbn/saved-search-component", | ||
"owner": "@elastic/obs-ux-logs-team" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
42 changes: 42 additions & 0 deletions
42
packages/kbn-saved-search-component/src/components/error.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<SavedSearchComponentErrorContentProps> = ({ | ||
error, | ||
}) => { | ||
return ( | ||
<EuiEmptyPrompt | ||
color="danger" | ||
iconType="error" | ||
title={<h2>{SavedSearchComponentErrorTitle}</h2>} | ||
body={ | ||
<EuiCodeBlock className="eui-textLeft" whiteSpace="pre"> | ||
<p>{error?.stack ?? error?.toString() ?? unknownErrorDescription}</p> | ||
</EuiCodeBlock> | ||
} | ||
layout="vertical" | ||
/> | ||
); | ||
}; | ||
|
||
const SavedSearchComponentErrorTitle = i18n.translate('savedSearchComponent.errorTitle', { | ||
defaultMessage: 'Error', | ||
}); | ||
|
||
const unknownErrorDescription = i18n.translate('savedSearchComponent.unknownErrorDescription', { | ||
defaultMessage: 'An unspecified error occurred.', | ||
}); |
214 changes: 214 additions & 0 deletions
214
packages/kbn-saved-search-component/src/components/saved_search.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<SavedSearchComponentProps> = (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<SerializedPanelState<SearchEmbeddableSerializedState>>(); | ||
|
||
const [error, setError] = useState<Error | undefined>(); | ||
|
||
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 <SavedSearchComponentErrorContent error={error} />; | ||
} | ||
|
||
return initialSerializedState ? ( | ||
<div | ||
css={css` | ||
height: ${height ?? '100%'}; | ||
> [data-test-subj='embeddedSavedSearchDocTable'] { | ||
height: 100%; | ||
} | ||
`} | ||
> | ||
<SavedSearchComponentTable {...props} initialSerializedState={initialSerializedState} /> | ||
</div> | ||
) : null; | ||
}; | ||
|
||
const SavedSearchComponentTable: React.FC< | ||
SavedSearchComponentProps & { | ||
initialSerializedState: SerializedPanelState<SearchEmbeddableSerializedState>; | ||
} | ||
> = (props) => { | ||
const { | ||
dependencies: { dataViews }, | ||
initialSerializedState, | ||
filters, | ||
query, | ||
timeRange, | ||
timestampField, | ||
index, | ||
} = props; | ||
const embeddableApi = useRef<SearchEmbeddableApi | undefined>(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 ( | ||
<ReactEmbeddableRenderer< | ||
SearchEmbeddableSerializedState, | ||
SearchEmbeddableRuntimeState, | ||
SearchEmbeddableApi | ||
> | ||
maybeId={undefined} | ||
type={SEARCH_EMBEDDABLE_TYPE} | ||
getParentApi={() => parentApi} | ||
onApiAvailable={(api) => { | ||
embeddableApi.current = api; | ||
}} | ||
hidePanelChrome | ||
/> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} |
Oops, something went wrong.