mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Discover / Logs] Add new "Saved Search component" (#199787)
## Summary Implements https://github.com/elastic/logs-dev/issues/111#issuecomment-2446470635. 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 👌](https://github.com/elastic/logs-dev/issues/111#issuecomment-2475350199). `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**  ## 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   --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
50a2ffa7f2
commit
b0122f547d
39 changed files with 637 additions and 477 deletions
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
26
packages/kbn-saved-search-component/README.md
Normal file
26
packages/kbn-saved-search-component/README.md
Normal file
|
@ -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
packages/kbn-saved-search-component/index.ts
Normal file
18
packages/kbn-saved-search-component/index.ts
Normal file
|
@ -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
packages/kbn-saved-search-component/jest.config.js
Normal file
14
packages/kbn-saved-search-component/jest.config.js
Normal file
|
@ -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
packages/kbn-saved-search-component/kibana.jsonc
Normal file
5
packages/kbn-saved-search-component/kibana.jsonc
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"type": "shared-browser",
|
||||
"id": "@kbn/saved-search-component",
|
||||
"owner": "@elastic/obs-ux-logs-team"
|
||||
}
|
7
packages/kbn-saved-search-component/package.json
Normal file
7
packages/kbn-saved-search-component/package.json
Normal file
|
@ -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
packages/kbn-saved-search-component/src/components/error.tsx
Normal file
42
packages/kbn-saved-search-component/src/components/error.tsx
Normal file
|
@ -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.',
|
||||
});
|
|
@ -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
|
||||
/>
|
||||
);
|
||||
};
|
31
packages/kbn-saved-search-component/src/types.ts
Normal file
31
packages/kbn-saved-search-component/src/types.ts
Normal file
|
@ -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;
|
||||
}
|
29
packages/kbn-saved-search-component/tsconfig.json
Normal file
29
packages/kbn-saved-search-component/tsconfig.json
Normal file
|
@ -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",
|
||||
]
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -36,12 +36,13 @@ interface DiscoverGridEmbeddableProps extends Omit<UnifiedDataTableProps, 'sampl
|
|||
onAddColumn: (column: string) => 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<DataTableRecord | undefined>(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
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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<Error | undefined>(undefined);
|
||||
const dataLoading$ = new BehaviorSubject<boolean | undefined>(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 = ({
|
|||
<SearchEmbeddableGridComponent
|
||||
api={{ ...api, fetchWarnings$, fetchContext$ }}
|
||||
dataView={dataView!}
|
||||
onAddFilter={isEsqlMode(savedSearch) ? undefined : onAddFilter}
|
||||
onAddFilter={
|
||||
isEsqlMode(savedSearch) ||
|
||||
initialState.nonPersistedDisplayOptions?.enableFilters === false
|
||||
? undefined
|
||||
: onAddFilter
|
||||
}
|
||||
enableDocumentViewer={
|
||||
initialState.nonPersistedDisplayOptions?.enableDocumentViewer !==
|
||||
undefined
|
||||
? initialState.nonPersistedDisplayOptions?.enableDocumentViewer
|
||||
: true
|
||||
}
|
||||
stateManager={searchEmbeddable.stateManager}
|
||||
/>
|
||||
</CellActionsProvider>
|
||||
|
|
|
@ -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<PublishesUnifiedSearch>;
|
||||
api: PublishesSavedSearch & PublishesWritableDataViews & Partial<PublishesWritableUnifiedSearch>;
|
||||
stateManager: SearchEmbeddableStateManager;
|
||||
comparators: StateComparators<SearchEmbeddableSerializedAttributes>;
|
||||
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: {
|
||||
|
|
|
@ -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<SerializableSavedSearch, 'serializedSearchSource'>;
|
||||
|
||||
// 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<Pick<SavedSearchAttributes, (typeof EDITABLE_SAVED_SEARCH_KEYS)[number]>> & {
|
||||
|
@ -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<HasEditCapabilities & PublishesSavedObjectId>;
|
||||
|
|
|
@ -67,6 +67,7 @@ export const deserializeState = async ({
|
|||
return {
|
||||
...savedSearch,
|
||||
...panelState,
|
||||
nonPersistedDisplayOptions: serializedState.rawState.nonPersistedDisplayOptions,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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"],
|
||||
|
|
|
@ -76,7 +76,6 @@ export const LogCategoriesResultContent: React.FC<LogCategoriesResultContentProp
|
|||
<LogCategoryDetailsFlyout
|
||||
logCategory={categoryDetailsServiceState.context.expandedCategory}
|
||||
onCloseFlyout={onCloseFlyout}
|
||||
categoryDetailsServiceState={categoryDetailsServiceState}
|
||||
logsSource={logsSource}
|
||||
dependencies={dependencies}
|
||||
documentFilters={documentFilters}
|
||||
|
|
|
@ -17,28 +17,32 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import React, { useMemo } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { StateFrom } from 'xstate5';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { QueryDslQueryContainer } from '@kbn/data-views-plugin/common/types';
|
||||
import { css } from '@emotion/react';
|
||||
import { FilterStateStore, buildCustomFilter } from '@kbn/es-query';
|
||||
import { LogCategory } from '../../types';
|
||||
import { LogCategoryPattern } from '../shared/log_category_pattern';
|
||||
import { categoryDetailsService } from '../../services/category_details_service';
|
||||
import {
|
||||
LogCategoryDocumentExamplesTable,
|
||||
LogCategoryDocumentExamplesTableDependencies,
|
||||
} from './log_category_document_examples_table';
|
||||
import { type ResolvedIndexNameLogsSourceConfiguration } from '../../utils/logs_source';
|
||||
import { LogCategoryDetailsLoadingContent } from './log_category_details_loading_content';
|
||||
import { LogCategoryDetailsErrorContent } from './log_category_details_error_content';
|
||||
import { DiscoverLink } from '../discover_link';
|
||||
import { DiscoverLink, DiscoverLinkDependencies } from '../discover_link';
|
||||
import { createCategoryQuery } from '../../services/categorize_logs_service/queries';
|
||||
|
||||
export type LogCategoriesFlyoutDependencies = LogCategoryDocumentExamplesTableDependencies;
|
||||
export type LogCategoriesFlyoutDependencies = LogCategoryDocumentExamplesTableDependencies &
|
||||
DiscoverLinkDependencies;
|
||||
|
||||
const flyoutBodyCss = css`
|
||||
.euiFlyoutBody__overflowContent {
|
||||
height: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
interface LogCategoryDetailsFlyoutProps {
|
||||
onCloseFlyout: () => void;
|
||||
logCategory: LogCategory;
|
||||
categoryDetailsServiceState: StateFrom<typeof categoryDetailsService>;
|
||||
dependencies: LogCategoriesFlyoutDependencies;
|
||||
logsSource: ResolvedIndexNameLogsSourceConfiguration;
|
||||
documentFilters?: QueryDslQueryContainer[];
|
||||
|
@ -51,7 +55,6 @@ interface LogCategoryDetailsFlyoutProps {
|
|||
export const LogCategoryDetailsFlyout: React.FC<LogCategoryDetailsFlyoutProps> = ({
|
||||
onCloseFlyout,
|
||||
logCategory,
|
||||
categoryDetailsServiceState,
|
||||
dependencies,
|
||||
logsSource,
|
||||
documentFilters,
|
||||
|
@ -61,11 +64,19 @@ export const LogCategoryDetailsFlyout: React.FC<LogCategoryDetailsFlyoutProps> =
|
|||
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<LogCategoryDetailsFlyoutProps> =
|
|||
},
|
||||
},
|
||||
];
|
||||
}, [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 (
|
||||
<EuiFlyout ownFocus onClose={() => onCloseFlyout()} aria-labelledby={flyoutTitleId}>
|
||||
|
@ -107,32 +131,12 @@ export const LogCategoryDetailsFlyout: React.FC<LogCategoryDetailsFlyoutProps> =
|
|||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody>
|
||||
{categoryDetailsServiceState.matches({ hasCategory: 'fetchingDocuments' }) ? (
|
||||
<LogCategoryDetailsLoadingContent
|
||||
message={i18n.translate(
|
||||
'xpack.observabilityLogsOverview.logCategoryDetailsFlyout.loadingMessage',
|
||||
{
|
||||
defaultMessage: 'Loading latest documents',
|
||||
}
|
||||
)}
|
||||
/>
|
||||
) : categoryDetailsServiceState.matches({ hasCategory: 'error' }) ? (
|
||||
<LogCategoryDetailsErrorContent
|
||||
title={i18n.translate(
|
||||
'xpack.observabilityLogsOverview.logCategoryDetailsFlyout.fetchingDocumentsErrorTitle',
|
||||
{
|
||||
defaultMessage: 'Failed to fetch documents',
|
||||
}
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<LogCategoryDocumentExamplesTable
|
||||
dependencies={dependencies}
|
||||
categoryDocuments={categoryDetailsServiceState.context.categoryDocuments}
|
||||
logsSource={logsSource}
|
||||
/>
|
||||
)}
|
||||
<EuiFlyoutBody css={flyoutBodyCss}>
|
||||
<LogCategoryDocumentExamplesTable
|
||||
dependencies={dependencies}
|
||||
logsSource={logsSource}
|
||||
filters={filters}
|
||||
/>
|
||||
</EuiFlyoutBody>
|
||||
</EuiFlyout>
|
||||
);
|
||||
|
|
|
@ -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<LogCategoryDocumentExamplesTableProps> = ({
|
||||
categoryDocuments,
|
||||
dependencies,
|
||||
filters,
|
||||
logsSource,
|
||||
}) => {
|
||||
const columns: Array<EuiBasicTableColumn<LogCategoryDocument>> = [
|
||||
{
|
||||
field: 'row',
|
||||
name: 'Timestamp',
|
||||
width: '25%',
|
||||
render: (row: any) => {
|
||||
return (
|
||||
<TimestampCell
|
||||
dependencies={dependencies}
|
||||
timestamp={row.raw[logsSource.timestampField]}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'row',
|
||||
name: 'Log level',
|
||||
width: '10%',
|
||||
render: (row: any) => {
|
||||
return (
|
||||
<LogLevelBadgeCell
|
||||
rowIndex={0}
|
||||
colIndex={0}
|
||||
columnId="row"
|
||||
isExpandable={true}
|
||||
isExpanded={false}
|
||||
isDetails={false}
|
||||
row={row}
|
||||
dataView={logsSource.dataView}
|
||||
fieldFormats={dependencies.fieldFormats}
|
||||
setCellProps={() => {}}
|
||||
closePopover={() => {}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'row',
|
||||
name: 'Summary',
|
||||
width: '65%',
|
||||
render: (row: any) => {
|
||||
return (
|
||||
<LazySummaryColumn
|
||||
rowIndex={0}
|
||||
colIndex={0}
|
||||
columnId="_source"
|
||||
isExpandable={true}
|
||||
isExpanded={false}
|
||||
isDetails={false}
|
||||
row={row}
|
||||
dataView={logsSource.dataView}
|
||||
fieldFormats={dependencies.fieldFormats}
|
||||
setCellProps={() => {}}
|
||||
closePopover={() => {}}
|
||||
density={DataGridDensity.COMPACT}
|
||||
rowHeight={ROWS_HEIGHT_OPTIONS.single}
|
||||
shouldShowFieldHandler={() => false}
|
||||
core={dependencies.core}
|
||||
share={dependencies.share}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
return (
|
||||
<>
|
||||
<EuiText size="xs" color="subdued">
|
||||
{i18n.translate(
|
||||
'xpack.observabilityLogsOverview.logCategoryDocumentExamplesTable.documentCountText',
|
||||
{
|
||||
defaultMessage: 'Displaying the latest {documentsCount} documents.',
|
||||
values: {
|
||||
documentsCount: categoryDocuments.length,
|
||||
},
|
||||
}
|
||||
)}
|
||||
</EuiText>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiBasicTable
|
||||
tableCaption={i18n.translate(
|
||||
'xpack.observabilityLogsOverview.logCategoryDocumentExamplesTable.tableCaption',
|
||||
{
|
||||
defaultMessage: 'Log category example documents table',
|
||||
}
|
||||
)}
|
||||
items={categoryDocuments}
|
||||
columns={columns}
|
||||
/>
|
||||
</>
|
||||
<LazySavedSearchComponent
|
||||
dependencies={{
|
||||
embeddable: dependencies.embeddable,
|
||||
dataViews: dependencies.dataViews,
|
||||
searchSource: dependencies.searchSource,
|
||||
}}
|
||||
filters={filters}
|
||||
index={logsSource.indexName}
|
||||
timestampField={logsSource.timestampField}
|
||||
displayOptions={{
|
||||
enableDocumentViewer: false,
|
||||
enableFilters: false,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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,7 +50,6 @@ export const categoryDetailsService = setup({
|
|||
idle: {
|
||||
on: {
|
||||
setExpandedCategory: {
|
||||
target: 'checkingCategoryState',
|
||||
actions: [
|
||||
{
|
||||
type: 'storeCategory',
|
||||
|
@ -89,103 +59,12 @@ export const categoryDetailsService = setup({
|
|||
},
|
||||
},
|
||||
},
|
||||
checkingCategoryState: {
|
||||
always: [
|
||||
{
|
||||
guard: {
|
||||
type: 'hasCategory',
|
||||
params: ({ event, context }) => {
|
||||
return {
|
||||
expandedCategory: context.expandedCategory,
|
||||
};
|
||||
},
|
||||
},
|
||||
target: '#hasCategory.fetchingDocuments',
|
||||
},
|
||||
{ target: 'idle' },
|
||||
],
|
||||
},
|
||||
hasCategory: {
|
||||
id: 'hasCategory',
|
||||
initial: 'fetchingDocuments',
|
||||
on: {
|
||||
setExpandedCategory: {
|
||||
target: 'checkingCategoryState',
|
||||
actions: [
|
||||
{
|
||||
type: 'storeCategory',
|
||||
params: ({ event }) => event,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
states: {
|
||||
fetchingDocuments: {
|
||||
invoke: {
|
||||
src: 'getCategoryDocuments',
|
||||
id: 'fetchCategoryDocumentExamples',
|
||||
input: ({ context }) => ({
|
||||
...context.parameters,
|
||||
categoryTerms: context.expandedCategory!.terms,
|
||||
}),
|
||||
onDone: [
|
||||
{
|
||||
guard: {
|
||||
type: 'hasDocumentExamples',
|
||||
params: ({ event }) => {
|
||||
return event.output;
|
||||
},
|
||||
},
|
||||
target: 'hasData',
|
||||
actions: [
|
||||
{
|
||||
type: 'storeDocuments',
|
||||
params: ({ event }) => {
|
||||
return event.output;
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
target: 'noData',
|
||||
actions: [
|
||||
{
|
||||
type: 'storeDocuments',
|
||||
params: ({ event }) => {
|
||||
return { categoryDocuments: [] };
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
onError: {
|
||||
target: 'error',
|
||||
actions: [
|
||||
{
|
||||
type: 'storeError',
|
||||
params: ({ event }) => ({ error: event.error }),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
hasData: {},
|
||||
noData: {},
|
||||
error: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
output: ({ context }) => ({
|
||||
categoryDocuments: context.categoryDocuments,
|
||||
}),
|
||||
output: ({ context }) => ({}),
|
||||
});
|
||||
|
||||
export const createCategoryDetailsServiceImplementations = ({
|
||||
search,
|
||||
}: CategoryDetailsServiceDependencies): MachineImplementationsFrom<
|
||||
typeof categoryDetailsService
|
||||
> => ({
|
||||
actors: {
|
||||
getCategoryDocuments: getCategoryDocuments({ search }),
|
||||
},
|
||||
});
|
||||
> => ({});
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
);
|
|
@ -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,
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
|
@ -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<DataTableRecord, 'flattened' | 'raw'>;
|
||||
}
|
||||
|
||||
export interface LogCategoryDetailsParams {
|
||||
additionalFilters: QueryDslQueryContainer[];
|
||||
|
@ -27,5 +22,3 @@ export interface LogCategoryDetailsParams {
|
|||
export interface CategoryDetailsServiceDependencies {
|
||||
search: ISearchGeneric;
|
||||
}
|
||||
|
||||
export type LogCategoryDocumentsParams = LogCategoryDetailsParams & { categoryTerms: string };
|
||||
|
|
|
@ -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",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -37,7 +37,8 @@
|
|||
"lens",
|
||||
"maps",
|
||||
"uiActions",
|
||||
"logsDataAccess"
|
||||
"logsDataAccess",
|
||||
"savedSearch",
|
||||
],
|
||||
"optionalPlugins": [
|
||||
"actions",
|
||||
|
|
|
@ -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 (
|
||||
<LogStream
|
||||
logView={{ type: 'log-view-reference', logViewId: 'default' }}
|
||||
columns={[{ type: 'timestamp' }, { type: 'message' }]}
|
||||
height={'60vh'}
|
||||
startTimestamp={moment(start).valueOf()}
|
||||
endTimestamp={moment(end).valueOf()}
|
||||
query={getInfrastructureKQLFilter({ data, serviceName, environment })}
|
||||
showFlyoutAction
|
||||
/>
|
||||
const logSources = useAsync(logSourcesService.getFlattenedLogSources);
|
||||
|
||||
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 ? (
|
||||
<LazySavedSearchComponent
|
||||
dependencies={{ embeddable, searchSource, dataViews }}
|
||||
index={logSources.value}
|
||||
timeRange={timeRange}
|
||||
query={query}
|
||||
height={'60vh'}
|
||||
displayOptions={{
|
||||
enableDocumentViewer: true,
|
||||
enableFilters: false,
|
||||
}}
|
||||
/>
|
||||
) : null;
|
||||
}
|
||||
|
||||
export function ServiceLogsOverview() {
|
||||
|
|
|
@ -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', {
|
||||
|
|
|
@ -127,6 +127,8 @@
|
|||
"@kbn/router-utils",
|
||||
"@kbn/react-hooks",
|
||||
"@kbn/alerting-comparators",
|
||||
"@kbn/saved-search-component",
|
||||
"@kbn/saved-search-plugin",
|
||||
],
|
||||
"exclude": ["target/**/*"]
|
||||
}
|
||||
|
|
|
@ -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<string[]>(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<string[]>(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;
|
||||
}
|
||||
|
|
|
@ -18,11 +18,13 @@
|
|||
"observabilityShared",
|
||||
"share",
|
||||
"usageCollection",
|
||||
"embeddable",
|
||||
],
|
||||
"optionalPlugins": [
|
||||
"observabilityAIAssistant",
|
||||
],
|
||||
"requiredBundles": ["kibanaUtils", "kibanaReact", "unifiedDocViewer"],
|
||||
"requiredBundles": ["kibanaUtils", "kibanaReact"],
|
||||
"extraPublicDirs": ["common"]
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
@ -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<
|
||||
|
|
|
@ -49,5 +49,7 @@
|
|||
"@kbn/charts-plugin",
|
||||
"@kbn/core-ui-settings-common",
|
||||
"@kbn/field-formats-plugin",
|
||||
"@kbn/embeddable-plugin",
|
||||
"@kbn/saved-search-plugin",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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 ""
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue