Skip to content

Commit

Permalink
Add new saved search component
Browse files Browse the repository at this point in the history
  • Loading branch information
Kerry350 committed Nov 20, 2024
1 parent 3c80061 commit e6fd5f5
Show file tree
Hide file tree
Showing 39 changed files with 548 additions and 473 deletions.
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,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-screenshotting-server @elastic/appex-sharedux
packages/kbn-search-api-keys-components @elastic/search-kibana
packages/kbn-search-api-keys-server @elastic/search-kibana
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,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",
Expand Down
26 changes: 26 additions & 0 deletions packages/kbn-saved-search-component/README.md
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}
/>
```
18 changes: 18 additions & 0 deletions packages/kbn-saved-search-component/index.ts
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,
}))
);
14 changes: 14 additions & 0 deletions packages/kbn-saved-search-component/jest.config.js
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'],
};
5 changes: 5 additions & 0 deletions packages/kbn-saved-search-component/kibana.jsonc
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"
}
7 changes: 7 additions & 0 deletions packages/kbn-saved-search-component/package.json
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
}
166 changes: 166 additions & 0 deletions packages/kbn-saved-search-component/src/components/saved_search.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/*
* 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 { SavedSearchComponentProps } from '../types';

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 { dependencies, timeRange, query, filters, index, timestampField, height } = props;

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() {
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,
});
}
}

createInitialSerializedState();

return () => {
abortController.abort();
};
}, [dependencies, filters, index, props.displayOptions, query, timeRange, timestampField]);

return initialSerializedState ? (
<div style={{ height: height ?? '100%' }}>
<SavedSearchComponentTable {...props} initialSerializedState={initialSerializedState} />
</div>
) : null;
};

const SavedSearchComponentTable: React.FC<
SavedSearchComponentProps & { initialSerializedState: any }
> = (props) => {
const { dependencies, 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(indexPattern: string) {
const { dataViews } = dependencies;
// Ad-hoc data view
const dataView = await dataViews.create({
title: index,
timeFieldName: timestampField ?? TIMESTAMP_FIELD,
});
if (!abortController.signal.aborted) {
embeddableApi?.current?.setDataViews([dataView]);
}
}

updateDataView(index);

return () => {
abortController.abort();
};
},
[dependencies, 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;
}}
/>
);
};
30 changes: 30 additions & 0 deletions packages/kbn-saved-search-component/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* 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';

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?: string | number;
displayOptions?: NonPersistedDisplayOptions;
}
28 changes: 28 additions & 0 deletions packages/kbn-saved-search-component/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "target/types",
"types": [
"jest",
"node",
"react"
]
},
"include": [
"**/*.ts",
"**/*.tsx",
],
"exclude": [
"target/**/*"
],
"kbn_references": [
"@kbn/embeddable-plugin",
"@kbn/shared-ux-utility",
"@kbn/discover-utils",
"@kbn/saved-search-plugin",
"@kbn/es-query",
"@kbn/data-plugin",
"@kbn/discover-plugin",
"@kbn/presentation-containers",
]
}
6 changes: 5 additions & 1 deletion packages/presentation/presentation_publishing/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ export interface PublishesDataViews {
dataViews: PublishingSubject<DataView[] | undefined>;
}

export type PublishesWritableDataViews = PublishesDataViews & {
setDataViews: (dataViews: DataView[]) => void;
};

export const apiPublishesDataViews = (
unknownApi: null | unknown
): unknownApi is PublishesDataViews => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,13 @@ interface DiscoverGridEmbeddableProps extends Omit<UnifiedDataTableProps, 'sampl
onAddColumn: (column: string) => void;
onRemoveColumn: (column: string) => void;
savedSearchId?: string;
enableFlyout: boolean;
}

export const DiscoverGridMemoized = React.memo(DiscoverGrid);

export function DiscoverGridEmbeddable(props: DiscoverGridEmbeddableProps) {
const { interceptedWarnings, ...gridProps } = props;
const { interceptedWarnings, enableFlyout, ...gridProps } = props;

const [expandedDoc, setExpandedDoc] = useState<DataTableRecord | undefined>(undefined);

Expand Down Expand Up @@ -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={enableFlyout ? renderDocumentView : undefined}
renderCustomToolbar={renderCustomToolbarWithElements}
externalCustomRenderers={cellRenderers}
enableComparisonMode
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ interface SavedSearchEmbeddableComponentProps {
};
dataView: DataView;
onAddFilter?: DocViewFilterFn;
enableFlyout: boolean;
stateManager: SearchEmbeddableStateManager;
}

Expand All @@ -59,6 +60,7 @@ export function SearchEmbeddableGridComponent({
api,
dataView,
onAddFilter,
enableFlyout,
stateManager,
}: SavedSearchEmbeddableComponentProps) {
const discoverServices = useDiscoverServices();
Expand Down Expand Up @@ -272,6 +274,7 @@ export function SearchEmbeddableGridComponent({
services={discoverServices}
showTimeCol={!discoverServices.uiSettings.get(DOC_HIDE_TIME_COLUMN_SETTING, false)}
dataGridDensityState={savedSearch.density}
enableFlyout={enableFlyout}
/>
);
}
Loading

0 comments on commit e6fd5f5

Please sign in to comment.