From 071fa37f5a069f7c54f1d692fea41d5414323b05 Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Thu, 23 Mar 2023 11:27:34 +0100 Subject: [PATCH 001/122] Add DiscoverSavedSearchContainer - A container to centralize functionality around the usage of saved searches in Discover --- .../discover/public/__mocks__/services.ts | 36 ++- .../discover_saved_search_container.test.ts | 217 ++++++++++++++ .../discover_saved_search_container.ts | 267 ++++++++++++++++++ .../main/utils/update_saved_search.ts | 88 ++++++ src/plugins/saved_search/public/index.ts | 1 + .../saved_searches/get_saved_searches.ts | 2 +- .../public/services/saved_searches/index.ts | 2 +- 7 files changed, 609 insertions(+), 4 deletions(-) create mode 100644 src/plugins/discover/public/application/main/services/discover_saved_search_container.test.ts create mode 100644 src/plugins/discover/public/application/main/services/discover_saved_search_container.ts create mode 100644 src/plugins/discover/public/application/main/utils/update_saved_search.ts diff --git a/src/plugins/discover/public/__mocks__/services.ts b/src/plugins/discover/public/__mocks__/services.ts index 8c1b10f236e9d..41d768a34032e 100644 --- a/src/plugins/discover/public/__mocks__/services.ts +++ b/src/plugins/discover/public/__mocks__/services.ts @@ -20,14 +20,22 @@ import { SAMPLE_ROWS_PER_PAGE_SETTING, SORT_DEFAULT_ORDER_SETTING, HIDE_ANNOUNCEMENTS, + SEARCH_ON_PAGE_LOAD_SETTING, } from '../../common'; -import { UI_SETTINGS, calculateBounds } from '@kbn/data-plugin/public'; +import { + UI_SETTINGS, + calculateBounds, + SearchSource, + IKibanaSearchResponse, +} from '@kbn/data-plugin/public'; import { TopNavMenu } from '@kbn/navigation-plugin/public'; import { FORMATS_UI_SETTINGS } from '@kbn/field-formats-plugin/common'; import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; import { fieldFormatsMock } from '@kbn/field-formats-plugin/common/mocks'; import { LocalStorageMock } from './local_storage_mock'; import { createDiscoverDataViewsMock } from './data_views'; +import { SearchSourceDependencies } from '@kbn/data-plugin/common'; +import { SearchResponse } from '@elastic/elasticsearch/lib/api/types'; export function createDiscoverServicesMock(): DiscoverServices { const dataPlugin = dataPluginMock.createStartContract(); @@ -57,6 +65,23 @@ export function createDiscoverServicesMock(): DiscoverServices { }, })); dataPlugin.dataViews = createDiscoverDataViewsMock(); + dataPlugin.search.searchSource.createEmpty = jest.fn(() => { + const deps = { + getConfig: jest.fn(), + } as unknown as SearchSourceDependencies; + const searchSource = new SearchSource({}, deps); + searchSource.fetch$ = jest.fn().mockReturnValue(of({ rawResponse: { hits: { total: 2 } } })); + searchSource.createChild = jest.fn((options = {}) => { + const childSearchSource = new SearchSource({}, deps); + childSearchSource.setParent(searchSource, options); + childSearchSource.fetch$ = () => + of({ rawResponse: { hits: { hits: [] } } } as unknown as IKibanaSearchResponse< + SearchResponse + >); + return childSearchSource; + }); + return searchSource; + }); return { core: coreMock.createStart(), @@ -110,6 +135,8 @@ export function createDiscoverServicesMock(): DiscoverServices { return 50; } else if (key === HIDE_ANNOUNCEMENTS) { return false; + } else if (key === SEARCH_ON_PAGE_LOAD_SETTING) { + return true; } }), isDefault: (key: string) => { @@ -149,7 +176,12 @@ export function createDiscoverServicesMock(): DiscoverServices { addSuccess: jest.fn(), }, expressions: expressionsPlugin, - savedObjectsTagging: {}, + savedObjectsTagging: { + ui: { + getTagIdsFromReferences: jest.fn().mockResolvedValue([]), + updateTagsReferences: jest.fn(), + }, + }, dataViews: dataPlugin.dataViews, timefilter: dataPlugin.query.timefilter.timefilter, lens: { EmbeddableComponent: jest.fn(() => null) }, diff --git a/src/plugins/discover/public/application/main/services/discover_saved_search_container.test.ts b/src/plugins/discover/public/application/main/services/discover_saved_search_container.test.ts new file mode 100644 index 0000000000000..20d12eb15e058 --- /dev/null +++ b/src/plugins/discover/public/application/main/services/discover_saved_search_container.test.ts @@ -0,0 +1,217 @@ +/* + * 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 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 or the Server + * Side Public License, v 1. + */ + +import { SavedObjectSaveOpts } from '@kbn/saved-objects-plugin/public'; + +const mockSaveSavedSearch = jest.fn().mockResolvedValue('123'); +jest.mock('@kbn/saved-search-plugin/public', () => { + const actualPlugin = jest.requireActual('@kbn/saved-search-plugin/public'); + return { + ...actualPlugin, + saveSavedSearch: (val: SavedSearch, opts?: SavedObjectSaveOpts) => + mockSaveSavedSearch(val, opts), + }; +}); +import { getSavedSearchContainer, isEqualSavedSearch } from './discover_saved_search_container'; +import type { SavedSearch } from '@kbn/saved-search-plugin/public'; +import { discoverServiceMock } from '../../../__mocks__/services'; +import { savedSearchMock, savedSearchMockWithTimeField } from '../../../__mocks__/saved_search'; +import { dataViewMock } from '../../../__mocks__/data_view'; +import { dataViewComplexMock } from '../../../__mocks__/data_view_complex'; + +describe('DiscoverSavedSearchContainer', () => { + const savedSearch = savedSearchMock; + const services = discoverServiceMock; + + describe('set', () => { + it('should update the current and initial state of the saved search', () => { + const container = getSavedSearchContainer({ services }); + const newSavedSearch: SavedSearch = { ...savedSearch, title: 'New title' }; + const result = container.set(newSavedSearch); + + expect(result).toBe(newSavedSearch); + expect(container.getState()).toBe(newSavedSearch); + const initialSavedSearch = container.getInitial$().getValue(); + const currentSavedSearch = container.getCurrent$().getValue(); + + expect(isEqualSavedSearch(initialSavedSearch, currentSavedSearch)).toBeTruthy(); + }); + + it('should reset hasChanged$ to false', () => { + const container = getSavedSearchContainer({ services }); + const newSavedSearch: SavedSearch = { ...savedSearch, title: 'New title' }; + + container.set(newSavedSearch); + expect(container.getHasChanged$().getValue()).toBe(false); + }); + }); + + describe('new', () => { + it('should create a new saved search', async () => { + const container = getSavedSearchContainer({ services }); + const result = await container.new(dataViewMock); + + expect(result.title).toBeUndefined(); + expect(result.id).toBeUndefined(); + const savedSearchState = container.getState(); + expect(savedSearchState.id).not.toEqual(savedSearch.id); + expect(savedSearchState.searchSource.getField('index')).toEqual( + savedSearch.searchSource.getField('index') + ); + }); + + it('should create a new saved search with provided DataView', async () => { + const container = getSavedSearchContainer({ services }); + const result = await container.new(dataViewMock); + expect(result.title).toBeUndefined(); + expect(result.id).toBeUndefined(); + expect(result.searchSource.getField('index')).toBe(dataViewMock); + expect(container.getHasChanged$().getValue()).toBe(false); + }); + }); + + describe('load', () => { + discoverServiceMock.data.search.searchSource.create = jest + .fn() + .mockReturnValue(savedSearchMock.searchSource); + discoverServiceMock.core.savedObjects.client.resolve = jest.fn().mockReturnValue({ + saved_object: { + attributes: { + kibanaSavedObjectMeta: { + searchSourceJSON: + '{"query":{"query":"","language":"kuery"},"filter":[],"indexRefName":"kibanaSavedObjectMeta.searchSourceJSON.index"}', + }, + title: 'The saved search that will save the world', + sort: [], + columns: ['test123'], + description: 'description', + hideChart: false, + }, + id: 'the-saved-search-id', + type: 'search', + references: [ + { + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + id: 'the-data-view-id', + type: 'index-pattern', + }, + ], + namespaces: ['default'], + }, + outcome: 'exactMatch', + }); + + it('loads a saved search', async () => { + const savedSearchContainer = getSavedSearchContainer({ + services: discoverServiceMock, + }); + await savedSearchContainer.load('the-saved-search-id'); + expect(savedSearchContainer.getInitial$().getValue().id).toEqual('the-saved-search-id'); + expect(savedSearchContainer.getCurrent$().getValue().id).toEqual('the-saved-search-id'); + expect(savedSearchContainer.getHasChanged$().getValue()).toEqual(false); + }); + }); + + describe('persist', () => { + const saveOptions = { confirmOverwrite: false }; + + it('calls saveSavedSearch with the given saved search and save options', async () => { + const savedSearchContainer = getSavedSearchContainer({ + services: discoverServiceMock, + }); + const savedSearchToPersist = { + ...savedSearchMockWithTimeField, + title: 'My updated saved search', + }; + + await savedSearchContainer.persist(savedSearchToPersist, saveOptions); + expect(mockSaveSavedSearch).toHaveBeenCalledWith(savedSearchToPersist, saveOptions); + }); + + it('sets the initial and current saved search to the persisted saved search', async () => { + const title = 'My updated saved search'; + const persistedSavedSearch = { + ...savedSearch, + title, + }; + const savedSearchContainer = getSavedSearchContainer({ + services: discoverServiceMock, + }); + + const result = await savedSearchContainer.persist(persistedSavedSearch, saveOptions); + expect(savedSearchContainer.getInitial$().getValue().title).toBe(title); + expect(savedSearchContainer.getCurrent$().getValue().title).toBe(title); + expect(result).toEqual({ id: '123' }); + }); + + it('emits false to the hasChanged$ BehaviorSubject', async () => { + const savedSearchContainer = getSavedSearchContainer({ + services: discoverServiceMock, + }); + const savedSearchToPersist = { + ...savedSearchMockWithTimeField, + title: 'My updated saved search', + }; + + await savedSearchContainer.persist(savedSearchToPersist, saveOptions); + expect(savedSearchContainer.getHasChanged$().getValue()).toBe(false); + }); + + it('Error thrown on persistence layer bubbling up, no changes to the initial saved search ', async () => { + mockSaveSavedSearch.mockImplementation(() => { + throw new Error('oh-noes'); + }); + + const savedSearchContainer = getSavedSearchContainer({ + services: discoverServiceMock, + }); + savedSearchContainer.set(savedSearch); + savedSearchContainer.update({ nextState: { hideChart: true } }); + expect(savedSearchContainer.getHasChanged$().getValue()).toBe(true); + try { + await savedSearchContainer.persist(savedSearch, saveOptions); + } catch (e) { + // intentional error + } + expect(savedSearchContainer.getHasChanged$().getValue()).toBe(true); + expect(savedSearchContainer.getInitial$().getValue().title).not.toBe( + 'My updated saved search' + ); + }); + }); + + describe('update', () => { + it('updates a saved search by app state providing hideChart', async () => { + const savedSearchContainer = getSavedSearchContainer({ + services: discoverServiceMock, + }); + savedSearchContainer.set(savedSearch); + const updated = await savedSearchContainer.update({ nextState: { hideChart: true } }); + expect(savedSearchContainer.getHasChanged$().getValue()).toBe(true); + savedSearchContainer.set(updated); + expect(savedSearchContainer.getHasChanged$().getValue()).toBe(false); + await savedSearchContainer.update({ nextState: { hideChart: false } }); + expect(savedSearchContainer.getHasChanged$().getValue()).toBe(true); + await savedSearchContainer.update({ nextState: { hideChart: true } }); + expect(savedSearchContainer.getHasChanged$().getValue()).toBe(false); + }); + it('updates a saved search by data view', async () => { + const savedSearchContainer = getSavedSearchContainer({ + services: discoverServiceMock, + }); + const updated = await savedSearchContainer.update({ nextDataView: dataViewMock }); + expect(savedSearchContainer.getHasChanged$().getValue()).toBe(true); + savedSearchContainer.set(updated); + expect(savedSearchContainer.getHasChanged$().getValue()).toBe(false); + await savedSearchContainer.update({ nextDataView: dataViewComplexMock }); + expect(savedSearchContainer.getHasChanged$().getValue()).toBe(true); + await savedSearchContainer.update({ nextDataView: dataViewMock }); + expect(savedSearchContainer.getHasChanged$().getValue()).toBe(false); + }); + }); +}); diff --git a/src/plugins/discover/public/application/main/services/discover_saved_search_container.ts b/src/plugins/discover/public/application/main/services/discover_saved_search_container.ts new file mode 100644 index 0000000000000..71075f9a16f14 --- /dev/null +++ b/src/plugins/discover/public/application/main/services/discover_saved_search_container.ts @@ -0,0 +1,267 @@ +/* + * 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 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 or the Server + * Side Public License, v 1. + */ + +import { + getEmptySavedSearch, + getSavedSearch, + SavedSearch, + saveSavedSearch, +} from '@kbn/saved-search-plugin/public'; +import { BehaviorSubject } from 'rxjs'; +import type { DataView } from '@kbn/data-views-plugin/common'; +import { SavedObjectSaveOpts } from '@kbn/saved-objects-plugin/public'; +import { isEqual } from 'lodash'; +import { restoreStateFromSavedSearch } from '../../../services/saved_searches/restore_from_saved_search'; +import { updateSavedSearch } from '../utils/update_saved_search'; +import { addLog } from '../../../utils/add_log'; +import { handleSourceColumnState } from '../../../utils/state_helpers'; +import { DiscoverAppState } from './discover_app_state_container'; +import { DiscoverServices } from '../../../build_services'; +import { getStateDefaults } from '../utils/get_state_defaults'; + +export interface UpdateParams { + nextDataView?: DataView | undefined; + nextState?: DiscoverAppState | undefined; + filterAndQuery?: boolean; +} + +/** + * Container for the saved search state, allowing to load, update and persist the saved search + * Can also be used to track changes to the saved search + * It centralizes functionality that was spread across the Discover main codebase + */ +export interface DiscoverSavedSearchContainer { + /** + * Get an BehaviorSubject which contains the current state of the current saved search + * All modifications are applied to this state + */ + getCurrent$: () => BehaviorSubject; + /** + * Get the id of the current saved search + */ + getId: () => string | undefined; + /** + * Get an BehaviorSubject which contains the initial state of the current saved search + * This is set when a saved search is loaded or a new saved search is initialized + */ + getInitial$: () => BehaviorSubject; + /** + * Get the title of the current saved search + */ + getTitle: () => string; + /** + * Get an BehaviorSubject containing the state if there have been changes to the initial state of the saved search + * Can be used to track if the saved search has been modified and displayed in the UI + */ + getHasChanged$: () => BehaviorSubject; + /** + * Get the current state of the saved search + */ + getState: () => SavedSearch; + /** + * Load a saved search by the given id + * Resets the initial and current state of the saved search + * @param id + * @param dataView + */ + load: (id: string, dataView?: DataView) => Promise; + /** + * Initialize a new saved search + * Resets the initial and current state of the saved search + * @param dataView + */ + new: (dataView?: DataView) => Promise; + /** + * Persist the given saved search + * Resets the initial and current state of the saved search + */ + persist: ( + savedSearch: SavedSearch, + saveOptions?: SavedObjectSaveOpts + ) => Promise<{ id: string | undefined } | undefined>; + /** + * Set the persisted & current state of the saved search + * Happens when a saved search is loaded or a new one is created + * @param savedSearch + */ + set: (savedSearch: SavedSearch) => SavedSearch; + /** + * Updates the current state of the saved search + * @param params + */ + update: (params: UpdateParams) => SavedSearch; +} + +export function getSavedSearchContainer({ + services, +}: { + services: DiscoverServices; +}): DiscoverSavedSearchContainer { + const initialSavedSearch = getEmptySavedSearch(services.data); + const savedSearchInitial$ = new BehaviorSubject(initialSavedSearch); + const savedSearchCurrent$ = new BehaviorSubject(copySavedSearch(initialSavedSearch)); + const hasChanged$ = new BehaviorSubject(false); + const set = (savedSearch: SavedSearch) => { + addLog('[savedSearch] set', savedSearch); + hasChanged$.next(false); + savedSearchCurrent$.next(savedSearch); + savedSearchInitial$.next(copySavedSearch(savedSearch)); + return savedSearch; + }; + const getState = () => savedSearchCurrent$.getValue(); + const getInitial$ = () => savedSearchInitial$; + const getCurrent$ = () => savedSearchCurrent$; + const getHasChanged$ = () => hasChanged$; + const getTitle = () => savedSearchCurrent$.getValue().title ?? ''; + const getId = () => savedSearchCurrent$.getValue().id; + + const newSavedSearch = async (nextDataView: DataView | undefined) => { + addLog('[savedSearch] new', { nextDataView }); + const dataView = nextDataView ?? getState().searchSource.getField('index'); + const nextSavedSearch = await getSavedSearch('', { + search: services.data.search, + savedObjectsClient: services.core.savedObjects.client, + spaces: services.spaces, + savedObjectsTagging: services.savedObjectsTagging, + }); + nextSavedSearch.searchSource.setField('index', dataView); + const newAppState = getDefaultAppState(nextSavedSearch, services); + const actualDataView = dataView ? dataView : nextSavedSearch.searchSource.getField('index')!; + const nextSavedSearchToSet = updateSavedSearch( + { + savedSearch: { ...nextSavedSearch }, + dataView: actualDataView, + state: newAppState, + services, + }, + true + ); + return set(nextSavedSearchToSet); + }; + + const persist = async (nextSavedSearch: SavedSearch, saveOptions?: SavedObjectSaveOpts) => { + addLog('[savedSearch] persist', { nextSavedSearch, saveOptions }); + + const id = await saveSavedSearch( + nextSavedSearch, + saveOptions || {}, + services.core.savedObjects.client, + services.savedObjectsTagging + ); + + if (id) { + set(nextSavedSearch); + } + return { id }; + }; + const update = ({ nextDataView, nextState, filterAndQuery }: UpdateParams) => { + addLog('[savedSearch] update', { nextDataView, nextState }); + + const previousSavedSearch = getState(); + const dataView = nextDataView + ? nextDataView + : previousSavedSearch.searchSource.getField('index')!; + + const nextSavedSearch = updateSavedSearch( + { + savedSearch: { ...previousSavedSearch }, + dataView, + state: nextState || {}, + services, + }, + !filterAndQuery + ); + + nextSavedSearch.searchSource.setField('index', dataView); + if (nextState) { + nextSavedSearch.searchSource + .setField('query', nextState.query) + .setField('filter', nextState.filters); + } + + const hasChanged = !isEqualSavedSearch(savedSearchInitial$.getValue(), nextSavedSearch); + hasChanged$.next(hasChanged); + savedSearchCurrent$.next(nextSavedSearch); + + addLog('[savedSearch] update done', nextSavedSearch); + return nextSavedSearch; + }; + + const load = async (id: string, dataView: DataView | undefined): Promise => { + const loadedSavedSearch = await getSavedSearch(id, { + search: services.data.search, + savedObjectsClient: services.core.savedObjects.client, + spaces: services.spaces, + savedObjectsTagging: services.savedObjectsTagging, + }); + if (!loadedSavedSearch.searchSource.getField('index') && dataView) { + loadedSavedSearch.searchSource.setField('index', dataView); + } + restoreStateFromSavedSearch({ + savedSearch: loadedSavedSearch, + timefilter: services.timefilter, + }); + return set(loadedSavedSearch); + }; + + return { + getCurrent$, + getHasChanged$, + getId, + getInitial$, + getState, + getTitle, + load, + new: newSavedSearch, + persist, + set, + update, + }; +} + +export function copySavedSearch(savedSearch: SavedSearch): SavedSearch { + // due to the stateful nature of searchSource it has to be copied separately + return { + ...savedSearch, + ...{ searchSource: savedSearch.searchSource.createCopy() }, + }; +} + +export function getDefaultAppState(savedSearch: SavedSearch, services: DiscoverServices) { + return handleSourceColumnState( + getStateDefaults({ + savedSearch, + services, + }), + services.uiSettings + ); +} + +export function isEqualSavedSearch(savedSearchPrev: SavedSearch, savedSearchNext: SavedSearch) { + const { searchSource: prevSearchSource, ...prevSavedSearch } = savedSearchPrev; + const { searchSource: nextSearchSource, ...nextSavedSearchWithoutSearchSource } = savedSearchNext; + + const keys = new Set([ + ...Object.keys(prevSavedSearch), + ...Object.keys(nextSavedSearchWithoutSearchSource), + ]); + const savedSearchDiff = [...keys].filter((key: string) => { + // @ts-expect-error + return !isEqual(prevSavedSearch[key], nextSavedSearchWithoutSearchSource[key]); + }); + + const searchSourceDiff = + !isEqual(prevSearchSource.getField('filter'), nextSearchSource.getField('filter')) || + !isEqual(prevSearchSource.getField('query'), nextSearchSource.getField('query')) || + !isEqual(prevSearchSource.getField('index'), nextSearchSource.getField('index')); + const hasChanged = Boolean(savedSearchDiff.length || searchSourceDiff); + if (hasChanged) { + addLog('[savedSearch] difference between initial and changed version', searchSourceDiff); + } + return !hasChanged; +} diff --git a/src/plugins/discover/public/application/main/utils/update_saved_search.ts b/src/plugins/discover/public/application/main/utils/update_saved_search.ts new file mode 100644 index 0000000000000..dbf05ebbb213b --- /dev/null +++ b/src/plugins/discover/public/application/main/utils/update_saved_search.ts @@ -0,0 +1,88 @@ +/* + * 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 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 or the Server + * Side Public License, v 1. + */ +import { SavedSearch, SortOrder } from '@kbn/saved-search-plugin/public'; +import { DataView } from '@kbn/data-views-plugin/common'; +import { isOfAggregateQueryType } from '@kbn/es-query'; +import { DiscoverAppState } from '../services/discover_app_state_container'; +import { DiscoverServices } from '../../../build_services'; + +export function updateSavedSearch( + { + savedSearch, + dataView, + state, + services, + }: { + savedSearch: SavedSearch; + dataView: DataView; + state: DiscoverAppState; + services: DiscoverServices; + }, + initial: boolean = false +) { + if (!initial) { + savedSearch.searchSource + .setField('index', dataView) + .setField('query', services.data.query.queryString.getQuery() || null) + .setField('filter', services.data.query.filterManager.getFilters()); + } else { + savedSearch.searchSource + .setField('index', dataView) + .setField('query', state.query) + .setField('filter', state.filters); + } + savedSearch.columns = state.columns || []; + savedSearch.sort = (state.sort as SortOrder[]) || []; + if (state.grid) { + savedSearch.grid = state.grid; + } + if (typeof state.hideChart !== 'undefined') { + savedSearch.hideChart = state.hideChart; + } + if (typeof state.rowHeight !== 'undefined') { + savedSearch.rowHeight = state.rowHeight; + } + + if (state.viewMode) { + savedSearch.viewMode = state.viewMode; + } + + if (typeof state.breakdownField !== 'undefined') { + savedSearch.breakdownField = state.breakdownField; + } else if (savedSearch.breakdownField) { + savedSearch.breakdownField = ''; + } + + if (state.hideAggregatedPreview) { + savedSearch.hideAggregatedPreview = state.hideAggregatedPreview; + } + + // add a flag here to identify text based language queries + // these should be filtered out from the visualize editor + const isTextBasedQuery = state.query && isOfAggregateQueryType(state.query); + if (savedSearch.isTextBasedQuery || isTextBasedQuery) { + savedSearch.isTextBasedQuery = isTextBasedQuery; + } + + savedSearch.usesAdHocDataView = !dataView.isPersisted(); + + const { from, to } = services.timefilter.getTime(); + const refreshInterval = services.timefilter.getRefreshInterval(); + savedSearch.timeRange = + savedSearch.timeRestore || savedSearch.timeRange + ? { + from, + to, + } + : undefined; + savedSearch.refreshInterval = + savedSearch.timeRestore || savedSearch.refreshInterval + ? { value: refreshInterval.value, pause: refreshInterval.pause } + : undefined; + return savedSearch; +} diff --git a/src/plugins/saved_search/public/index.ts b/src/plugins/saved_search/public/index.ts index 506e90825209a..0aebc8c8c60b4 100644 --- a/src/plugins/saved_search/public/index.ts +++ b/src/plugins/saved_search/public/index.ts @@ -15,6 +15,7 @@ export { getSavedSearchUrlConflictMessage, throwErrorOnSavedSearchUrlConflict, saveSavedSearch, + getEmptySavedSearch, } from './services/saved_searches'; export { VIEW_MODE } from '../common'; diff --git a/src/plugins/saved_search/public/services/saved_searches/get_saved_searches.ts b/src/plugins/saved_search/public/services/saved_searches/get_saved_searches.ts index 0cae15e729209..f8c5ee55bf1aa 100644 --- a/src/plugins/saved_search/public/services/saved_searches/get_saved_searches.ts +++ b/src/plugins/saved_search/public/services/saved_searches/get_saved_searches.ts @@ -24,7 +24,7 @@ interface GetSavedSearchDependencies { savedObjectsTagging?: SavedObjectsTaggingApi; } -const getEmptySavedSearch = ({ +export const getEmptySavedSearch = ({ search, }: { search: DataPublicPluginStart['search']; diff --git a/src/plugins/saved_search/public/services/saved_searches/index.ts b/src/plugins/saved_search/public/services/saved_searches/index.ts index 3aa4120e19e78..30e945c383f3f 100644 --- a/src/plugins/saved_search/public/services/saved_searches/index.ts +++ b/src/plugins/saved_search/public/services/saved_searches/index.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -export { getSavedSearch } from './get_saved_searches'; +export { getSavedSearch, getEmptySavedSearch } from './get_saved_searches'; export { getSavedSearchUrl, getSavedSearchFullPathUrl, From 2d8a2b58c631bcbcf1de73e002d8a7bfb31462a2 Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Thu, 23 Mar 2023 12:11:00 +0100 Subject: [PATCH 002/122] Make use of DiscoverSavedSearchContainer --- .../discover/public/__mocks__/data_view.ts | 1 + .../public/__mocks__/data_view_complex.ts | 9 + .../discover/public/__mocks__/data_views.ts | 17 +- .../public/__mocks__/discover_state.mock.ts | 17 +- .../discover/public/__mocks__/saved_search.ts | 4 + .../field_stats_table/field_stats_tab.tsx | 3 + .../field_stats_table/field_stats_table.tsx | 6 +- .../__stories__/discover_layout.stories.tsx | 3 +- .../layout/__stories__/get_layout_props.ts | 2 +- .../layout/discover_documents.test.tsx | 7 - .../components/layout/discover_documents.tsx | 8 +- .../layout/discover_histogram_layout.test.tsx | 32 +- .../layout/discover_histogram_layout.tsx | 21 +- .../layout/discover_layout.test.tsx | 1 - .../components/layout/discover_layout.tsx | 68 +-- .../layout/discover_main_content.test.tsx | 4 - .../layout/discover_main_content.tsx | 8 - .../main/components/layout/types.ts | 31 -- .../top_nav/discover_topnav.test.tsx | 9 - .../components/top_nav/discover_topnav.tsx | 75 +-- .../top_nav/get_top_nav_links.test.ts | 12 - .../components/top_nav/get_top_nav_links.tsx | 29 +- .../top_nav/on_save_search.test.tsx | 42 +- .../components/top_nav/on_save_search.tsx | 72 ++- .../top_nav/open_alerts_popover.test.tsx | 13 +- .../top_nav/open_alerts_popover.tsx | 58 +-- .../main/discover_main_app.test.tsx | 6 +- .../application/main/discover_main_app.tsx | 111 ++--- .../main/discover_main_route.test.tsx | 2 +- .../application/main/discover_main_route.tsx | 204 +++----- .../main/hooks/use_adhoc_data_views.test.tsx | 55 +-- .../main/hooks/use_adhoc_data_views.ts | 62 +-- .../main/hooks/use_discover_state.test.tsx | 43 -- .../main/hooks/use_discover_state.ts | 228 --------- .../main/hooks/use_filters_validation.ts | 56 --- .../main/hooks/use_inspector.test.ts | 6 +- .../application/main/hooks/use_inspector.ts | 16 +- .../main/hooks/use_saved_search_messages.ts | 8 +- .../main/hooks/use_search_session.test.ts | 9 +- .../main/hooks/use_search_session.ts | 7 +- .../use_test_based_query_language.test.ts | 31 +- .../hooks/use_text_based_query_language.ts | 11 +- .../application/main/hooks/use_url.test.ts | 47 +- .../public/application/main/hooks/use_url.ts | 14 +- .../main/hooks/use_url_tracking.ts | 30 +- .../hooks/utils/build_state_subscribe.test.ts | 89 +--- .../main/hooks/utils/build_state_subscribe.ts | 64 +-- .../main/hooks/utils/change_data_view.test.ts | 15 +- .../main/hooks/utils/change_data_view.ts | 29 +- .../services/discover_app_state_container.ts | 63 ++- .../discover_data_state_container.test.ts | 12 +- .../services/discover_data_state_container.ts | 43 +- .../main/services/discover_search_session.ts | 1 - .../main/services/discover_state.test.ts | 439 +++++++++++++++--- .../main/services/discover_state.ts | 405 ++++++++++++---- .../main/services/discover_state_provider.tsx | 27 +- .../main/services/load_saved_search.ts | 46 ++ .../application/main/utils/fetch_all.test.ts | 41 +- .../application/main/utils/fetch_all.ts | 32 +- .../main/utils/get_fetch_observable.ts | 2 - .../main/utils/persist_saved_search.ts | 107 ----- .../main/utils/resolve_data_view.test.ts | 23 +- .../main/utils/resolve_data_view.ts | 54 +-- .../main/utils/update_search_source.test.ts | 51 +- .../main/utils/update_search_source.ts | 55 +-- .../hooks/use_confirm_persistence_prompt.ts | 34 +- .../components/doc_viewer_table/table.tsx | 2 +- .../restore_from_saved_search.ts | 10 +- src/plugins/discover/public/utils/add_log.ts | 13 +- .../discover/public/utils/breadcrumbs.ts | 7 +- src/plugins/discover/tsconfig.json | 1 - .../apps/discover/group1/_discover.ts | 2 +- .../group1/_discover_histogram_breakdown.ts | 4 +- .../apps/discover/group2/_adhoc_data_views.ts | 41 -- .../translations/translations/fr-FR.json | 3 - .../translations/translations/ja-JP.json | 5 +- .../translations/translations/zh-CN.json | 5 +- 77 files changed, 1446 insertions(+), 1777 deletions(-) delete mode 100644 src/plugins/discover/public/application/main/components/layout/types.ts delete mode 100644 src/plugins/discover/public/application/main/hooks/use_discover_state.test.tsx delete mode 100644 src/plugins/discover/public/application/main/hooks/use_discover_state.ts delete mode 100644 src/plugins/discover/public/application/main/hooks/use_filters_validation.ts create mode 100644 src/plugins/discover/public/application/main/services/load_saved_search.ts delete mode 100644 src/plugins/discover/public/application/main/utils/persist_saved_search.ts diff --git a/src/plugins/discover/public/__mocks__/data_view.ts b/src/plugins/discover/public/__mocks__/data_view.ts index bb57d9eb932ed..339c9efc12264 100644 --- a/src/plugins/discover/public/__mocks__/data_view.ts +++ b/src/plugins/discover/public/__mocks__/data_view.ts @@ -112,6 +112,7 @@ export const buildDataViewMock = ({ getTimeField: () => { return dataViewFields.find((field) => field.name === timeFieldName); }, + toSpec: () => ({}), } as unknown as DataView; dataView.isTimeBased = () => !!timeFieldName; diff --git a/src/plugins/discover/public/__mocks__/data_view_complex.ts b/src/plugins/discover/public/__mocks__/data_view_complex.ts index a90e858b00a3e..6953c044391ae 100644 --- a/src/plugins/discover/public/__mocks__/data_view_complex.ts +++ b/src/plugins/discover/public/__mocks__/data_view_complex.ts @@ -417,3 +417,12 @@ export const dataViewComplexMock = buildDataViewMock({ fields, timeFieldName: 'data', }); + +export const dataViewAdHoc = { + ...buildDataViewMock({ + name: 'data-view-ad-hoc', + fields, + timeFieldName: 'time', + }), + isPersisted: () => false, +} as DataView; diff --git a/src/plugins/discover/public/__mocks__/data_views.ts b/src/plugins/discover/public/__mocks__/data_views.ts index 7b599318917a0..a9e5a880e9196 100644 --- a/src/plugins/discover/public/__mocks__/data_views.ts +++ b/src/plugins/discover/public/__mocks__/data_views.ts @@ -11,27 +11,34 @@ import { dataViewMock } from './data_view'; import { dataViewComplexMock } from './data_view_complex'; import { dataViewWithTimefieldMock } from './data_view_with_timefield'; +export const dataViewMockList = [dataViewMock, dataViewComplexMock, dataViewWithTimefieldMock]; + export function createDiscoverDataViewsMock() { return { getCache: async () => { return [dataViewMock]; }, get: async (id: string) => { - if (id === 'the-data-view-id') { - return Promise.resolve(dataViewMock); - } else if (id === 'invalid-data-view-id') { - return Promise.reject('Invald'); + if (id === 'invalid-data-view-id') { + return Promise.reject('Invalid'); + } + const dataView = dataViewMockList.find((dv) => dv.id === id); + if (dataView) { + return Promise.resolve(dataView); + } else { + return Promise.reject(`DataView ${id} not found`); } }, getDefaultDataView: jest.fn(() => dataViewMock), updateSavedObject: jest.fn(), getIdsWithTitle: jest.fn(() => { - return Promise.resolve([dataViewMock, dataViewComplexMock, dataViewWithTimefieldMock]); + return Promise.resolve(dataViewMockList); }), createFilter: jest.fn(), create: jest.fn(), clearInstanceCache: jest.fn(), getFieldsForIndexPattern: jest.fn((dataView) => dataView.fields), + refreshFields: jest.fn(), } as unknown as jest.Mocked; } diff --git a/src/plugins/discover/public/__mocks__/discover_state.mock.ts b/src/plugins/discover/public/__mocks__/discover_state.mock.ts index 7155dd33ac5c0..3e0a36de1d595 100644 --- a/src/plugins/discover/public/__mocks__/discover_state.mock.ts +++ b/src/plugins/discover/public/__mocks__/discover_state.mock.ts @@ -9,13 +9,24 @@ import { createBrowserHistory } from 'history'; import { getDiscoverStateContainer } from '../application/main/services/discover_state'; import { savedSearchMockWithTimeField, savedSearchMock } from './saved_search'; import { discoverServiceMock } from './services'; +import { SavedSearch } from '@kbn/saved-search-plugin/public'; -export function getDiscoverStateMock({ isTimeBased = true }) { +export function getDiscoverStateMock({ + isTimeBased = true, + savedSearch, +}: { + isTimeBased?: boolean; + savedSearch?: SavedSearch; +}) { const history = createBrowserHistory(); history.push('/'); - return getDiscoverStateContainer({ - savedSearch: isTimeBased ? savedSearchMockWithTimeField : savedSearchMock, + const container = getDiscoverStateContainer({ services: discoverServiceMock, history, }); + container.savedSearchState.set( + savedSearch ? savedSearch : isTimeBased ? savedSearchMockWithTimeField : savedSearchMock + ); + + return container; } diff --git a/src/plugins/discover/public/__mocks__/saved_search.ts b/src/plugins/discover/public/__mocks__/saved_search.ts index de97a31d65632..27b144a5aa081 100644 --- a/src/plugins/discover/public/__mocks__/saved_search.ts +++ b/src/plugins/discover/public/__mocks__/saved_search.ts @@ -21,6 +21,10 @@ export const savedSearchMockWithTimeField = { searchSource: createSearchSourceMock({ index: dataViewWithTimefieldMock }), } as unknown as SavedSearch; +export const savedSearchMockWithTimeFieldNew = { + searchSource: createSearchSourceMock({ index: dataViewWithTimefieldMock }), +} as unknown as SavedSearch; + export const savedSearchMockWithSQL = { id: 'the-saved-search-id-sql', searchSource: createSearchSourceMock({ diff --git a/src/plugins/discover/public/application/main/components/field_stats_table/field_stats_tab.tsx b/src/plugins/discover/public/application/main/components/field_stats_table/field_stats_tab.tsx index 87dcbb2297c4f..9ac42f7ad863d 100644 --- a/src/plugins/discover/public/application/main/components/field_stats_table/field_stats_tab.tsx +++ b/src/plugins/discover/public/application/main/components/field_stats_table/field_stats_tab.tsx @@ -8,6 +8,7 @@ import React from 'react'; import { useQuerySubscriber } from '@kbn/unified-field-list-plugin/public'; +import { useSavedSearch } from '../../services/discover_state_provider'; import { FieldStatisticsTable, type FieldStatisticsTableProps } from './field_stats_table'; import { useDiscoverServices } from '../../../../hooks/use_discover_services'; @@ -17,10 +18,12 @@ export const FieldStatisticsTab: React.FC diff --git a/src/plugins/discover/public/application/main/components/field_stats_table/field_stats_table.tsx b/src/plugins/discover/public/application/main/components/field_stats_table/field_stats_table.tsx index 0bb6947dc7674..0aa6f24fba287 100644 --- a/src/plugins/discover/public/application/main/components/field_stats_table/field_stats_table.tsx +++ b/src/plugins/discover/public/application/main/components/field_stats_table/field_stats_table.tsx @@ -111,13 +111,13 @@ export interface FieldStatisticsTableProps { export const FieldStatisticsTable = (props: FieldStatisticsTableProps) => { const { dataView, - savedSearch, query, columns, filters, stateContainer, onAddFilter, trackUiMetric, + savedSearch, searchSessionId, } = props; const totalHits$ = stateContainer?.dataState.data$.totalHits$; @@ -185,7 +185,6 @@ export const FieldStatisticsTable = (props: FieldStatisticsTableProps) => { }, [ embeddable, dataView, - savedSearch, query, columns, filters, @@ -193,6 +192,7 @@ export const FieldStatisticsTable = (props: FieldStatisticsTableProps) => { searchSessionId, totalHits$, stateContainer, + savedSearch, ]); useEffect(() => { @@ -219,7 +219,7 @@ export const FieldStatisticsTable = (props: FieldStatisticsTableProps) => { const initializedEmbeddable = await factory.create({ id: 'discover_data_visualizer_grid', dataView, - savedSearch, + savedSearch: stateContainer?.savedSearchState.getState(), query, showPreviewByDefault, onAddFilter, diff --git a/src/plugins/discover/public/application/main/components/layout/__stories__/discover_layout.stories.tsx b/src/plugins/discover/public/application/main/components/layout/__stories__/discover_layout.stories.tsx index 27214f34d8a23..80db81bb4228b 100644 --- a/src/plugins/discover/public/application/main/components/layout/__stories__/discover_layout.stories.tsx +++ b/src/plugins/discover/public/application/main/components/layout/__stories__/discover_layout.stories.tsx @@ -14,9 +14,8 @@ import { DiscoverAppState } from '../../../services/discover_app_state_container import { getDataViewMock } from '../../../../../__mocks__/__storybook_mocks__/get_data_view_mock'; import { withDiscoverServices } from '../../../../../__mocks__/__storybook_mocks__/with_discover_services'; import { getDocumentsLayoutProps, getPlainRecordLayoutProps } from './get_layout_props'; -import { DiscoverLayout } from '../discover_layout'; +import { DiscoverLayout, DiscoverLayoutProps } from '../discover_layout'; import { setHeaderActionMenuMounter } from '../../../../../kibana_services'; -import { DiscoverLayoutProps } from '../types'; setHeaderActionMenuMounter(() => void 0); diff --git a/src/plugins/discover/public/application/main/components/layout/__stories__/get_layout_props.ts b/src/plugins/discover/public/application/main/components/layout/__stories__/get_layout_props.ts index 1082a5b127397..0d41251ce4542 100644 --- a/src/plugins/discover/public/application/main/components/layout/__stories__/get_layout_props.ts +++ b/src/plugins/discover/public/application/main/components/layout/__stories__/get_layout_props.ts @@ -23,7 +23,7 @@ import { import { buildDataTableRecordList } from '../../../../../utils/build_data_record'; import { esHits } from '../../../../../__mocks__/es_hits'; import { SavedSearch } from '../../../../..'; -import { DiscoverLayoutProps } from '../types'; +import { DiscoverLayoutProps } from '../discover_layout'; import { DiscoverStateContainer, getDiscoverStateContainer, diff --git a/src/plugins/discover/public/application/main/components/layout/discover_documents.test.tsx b/src/plugins/discover/public/application/main/components/layout/discover_documents.test.tsx index f366d37d8d7bb..1360a49676895 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_documents.test.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_documents.test.tsx @@ -11,7 +11,6 @@ import { BehaviorSubject } from 'rxjs'; import { mountWithIntl } from '@kbn/test-jest-helpers'; import { setHeaderActionMenuMounter } from '../../../../kibana_services'; import { esHits } from '../../../../__mocks__/es_hits'; -import { savedSearchMock } from '../../../../__mocks__/saved_search'; import { DataDocuments$ } from '../../services/discover_data_state_container'; import { discoverServiceMock } from '../../../../__mocks__/services'; import { FetchStatus } from '../../../types'; @@ -41,15 +40,9 @@ function mountComponent(fetchStatus: FetchStatus, hits: EsHitRecord[]) { stateContainer.dataState.data$.documents$ = documents$; const props = { - expandedDoc: undefined, dataView: dataViewMock, onAddFilter: jest.fn(), - savedSearch: savedSearchMock, - searchSource: savedSearchMock.searchSource, - setExpandedDoc: jest.fn(), - state: { columns: [] }, stateContainer, - navigateTo: jest.fn(), onFieldEdited: jest.fn(), }; diff --git a/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx b/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx index 9804a093113a7..ba6fc75b15f5d 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx @@ -15,8 +15,7 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { DataView } from '@kbn/data-views-plugin/public'; -import { SavedSearch, SortOrder } from '@kbn/saved-search-plugin/public'; -import { DataTableRecord } from '../../../../types'; +import { SortOrder } from '@kbn/saved-search-plugin/public'; import { useInternalStateSelector } from '../../services/discover_internal_state_container'; import { useAppStateSelector } from '../../services/discover_app_state_container'; import { useDiscoverServices } from '../../../../hooks/use_discover_services'; @@ -38,6 +37,7 @@ import { DocTableInfinite } from '../../../../components/doc_table/doc_table_inf import { DocumentExplorerCallout } from '../document_explorer_callout'; import { DocumentExplorerUpdateCallout } from '../document_explorer_callout/document_explorer_update_callout'; import { DiscoverTourProvider } from '../../../../components/discover_tour'; +import { DataTableRecord } from '../../../../types'; import { getRawRecordType } from '../../utils/get_raw_record_type'; import { DiscoverGridFlyout } from '../../../../components/discover_grid/discover_grid_flyout'; import { DocViewer } from '../../../../services/doc_views/components/doc_viewer'; @@ -63,19 +63,17 @@ export const onResize = ( function DiscoverDocumentsComponent({ dataView, onAddFilter, - savedSearch, stateContainer, onFieldEdited, }: { dataView: DataView; - navigateTo: (url: string) => void; onAddFilter?: DocViewFilterFn; - savedSearch: SavedSearch; stateContainer: DiscoverStateContainer; onFieldEdited?: () => void; }) { const services = useDiscoverServices(); const documents$ = stateContainer.dataState.data$.documents$; + const savedSearch = stateContainer.savedSearchState.getState(); const { dataViews, capabilities, uiSettings } = services; const [query, sort, rowHeight, rowsPerPage, grid, columns, index] = useAppStateSelector( (state) => { diff --git a/src/plugins/discover/public/application/main/components/layout/discover_histogram_layout.test.tsx b/src/plugins/discover/public/application/main/components/layout/discover_histogram_layout.test.tsx index 2547011242081..b780a4deb2467 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_histogram_layout.test.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_histogram_layout.test.tsx @@ -28,7 +28,6 @@ import { SavedSearch, VIEW_MODE } from '@kbn/saved-search-plugin/public'; import { CoreTheme } from '@kbn/core/public'; import { Storage } from '@kbn/kibana-utils-plugin/public'; import { createSearchSessionMock } from '../../../../__mocks__/search_session'; -import { RequestAdapter } from '@kbn/inspector-plugin/public'; import { searchSourceInstanceMock } from '@kbn/data-plugin/common/search/search_source/mocks'; import { getSessionServiceMock } from '@kbn/data-plugin/public/search/session/mocks'; import { ResetSearchButton } from './reset_search_button'; @@ -36,8 +35,8 @@ import { getDiscoverStateMock } from '../../../../__mocks__/discover_state.mock' import { DiscoverMainProvider } from '../../services/discover_state_provider'; import { act } from 'react-dom/test-utils'; -function getStateContainer() { - const stateContainer = getDiscoverStateMock({ isTimeBased: true }); +function getStateContainer(savedSearch?: SavedSearch) { + const stateContainer = getDiscoverStateMock({ isTimeBased: true, savedSearch }); stateContainer.appState.update({ interval: 'auto', @@ -51,14 +50,12 @@ const mountComponent = async ({ isPlainRecord = false, storage, savedSearch = savedSearchMock, - resetSavedSearch = jest.fn(), searchSessionId = '123', }: { isPlainRecord?: boolean; isTimeBased?: boolean; storage?: Storage; savedSearch?: SavedSearch; - resetSavedSearch?(): void; searchSessionId?: string | null; } = {}) => { let services = discoverServiceMock; @@ -112,22 +109,19 @@ const mountComponent = async ({ session.getSession$.mockReturnValue(new BehaviorSubject(searchSessionId ?? undefined)); - const stateContainer = getStateContainer(); + const stateContainer = getStateContainer(savedSearch); stateContainer.dataState.data$ = savedSearchData$; + stateContainer.actions.undoChanges = jest.fn(); const props: DiscoverHistogramLayoutProps = { isPlainRecord, dataView: dataViewMock, - navigateTo: jest.fn(), - savedSearch, stateContainer, onFieldEdited: jest.fn(), columns: [], viewMode: VIEW_MODE.DOCUMENT_LEVEL, onAddFilter: jest.fn(), - resetSavedSearch, resizeRef: { current: null }, - inspectorAdapters: { requests: new RequestAdapter() }, }; stateContainer.searchSessionManager = createSearchSessionMock(session).searchSessionManager; @@ -147,45 +141,45 @@ const mountComponent = async ({ await act(() => new Promise((resolve) => setTimeout(resolve, 0))); component.update(); - return component; + return { component, stateContainer }; }; describe('Discover histogram layout component', () => { describe('render', () => { it('should render null if there is no search session', async () => { - const component = await mountComponent({ searchSessionId: null }); + const { component } = await mountComponent({ searchSessionId: null }); expect(component.isEmptyRender()).toBe(true); }); it('should not render null if there is a search session', async () => { - const component = await mountComponent(); + const { component } = await mountComponent(); expect(component.isEmptyRender()).toBe(false); }); it('should not render null if there is no search session, but isPlainRecord is true', async () => { - const component = await mountComponent({ isPlainRecord: true }); + const { component } = await mountComponent({ isPlainRecord: true }); expect(component.isEmptyRender()).toBe(false); }); }); describe('reset search button', () => { it('renders the button when there is a saved search', async () => { - const component = await mountComponent(); + const { component } = await mountComponent(); expect(component.find(ResetSearchButton).exists()).toBe(true); }); it('does not render the button when there is no saved search', async () => { - const component = await mountComponent({ + const { component } = await mountComponent({ savedSearch: { ...savedSearchMock, id: undefined }, }); expect(component.find(ResetSearchButton).exists()).toBe(false); }); it('should call resetSavedSearch when clicked', async () => { - const resetSavedSearch = jest.fn(); - const component = await mountComponent({ resetSavedSearch }); + const { component, stateContainer } = await mountComponent(); + expect(component.find(ResetSearchButton).exists()).toBe(true); component.find(ResetSearchButton).find('button').simulate('click'); - expect(resetSavedSearch).toHaveBeenCalled(); + expect(stateContainer.actions.undoChanges).toHaveBeenCalled(); }); }); }); diff --git a/src/plugins/discover/public/application/main/components/layout/discover_histogram_layout.tsx b/src/plugins/discover/public/application/main/components/layout/discover_histogram_layout.tsx index 5a74e58184ff0..56bc931c5f382 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_histogram_layout.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_histogram_layout.tsx @@ -11,14 +11,11 @@ import { UnifiedHistogramContainer } from '@kbn/unified-histogram-plugin/public' import { css } from '@emotion/react'; import useObservable from 'react-use/lib/useObservable'; import { useDiscoverHistogram } from './use_discover_histogram'; -import type { InspectorAdapters } from '../../hooks/use_inspector'; import { type DiscoverMainContentProps, DiscoverMainContent } from './discover_main_content'; import { ResetSearchButton } from './reset_search_button'; export interface DiscoverHistogramLayoutProps extends DiscoverMainContentProps { - resetSavedSearch: () => void; resizeRef: RefObject; - inspectorAdapters: InspectorAdapters; } const histogramLayoutCss = css` @@ -28,29 +25,24 @@ const histogramLayoutCss = css` export const DiscoverHistogramLayout = ({ isPlainRecord, dataView, - resetSavedSearch, - savedSearch, stateContainer, resizeRef, - inspectorAdapters, ...mainContentProps }: DiscoverHistogramLayoutProps) => { + const { dataState, savedSearchState } = stateContainer; const commonProps = { dataView, stateContainer, - savedSearchData$: stateContainer.dataState.data$, + savedSearchData$: dataState.data$, }; const searchSessionId = useObservable(stateContainer.searchSessionManager.searchSessionId$); const { hideChart, setUnifiedHistogramApi } = useDiscoverHistogram({ - inspectorAdapters, - savedSearchFetch$: stateContainer.dataState.fetch$, + inspectorAdapters: stateContainer.dataState.inspectorAdapters, + savedSearchFetch$: dataState.fetch$, searchSessionId, ...commonProps, }); - - // Initialized when the first search has been requested or - // when in text-based mode since search sessions are not supported if (!searchSessionId && !isPlainRecord) { return null; } @@ -60,14 +52,15 @@ export const DiscoverHistogramLayout = ({ ref={setUnifiedHistogramApi} resizeRef={resizeRef} appendHitsCounter={ - savedSearch?.id ? : undefined + savedSearchState.getId() ? ( + + ) : undefined } css={histogramLayoutCss} > void; + stateContainer: DiscoverStateContainer; + persistDataView: (dataView: DataView) => Promise; +} + export function DiscoverLayout({ - inspectorAdapters, navigateTo, - onChangeDataView, - onUpdateQuery, - resetSavedSearch, - savedSearch, - searchSource, stateContainer, persistDataView, - updateAdHocDataViewId, - updateDataViewList, }: DiscoverLayoutProps) { const { trackUiMetric, @@ -122,8 +120,6 @@ export function DiscoverLayout({ const onOpenInspector = useInspector({ inspector, stateContainer, - inspectorAdapters, - savedSearch, }); const { @@ -160,11 +156,11 @@ export function DiscoverLayout({ onRemoveColumn(removedFieldName); } if (!dataView.isPersisted()) { - await updateAdHocDataViewId(dataView); + await stateContainer.actions.updateAdHocDataViewId(); } stateContainer.dataState.refetch$.next('reset'); }, - [dataView, stateContainer, updateAdHocDataViewId, currentColumns, onRemoveColumn] + [dataView, stateContainer, currentColumns, onRemoveColumn] ); const onDisableFilters = useCallback(() => { @@ -180,19 +176,6 @@ export function DiscoverLayout({ }, [isSidebarClosed, storage]); const contentCentered = resultState === 'uninitialized' || resultState === 'none'; - const onDataViewCreated = useCallback( - async (nextDataView: DataView) => { - if (!nextDataView.isPersisted()) { - stateContainer.actions.appendAdHocDataViews(nextDataView); - } else { - await stateContainer.actions.loadDataViewList(); - } - if (nextDataView.id) { - onChangeDataView(nextDataView.id); - } - }, - [onChangeDataView, stateContainer] - ); const savedSearchTitle = useRef(null); useEffect(() => { @@ -223,11 +206,7 @@ export function DiscoverLayout({ } if (resultState === 'uninitialized') { - return ( - stateContainer.dataState.refetch$.next(undefined)} - /> - ); + return stateContainer.dataState.fetch()} />; } return ( @@ -235,16 +214,12 @@ export function DiscoverLayout({ {resultState === 'loading' && } @@ -253,16 +228,12 @@ export function DiscoverLayout({ currentColumns, data, dataView, - inspectorAdapters, isPlainRecord, isTimeBased, - navigateTo, onAddFilter, onDisableFilters, onFieldEdited, - resetSavedSearch, resultState, - savedSearch, stateContainer, viewMode, ]); @@ -275,11 +246,11 @@ export function DiscoverLayout({ tabIndex={-1} ref={savedSearchTitle} > - {savedSearch.title + {stateContainer.savedSearchState.getTitle() ? i18n.translate('discover.pageTitleWithSavedSearch', { defaultMessage: 'Discover - {savedSearchTitle}', values: { - savedSearchTitle: savedSearch.title, + savedSearchTitle: stateContainer.savedSearchState.getTitle(), }, }) : i18n.translate('discover.pageTitleWithoutSavedSearch', { @@ -291,23 +262,16 @@ export function DiscoverLayout({ query={query} navigateTo={navigateTo} savedQuery={savedQuery} - savedSearch={savedSearch} - searchSource={searchSource} stateContainer={stateContainer} - updateQuery={onUpdateQuery} - resetSavedSearch={resetSavedSearch} - onChangeDataView={onChangeDataView} - onDataViewCreated={onDataViewCreated} + updateQuery={stateContainer.actions.onUpdateQuery} isPlainRecord={isPlainRecord} textBasedLanguageModeErrors={textBasedLanguageModeErrors} onFieldEdited={onFieldEdited} persistDataView={persistDataView} - updateAdHocDataViewId={updateAdHocDataViewId} - updateDataViewList={updateDataViewList} /> @@ -319,14 +283,14 @@ export function DiscoverLayout({ columns={currentColumns} onAddFilter={!isPlainRecord ? onAddFilter : undefined} onRemoveField={onRemoveColumn} - onChangeDataView={onChangeDataView} + onChangeDataView={stateContainer.actions.onChangeDataView} selectedDataView={dataView} isClosed={isSidebarClosed} trackUiMetric={trackUiMetric} useNewFieldsApi={useNewFieldsApi} onFieldEdited={onFieldEdited} viewMode={viewMode} - onDataViewCreated={onDataViewCreated} + onDataViewCreated={stateContainer.actions.onDataViewCreated} availableFields$={stateContainer.dataState.data$.availableFields$} /> diff --git a/src/plugins/discover/public/application/main/components/layout/discover_main_content.test.tsx b/src/plugins/discover/public/application/main/components/layout/discover_main_content.test.tsx index b85580e75b7c1..5ab1a6e4d6e3b 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_main_content.test.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_main_content.test.tsx @@ -11,7 +11,6 @@ import { BehaviorSubject, of } from 'rxjs'; import { mountWithIntl } from '@kbn/test-jest-helpers'; import { esHits } from '../../../../__mocks__/es_hits'; import { dataViewMock } from '../../../../__mocks__/data_view'; -import { savedSearchMock } from '../../../../__mocks__/saved_search'; import { AvailableFields$, DataDocuments$, @@ -39,7 +38,6 @@ const mountComponent = ({ isPlainRecord = false, viewMode = VIEW_MODE.DOCUMENT_LEVEL, storage, - savedSearch = savedSearchMock, }: { hideChart?: boolean; isPlainRecord?: boolean; @@ -99,8 +97,6 @@ const mountComponent = ({ const props: DiscoverMainContentProps = { isPlainRecord, dataView: dataViewMock, - navigateTo: jest.fn(), - savedSearch, stateContainer, onFieldEdited: jest.fn(), columns: [], diff --git a/src/plugins/discover/public/application/main/components/layout/discover_main_content.tsx b/src/plugins/discover/public/application/main/components/layout/discover_main_content.tsx index 4686aaaca1f10..90f319f6f8a3b 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_main_content.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_main_content.tsx @@ -7,7 +7,6 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule } from '@elastic/eui'; -import { SavedSearch } from '@kbn/saved-search-plugin/public'; import React, { useCallback } from 'react'; import { DataView } from '@kbn/data-views-plugin/common'; import { METRIC_TYPE } from '@kbn/analytics'; @@ -25,9 +24,7 @@ import { useDataState } from '../../hooks/use_data_state'; export interface DiscoverMainContentProps { dataView: DataView; - savedSearch: SavedSearch; isPlainRecord: boolean; - navigateTo: (url: string) => void; stateContainer: DiscoverStateContainer; viewMode: VIEW_MODE; onAddFilter: DocViewFilterFn | undefined; @@ -38,13 +35,11 @@ export interface DiscoverMainContentProps { export const DiscoverMainContent = ({ dataView, isPlainRecord, - navigateTo, viewMode, onAddFilter, onFieldEdited, columns, stateContainer, - savedSearch, }: DiscoverMainContentProps) => { const { trackUiMetric } = useDiscoverServices(); @@ -91,15 +86,12 @@ export const DiscoverMainContent = ({ {viewMode === VIEW_MODE.DOCUMENT_LEVEL ? ( ) : ( void; - onChangeDataView: (id: string) => void; - onUpdateQuery: ( - payload: { dateRange: TimeRange; query?: Query | AggregateQuery }, - isUpdate?: boolean - ) => void; - resetSavedSearch: () => void; - savedSearch: SavedSearch; - searchSource: ISearchSource; - stateContainer: DiscoverStateContainer; - persistDataView: (dataView: DataView) => Promise; - updateAdHocDataViewId: (dataView: DataView) => Promise; - updateDataViewList: (newAdHocDataViews: DataView[]) => void; -} diff --git a/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.test.tsx b/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.test.tsx index 85c7e381588ea..122476b815310 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.test.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.test.tsx @@ -9,10 +9,8 @@ import React from 'react'; import { mountWithIntl } from '@kbn/test-jest-helpers'; import { dataViewMock } from '../../../../__mocks__/data_view'; -import { savedSearchMock } from '../../../../__mocks__/saved_search'; import { DiscoverTopNav, DiscoverTopNavProps } from './discover_topnav'; import { TopNavMenu, TopNavMenuData } from '@kbn/navigation-plugin/public'; -import { ISearchSource } from '@kbn/data-plugin/public'; import { Query } from '@kbn/es-query'; import { setHeaderActionMenuMounter } from '../../../../kibana_services'; import { discoverServiceMock } from '../../../../__mocks__/services'; @@ -35,21 +33,14 @@ function getProps(savePermissions = true): DiscoverTopNavProps { return { stateContainer, - savedSearch: savedSearchMock, navigateTo: jest.fn(), query: {} as Query, savedQuery: '', updateQuery: jest.fn(), onOpenInspector: jest.fn(), - searchSource: {} as ISearchSource, - resetSavedSearch: () => {}, onFieldEdited: jest.fn(), - onChangeDataView: jest.fn(), isPlainRecord: false, persistDataView: jest.fn(), - updateAdHocDataViewId: jest.fn(), - updateDataViewList: jest.fn(), - onDataViewCreated: jest.fn(), }; } diff --git a/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx b/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx index bf4b608a3c017..7e7a75eb3cefd 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx @@ -6,23 +6,19 @@ * Side Public License, v 1. */ import React, { useCallback, useEffect, useMemo, useRef } from 'react'; -import { useHistory } from 'react-router-dom'; import type { Query, TimeRange, AggregateQuery } from '@kbn/es-query'; import { DataViewType, type DataView } from '@kbn/data-views-plugin/public'; import type { DataViewPickerProps } from '@kbn/unified-search-plugin/public'; import { useInternalStateSelector } from '../../services/discover_internal_state_container'; import { ENABLE_SQL } from '../../../../../common'; import { useDiscoverServices } from '../../../../hooks/use_discover_services'; -import { DiscoverLayoutProps } from '../layout/types'; +import { DiscoverLayoutProps } from '../layout/discover_layout'; import { getTopNavLinks } from './get_top_nav_links'; import { getHeaderActionMenuMounter } from '../../../../kibana_services'; import { DiscoverStateContainer } from '../../services/discover_state'; import { onSaveSearch } from './on_save_search'; -export type DiscoverTopNavProps = Pick< - DiscoverLayoutProps, - 'navigateTo' | 'savedSearch' | 'searchSource' -> & { +export type DiscoverTopNavProps = Pick & { onOpenInspector: () => void; query?: Query | AggregateQuery; savedQuery?: string; @@ -31,15 +27,10 @@ export type DiscoverTopNavProps = Pick< isUpdate?: boolean ) => void; stateContainer: DiscoverStateContainer; - resetSavedSearch: () => void; - onChangeDataView: (dataView: string) => void; - onDataViewCreated: (dataView: DataView) => void; isPlainRecord: boolean; textBasedLanguageModeErrors?: Error; onFieldEdited: () => Promise; persistDataView: (dataView: DataView) => Promise; - updateAdHocDataViewId: (dataView: DataView) => Promise; - updateDataViewList: (newAdHocDataViews: DataView[]) => void; }; export const DiscoverTopNav = ({ @@ -48,20 +39,12 @@ export const DiscoverTopNav = ({ savedQuery, stateContainer, updateQuery, - searchSource, navigateTo, - savedSearch, - resetSavedSearch, - onChangeDataView, - onDataViewCreated, isPlainRecord, textBasedLanguageModeErrors, onFieldEdited, persistDataView, - updateAdHocDataViewId, - updateDataViewList, }: DiscoverTopNavProps) => { - const history = useHistory(); const adHocDataViews = useInternalStateSelector((state) => state.adHocDataViews); const dataView = useInternalStateSelector((state) => state.dataView!); const savedDataViews = useInternalStateSelector((state) => state.savedDataViews); @@ -80,17 +63,6 @@ export const DiscoverTopNav = ({ const { AggregateQueryTopNavMenu } = navigation.ui; - const onOpenSavedSearch = useCallback( - (newSavedSearchId: string) => { - if (savedSearch.id && savedSearch.id === newSavedSearchId) { - resetSavedSearch(); - } else { - history.push(`/view/${encodeURIComponent(newSavedSearchId)}`); - } - }, - [history, resetSavedSearch, savedSearch.id] - ); - useEffect(() => { return () => { // Make sure to close the editors when unmounting @@ -131,57 +103,32 @@ export const DiscoverTopNav = ({ const createNewDataView = useCallback(() => { closeDataViewEditor.current = dataViewEditor.openEditor({ - onSave: onDataViewCreated, + onSave: stateContainer.actions.onDataViewCreated, allowAdHocDataView: true, }); - }, [dataViewEditor, onDataViewCreated]); - - const onCreateDefaultAdHocDataView = useCallback( - async (pattern: string) => { - const newDataView = await dataViews.create({ - title: pattern, - }); - if (newDataView.fields.getByName('@timestamp')?.type === 'date') { - newDataView.timeFieldName = '@timestamp'; - } - - stateContainer.actions.appendAdHocDataViews(newDataView); - onChangeDataView(newDataView.id!); - }, - [dataViews, onChangeDataView, stateContainer.actions] - ); + }, [dataViewEditor, stateContainer]); const topNavMenu = useMemo( () => getTopNavLinks({ dataView, navigateTo, - savedSearch, services, state: stateContainer, onOpenInspector, - searchSource, - onOpenSavedSearch, isPlainRecord, adHocDataViews, - updateDataViewList, persistDataView, - updateAdHocDataViewId, }), [ dataView, navigateTo, - savedSearch, services, stateContainer, onOpenInspector, - searchSource, - onOpenSavedSearch, isPlainRecord, adHocDataViews, persistDataView, - updateAdHocDataViewId, - updateDataViewList, ] ); @@ -192,7 +139,7 @@ export const DiscoverTopNav = ({ dataViews.clearInstanceCache(editedDataView.id); stateContainer.actions.setDataView(await dataViews.create(editedDataView.toSpec(), true)); } else { - await updateAdHocDataViewId(editedDataView); + await stateContainer.actions.updateAdHocDataViewId(); } stateContainer.actions.loadDataViewList(); stateContainer.dataState.fetch(); @@ -228,8 +175,8 @@ export const DiscoverTopNav = ({ currentDataViewId: dataView?.id, onAddField: addField, onDataViewCreated: createNewDataView, - onCreateDefaultAdHocDataView, - onChangeDataView, + onCreateDefaultAdHocDataView: stateContainer.actions.onCreateDefaultAdHocDataView, + onChangeDataView: stateContainer.actions.onChangeDataView, textBasedLanguages: supportedTextBasedLanguages as DataViewPickerProps['textBasedLanguages'], adHocDataViews, savedDataViews, @@ -239,17 +186,15 @@ export const DiscoverTopNav = ({ const onTextBasedSavedAndExit = useCallback( ({ onSave, onCancel }) => { onSaveSearch({ - savedSearch, + savedSearch: stateContainer.savedSearchState.getState(), services, - dataView, navigateTo, state: stateContainer, onClose: onCancel, onSaveCb: onSave, - updateAdHocDataViewId, }); }, - [dataView, navigateTo, savedSearch, services, stateContainer, updateAdHocDataViewId] + [navigateTo, services, stateContainer] ); return ( @@ -262,7 +207,7 @@ export const DiscoverTopNav = ({ query={query} setMenuMountPoint={setMenuMountPoint} savedQueryId={savedQuery} - screenTitle={savedSearch.title} + screenTitle={stateContainer.savedSearchState.getTitle()} showDatePicker={showDatePicker} showSaveQuery={!isPlainRecord && Boolean(services.capabilities.discover.saveQuery)} showSearchBar={true} diff --git a/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.test.ts b/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.test.ts index 0503fd4ff37e9..a729ed93ba063 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.test.ts +++ b/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.test.ts @@ -6,10 +6,8 @@ * Side Public License, v 1. */ -import { ISearchSource } from '@kbn/data-plugin/public'; import { getTopNavLinks } from './get_top_nav_links'; import { dataViewMock } from '../../../../__mocks__/data_view'; -import { savedSearchMock } from '../../../../__mocks__/saved_search'; import { DiscoverServices } from '../../../../build_services'; import { DiscoverStateContainer } from '../../services/discover_state'; @@ -31,16 +29,11 @@ test('getTopNavLinks result', () => { dataView: dataViewMock, navigateTo: jest.fn(), onOpenInspector: jest.fn(), - savedSearch: savedSearchMock, services, state, - searchSource: {} as ISearchSource, - onOpenSavedSearch: () => {}, isPlainRecord: false, persistDataView: jest.fn(), - updateDataViewList: jest.fn(), adHocDataViews: [], - updateAdHocDataViewId: jest.fn(), }); expect(topNavLinks).toMatchInlineSnapshot(` Array [ @@ -97,16 +90,11 @@ test('getTopNavLinks result for sql mode', () => { dataView: dataViewMock, navigateTo: jest.fn(), onOpenInspector: jest.fn(), - savedSearch: savedSearchMock, services, state, - searchSource: {} as ISearchSource, - onOpenSavedSearch: () => {}, isPlainRecord: true, persistDataView: jest.fn(), - updateDataViewList: jest.fn(), adHocDataViews: [], - updateAdHocDataViewId: jest.fn(), }); expect(topNavLinks).toMatchInlineSnapshot(` Array [ diff --git a/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.tsx b/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.tsx index 59723e5b1225f..3a0555925ed4b 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.tsx @@ -7,11 +7,9 @@ */ import { i18n } from '@kbn/i18n'; -import type { ISearchSource } from '@kbn/data-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public'; import { unhashUrl } from '@kbn/kibana-utils-plugin/public'; import type { TopNavMenuData } from '@kbn/navigation-plugin/public'; -import { SavedSearch } from '@kbn/saved-search-plugin/public'; import { showOpenSearchPanel } from './show_open_search_panel'; import { getSharingData, showPublicUrlSwitch } from '../../../../utils/get_sharing_data'; import { DiscoverServices } from '../../../../build_services'; @@ -26,31 +24,21 @@ import { openAlertsPopover } from './open_alerts_popover'; export const getTopNavLinks = ({ dataView, navigateTo, - savedSearch, services, state, onOpenInspector, - searchSource, - onOpenSavedSearch, isPlainRecord, persistDataView, adHocDataViews, - updateDataViewList, - updateAdHocDataViewId, }: { dataView: DataView; navigateTo: (url: string) => void; - savedSearch: SavedSearch; services: DiscoverServices; state: DiscoverStateContainer; onOpenInspector: () => void; - searchSource: ISearchSource; - onOpenSavedSearch: (id: string) => void; isPlainRecord: boolean; adHocDataViews: DataView[]; - updateDataViewList: (dataView: DataView[]) => void; persistDataView: (dataView: DataView) => Promise; - updateAdHocDataViewId: (dataView: DataView) => Promise; }): TopNavMenuData[] => { const options = { id: 'options', @@ -83,11 +71,9 @@ export const getTopNavLinks = ({ I18nContext: services.core.i18n.Context, theme$: services.core.theme.theme$, anchorElement, - searchSource: savedSearch.searchSource, services, + stateContainer: state, adHocDataViews, - updateDataViewList, - savedQueryId: state.appState.getState().savedQuery, }); }, testId: 'discoverAlertsButton', @@ -118,12 +104,10 @@ export const getTopNavLinks = ({ emphasize: true, run: (anchorElement: HTMLElement) => { onSaveSearch({ - savedSearch, + savedSearch: state.savedSearchState.getState(), services, - dataView, navigateTo, state, - updateAdHocDataViewId, onClose: () => { anchorElement?.focus(); }, @@ -142,7 +126,7 @@ export const getTopNavLinks = ({ testId: 'discoverOpenButton', run: () => showOpenSearchPanel({ - onOpenSavedSearch, + onOpenSavedSearch: state.actions.onOpenSavedSearch, I18nContext: services.core.i18n.Context, theme$: services.core.theme.theme$, services, @@ -163,7 +147,12 @@ export const getTopNavLinks = ({ if (!services.share || !updatedDataView) { return; } - const sharingData = await getSharingData(searchSource, state.appState.getState(), services); + const savedSearch = state.savedSearchState.getState(); + const sharingData = await getSharingData( + savedSearch.searchSource, + state.appState.getState(), + services + ); services.share.toggleShareContextMenu({ anchorElement, diff --git a/src/plugins/discover/public/application/main/components/top_nav/on_save_search.test.tsx b/src/plugins/discover/public/application/main/components/top_nav/on_save_search.test.tsx index a7efab7d984a9..3945a21024a34 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/on_save_search.test.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/on_save_search.test.tsx @@ -8,16 +8,11 @@ import * as savedObjectsPlugin from '@kbn/saved-objects-plugin/public'; jest.mock('@kbn/saved-objects-plugin/public'); -jest.mock('../../utils/persist_saved_search', () => ({ - persistSavedSearch: jest.fn(() => ({ id: 'the-saved-search-id' })), -})); import { onSaveSearch } from './on_save_search'; -import { dataViewMock } from '../../../../__mocks__/data_view'; import { savedSearchMock } from '../../../../__mocks__/saved_search'; import { getDiscoverStateContainer } from '../../services/discover_state'; import { ReactElement } from 'react'; import { discoverServiceMock } from '../../../../__mocks__/services'; -import * as persistSavedSearchUtils from '../../utils/persist_saved_search'; import { SavedSearch } from '@kbn/saved-search-plugin/public'; import { createBrowserHistory } from 'history'; @@ -25,10 +20,10 @@ function getStateContainer() { const savedSearch = savedSearchMock; const history = createBrowserHistory(); const stateContainer = getDiscoverStateContainer({ - savedSearch, services: discoverServiceMock, history, }); + stateContainer.savedSearchState.set(savedSearch); stateContainer.appState.getState = jest.fn(() => ({ rowsPerPage: 250, })); @@ -38,12 +33,10 @@ function getStateContainer() { describe('onSaveSearch', () => { it('should call showSaveModal', async () => { await onSaveSearch({ - dataView: dataViewMock, navigateTo: jest.fn(), savedSearch: savedSearchMock, services: discoverServiceMock, state: getStateContainer(), - updateAdHocDataViewId: jest.fn(), }); expect(savedObjectsPlugin.showSaveModal).toHaveBeenCalled(); @@ -55,7 +48,6 @@ describe('onSaveSearch', () => { saveModal = modal; }); await onSaveSearch({ - dataView: dataViewMock, navigateTo: jest.fn(), savedSearch: { ...savedSearchMock, @@ -63,7 +55,6 @@ describe('onSaveSearch', () => { }, services: discoverServiceMock, state: getStateContainer(), - updateAdHocDataViewId: jest.fn(), }); expect(saveModal?.props.tags).toEqual(['tag1', 'tag2']); }); @@ -77,21 +68,19 @@ describe('onSaveSearch', () => { ...savedSearchMock, tags: ['tag1', 'tag2'], }; + const state = getStateContainer(); await onSaveSearch({ - dataView: dataViewMock, navigateTo: jest.fn(), savedSearch, services: discoverServiceMock, - state: getStateContainer(), - updateAdHocDataViewId: jest.fn(), + state, }); expect(savedSearch.tags).toEqual(['tag1', 'tag2']); - jest - .spyOn(persistSavedSearchUtils, 'persistSavedSearch') - .mockImplementationOnce((newSavedSearch, _) => { - savedSearch = newSavedSearch; - return Promise.resolve({ id: newSavedSearch.id }); - }); + + state.savedSearchState.persist = jest.fn().mockImplementationOnce((newSavedSearch, _) => { + savedSearch = newSavedSearch; + return Promise.resolve(newSavedSearch.id); + }); saveModal?.props.onSave({ newTitle: savedSearch.title, newCopyOnSave: false, @@ -113,24 +102,21 @@ describe('onSaveSearch', () => { ...savedSearchMock, tags: ['tag1', 'tag2'], }; + const state = getStateContainer(); await onSaveSearch({ - dataView: dataViewMock, navigateTo: jest.fn(), savedSearch, services: { ...serviceMock, savedObjectsTagging: undefined, }, - state: getStateContainer(), - updateAdHocDataViewId: jest.fn(), + state, }); expect(savedSearch.tags).toEqual(['tag1', 'tag2']); - jest - .spyOn(persistSavedSearchUtils, 'persistSavedSearch') - .mockImplementationOnce((newSavedSearch, _) => { - savedSearch = newSavedSearch; - return Promise.resolve({ id: newSavedSearch.id }); - }); + state.savedSearchState.persist = jest.fn().mockImplementationOnce((newSavedSearch, _) => { + savedSearch = newSavedSearch; + return Promise.resolve(newSavedSearch.id); + }); saveModal?.props.onSave({ newTitle: savedSearch.title, newCopyOnSave: false, diff --git a/src/plugins/discover/public/application/main/components/top_nav/on_save_search.tsx b/src/plugins/discover/public/application/main/components/top_nav/on_save_search.tsx index 4d88e48b8adf5..9c3d792605ab3 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/on_save_search.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/on_save_search.tsx @@ -11,16 +11,12 @@ import { i18n } from '@kbn/i18n'; import { EuiFormRow, EuiSwitch } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { SavedObjectSaveModal, showSaveModal, OnSaveProps } from '@kbn/saved-objects-plugin/public'; -import { DataView } from '@kbn/data-views-plugin/public'; import { SavedSearch, SaveSavedSearchOptions } from '@kbn/saved-search-plugin/public'; import { DiscoverServices } from '../../../../build_services'; import { DiscoverStateContainer } from '../../services/discover_state'; -import { setBreadcrumbsTitle } from '../../../../utils/breadcrumbs'; -import { persistSavedSearch } from '../../utils/persist_saved_search'; import { DOC_TABLE_LEGACY } from '../../../../../common'; async function saveDataSource({ - dataView, navigateTo, savedSearch, saveOptions, @@ -28,7 +24,6 @@ async function saveDataSource({ state, navigateOrReloadSavedSearch, }: { - dataView: DataView; navigateTo: (url: string) => void; savedSearch: SavedSearch; saveOptions: SaveSavedSearchOptions; @@ -53,16 +48,7 @@ async function saveDataSource({ navigateTo(`/view/${encodeURIComponent(id)}`); } else { // Update defaults so that "reload saved query" functions correctly - state.appState.resetWithSavedSearch(savedSearch); - services.chrome.docTitle.change(savedSearch.title!); - - setBreadcrumbsTitle( - { - ...savedSearch, - id: prevSavedSearchId ?? id, - }, - services.chrome - ); + state.actions.undoChanges(); } } } @@ -79,32 +65,31 @@ async function saveDataSource({ text: error.message, }); } - return persistSavedSearch(savedSearch, { - dataView, - onError, - onSuccess, - saveOptions, - services, - state: state.appState.getState(), - }); + + try { + const nextSavedSearch = await state.savedSearchState.persist(savedSearch, saveOptions); + if (nextSavedSearch) { + onSuccess(nextSavedSearch.id!); + } + return nextSavedSearch; + } catch (e) { + onError(e); + return savedSearch; + } } export async function onSaveSearch({ - dataView, navigateTo, savedSearch, services, state, onClose, onSaveCb, - updateAdHocDataViewId, }: { - dataView: DataView; navigateTo: (path: string) => void; savedSearch: SavedSearch; services: DiscoverServices; state: DiscoverStateContainer; - updateAdHocDataViewId: (dataView: DataView) => Promise; onClose?: () => void; onSaveCb?: () => void; }) { @@ -146,21 +131,25 @@ export async function onSaveSearch({ isTitleDuplicateConfirmed, }; - const updatedDataView = - !dataView.isPersisted() && newCopyOnSave ? await updateAdHocDataViewId(dataView) : dataView; + if (newCopyOnSave) { + await state.actions.updateAdHocDataViewId(); + } const navigateOrReloadSavedSearch = !Boolean(onSaveCb); - const response = await saveDataSource({ - dataView: updatedDataView, - saveOptions, - services, - navigateTo, - savedSearch, - state, - navigateOrReloadSavedSearch, - }); - // If the save wasn't successful, put the original values back. - if (!response.id || response.error) { + try { + const response = await saveDataSource({ + saveOptions, + services, + navigateTo, + savedSearch, + state, + navigateOrReloadSavedSearch, + }); + onSaveCb?.(); + return response; + } catch (e) { + // If the save wasn't successful, put the original values back. + savedSearch.title = currentTitle; savedSearch.timeRestore = currentTimeRestore; savedSearch.rowsPerPage = currentRowsPerPage; @@ -168,11 +157,8 @@ export async function onSaveSearch({ if (savedObjectsTagging) { savedSearch.tags = currentTags; } - } else { state.appState.resetInitialState(); } - onSaveCb?.(); - return response; }; const saveModal = ( diff --git a/src/plugins/discover/public/application/main/components/top_nav/open_alerts_popover.test.tsx b/src/plugins/discover/public/application/main/components/top_nav/open_alerts_popover.test.tsx index 7412dfe599cbe..7a434a4673fa1 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/open_alerts_popover.test.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/open_alerts_popover.test.tsx @@ -9,30 +9,31 @@ import React, { ReactNode } from 'react'; import { mountWithIntl } from '@kbn/test-jest-helpers'; import { findTestSubject } from '@elastic/eui/lib/test'; -import { createSearchSourceMock } from '@kbn/data-plugin/public/mocks'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { AlertsPopover } from './open_alerts_popover'; import { discoverServiceMock } from '../../../../__mocks__/services'; import { dataViewWithTimefieldMock } from '../../../../__mocks__/data_view_with_timefield'; import { dataViewMock } from '../../../../__mocks__/data_view'; +import { getDiscoverStateMock } from '../../../../__mocks__/discover_state.mock'; const Context = ({ children }: { children: ReactNode }) => <>{children}; -const mount = (dataView = dataViewMock) => - mountWithIntl( +const mount = (dataView = dataViewMock) => { + const stateContainer = getDiscoverStateMock({ isTimeBased: true }); + stateContainer.actions.setDataView(dataView); + return mountWithIntl( ); +}; describe('OpenAlertsPopover', () => { it('should render with the create search threshold rule button disabled if the data view has no time field', () => { diff --git a/src/plugins/discover/public/application/main/components/top_nav/open_alerts_popover.tsx b/src/plugins/discover/public/application/main/components/top_nav/open_alerts_popover.tsx index 05eac2fb933b5..f722bc5558bee 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/open_alerts_popover.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/open_alerts_popover.tsx @@ -12,10 +12,10 @@ import type { Observable } from 'rxjs'; import type { CoreTheme, I18nStart } from '@kbn/core/public'; import { EuiWrappingPopover, EuiContextMenu } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import type { DataView, ISearchSource } from '@kbn/data-plugin/common'; +import type { DataView } from '@kbn/data-plugin/common'; import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; +import { DiscoverStateContainer } from '../../services/discover_state'; import { DiscoverServices } from '../../../../build_services'; -import { updateSearchSource } from '../../utils/update_search_source'; const container = document.createElement('div'); let isOpen = false; @@ -25,12 +25,11 @@ const ALERT_TYPE_ID = '.es-query'; interface AlertsPopoverProps { onClose: () => void; anchorElement: HTMLElement; - searchSource: ISearchSource; + stateContainer: DiscoverStateContainer; savedQueryId?: string; adHocDataViews: DataView[]; I18nContext: I18nStart['Context']; services: DiscoverServices; - updateDataViewList: (dataViews: DataView[]) => void; } interface EsQueryAlertMetaData { @@ -39,15 +38,13 @@ interface EsQueryAlertMetaData { } export function AlertsPopover({ - searchSource, anchorElement, - savedQueryId, adHocDataViews, services, + stateContainer, onClose: originalOnClose, - updateDataViewList, }: AlertsPopoverProps) { - const dataView = searchSource.getField('index')!; + const dataView = stateContainer.internalState.getState().dataView; const { triggersActionsUi } = services; const [alertFlyoutVisible, setAlertFlyoutVisibility] = useState(false); const onClose = useCallback(() => { @@ -59,20 +56,15 @@ export function AlertsPopover({ * Provides the default parameters used to initialize the new rule */ const getParams = useCallback(() => { - const nextSearchSource = searchSource.createCopy(); - updateSearchSource(nextSearchSource, true, { - dataView: searchSource.getField('index')!, - services, - sort: [], - useNewFieldsApi: true, - }); - + const savedQueryId = stateContainer.appState.getState().savedQuery; return { searchType: 'searchSource', - searchConfiguration: nextSearchSource.getSerializedFields(), + searchConfiguration: stateContainer.savedSearchState + .getState() + .searchSource.getSerializedFields(), savedQueryId, }; - }, [savedQueryId, searchSource, services]); + }, [stateContainer]); const discoverMetadata: EsQueryAlertMetaData = useMemo( () => ({ @@ -87,8 +79,9 @@ export function AlertsPopover({ return; } - const onFinishFlyoutInteraction = (metadata: EsQueryAlertMetaData) => { - updateDataViewList(metadata.adHocDataViewList); + const onFinishFlyoutInteraction = async (metadata: EsQueryAlertMetaData) => { + await stateContainer.actions.loadDataViewList(); + stateContainer.internalState.transitions.setAdHocDataViews(metadata.adHocDataViewList); }; return triggersActionsUi?.getAddRuleFlyout({ @@ -105,16 +98,9 @@ export function AlertsPopover({ ruleTypeId: ALERT_TYPE_ID, initialValues: { params: getParams() }, }); - }, [ - alertFlyoutVisible, - triggersActionsUi, - discoverMetadata, - getParams, - updateDataViewList, - onClose, - ]); - - const hasTimeFieldName = dataView.timeFieldName; + }, [alertFlyoutVisible, triggersActionsUi, discoverMetadata, getParams, onClose, stateContainer]); + + const hasTimeFieldName = Boolean(dataView?.timeFieldName); const panels = [ { id: 'mainPanel', @@ -180,20 +166,16 @@ export function openAlertsPopover({ I18nContext, theme$, anchorElement, - searchSource, + stateContainer, services, adHocDataViews, - savedQueryId, - updateDataViewList, }: { I18nContext: I18nStart['Context']; theme$: Observable; anchorElement: HTMLElement; - searchSource: ISearchSource; + stateContainer: DiscoverStateContainer; services: DiscoverServices; adHocDataViews: DataView[]; - savedQueryId?: string; - updateDataViewList: (dataViews: DataView[]) => void; }) { if (isOpen) { closeAlertsPopover(); @@ -210,12 +192,10 @@ export function openAlertsPopover({ diff --git a/src/plugins/discover/public/application/main/discover_main_app.test.tsx b/src/plugins/discover/public/application/main/discover_main_app.test.tsx index f3cc7f9cee43d..5f28befee9301 100644 --- a/src/plugins/discover/public/application/main/discover_main_app.test.tsx +++ b/src/plugins/discover/public/application/main/discover_main_app.test.tsx @@ -12,7 +12,6 @@ import { DataViewListItem } from '@kbn/data-views-plugin/public'; import { dataViewMock } from '../../__mocks__/data_view'; import { DiscoverMainApp } from './discover_main_app'; import { DiscoverTopNav } from './components/top_nav/discover_topnav'; -import { savedSearchMock } from '../../__mocks__/saved_search'; import { setHeaderActionMenuMounter, setUrlTracker } from '../../kibana_services'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { discoverServiceMock } from '../../__mocks__/services'; @@ -36,9 +35,9 @@ describe('DiscoverMainApp', () => { }) as unknown as DataViewListItem[]; const stateContainer = getDiscoverStateMock({ isTimeBased: true }); stateContainer.actions.setDataView(dataViewMock); + stateContainer.internalState.transitions.setSavedDataViews(dataViewList); const props = { - dataViewList, - savedSearch: savedSearchMock, + stateContainer, }; const history = createMemoryHistory({ initialEntries: ['/'], @@ -60,7 +59,6 @@ describe('DiscoverMainApp', () => { await component.update(); expect(component.find(DiscoverTopNav).exists()).toBe(true); - expect(component.find(DiscoverTopNav).prop('savedSearch')).toEqual(savedSearchMock); }); }); }); diff --git a/src/plugins/discover/public/application/main/discover_main_app.tsx b/src/plugins/discover/public/application/main/discover_main_app.tsx index 176fd2a3044af..9efc0c9d8a46e 100644 --- a/src/plugins/discover/public/application/main/discover_main_app.tsx +++ b/src/plugins/discover/public/application/main/discover_main_app.tsx @@ -7,32 +7,31 @@ */ import React, { useCallback, useEffect } from 'react'; import { useHistory } from 'react-router-dom'; -import { SavedSearch } from '@kbn/saved-search-plugin/public'; -import { DataViewListItem } from '@kbn/data-views-plugin/public'; +import { useUrlTracking } from './hooks/use_url_tracking'; +import { useSearchSession } from './hooks/use_search_session'; +import { DiscoverStateContainer } from './services/discover_state'; import { DiscoverLayout } from './components/layout'; import { setBreadcrumbsTitle } from '../../utils/breadcrumbs'; import { addHelpMenuToAppChrome } from '../../components/help_menu/help_menu_util'; -import { useDiscoverState } from './hooks/use_discover_state'; import { useUrl } from './hooks/use_url'; import { useDiscoverServices } from '../../hooks/use_discover_services'; import { useSavedSearchAliasMatchRedirect } from '../../hooks/saved_search_alias_match_redirect'; -import { DiscoverMainProvider } from './services/discover_state_provider'; +import { useSavedSearchInitial } from './services/discover_state_provider'; +import { useAdHocDataViews } from './hooks/use_adhoc_data_views'; +import { useTextBasedQueryLanguage } from './hooks/use_text_based_query_language'; const DiscoverLayoutMemoized = React.memo(DiscoverLayout); export interface DiscoverMainProps { /** - * List of available data views + * Central state container */ - dataViewList: DataViewListItem[]; - /** - * Current instance of SavedSearch - */ - savedSearch: SavedSearch; + stateContainer: DiscoverStateContainer; } export function DiscoverMainApp(props: DiscoverMainProps) { - const { savedSearch, dataViewList } = props; + const { stateContainer } = props; + const savedSearch = useSavedSearchInitial(); const services = useDiscoverServices(); const { chrome, docLinks, data, spaces, history } = services; const usedHistory = useHistory(); @@ -43,78 +42,66 @@ export function DiscoverMainApp(props: DiscoverMainProps) { [usedHistory] ); + useUrlTracking(stateContainer.savedSearchState); + /** - * State related logic + * Search session logic */ - const { - inspectorAdapters, - onChangeDataView, - onUpdateQuery, - persistDataView, - updateAdHocDataViewId, - resetSavedSearch, - searchSource, + useSearchSession({ services, stateContainer }); + + /** + * Adhoc data views functionality + */ + const { persistDataView } = useAdHocDataViews({ stateContainer, - updateDataViewList, - } = useDiscoverState({ - services, - history: usedHistory, - savedSearch, + trackUiMetric: services.trackUiMetric, }); /** - * Url / Routing logic + * State changes (data view, columns), when a text base query result is returned */ - useUrl({ history: usedHistory, resetSavedSearch }); - + useTextBasedQueryLanguage({ + dataViews: services.dataViews, + stateContainer, + }); /** - * SavedSearch depended initializing + * Start state syncing and fetch data if necessary */ useEffect(() => { - const pageTitleSuffix = savedSearch.id && savedSearch.title ? `: ${savedSearch.title}` : ''; - chrome.docTitle.change(`Discover${pageTitleSuffix}`); - setBreadcrumbsTitle(savedSearch, chrome); - return () => { - data.search.session.clear(); - }; - }, [savedSearch, chrome, data]); + const unsubscribe = stateContainer.actions.initializeAndSync(); + stateContainer.actions.fetchData(true); + return () => unsubscribe(); + }, [stateContainer]); /** - * Initializing syncing with state and help menu + * Url / Routing logic */ - useEffect(() => { - addHelpMenuToAppChrome(chrome, docLinks); - }, [stateContainer, chrome, docLinks]); + useUrl({ history: usedHistory, stateContainer }); /** - * Set initial data view list - * Can be removed once the state container work was completed + * SavedSearch dependend initializing */ useEffect(() => { - stateContainer.internalState.transitions.setSavedDataViews(dataViewList); - }, [stateContainer, dataViewList]); + const pageTitleSuffix = savedSearch.id && savedSearch.title ? `: ${savedSearch.title}` : ''; + chrome.docTitle.change(`Discover${pageTitleSuffix}`); + setBreadcrumbsTitle(savedSearch.title, chrome); + }, [savedSearch.id, savedSearch.title, chrome, data]); - const resetCurrentSavedSearch = useCallback(() => { - resetSavedSearch(savedSearch.id); - }, [resetSavedSearch, savedSearch]); + useEffect(() => { + addHelpMenuToAppChrome(chrome, docLinks); + return () => { + // clear session when navigating away from discover main + data.search.session.clear(); + }; + }, [data.search.session, chrome, docLinks]); useSavedSearchAliasMatchRedirect({ savedSearch, spaces, history }); return ( - - - + ); } diff --git a/src/plugins/discover/public/application/main/discover_main_route.test.tsx b/src/plugins/discover/public/application/main/discover_main_route.test.tsx index ae3034896c268..1e2368ed786bf 100644 --- a/src/plugins/discover/public/application/main/discover_main_route.test.tsx +++ b/src/plugins/discover/public/application/main/discover_main_route.test.tsx @@ -96,7 +96,7 @@ function getServicesMock(hasESData = true, hasUserDataView = true) { const fields: Record = {}; const empty = { ...searchSourceInstanceMock, - setField: (key: string, value: unknown) => (fields[key] = value), + setField: jest.fn((key: string, value: unknown) => (fields[key] = value)).mockReturnThis(), getField: (key: string) => fields[key], }; return empty as unknown as SearchSource; diff --git a/src/plugins/discover/public/application/main/discover_main_route.tsx b/src/plugins/discover/public/application/main/discover_main_route.tsx index 54e90c01b24f3..088cbcdcbe406 100644 --- a/src/plugins/discover/public/application/main/discover_main_route.tsx +++ b/src/plugins/discover/public/application/main/discover_main_route.tsx @@ -7,32 +7,26 @@ */ import React, { useEffect, useState, memo, useCallback, useMemo } from 'react'; import { useParams, useHistory } from 'react-router-dom'; -import { DataViewListItem } from '@kbn/data-plugin/public'; -import { isOfAggregateQueryType } from '@kbn/es-query'; -import { DataViewSavedObjectConflictError, type DataView } from '@kbn/data-views-plugin/public'; -import { redirectWhenMissing } from '@kbn/kibana-utils-plugin/public'; +import type { DataView } from '@kbn/data-views-plugin/public'; +import { redirectWhenMissing, SavedObjectNotFound } from '@kbn/kibana-utils-plugin/public'; import { useExecutionContext } from '@kbn/kibana-react-plugin/public'; import { AnalyticsNoDataPageKibanaProvider, AnalyticsNoDataPage, } from '@kbn/shared-ux-page-analytics-no-data'; -import { - SavedSearch, - getSavedSearch, - getSavedSearchFullPathUrl, -} from '@kbn/saved-search-plugin/public'; +import { getSavedSearchFullPathUrl } from '@kbn/saved-search-plugin/public'; import useObservable from 'react-use/lib/useObservable'; +import { useSingleton } from './hooks/use_singleton'; import { MainHistoryLocationState } from '../../../common/locator'; -import { getDiscoverStateContainer } from './services/discover_state'; -import { loadDataView, resolveDataView } from './utils/resolve_data_view'; +import { DiscoverStateContainer, getDiscoverStateContainer } from './services/discover_state'; import { DiscoverMainApp } from './discover_main_app'; import { getRootBreadcrumbs, getSavedSearchBreadcrumbs } from '../../utils/breadcrumbs'; import { LoadingIndicator } from '../../components/common/loading_indicator'; import { DiscoverError } from '../../components/common/error_alert'; import { useDiscoverServices } from '../../hooks/use_discover_services'; import { getScopedHistory, getUrlTracker } from '../../kibana_services'; -import { restoreStateFromSavedSearch } from '../../services/saved_searches/restore_from_saved_search'; import { useAlertResultsToast } from './hooks/use_alert_results_toast'; +import { DiscoverMainProvider } from './services/discover_state_provider'; const DiscoverMainAppMemoized = memo(DiscoverMainApp); @@ -51,21 +45,24 @@ export function DiscoverMainRoute(props: Props) { const { core, chrome, - uiSettings: config, data, toastNotifications, http: { basePath }, dataViewEditor, } = services; + const { id } = useParams(); + const stateContainer = useSingleton(() => + getDiscoverStateContainer({ + history, + services, + }) + ); const [error, setError] = useState(); const [loading, setLoading] = useState(true); - const [savedSearch, setSavedSearch] = useState(); - const [dataViewList, setDataViewList] = useState([]); const [hasESData, setHasESData] = useState(false); const [hasUserDataView, setHasUserDataView] = useState(false); const [showNoDataPage, setShowNoDataPage] = useState(false); const hasCustomBranding = useObservable(core.customBranding.hasCustomBranding$, false); - const { id } = useParams(); /** * Get location state of scoped history only on initial load @@ -86,115 +83,69 @@ export function DiscoverMainRoute(props: Props) { id: id || 'new', }); - const loadDefaultOrCurrentDataView = useCallback( - async (nextSavedSearch: SavedSearch) => { - try { - const hasUserDataViewValue = await data.dataViews.hasData - .hasUserDataView() - .catch(() => false); - const hasESDataValue = - isDev || (await data.dataViews.hasData.hasESData().catch(() => false)); - setHasUserDataView(hasUserDataViewValue); - setHasESData(hasESDataValue); - - if (!hasUserDataViewValue) { - setShowNoDataPage(true); - return; - } - - let defaultDataView: DataView | null = null; - try { - defaultDataView = await data.dataViews.getDefaultDataView({ displayErrors: false }); - } catch (e) { - // - } - - if (!defaultDataView) { - setShowNoDataPage(true); - return; - } - - const { appState } = getDiscoverStateContainer({ - history, - savedSearch: nextSavedSearch, - services, - }); - const { index, query } = appState.getState(); - const ip = await loadDataView( - data.dataViews, - config, - index, - historyLocationState?.dataViewSpec - ); - - const ipList = ip.list; - const isTextBasedQuery = query && isOfAggregateQueryType(query); - const dataViewData = resolveDataView( - ip, - nextSavedSearch.searchSource, - toastNotifications, - isTextBasedQuery - ); - setDataViewList(ipList); + const checkData = useCallback(async () => { + try { + const hasUserDataViewValue = await data.dataViews.hasData + .hasUserDataView() + .catch(() => false); + const hasESDataValue = isDev || (await data.dataViews.hasData.hasESData().catch(() => false)); + setHasUserDataView(hasUserDataViewValue); + setHasESData(hasESDataValue); + + if (!hasUserDataViewValue) { + setShowNoDataPage(true); + return false; + } - return dataViewData; + let defaultDataView: DataView | null = null; + try { + defaultDataView = await data.dataViews.getDefaultDataView({ displayErrors: false }); } catch (e) { - setError(e); + // } - }, - [ - config, - data.dataViews, - history, - isDev, - historyLocationState?.dataViewSpec, - toastNotifications, - services, - ] - ); + + if (!defaultDataView) { + setShowNoDataPage(true); + return false; + } + return true; + } catch (e) { + setError(e); + return false; + } + }, [data.dataViews, isDev]); const loadSavedSearch = useCallback( async (nextDataView?: DataView) => { + setLoading(true); + if (!nextDataView && !(await checkData())) { + setLoading(false); + return; + } try { - setLoading(true); - const currentSavedSearch = await getSavedSearch(id, { - search: services.data.search, - savedObjectsClient: core.savedObjects.client, - spaces: services.spaces, - savedObjectsTagging: services.savedObjectsTagging, - }); - - const currentDataView = nextDataView - ? nextDataView - : await loadDefaultOrCurrentDataView(currentSavedSearch); - - if (!currentDataView) { - return; - } - - if (!currentSavedSearch.searchSource.getField('index')) { - currentSavedSearch.searchSource.setField('index', currentDataView); - } - - restoreStateFromSavedSearch({ - savedSearch: currentSavedSearch, - timefilter: services.timefilter, - }); - - setSavedSearch(currentSavedSearch); - - if (currentSavedSearch.id) { + await stateContainer.actions.loadDataViewList(); + const currentSavedSearch = await stateContainer.actions.loadSavedSearch( + id, + nextDataView, + historyLocationState?.dataViewSpec + ); + if (currentSavedSearch?.id) { chrome.recentlyAccessed.add( getSavedSearchFullPathUrl(currentSavedSearch.id), currentSavedSearch.title ?? '', currentSavedSearch.id ); } + + chrome.setBreadcrumbs( + currentSavedSearch && currentSavedSearch.title + ? getSavedSearchBreadcrumbs(currentSavedSearch.title) + : getRootBreadcrumbs() + ); + setLoading(false); } catch (e) { - if (e instanceof DataViewSavedObjectConflictError) { - setError(e); - } else { + if (e instanceof SavedObjectNotFound) { redirectWhenMissing({ history, navigateToApp: core.application.navigateToApp, @@ -212,21 +163,20 @@ export function DiscoverMainRoute(props: Props) { }, theme: core.theme, })(e); + } else { + setError(e); } } }, [ + checkData, + stateContainer.actions, id, - services.data, - services.spaces, - services.timefilter, - services.savedObjectsTagging, - core.savedObjects.client, + historyLocationState?.dataViewSpec, + chrome, + history, core.application.navigateToApp, core.theme, - loadDefaultOrCurrentDataView, - chrome.recentlyAccessed, - history, basePath, toastNotifications, ] @@ -246,15 +196,7 @@ export function DiscoverMainRoute(props: Props) { useEffect(() => { loadSavedSearch(); - }, [loadSavedSearch]); - - useEffect(() => { - chrome.setBreadcrumbs( - savedSearch && savedSearch.title - ? getSavedSearchBreadcrumbs(savedSearch.title) - : getRootBreadcrumbs() - ); - }, [chrome, savedSearch]); + }, [loadSavedSearch, id]); if (showNoDataPage) { const analyticsServices = { @@ -284,9 +226,13 @@ export function DiscoverMainRoute(props: Props) { return ; } - if (loading || !savedSearch) { + if (loading) { return ; } - return ; + return ( + + + + ); } diff --git a/src/plugins/discover/public/application/main/hooks/use_adhoc_data_views.test.tsx b/src/plugins/discover/public/application/main/hooks/use_adhoc_data_views.test.tsx index 770373f74825f..570603ebcad9b 100644 --- a/src/plugins/discover/public/application/main/hooks/use_adhoc_data_views.test.tsx +++ b/src/plugins/discover/public/application/main/hooks/use_adhoc_data_views.test.tsx @@ -7,10 +7,8 @@ */ import React from 'react'; -import { createSearchSourceMock } from '@kbn/data-plugin/public/mocks'; import type { DataView } from '@kbn/data-views-plugin/public'; -import { act, renderHook } from '@testing-library/react-hooks'; -import { discoverServiceMock as mockDiscoverServices } from '../../../__mocks__/services'; +import { renderHook } from '@testing-library/react-hooks'; import { useAdHocDataViews } from './use_adhoc_data_views'; import * as persistencePromptModule from '../../../hooks/use_confirm_persistence_prompt'; import { urlTrackerMock } from '../../../__mocks__/url_tracker.mock'; @@ -65,27 +63,20 @@ const mockDataView = { isPersisted: () => false, getName: () => 'mock-data-view', toSpec: () => ({}), + isTimeBased: () => true, } as DataView; -const savedSearchMock = { - id: 'some-id', - searchSource: createSearchSourceMock({ index: mockDataView }), -}; - describe('useAdHocDataViews', () => { it('should save data view with new id and update saved search', async () => { - const stateContainer = getDiscoverStateMock({ isTimeBased: true }); + const stateContainer = getDiscoverStateMock({ + isTimeBased: true, + }); + stateContainer.actions.setDataView(mockDataView); const hook = renderHook( () => useAdHocDataViews({ - dataView: mockDataView, - savedSearch: savedSearchMock, stateContainer, - setUrlTracking: jest.fn(), - dataViews: mockDiscoverServices.dataViews, - filterManager: mockDiscoverServices.filterManager, - toastNotifications: mockDiscoverServices.toastNotifications, }), { wrapper: ({ children }: { children: React.ReactElement }) => ( @@ -101,38 +92,4 @@ describe('useAdHocDataViews', () => { expect(updateSavedSearchCall[0].dataView.id).toEqual('updated-mock-id'); expect(savedDataView!.id).toEqual('updated-mock-id'); }); - - it('should update id of adhoc data view correctly', async () => { - const dataViewsCreateMock = mockDiscoverServices.dataViews.create as jest.Mock; - dataViewsCreateMock.mockImplementation(() => ({ - ...mockDataView, - id: 'updated-mock-id', - })); - const stateContainer = getDiscoverStateMock({ isTimeBased: true }); - const hook = renderHook( - () => - useAdHocDataViews({ - dataView: mockDataView, - savedSearch: savedSearchMock, - stateContainer: getDiscoverStateMock({ isTimeBased: true }), - setUrlTracking: jest.fn(), - dataViews: mockDiscoverServices.dataViews, - filterManager: mockDiscoverServices.filterManager, - toastNotifications: mockDiscoverServices.toastNotifications, - }), - { - wrapper: ({ children }: { children: React.ReactElement }) => ( - {children} - ), - } - ); - - let updatedDataView: DataView; - await act(async () => { - updatedDataView = await hook.result.current.updateAdHocDataViewId(mockDataView); - }); - - expect(mockDiscoverServices.dataViews.clearInstanceCache).toHaveBeenCalledWith(mockDataView.id); - expect(updatedDataView!.id).toEqual('updated-mock-id'); - }); }); diff --git a/src/plugins/discover/public/application/main/hooks/use_adhoc_data_views.ts b/src/plugins/discover/public/application/main/hooks/use_adhoc_data_views.ts index 31e96f01a2b74..b8ef0d54b0a29 100644 --- a/src/plugins/discover/public/application/main/hooks/use_adhoc_data_views.ts +++ b/src/plugins/discover/public/application/main/hooks/use_adhoc_data_views.ts @@ -7,80 +7,40 @@ */ import { useCallback, useEffect } from 'react'; -import { v4 as uuidv4 } from 'uuid'; -import type { DataView, DataViewsContract } from '@kbn/data-views-plugin/public'; -import { SavedSearch } from '@kbn/saved-search-plugin/public'; -import type { FilterManager } from '@kbn/data-plugin/public'; -import type { ToastsStart } from '@kbn/core-notifications-browser'; import { METRIC_TYPE } from '@kbn/analytics'; +import { isOfAggregateQueryType } from '@kbn/es-query'; import { ADHOC_DATA_VIEW_RENDER_EVENT } from '../../../constants'; import { useConfirmPersistencePrompt } from '../../../hooks/use_confirm_persistence_prompt'; import { DiscoverStateContainer } from '../services/discover_state'; -import { useFiltersValidation } from './use_filters_validation'; -import { updateFiltersReferences } from '../utils/update_filter_references'; export const useAdHocDataViews = ({ - dataView, - savedSearch, stateContainer, - setUrlTracking, - filterManager, - dataViews, - toastNotifications, trackUiMetric, - isTextBasedMode, }: { - dataView: DataView; - savedSearch: SavedSearch; stateContainer: DiscoverStateContainer; - setUrlTracking: (dataView: DataView) => void; - dataViews: DataViewsContract; - filterManager: FilterManager; - toastNotifications: ToastsStart; trackUiMetric?: (metricType: string, eventName: string | string[], count?: number) => void; - isTextBasedMode?: boolean; }) => { + const query = stateContainer.appState.getState().query; + const isTextBasedMode = query && isOfAggregateQueryType(query); + const dataView = stateContainer.internalState.getState().dataView; + useEffect(() => { - if (!dataView.isPersisted()) { + if (dataView && !dataView.isPersisted()) { trackUiMetric?.(METRIC_TYPE.COUNT, ADHOC_DATA_VIEW_RENDER_EVENT); } }, [dataView, isTextBasedMode, trackUiMetric]); - /** - * Takes care of checking data view id references in filters - */ - useFiltersValidation({ savedSearch, filterManager, toastNotifications }); - - /** - * When saving a saved search with an ad hoc data view, a new id needs to be generated for the data view - * This is to prevent duplicate ids messing with our system - */ - const updateAdHocDataViewId = useCallback( - async (prevDataView: DataView) => { - const newDataView = await dataViews.create({ ...prevDataView.toSpec(), id: uuidv4() }); - dataViews.clearInstanceCache(prevDataView.id); - - updateFiltersReferences(prevDataView, newDataView); - - stateContainer.actions.replaceAdHocDataViewWithId(prevDataView.id!, newDataView); - await stateContainer.appState.update({ index: newDataView.id }, true); - - setUrlTracking(newDataView); - return newDataView; - }, - [dataViews, setUrlTracking, stateContainer] - ); - const { openConfirmSavePrompt, updateSavedSearch } = useConfirmPersistencePrompt(stateContainer); const persistDataView = useCallback(async () => { - const currentDataView = savedSearch.searchSource.getField('index')!; + const currentDataView = stateContainer.internalState.getState().dataView; + const savedSearch = stateContainer.savedSearchState.getState(); if (!currentDataView || currentDataView.isPersisted()) { return currentDataView; } const createdDataView = await openConfirmSavePrompt(currentDataView); if (!createdDataView) { - return currentDataView; // persistance cancelled + return; // persistance cancelled } if (savedSearch.id) { @@ -90,7 +50,7 @@ export const useAdHocDataViews = ({ } return createdDataView; - }, [stateContainer, openConfirmSavePrompt, savedSearch, updateSavedSearch]); + }, [stateContainer, openConfirmSavePrompt, updateSavedSearch]); - return { persistDataView, updateAdHocDataViewId }; + return { persistDataView }; }; diff --git a/src/plugins/discover/public/application/main/hooks/use_discover_state.test.tsx b/src/plugins/discover/public/application/main/hooks/use_discover_state.test.tsx deleted file mode 100644 index c590722d98bac..0000000000000 --- a/src/plugins/discover/public/application/main/hooks/use_discover_state.test.tsx +++ /dev/null @@ -1,43 +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 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 or the Server - * Side Public License, v 1. - */ -import React from 'react'; -import { renderHook } from '@testing-library/react-hooks'; -import { SearchSource } from '@kbn/data-plugin/public'; -import { createSearchSessionMock } from '../../../__mocks__/search_session'; -import { discoverServiceMock } from '../../../__mocks__/services'; -import { savedSearchMock } from '../../../__mocks__/saved_search'; -import { useDiscoverState } from './use_discover_state'; -import { setUrlTracker } from '../../../kibana_services'; -import { urlTrackerMock } from '../../../__mocks__/url_tracker.mock'; -import { DiscoverMainProvider } from '../services/discover_state_provider'; -import { getDiscoverStateMock } from '../../../__mocks__/discover_state.mock'; -setUrlTracker(urlTrackerMock); - -describe('test useDiscoverState', () => { - test('return is valid', async () => { - const { history } = createSearchSessionMock(); - const stateContainer = getDiscoverStateMock({ isTimeBased: true }); - - const { result } = renderHook( - () => { - return useDiscoverState({ - services: discoverServiceMock, - history, - savedSearch: savedSearchMock, - }); - }, - { - wrapper: ({ children }: { children: React.ReactElement }) => ( - {children} - ), - } - ); - expect(result.current.stateContainer).toBeInstanceOf(Object); - expect(result.current.searchSource).toBeInstanceOf(SearchSource); - }); -}); diff --git a/src/plugins/discover/public/application/main/hooks/use_discover_state.ts b/src/plugins/discover/public/application/main/hooks/use_discover_state.ts deleted file mode 100644 index 00d30e74fadbc..0000000000000 --- a/src/plugins/discover/public/application/main/hooks/use_discover_state.ts +++ /dev/null @@ -1,228 +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 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 or the Server - * Side Public License, v 1. - */ -import { useMemo, useEffect, useState, useCallback } from 'react'; -import { History } from 'history'; -import { isOfAggregateQueryType } from '@kbn/es-query'; -import { type DataView, DataViewType } from '@kbn/data-views-plugin/public'; -import { SavedSearch, getSavedSearch } from '@kbn/saved-search-plugin/public'; -import { buildStateSubscribe } from './utils/build_state_subscribe'; -import { changeDataView } from './utils/change_data_view'; -import { useSearchSession } from './use_search_session'; -import { FetchStatus } from '../../types'; -import { useTextBasedQueryLanguage } from './use_text_based_query_language'; -import { useUrlTracking } from './use_url_tracking'; -import { getDiscoverStateContainer } from '../services/discover_state'; -import { getStateDefaults } from '../utils/get_state_defaults'; -import { DiscoverServices } from '../../../build_services'; -import { restoreStateFromSavedSearch } from '../../../services/saved_searches/restore_from_saved_search'; -import { useAdHocDataViews } from './use_adhoc_data_views'; - -export function useDiscoverState({ - services, - history, - savedSearch, -}: { - services: DiscoverServices; - savedSearch: SavedSearch; - history: History; -}) { - const { data, filterManager, dataViews, toastNotifications, trackUiMetric } = services; - - const dataView = savedSearch.searchSource.getField('index')!; - - const searchSource = useMemo(() => { - savedSearch.searchSource.setField('index', dataView); - return savedSearch.searchSource.createChild(); - }, [savedSearch, dataView]); - - const stateContainer = useMemo(() => { - const container = getDiscoverStateContainer({ - history, - savedSearch, - services, - }); - const nextDataView = savedSearch.searchSource.getField('index')!; - container.actions.setDataView(nextDataView); - if (!nextDataView.isPersisted()) { - container.actions.appendAdHocDataViews(nextDataView); - } - return container; - }, [history, savedSearch, services]); - - const { setUrlTracking } = useUrlTracking(savedSearch, dataView); - - const { appState, searchSessionManager } = stateContainer; - - const [state, setState] = useState(appState.getState()); - - /** - * Search session logic - */ - useSearchSession({ services, stateContainer, savedSearch }); - - /** - * Adhoc data views functionality - */ - const isTextBasedMode = state?.query && isOfAggregateQueryType(state?.query); - const { persistDataView, updateAdHocDataViewId } = useAdHocDataViews({ - dataView, - dataViews, - stateContainer, - savedSearch, - setUrlTracking, - filterManager, - toastNotifications, - trackUiMetric, - isTextBasedMode, - }); - - /** - * Updates data views selector state - */ - const updateDataViewList = useCallback( - async (newAdHocDataViews: DataView[]) => { - await stateContainer.actions.loadDataViewList(); - stateContainer.actions.setAdHocDataViews(newAdHocDataViews); - }, - [stateContainer.actions] - ); - - /** - * Data fetching logic - */ - const { data$, refetch$, reset, inspectorAdapters, initialFetchStatus } = - stateContainer.dataState; - /** - * State changes (data view, columns), when a text base query result is returned - */ - useTextBasedQueryLanguage({ - documents$: data$.documents$, - dataViews, - stateContainer, - savedSearch, - }); - - /** - * Reset to display loading spinner when savedSearch is changing - */ - useEffect(() => reset(), [savedSearch.id, reset]); - - /** - * Sync URL state with local app state on saved search load - * or dataView / savedSearch switch - */ - useEffect(() => { - const stopSync = stateContainer.actions.initializeAndSync(dataView, filterManager, data); - setState(stateContainer.appState.getState()); - - return () => stopSync(); - }, [stateContainer, filterManager, data, dataView]); - - /** - * Data store subscribing to trigger fetching - */ - useEffect(() => { - const stopSync = stateContainer.dataState.subscribe(); - return () => stopSync(); - }, [stateContainer]); - - /** - * Track state changes that should trigger a fetch - */ - useEffect(() => { - const unsubscribe = appState.subscribe( - buildStateSubscribe({ stateContainer, savedSearch, setState }) - ); - return () => unsubscribe(); - }, [appState, savedSearch, services, stateContainer]); - - /** - * Function triggered when user changes data view in the sidebar - */ - const onChangeDataView = useCallback( - async (id: string) => { - await changeDataView(id, { services, discoverState: stateContainer, setUrlTracking }); - stateContainer.internalState.transitions.setExpandedDoc(undefined); - }, - [services, setUrlTracking, stateContainer] - ); - - /** - * function to revert any changes to a given saved search - */ - const resetSavedSearch = useCallback( - async (id?: string) => { - const newSavedSearch = await getSavedSearch(id, { - search: services.data.search, - savedObjectsClient: services.core.savedObjects.client, - spaces: services.spaces, - savedObjectsTagging: services.savedObjectsTagging, - }); - - const newDataView = newSavedSearch.searchSource.getField('index') || dataView; - newSavedSearch.searchSource.setField('index', newDataView); - const newAppState = getStateDefaults({ - savedSearch: newSavedSearch, - services, - }); - - restoreStateFromSavedSearch({ - savedSearch: newSavedSearch, - timefilter: services.timefilter, - }); - - await stateContainer.appState.update(newAppState, true); - setState(newAppState); - }, - [services, dataView, stateContainer] - ); - - /** - * Function triggered when the user changes the query in the search bar - */ - const onUpdateQuery = useCallback( - (_payload, isUpdate?: boolean) => { - if (isUpdate === false) { - searchSessionManager.removeSearchSessionIdFromURL({ replace: false }); - refetch$.next(undefined); - } - }, - [refetch$, searchSessionManager] - ); - - /** - * Trigger data fetching on dataView or savedSearch changes - */ - useEffect(() => { - if (dataView && initialFetchStatus === FetchStatus.LOADING) { - refetch$.next(undefined); - } - }, [initialFetchStatus, refetch$, dataView, savedSearch.id]); - - /** - * We need to make sure the auto refresh interval is disabled for - * non-time series data or rollups since we don't show the date picker - */ - useEffect(() => { - if (dataView && (!dataView.isTimeBased() || dataView.type === DataViewType.ROLLUP)) { - stateContainer.actions.pauseAutoRefreshInterval(); - } - }, [dataView, stateContainer]); - - return { - inspectorAdapters, - resetSavedSearch, - onChangeDataView, - onUpdateQuery, - searchSource, - stateContainer, - persistDataView, - updateAdHocDataViewId, - updateDataViewList, - }; -} diff --git a/src/plugins/discover/public/application/main/hooks/use_filters_validation.ts b/src/plugins/discover/public/application/main/hooks/use_filters_validation.ts deleted file mode 100644 index f093edbd7c65b..0000000000000 --- a/src/plugins/discover/public/application/main/hooks/use_filters_validation.ts +++ /dev/null @@ -1,56 +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 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 or the Server - * Side Public License, v 1. - */ - -import { IToasts, ToastsStart } from '@kbn/core/public'; -import { FilterManager } from '@kbn/data-plugin/public'; -import { i18n } from '@kbn/i18n'; -import { SavedSearch } from '@kbn/saved-search-plugin/public'; -import { useEffect } from 'react'; -import { debounceTime } from 'rxjs'; - -const addInvalidFiltersWarn = (toastNotifications: IToasts) => { - const warningTitle = i18n.translate('discover.invalidFiltersWarnToast.title', { - defaultMessage: 'Different index references', - }); - toastNotifications.addWarning({ - title: warningTitle, - text: i18n.translate('discover.invalidFiltersWarnToast.description', { - defaultMessage: - 'Data view id references in some of the applied filters differ from the current data view.', - }), - 'data-test-subj': 'invalidFiltersWarnToast', - }); -}; - -export const useFiltersValidation = ({ - savedSearch, - filterManager, - toastNotifications, -}: { - savedSearch: SavedSearch; - filterManager: FilterManager; - toastNotifications: ToastsStart; -}) => { - useEffect(() => { - const subscription = filterManager - .getUpdates$() - .pipe(debounceTime(500)) - .subscribe(() => { - const currentFilters = filterManager.getFilters(); - const dataView = savedSearch.searchSource.getField('index'); - const areFiltersInvalid = - dataView && - !dataView.isPersisted() && - !currentFilters.every((current) => current.meta.index === dataView.id); - if (areFiltersInvalid) { - addInvalidFiltersWarn(toastNotifications); - } - }); - return () => subscription.unsubscribe(); - }, [filterManager, savedSearch.searchSource, toastNotifications]); -}; diff --git a/src/plugins/discover/public/application/main/hooks/use_inspector.test.ts b/src/plugins/discover/public/application/main/hooks/use_inspector.test.ts index 7f30ef8c51b47..57e94aa7a0db1 100644 --- a/src/plugins/discover/public/application/main/hooks/use_inspector.test.ts +++ b/src/plugins/discover/public/application/main/hooks/use_inspector.test.ts @@ -8,7 +8,6 @@ import { act, renderHook } from '@testing-library/react-hooks'; import { discoverServiceMock } from '../../../__mocks__/services'; -import { savedSearchMock } from '../../../__mocks__/saved_search'; import { useInspector } from './use_inspector'; import { Adapters, RequestAdapter } from '@kbn/inspector-plugin/common'; import { OverlayRef } from '@kbn/core/public'; @@ -29,15 +28,14 @@ describe('test useInspector', () => { stateContainer.internalState.transitions.setExpandedDoc({} as unknown as DataTableRecord); const { result } = renderHook(() => { return useInspector({ - inspectorAdapters: { requests, lensRequests }, - savedSearch: savedSearchMock, - inspector: discoverServiceMock.inspector, stateContainer, + inspector: discoverServiceMock.inspector, }); }); await act(async () => { result.current(); }); + expect(discoverServiceMock.inspector.open).toHaveBeenCalled(); expect(adapters?.requests).toBeInstanceOf(AggregateRequestAdapter); expect(adapters?.requests?.getRequests()).toEqual([ diff --git a/src/plugins/discover/public/application/main/hooks/use_inspector.ts b/src/plugins/discover/public/application/main/hooks/use_inspector.ts index 2d4179801faa0..45120d524144f 100644 --- a/src/plugins/discover/public/application/main/hooks/use_inspector.ts +++ b/src/plugins/discover/public/application/main/hooks/use_inspector.ts @@ -12,7 +12,6 @@ import { RequestAdapter, Start as InspectorPublicPluginStart, } from '@kbn/inspector-plugin/public'; -import { SavedSearch } from '@kbn/saved-search-plugin/public'; import { DiscoverStateContainer } from '../services/discover_state'; import { AggregateRequestAdapter } from '../utils/aggregate_request_adapter'; @@ -24,11 +23,7 @@ export interface InspectorAdapters { export function useInspector({ inspector, stateContainer, - inspectorAdapters, - savedSearch, }: { - inspectorAdapters: InspectorAdapters; - savedSearch: SavedSearch; inspector: InspectorPublicPluginStart; stateContainer: DiscoverStateContainer; }) { @@ -37,6 +32,7 @@ export function useInspector({ const onOpenInspector = useCallback(() => { // prevent overlapping stateContainer.internalState.transitions.setExpandedDoc(undefined); + const inspectorAdapters = stateContainer.dataState.inspectorAdapters; const requestAdapters = inspectorAdapters.lensRequests ? [inspectorAdapters.requests, inspectorAdapters.lensRequests] @@ -44,17 +40,11 @@ export function useInspector({ const session = inspector.open( { requests: new AggregateRequestAdapter(requestAdapters) }, - { title: savedSearch.title } + { title: stateContainer.savedSearchState.getTitle() } ); setInspectorSession(session); - }, [ - stateContainer, - inspectorAdapters.lensRequests, - inspectorAdapters.requests, - inspector, - savedSearch.title, - ]); + }, [stateContainer, inspector]); useEffect(() => { return () => { diff --git a/src/plugins/discover/public/application/main/hooks/use_saved_search_messages.ts b/src/plugins/discover/public/application/main/hooks/use_saved_search_messages.ts index 7be14ca9c802e..060b12053c870 100644 --- a/src/plugins/discover/public/application/main/hooks/use_saved_search_messages.ts +++ b/src/plugins/discover/public/application/main/hooks/use_saved_search_messages.ts @@ -15,6 +15,7 @@ import type { DataTotalHits$, SavedSearchData, } from '../services/discover_data_state_container'; +import { RecordRawType } from '../services/discover_data_state_container'; /** * Sends COMPLETE message to the main$ observable with the information * that no documents have been found, allowing Discover to show a no @@ -86,8 +87,11 @@ export function sendErrorMsg(data$: DataMain$ | DataDocuments$ | DataTotalHits$, * Sends a RESET message to all data subjects * Needed when data view is switched or a new runtime field is added */ -export function sendResetMsg(data: SavedSearchData, initialFetchStatus: FetchStatus) { - const recordRawType = data.main$.getValue().recordRawType; +export function sendResetMsg( + data: SavedSearchData, + initialFetchStatus: FetchStatus, + recordRawType: RecordRawType +) { data.main$.next({ fetchStatus: initialFetchStatus, foundDocuments: undefined, diff --git a/src/plugins/discover/public/application/main/hooks/use_search_session.test.ts b/src/plugins/discover/public/application/main/hooks/use_search_session.test.ts index 4f483a3e01c31..0e2606c063f75 100644 --- a/src/plugins/discover/public/application/main/hooks/use_search_session.test.ts +++ b/src/plugins/discover/public/application/main/hooks/use_search_session.test.ts @@ -8,18 +8,14 @@ import { useSearchSession } from './use_search_session'; import { renderHook } from '@testing-library/react-hooks'; -import { createSearchSessionMock } from '../../../__mocks__/search_session'; import { discoverServiceMock } from '../../../__mocks__/services'; import { savedSearchMock } from '../../../__mocks__/saved_search'; -import { getDiscoverStateContainer } from '../services/discover_state'; +import { getDiscoverStateMock } from '../../../__mocks__/discover_state.mock'; describe('test useSearchSession', () => { test('getting the next session id', async () => { - const { history } = createSearchSessionMock(); - const stateContainer = getDiscoverStateContainer({ + const stateContainer = getDiscoverStateMock({ savedSearch: savedSearchMock, - history, - services: discoverServiceMock, }); const nextId = 'id'; @@ -29,7 +25,6 @@ describe('test useSearchSession', () => { return useSearchSession({ services: discoverServiceMock, stateContainer, - savedSearch: savedSearchMock, }); }); expect(stateContainer.searchSessionManager.getNextSearchSessionId()).toBe('id'); diff --git a/src/plugins/discover/public/application/main/hooks/use_search_session.ts b/src/plugins/discover/public/application/main/hooks/use_search_session.ts index 8dadbd9015f1e..dc08eb7fe0b4c 100644 --- a/src/plugins/discover/public/application/main/hooks/use_search_session.ts +++ b/src/plugins/discover/public/application/main/hooks/use_search_session.ts @@ -7,7 +7,6 @@ */ import { useEffect } from 'react'; import { noSearchSessionStorageCapabilityMessage } from '@kbn/data-plugin/public'; -import { SavedSearch } from '@kbn/saved-search-plugin/public'; import { createSearchSessionRestorationDataProvider, DiscoverStateContainer, @@ -17,11 +16,9 @@ import { DiscoverServices } from '../../../build_services'; export function useSearchSession({ services, stateContainer, - savedSearch, }: { services: DiscoverServices; stateContainer: DiscoverStateContainer; - savedSearch: SavedSearch; }) { const { data, capabilities } = services; @@ -30,7 +27,7 @@ export function useSearchSession({ createSearchSessionRestorationDataProvider({ appStateContainer: stateContainer.appState, data, - getSavedSearch: () => savedSearch, + getSavedSearch: () => stateContainer.savedSearchState.getState(), }), { isDisabled: () => @@ -42,5 +39,5 @@ export function useSearchSession({ }, } ); - }, [capabilities.discover.storeSearchSession, data, savedSearch, stateContainer.appState]); + }, [capabilities.discover.storeSearchSession, data, stateContainer]); } diff --git a/src/plugins/discover/public/application/main/hooks/use_test_based_query_language.test.ts b/src/plugins/discover/public/application/main/hooks/use_test_based_query_language.test.ts index 7ac2fb6ef7916..8d0a9876118bb 100644 --- a/src/plugins/discover/public/application/main/hooks/use_test_based_query_language.test.ts +++ b/src/plugins/discover/public/application/main/hooks/use_test_based_query_language.test.ts @@ -11,9 +11,8 @@ import { waitFor } from '@testing-library/react'; import { DataViewsContract } from '@kbn/data-plugin/public'; import { discoverServiceMock } from '../../../__mocks__/services'; import { useTextBasedQueryLanguage } from './use_text_based_query_language'; -import { BehaviorSubject } from 'rxjs'; import { FetchStatus } from '../../types'; -import { DataDocuments$, RecordRawType } from '../services/discover_data_state_container'; +import { RecordRawType } from '../services/discover_data_state_container'; import { DataTableRecord } from '../../../types'; import { AggregateQuery, Query } from '@kbn/es-query'; import { dataViewMock } from '../../../__mocks__/data_view'; @@ -36,11 +35,9 @@ function getHookProps( fetchStatus: FetchStatus.LOADING, query, }; - - const documents$ = new BehaviorSubject(msgLoading) as DataDocuments$; + stateContainer.dataState.data$.documents$.next(msgLoading); return { - documents$, dataViews: dataViewsService ?? discoverServiceMock.dataViews, stateContainer, savedSearch: savedSearchMock, @@ -64,7 +61,7 @@ const msgComplete = { describe('useTextBasedQueryLanguage', () => { test('a text based query should change state when loading and finished', async () => { const props = getHookProps(query); - const { documents$, replaceUrlState } = props; + const { replaceUrlState, stateContainer } = props; renderHook(() => useTextBasedQueryLanguage(props)); @@ -73,7 +70,7 @@ describe('useTextBasedQueryLanguage', () => { replaceUrlState.mockReset(); - documents$.next(msgComplete); + stateContainer.dataState.data$.documents$.next(msgComplete); await waitFor(() => expect(replaceUrlState).toHaveBeenCalledTimes(1)); await waitFor(() => { @@ -85,11 +82,12 @@ describe('useTextBasedQueryLanguage', () => { }); test('changing a text based query with different result columns should change state when loading and finished', async () => { const props = getHookProps(query); - const { documents$, replaceUrlState } = props; + const { stateContainer, replaceUrlState } = props; + const documents$ = stateContainer.dataState.data$.documents$; renderHook(() => useTextBasedQueryLanguage(props)); - documents$.next(msgComplete); + stateContainer.dataState.data$.documents$.next(msgComplete); await waitFor(() => expect(replaceUrlState).toHaveBeenCalledTimes(2)); replaceUrlState.mockReset(); @@ -116,7 +114,8 @@ describe('useTextBasedQueryLanguage', () => { }); test('only changing a text based query with same result columns should not change columns', async () => { const props = getHookProps(query); - const { documents$, replaceUrlState } = props; + const { replaceUrlState, stateContainer } = props; + const documents$ = stateContainer.dataState.data$.documents$; renderHook(() => useTextBasedQueryLanguage(props)); @@ -160,7 +159,8 @@ describe('useTextBasedQueryLanguage', () => { }); test('if its not a text based query coming along, it should be ignored', async () => { const props = getHookProps(query); - const { documents$, replaceUrlState } = props; + const { replaceUrlState, stateContainer } = props; + const documents$ = stateContainer.dataState.data$.documents$; renderHook(() => useTextBasedQueryLanguage(props)); @@ -206,7 +206,8 @@ describe('useTextBasedQueryLanguage', () => { props.stateContainer.appState.getState = jest.fn(() => { return { columns: ['field1'], index: 'the-data-view-id' }; }); - const { documents$, replaceUrlState } = props; + const { stateContainer, replaceUrlState } = props; + const documents$ = stateContainer.dataState.data$.documents$; renderHook(() => useTextBasedQueryLanguage(props)); documents$.next({ @@ -245,7 +246,8 @@ describe('useTextBasedQueryLanguage', () => { props.stateContainer.appState.getState = jest.fn(() => { return { columns: [], index: 'the-data-view-id' }; }); - const { documents$, replaceUrlState } = props; + const { stateContainer, replaceUrlState } = props; + const documents$ = stateContainer.dataState.data$.documents$; renderHook(() => useTextBasedQueryLanguage(props)); documents$.next({ @@ -318,7 +320,8 @@ describe('useTextBasedQueryLanguage', () => { create: dataViewsCreateMock, }; const props = getHookProps(query, dataViewsService); - const { documents$, replaceUrlState } = props; + const { stateContainer, replaceUrlState } = props; + const documents$ = stateContainer.dataState.data$.documents$; renderHook(() => useTextBasedQueryLanguage(props)); diff --git a/src/plugins/discover/public/application/main/hooks/use_text_based_query_language.ts b/src/plugins/discover/public/application/main/hooks/use_text_based_query_language.ts index 5e2eb33efdbe9..050ef6513a386 100644 --- a/src/plugins/discover/public/application/main/hooks/use_text_based_query_language.ts +++ b/src/plugins/discover/public/application/main/hooks/use_text_based_query_language.ts @@ -14,9 +14,7 @@ import { } from '@kbn/es-query'; import { useCallback, useEffect, useRef } from 'react'; import type { DataViewsContract } from '@kbn/data-views-plugin/public'; -import { SavedSearch } from '@kbn/saved-search-plugin/public'; import type { DiscoverStateContainer } from '../services/discover_state'; -import type { DataDocuments$ } from '../services/discover_data_state_container'; import { FetchStatus } from '../../types'; const MAX_NUM_OF_COLUMNS = 50; @@ -26,20 +24,17 @@ const MAX_NUM_OF_COLUMNS = 50; * If necessary this is setting displayed columns and selected data view */ export function useTextBasedQueryLanguage({ - documents$, dataViews, stateContainer, - savedSearch, }: { - documents$: DataDocuments$; stateContainer: DiscoverStateContainer; dataViews: DataViewsContract; - savedSearch: SavedSearch; }) { const prev = useRef<{ query: AggregateQuery | Query | undefined; columns: string[] }>({ columns: [], query: undefined, }); + const savedSearch = stateContainer.savedSearchState.getInitial$().getValue(); const cleanup = useCallback(() => { if (prev.current.query) { @@ -52,7 +47,7 @@ export function useTextBasedQueryLanguage({ }, []); useEffect(() => { - const subscription = documents$.subscribe(async (next) => { + const subscription = stateContainer.dataState.data$.documents$.subscribe(async (next) => { const { query, recordRawType } = next; if (!query || next.fetchStatus === FetchStatus.ERROR) { return; @@ -127,5 +122,5 @@ export function useTextBasedQueryLanguage({ cleanup(); subscription.unsubscribe(); }; - }, [documents$, dataViews, stateContainer, savedSearch, cleanup]); + }, [dataViews, stateContainer, savedSearch, cleanup]); } diff --git a/src/plugins/discover/public/application/main/hooks/use_url.test.ts b/src/plugins/discover/public/application/main/hooks/use_url.test.ts index 9d8191f3581e4..e4774ac9a435c 100644 --- a/src/plugins/discover/public/application/main/hooks/use_url.test.ts +++ b/src/plugins/discover/public/application/main/hooks/use_url.test.ts @@ -8,20 +8,41 @@ import { renderHook } from '@testing-library/react-hooks'; import { createSearchSessionMock } from '../../../__mocks__/search_session'; import { useUrl } from './use_url'; +import { getDiscoverStateMock } from '../../../__mocks__/discover_state.mock'; +import { + savedSearchMockWithTimeField, + savedSearchMockWithTimeFieldNew, +} from '../../../__mocks__/saved_search'; +import { SavedSearch } from '@kbn/saved-search-plugin/public'; -describe('test useUrl', () => { - test('resetSavedSearch is triggered once path it changed to /', () => { - const { history } = createSearchSessionMock(); - history.push('/view'); - const props = { - history, - resetSavedSearch: jest.fn(), - }; - renderHook(() => useUrl(props)); - history.push('/new'); - expect(props.resetSavedSearch).toHaveBeenCalledTimes(0); +function prepareTest(savedSearch: SavedSearch, path: string) { + const { history } = createSearchSessionMock(); + const stateContainer = getDiscoverStateMock({ isTimeBased: true }); + stateContainer.savedSearchState.set(savedSearch); + stateContainer.actions.loadSavedSearch = jest.fn(); - history.push('/'); - expect(props.resetSavedSearch).toHaveBeenCalledTimes(1); + renderHook(() => + useUrl({ + history, + stateContainer, + }) + ); + history.push(path); + return { load: stateContainer.actions.loadSavedSearch }; +} +describe('test useUrl when the url is changed to /', () => { + test('loadSavedSearch is not triggered when the url is e.g. /new', () => { + // the switch to loading the new saved search is taken care in the main route + const { load } = prepareTest(savedSearchMockWithTimeFieldNew, '/new'); + expect(load).toHaveBeenCalledTimes(0); + }); + test('loadSavedSearch is not triggered when a persisted saved search is pre-selected', () => { + // the switch to loading the new saved search is taken care in the main route + const { load } = prepareTest(savedSearchMockWithTimeField, '/'); + expect(load).toHaveBeenCalledTimes(0); + }); + test('loadSavedSearch is triggered when a new saved search is pre-selected ', () => { + const { load } = prepareTest(savedSearchMockWithTimeFieldNew, '/'); + expect(load).toHaveBeenCalledTimes(1); }); }); diff --git a/src/plugins/discover/public/application/main/hooks/use_url.ts b/src/plugins/discover/public/application/main/hooks/use_url.ts index bc5554c35b36f..c43c4769a724d 100644 --- a/src/plugins/discover/public/application/main/hooks/use_url.ts +++ b/src/plugins/discover/public/application/main/hooks/use_url.ts @@ -7,12 +7,13 @@ */ import { useEffect } from 'react'; import { History } from 'history'; +import { DiscoverStateContainer } from '../services/discover_state'; export function useUrl({ history, - resetSavedSearch, + stateContainer, }: { history: History; - resetSavedSearch: (val?: string) => void; + stateContainer: DiscoverStateContainer; }) { /** * Url / Routing logic @@ -21,11 +22,12 @@ export function useUrl({ // this listener is waiting for such a path http://localhost:5601/app/discover#/ // which could be set through pressing "New" button in top nav or go to "Discover" plugin from the sidebar // to reload the page in a right way - const unlistenHistoryBasePath = history.listen(({ pathname, search, hash }) => { - if (!search && !hash && pathname === '/') { - resetSavedSearch(); + const unlistenHistoryBasePath = history.listen(async ({ pathname, search, hash }) => { + if (!search && !hash && pathname === '/' && !stateContainer.savedSearchState.getState().id) { + await stateContainer.actions.loadSavedSearch(); + stateContainer.actions.fetchData(true); } }); return () => unlistenHistoryBasePath(); - }, [history, resetSavedSearch]); + }, [history, stateContainer]); } diff --git a/src/plugins/discover/public/application/main/hooks/use_url_tracking.ts b/src/plugins/discover/public/application/main/hooks/use_url_tracking.ts index 4bed6f846d234..138ca8ce011da 100644 --- a/src/plugins/discover/public/application/main/hooks/use_url_tracking.ts +++ b/src/plugins/discover/public/application/main/hooks/use_url_tracking.ts @@ -5,26 +5,26 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import { useCallback, useEffect } from 'react'; -import { DataView } from '@kbn/data-views-plugin/common'; -import { SavedSearch } from '@kbn/saved-search-plugin/public'; +import { useEffect } from 'react'; +import { DiscoverSavedSearchContainer } from '../services/discover_saved_search_container'; import { getUrlTracker } from '../../../kibana_services'; /** * Enable/disable kbn url tracking (That's the URL used when selecting Discover in the side menu) */ -export function useUrlTracking(savedSearch: SavedSearch, dataView: DataView) { - const setUrlTracking = useCallback( - (actualDataView: DataView) => { - const trackingEnabled = Boolean(actualDataView.isPersisted() || savedSearch.id); - getUrlTracker().setTrackingEnabled(trackingEnabled); - }, - [savedSearch] - ); - +export function useUrlTracking(savedSearchContainer: DiscoverSavedSearchContainer) { useEffect(() => { - setUrlTracking(dataView); - }, [dataView, savedSearch.id, setUrlTracking]); + const subscription = savedSearchContainer.getCurrent$().subscribe((savedSearch) => { + const dataView = savedSearch.searchSource.getField('index'); + if (!dataView) { + return; + } + const trackingEnabled = Boolean(dataView.isPersisted() || savedSearch.id); + getUrlTracker().setTrackingEnabled(trackingEnabled); + }); - return { setUrlTracking }; + return () => { + subscription.unsubscribe(); + }; + }, [savedSearchContainer]); } diff --git a/src/plugins/discover/public/application/main/hooks/utils/build_state_subscribe.test.ts b/src/plugins/discover/public/application/main/hooks/utils/build_state_subscribe.test.ts index 2ea8275dea013..b8267fa333e90 100644 --- a/src/plugins/discover/public/application/main/hooks/utils/build_state_subscribe.test.ts +++ b/src/plugins/discover/public/application/main/hooks/utils/build_state_subscribe.test.ts @@ -5,130 +5,89 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import { createBrowserHistory } from 'history'; import { buildStateSubscribe } from './build_state_subscribe'; import { savedSearchMock } from '../../../../__mocks__/saved_search'; import { FetchStatus } from '../../../types'; -import { getDiscoverStateContainer } from '../../services/discover_state'; -import { discoverServiceMock } from '../../../../__mocks__/services'; import { dataViewComplexMock } from '../../../../__mocks__/data_view_complex'; +import { getDiscoverStateMock } from '../../../../__mocks__/discover_state.mock'; describe('buildStateSubscribe', () => { const savedSearch = savedSearchMock; - const history = createBrowserHistory(); - const stateContainer = getDiscoverStateContainer({ - savedSearch, - services: discoverServiceMock, - history, - }); + const stateContainer = getDiscoverStateMock({ savedSearch }); stateContainer.dataState.refetch$.next = jest.fn(); stateContainer.dataState.reset = jest.fn(); stateContainer.actions.setDataView = jest.fn(); - stateContainer.actions.loadAndResolveDataView = jest.fn(() => + const loadAndResolveDataView = jest.fn(() => Promise.resolve({ fallback: false, dataView: dataViewComplexMock }) ); - const setState = jest.fn(); + const getSubscribeFn = () => { + return buildStateSubscribe({ + appState: stateContainer.appState, + savedSearchState: stateContainer.savedSearchState, + dataState: stateContainer.dataState, + loadAndResolveDataView, + setDataView: stateContainer.actions.setDataView, + }); + }; beforeEach(() => { jest.clearAllMocks(); }); - it('should set the data view if the index has changed, but no refetch should be triggered', async () => { - await buildStateSubscribe({ - stateContainer, - savedSearch, - setState, - })({ index: dataViewComplexMock.id }); + it('should set the data view if the index has changed, and refetch should be triggered', async () => { + await getSubscribeFn()({ index: dataViewComplexMock.id }); expect(stateContainer.actions.setDataView).toHaveBeenCalledWith(dataViewComplexMock); expect(stateContainer.dataState.reset).toHaveBeenCalled(); - expect(stateContainer.dataState.refetch$.next).not.toHaveBeenCalled(); - expect(setState).toHaveBeenCalled(); + expect(stateContainer.dataState.refetch$.next).toHaveBeenCalled(); }); it('should not call refetch$ if nothing changes', async () => { - await buildStateSubscribe({ - stateContainer, - savedSearch, - setState, - })(stateContainer.appState.getState()); + await getSubscribeFn()(stateContainer.appState.getState()); expect(stateContainer.dataState.refetch$.next).toHaveBeenCalled(); - expect(setState).toHaveBeenCalled(); }); it('should call refetch$ if the chart is hidden', async () => { - await buildStateSubscribe({ - stateContainer, - savedSearch, - setState, - })({ hideChart: true }); + await getSubscribeFn()({ hideChart: true }); expect(stateContainer.dataState.refetch$.next).toHaveBeenCalled(); - expect(setState).toHaveBeenCalled(); }); it('should call refetch$ if the chart interval has changed', async () => { - await buildStateSubscribe({ - stateContainer, - savedSearch, - setState, - })({ interval: 's' }); + await getSubscribeFn()({ interval: 's' }); expect(stateContainer.dataState.refetch$.next).toHaveBeenCalled(); - expect(setState).toHaveBeenCalled(); }); it('should call refetch$ if breakdownField has changed', async () => { - await buildStateSubscribe({ - stateContainer, - savedSearch, - setState, - })({ breakdownField: '💣' }); + await getSubscribeFn()({ breakdownField: '💣' }); expect(stateContainer.dataState.refetch$.next).toHaveBeenCalled(); - expect(setState).toHaveBeenCalled(); }); it('should call refetch$ if sort has changed', async () => { - await buildStateSubscribe({ - stateContainer, - savedSearch, - setState, - })({ sort: [['field', 'test']] }); + await getSubscribeFn()({ sort: [['field', 'test']] }); expect(stateContainer.dataState.refetch$.next).toHaveBeenCalled(); - expect(setState).toHaveBeenCalled(); }); it('should not execute setState function if initialFetchStatus is UNINITIALIZED', async () => { - const stateSubscribeFn = await buildStateSubscribe({ - stateContainer, - savedSearch, - setState, - }); - stateContainer.dataState.initialFetchStatus = FetchStatus.UNINITIALIZED; + const stateSubscribeFn = getSubscribeFn(); + stateContainer.dataState.getInitialFetchStatus = jest.fn(() => FetchStatus.UNINITIALIZED); await stateSubscribeFn({ index: dataViewComplexMock.id }); expect(stateContainer.dataState.reset).toHaveBeenCalled(); - expect(setState).not.toHaveBeenCalled(); }); it('should not execute setState twice if the identical data view change is propagated twice', async () => { - const stateSubscribeFn = await buildStateSubscribe({ - stateContainer, - savedSearch, - setState, - }); - await stateSubscribeFn({ index: dataViewComplexMock.id }); + await getSubscribeFn()({ index: dataViewComplexMock.id }); - expect(setState).toBeCalledTimes(0); expect(stateContainer.dataState.reset).toBeCalledTimes(1); stateContainer.appState.getPrevious = jest.fn(() => ({ index: dataViewComplexMock.id })); - await stateSubscribeFn({ index: dataViewComplexMock.id }); - expect(setState).toBeCalledTimes(0); + await getSubscribeFn()({ index: dataViewComplexMock.id }); expect(stateContainer.dataState.reset).toBeCalledTimes(1); }); }); diff --git a/src/plugins/discover/public/application/main/hooks/utils/build_state_subscribe.ts b/src/plugins/discover/public/application/main/hooks/utils/build_state_subscribe.ts index ebe8e22935382..422be13ab8323 100644 --- a/src/plugins/discover/public/application/main/hooks/utils/build_state_subscribe.ts +++ b/src/plugins/discover/public/application/main/hooks/utils/build_state_subscribe.ts @@ -5,10 +5,16 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import { SavedSearch } from '@kbn/saved-search-plugin/public'; import { isEqual } from 'lodash'; +import type { DataView, DataViewSpec } from '@kbn/data-views-plugin/common'; +import { DiscoverSavedSearchContainer } from '../../services/discover_saved_search_container'; +import { DiscoverDataStateContainer } from '../../services/discover_data_state_container'; import { DiscoverStateContainer } from '../../services/discover_state'; -import { DiscoverAppState, isEqualState } from '../../services/discover_app_state_container'; +import { + DiscoverAppState, + DiscoverAppStateContainer, + isEqualState, +} from '../../services/discover_app_state_container'; import { addLog } from '../../../../utils/add_log'; import { FetchStatus } from '../../../types'; @@ -16,28 +22,33 @@ import { FetchStatus } from '../../../types'; * Builds a subscribe function for the AppStateContainer, that is executed when the AppState changes in URL * or programmatically. It's main purpose is to detect which changes should trigger a refetch of the data. * @param stateContainer - * @param savedSearch - * @param setState */ export const buildStateSubscribe = ({ - stateContainer, - savedSearch, - setState, + appState, + savedSearchState, + dataState, + loadAndResolveDataView, + setDataView, }: { - stateContainer: DiscoverStateContainer; - savedSearch: SavedSearch; - setState: (state: DiscoverAppState) => void; + appState: DiscoverAppStateContainer; + savedSearchState: DiscoverSavedSearchContainer; + dataState: DiscoverDataStateContainer; + loadAndResolveDataView: ( + id?: string, + dataViewSpec?: DataViewSpec + ) => Promise<{ fallback: boolean; dataView: DataView }>; + setDataView: DiscoverStateContainer['actions']['setDataView']; }) => async (nextState: DiscoverAppState) => { - const prevState = stateContainer.appState.getPrevious(); + const prevState = appState.getPrevious(); + const savedSearch = savedSearchState.getState(); if (isEqualState(prevState, nextState)) { - addLog('[appstate] subscribe update ignored due to no changes'); + addLog('[appstate] subscribe update ignored due to no changes', { prevState, nextState }); return; } addLog('[appstate] subscribe triggered', nextState); - const { hideChart, interval, breakdownField, sort, index } = - stateContainer.appState.getPrevious(); + const { hideChart, interval, breakdownField, sort, index } = appState.getPrevious(); // Cast to boolean to avoid false positives when comparing // undefined and false, which would trigger a refetch const chartDisplayChanged = Boolean(nextState.hideChart) !== Boolean(hideChart); @@ -45,26 +56,26 @@ export const buildStateSubscribe = const breakdownFieldChanged = nextState.breakdownField !== breakdownField; const docTableSortChanged = !isEqual(nextState.sort, sort); const dataViewChanged = !isEqual(nextState.index, index); + let savedSearchDataView; // NOTE: this is also called when navigating from discover app to context app if (nextState.index && dataViewChanged) { - const { dataView: nextDataView, fallback } = - await stateContainer.actions.loadAndResolveDataView(nextState.index, savedSearch); + const { dataView: nextDataView, fallback } = await loadAndResolveDataView(nextState.index); // If the requested data view is not found, don't try to load it, // and instead reset the app state to the fallback data view if (fallback) { - stateContainer.appState.update({ index: nextDataView.id }, true); + appState.update({ index: nextDataView.id }, true); return; } savedSearch.searchSource.setField('index', nextDataView); - stateContainer.dataState.reset(); - stateContainer.actions.setDataView(nextDataView); + dataState.reset(savedSearch); + setDataView(nextDataView); + savedSearchDataView = nextDataView; } - if ( - dataViewChanged && - stateContainer.dataState.initialFetchStatus === FetchStatus.UNINITIALIZED - ) { + savedSearchState.update({ nextDataView: savedSearchDataView, nextState }); + + if (dataViewChanged && dataState.getInitialFetchStatus() === FetchStatus.UNINITIALIZED) { // stop execution if given data view has changed, and it's not configured to initially start a search in Discover return; } @@ -73,11 +84,10 @@ export const buildStateSubscribe = chartDisplayChanged || chartIntervalChanged || breakdownFieldChanged || - docTableSortChanged + docTableSortChanged || + dataViewChanged ) { addLog('[appstate] subscribe triggers data fetching'); - stateContainer.dataState.refetch$.next(undefined); + dataState.fetch(); } - - setState(nextState); }; diff --git a/src/plugins/discover/public/application/main/hooks/utils/change_data_view.test.ts b/src/plugins/discover/public/application/main/hooks/utils/change_data_view.test.ts index 04afdac829f97..37a503716af0e 100644 --- a/src/plugins/discover/public/application/main/hooks/utils/change_data_view.test.ts +++ b/src/plugins/discover/public/application/main/hooks/utils/change_data_view.test.ts @@ -8,32 +8,29 @@ import { changeDataView } from './change_data_view'; import { savedSearchMock } from '../../../../__mocks__/saved_search'; -import { createBrowserHistory } from 'history'; -import { getDiscoverStateContainer } from '../../services/discover_state'; import { discoverServiceMock } from '../../../../__mocks__/services'; import type { DataView } from '@kbn/data-views-plugin/common'; import { dataViewComplexMock } from '../../../../__mocks__/data_view_complex'; +import { getDiscoverStateMock } from '../../../../__mocks__/discover_state.mock'; const setupTestParams = (dataView: DataView | undefined) => { const savedSearch = savedSearchMock; const services = discoverServiceMock; - const history = createBrowserHistory(); - const discoverState = getDiscoverStateContainer({ + + const discoverState = getDiscoverStateMock({ savedSearch, - services: discoverServiceMock, - history, }); discoverState.internalState.transitions.setDataView(savedSearch.searchSource.getField('index')!); services.dataViews.get = jest.fn(() => Promise.resolve(dataView as DataView)); discoverState.appState.update = jest.fn(); - return { services, discoverState, setUrlTracking: jest.fn() }; + return { services, appState: discoverState.appState, internalState: discoverState.internalState }; }; describe('changeDataView', () => { it('should set the right app state when a valid data view to switch to is given', async () => { const params = setupTestParams(dataViewComplexMock as DataView); await changeDataView('data-view-with-various-field-types', params); - expect(params.discoverState.appState.update).toHaveBeenCalledWith({ + expect(params.appState.update).toHaveBeenCalledWith({ columns: ['default_column'], index: 'data-view-with-various-field-types-id', sort: [['data', 'desc']], @@ -43,6 +40,6 @@ describe('changeDataView', () => { it('should not set the app state when an invalid data view to switch to is given', async () => { const params = setupTestParams(undefined); await changeDataView('data-view-with-various-field-types', params); - expect(params.discoverState.appState.update).not.toHaveBeenCalled(); + expect(params.appState.update).not.toHaveBeenCalled(); }); }); diff --git a/src/plugins/discover/public/application/main/hooks/utils/change_data_view.ts b/src/plugins/discover/public/application/main/hooks/utils/change_data_view.ts index c53e0ec9da617..0912758b74663 100644 --- a/src/plugins/discover/public/application/main/hooks/utils/change_data_view.ts +++ b/src/plugins/discover/public/application/main/hooks/utils/change_data_view.ts @@ -8,39 +8,36 @@ import { SortOrder } from '@kbn/saved-search-plugin/public'; import { DataView } from '@kbn/data-views-plugin/common'; +import { DiscoverInternalStateContainer } from '../../services/discover_internal_state_container'; +import { DiscoverAppStateContainer } from '../../services/discover_app_state_container'; import { addLog } from '../../../../utils/add_log'; import { DiscoverServices } from '../../../../build_services'; -import { DiscoverStateContainer } from '../../services/discover_state'; import { getDataViewAppState } from '../../utils/get_switch_data_view_app_state'; import { MODIFY_COLUMNS_ON_SWITCH, SORT_DEFAULT_ORDER_SETTING } from '../../../../../common'; /** * Function executed when switching data view in the UI - * @param id - * @param services - * @param discoverState - * @param setUrlTracking */ export async function changeDataView( - id: string, + id: string | DataView, { services, - discoverState, - setUrlTracking, + internalState, + appState, }: { services: DiscoverServices; - discoverState: DiscoverStateContainer; - setUrlTracking: (dataView: DataView) => void; + internalState: DiscoverInternalStateContainer; + appState: DiscoverAppStateContainer; } ) { addLog('[ui] changeDataView', { id }); const { dataViews, uiSettings } = services; - const dataView = discoverState.internalState.getState().dataView; - const state = discoverState.appState.getState(); + const dataView = internalState.getState().dataView; + const state = appState.getState(); let nextDataView: DataView | null = null; try { - nextDataView = await dataViews.get(id, false); + nextDataView = typeof id === 'string' ? await dataViews.get(id, false) : id; } catch (e) { // } @@ -56,7 +53,9 @@ export async function changeDataView( state.query ); - setUrlTracking(nextDataView); - discoverState.appState.update(nextAppState); + appState.update(nextAppState); + if (internalState.getState().expandedDoc) { + internalState.transitions.setExpandedDoc(undefined); + } } } diff --git a/src/plugins/discover/public/application/main/services/discover_app_state_container.ts b/src/plugins/discover/public/application/main/services/discover_app_state_container.ts index eb552ea023f9b..ba5c38862da80 100644 --- a/src/plugins/discover/public/application/main/services/discover_app_state_container.ts +++ b/src/plugins/discover/public/application/main/services/discover_app_state_container.ts @@ -33,6 +33,10 @@ import { DiscoverGridSettings } from '../../../components/discover_grid/types'; export const APP_STATE_URL_KEY = '_a'; export interface DiscoverAppStateContainer extends ReduxLikeStateContainer { + /** + * Returns if the current URL is empty + */ + isEmptyURL: () => boolean; /** * Returns the previous state, used for diffing e.g. if fetching new data is necessary */ @@ -180,7 +184,7 @@ export const getDiscoverAppStateContainer = ({ }; const startAppStateUrlSync = () => { - addLog('[appState] startAppStateUrlSync'); + addLog('[appState] start syncing state with URL'); return syncState({ storageKey: APP_STATE_URL_KEY, stateContainer: enhancedAppContainer, @@ -189,24 +193,29 @@ export const getDiscoverAppStateContainer = ({ }; const initializeAndSync = (currentSavedSearch: SavedSearch) => { - addLog('[appState] initializeAndSync', currentSavedSearch); - const dataView = currentSavedSearch.searchSource.getField('index')!; - if (appStateContainer.getState().index !== dataView.id) { + addLog('[appState] initialize state and sync with URL', currentSavedSearch); + const { filterManager, data } = services; + + // searchsource is the source of truth + const dataView = currentSavedSearch.searchSource.getField('index'); + const filters = currentSavedSearch.searchSource.getField('filter'); + const query = currentSavedSearch.searchSource.getField('query'); + if (appStateContainer.getState().index !== dataView?.id) { // used data view is different from the given by url/state which is invalid - setState(appStateContainer, { index: dataView.id }); + setState(appStateContainer, { index: dataView?.id }); } // sync initial app filters from state to filterManager - const filters = appStateContainer.getState().filters || []; - if (filters) { - services.filterManager.setAppFilters(cloneDeep(filters)); + if (Array.isArray(filters) && filters.length) { + filterManager.setAppFilters(cloneDeep(filters)); + } else { + filterManager.setAppFilters([]); } - const query = appStateContainer.getState().query; if (query) { - services.data.query.queryString.setQuery(query); + data.query.queryString.setQuery(query); } const stopSyncingQueryAppStateWithStateContainer = connectToQueryState( - services.data.query, + data.query, appStateContainer, { filters: FilterStateStore.APP_STATE, @@ -216,23 +225,21 @@ export const getDiscoverAppStateContainer = ({ // syncs `_g` portion of url with query services const { stop: stopSyncingGlobalStateWithUrl } = syncGlobalQueryStateWithUrl( - services.data.query, + data.query, stateStorage ); // some filters may not be valid for this context, so update // the filter manager with a modified list of valid filters - const currentFilters = services.filterManager.getFilters(); - const validFilters = getValidFilters(dataView, currentFilters); + const currentFilters = filterManager.getFilters(); + const validFilters = getValidFilters(dataView!, currentFilters); if (!isEqual(currentFilters, validFilters)) { - services.filterManager.setFilters(validFilters); + filterManager.setFilters(validFilters); } const { start, stop } = startAppStateUrlSync(); - - replaceUrlState({}).then(() => { - start(); - }); + // current state need to be pushed to url + replaceUrlState({}).then(() => start()); return () => { stopSyncingQueryAppStateWithStateContainer(); @@ -257,10 +264,15 @@ export const getDiscoverAppStateContainer = ({ } }; + const isEmptyURL = () => { + return stateStorage.get(APP_STATE_URL_KEY) === null; + }; + const getPrevious = () => previousState; return { ...enhancedAppContainer, + isEmptyURL, getPrevious, hasChanged, initAndSync: initializeAndSync, @@ -286,16 +298,19 @@ function getInitialState( savedSearch: SavedSearch, services: DiscoverServices ) { - const appStateFromUrl = cleanupUrlState(stateStorage.get(APP_STATE_URL_KEY) as AppStateUrl); + const stateStorageURL = stateStorage.get(APP_STATE_URL_KEY) as AppStateUrl; + const appStateFromUrl = cleanupUrlState(stateStorageURL); const defaultAppState = getStateDefaults({ savedSearch, services, }); return handleSourceColumnState( - { - ...defaultAppState, - ...appStateFromUrl, - }, + stateStorageURL === null + ? defaultAppState + : { + ...defaultAppState, + ...appStateFromUrl, + }, services.uiSettings ); } diff --git a/src/plugins/discover/public/application/main/services/discover_data_state_container.test.ts b/src/plugins/discover/public/application/main/services/discover_data_state_container.test.ts index fcdce1b76a1a0..860ff1c096c4a 100644 --- a/src/plugins/discover/public/application/main/services/discover_data_state_container.test.ts +++ b/src/plugins/discover/public/application/main/services/discover_data_state_container.test.ts @@ -9,12 +9,10 @@ import { Subject } from 'rxjs'; import { waitFor } from '@testing-library/react'; import { discoverServiceMock } from '../../../__mocks__/services'; import { savedSearchMockWithSQL } from '../../../__mocks__/saved_search'; -import { getDiscoverStateContainer } from './discover_state'; import { FetchStatus } from '../../types'; import { setUrlTracker } from '../../../kibana_services'; import { urlTrackerMock } from '../../../__mocks__/url_tracker.mock'; import { RecordRawType } from './discover_data_state_container'; -import { createBrowserHistory } from 'history'; import { getDiscoverStateMock } from '../../../__mocks__/discover_state.mock'; setUrlTracker(urlTrackerMock); @@ -70,21 +68,19 @@ describe('test getDataStateContainer', () => { await waitFor(() => { expect(dataState.data$.main$.value.fetchStatus).toBe(FetchStatus.COMPLETE); }); - dataState.reset(); + dataState.reset(stateContainer.savedSearchState.getState()); await waitFor(() => { expect(dataState.data$.main$.value.fetchStatus).toBe(FetchStatus.LOADING); }); - unsubscribe(); }); test('useSavedSearch returns plain record raw type', async () => { - const history = createBrowserHistory(); - const stateContainer = getDiscoverStateContainer({ + const stateContainer = getDiscoverStateMock({ savedSearch: savedSearchMockWithSQL, - services: discoverServiceMock, - history, }); + stateContainer.savedSearchState.load = jest.fn().mockResolvedValue(savedSearchMockWithSQL); + await stateContainer.actions.loadSavedSearch(savedSearchMockWithSQL.id); expect(stateContainer.dataState.data$.main$.getValue().recordRawType).toBe(RecordRawType.PLAIN); }); diff --git a/src/plugins/discover/public/application/main/services/discover_data_state_container.ts b/src/plugins/discover/public/application/main/services/discover_data_state_container.ts index 7bac15b6a5808..07e1f089bcd13 100644 --- a/src/plugins/discover/public/application/main/services/discover_data_state_container.ts +++ b/src/plugins/discover/public/application/main/services/discover_data_state_container.ts @@ -12,7 +12,6 @@ import { RequestAdapter } from '@kbn/inspector-plugin/common'; import { SavedSearch } from '@kbn/saved-search-plugin/public'; import { AggregateQuery, Query } from '@kbn/es-query'; import type { SearchResponse } from '@elastic/elasticsearch/lib/api/types'; -import { ReduxLikeStateContainer } from '@kbn/kibana-utils-plugin/common'; import { getRawRecordType } from '../utils/get_raw_record_type'; import { DiscoverAppState } from './discover_app_state_container'; import { DiscoverServices } from '../../../build_services'; @@ -108,17 +107,17 @@ export interface DiscoverDataStateContainer { /** * resetting all data observable to initial state */ - reset: () => void; + reset: (savedSearch: SavedSearch) => void; /** * Available Inspector Adaptor allowing to get details about recent requests to ES */ - inspectorAdapters: { requests: RequestAdapter }; + inspectorAdapters: { requests: RequestAdapter; lensRequests?: RequestAdapter }; /** - * Initial fetch status + * Return the initial fetch status * UNINITIALIZED: data is not fetched initially, without user triggering it * LOADING: data is fetched initially (when Discover is rendered, or data views are switched) */ - initialFetchStatus: FetchStatus; + getInitialFetchStatus: () => FetchStatus; } /** * Container responsible for fetching of data in Discover Main @@ -130,13 +129,11 @@ export function getDataStateContainer({ searchSessionManager, getAppState, getSavedSearch, - appStateContainer, }: { services: DiscoverServices; searchSessionManager: DiscoverSearchSessionManager; getAppState: () => DiscoverAppState; getSavedSearch: () => SavedSearch; - appStateContainer: ReduxLikeStateContainer; }): DiscoverDataStateContainer { const { data, uiSettings, toastNotifications } = services; const { timefilter } = data.query.timefilter; @@ -149,20 +146,20 @@ export function getDataStateContainer({ * to be processed correctly */ const refetch$ = new Subject(); - const shouldSearchOnPageLoad = - uiSettings.get(SEARCH_ON_PAGE_LOAD_SETTING) || - getSavedSearch().id !== undefined || - !timefilter.getRefreshInterval().pause || - searchSessionManager.hasSearchSessionIdInURL(); - const initialFetchStatus = shouldSearchOnPageLoad - ? FetchStatus.LOADING - : FetchStatus.UNINITIALIZED; + const getInitialFetchStatus = () => { + const shouldSearchOnPageLoad = + uiSettings.get(SEARCH_ON_PAGE_LOAD_SETTING) || + getSavedSearch().id !== undefined || + !timefilter.getRefreshInterval().pause || + searchSessionManager.hasSearchSessionIdInURL(); + return shouldSearchOnPageLoad ? FetchStatus.LOADING : FetchStatus.UNINITIALIZED; + }; /** * The observables the UI (aka React component) subscribes to get notified about * the changes in the data fetching process (high level: fetching started, data was received) */ - const initialState = { fetchStatus: initialFetchStatus, recordRawType }; + const initialState = { fetchStatus: getInitialFetchStatus(), recordRawType }; const dataSubjects: SavedSearchData = { main$: new BehaviorSubject(initialState), documents$: new BehaviorSubject(initialState), @@ -202,14 +199,13 @@ export function getDataStateContainer({ abortController = new AbortController(); const prevAutoRefreshDone = autoRefreshDone; - await fetchAll(dataSubjects, getSavedSearch().searchSource, reset, { + await fetchAll(dataSubjects, reset, { abortController, - data, - initialFetchStatus, + initialFetchStatus: getInitialFetchStatus(), inspectorAdapters, searchSessionId, services, - appStateContainer, + getAppState, savedSearch: getSavedSearch(), useNewFieldsApi: !uiSettings.get(SEARCH_FIELDS_FROM_SOURCE), }); @@ -239,7 +235,10 @@ export function getDataStateContainer({ return refetch$; }; - const reset = () => sendResetMsg(dataSubjects, initialFetchStatus); + const reset = (savedSearch: SavedSearch) => { + const recordType = getRawRecordType(savedSearch.searchSource.getField('query')); + sendResetMsg(dataSubjects, getInitialFetchStatus(), recordType); + }; return { fetch: fetchQuery, @@ -249,6 +248,6 @@ export function getDataStateContainer({ subscribe, reset, inspectorAdapters, - initialFetchStatus, + getInitialFetchStatus, }; } diff --git a/src/plugins/discover/public/application/main/services/discover_search_session.ts b/src/plugins/discover/public/application/main/services/discover_search_session.ts index 0cbaf74159a80..c6c64ed89c10a 100644 --- a/src/plugins/discover/public/application/main/services/discover_search_session.ts +++ b/src/plugins/discover/public/application/main/services/discover_search_session.ts @@ -67,7 +67,6 @@ export class DiscoverSearchSessionManager { this.deps.session.restore(searchSessionIdFromURL); } } - return searchSessionIdFromURL ?? this.deps.session.start(); } diff --git a/src/plugins/discover/public/application/main/services/discover_state.test.ts b/src/plugins/discover/public/application/main/services/discover_state.test.ts index c2efd78b24c8f..7b90df6c0f282 100644 --- a/src/plugins/discover/public/application/main/services/discover_state.test.ts +++ b/src/plugins/discover/public/application/main/services/discover_state.test.ts @@ -14,32 +14,67 @@ import { import { createBrowserHistory, History } from 'history'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import type { SavedSearch, SortOrder } from '@kbn/saved-search-plugin/public'; -import { savedSearchMock, savedSearchMockWithTimeField } from '../../../__mocks__/saved_search'; +import { + savedSearchMock, + savedSearchMockWithTimeField, + savedSearchMockWithTimeFieldNew, +} from '../../../__mocks__/saved_search'; import { discoverServiceMock } from '../../../__mocks__/services'; import { dataViewMock } from '../../../__mocks__/data_view'; -import { dataViewComplexMock } from '../../../__mocks__/data_view_complex'; import { DiscoverAppStateContainer } from './discover_app_state_container'; +import { waitFor } from '@testing-library/react'; +import { FetchStatus } from '../../types'; +import { dataViewAdHoc, dataViewComplexMock } from '../../../__mocks__/data_view_complex'; +import { copySavedSearch } from './discover_saved_search_container'; -let history: History; -let state: DiscoverStateContainer; -const getCurrentUrl = () => history.createHref(history.location); const startSync = (appState: DiscoverAppStateContainer) => { const { start, stop } = appState.syncState(); start(); return stop; }; +async function getState(url: string, savedSearch?: SavedSearch) { + const nextHistory = createBrowserHistory(); + nextHistory.push(url); + const nextState = getDiscoverStateContainer({ + services: discoverServiceMock, + history: nextHistory, + }); + await nextState.actions.loadDataViewList(); + if (savedSearch) { + nextState.savedSearchState.load = jest.fn(() => { + nextState.savedSearchState.set(copySavedSearch(savedSearch)); + return Promise.resolve(savedSearch); + }); + } else { + nextState.savedSearchState.load = jest.fn(() => { + nextState.savedSearchState.set(copySavedSearch(savedSearchMockWithTimeFieldNew)); + return Promise.resolve(savedSearchMockWithTimeFieldNew); + }); + } + + const getCurrentUrl = () => nextHistory.createHref(nextHistory.location); + return { + history: nextHistory, + state: nextState, + getCurrentUrl, + }; +} + describe('Test discover state', () => { let stopSync = () => {}; + let history: History; + let state: DiscoverStateContainer; + const getCurrentUrl = () => history.createHref(history.location); beforeEach(async () => { history = createBrowserHistory(); history.push('/'); state = getDiscoverStateContainer({ - savedSearch: savedSearchMock, services: discoverServiceMock, history, }); + state.savedSearchState.set(savedSearchMock); await state.appState.update({}, true); stopSync = startSync(state.appState); }); @@ -88,65 +123,38 @@ describe('Test discover state', () => { test('pauseAutoRefreshInterval sets refreshInterval.pause to true', async () => { history.push('/#?_g=(refreshInterval:(pause:!f,value:5000))'); expect(getCurrentUrl()).toBe('/#?_g=(refreshInterval:(pause:!f,value:5000))'); - await state.actions.pauseAutoRefreshInterval(); + await state.actions.setDataView(dataViewMock); expect(getCurrentUrl()).toBe('/#?_g=(refreshInterval:(pause:!t,value:5000))'); }); }); describe('Test discover initial state sort handling', () => { test('Non-empty sort in URL should not be overwritten by saved search sort', async () => { - history = createBrowserHistory(); - history.push('/#?_a=(sort:!(!(order_date,desc)))'); + const savedSearch = { + ...savedSearchMockWithTimeField, + ...{ sort: [['bytes', 'desc']] }, + } as SavedSearch; - state = getDiscoverStateContainer({ - savedSearch: { ...savedSearchMock, ...{ sort: [['bytes', 'desc']] } }, - services: discoverServiceMock, - history, - }); - await state.appState.update({}, true); - const stopSync = startSync(state.appState); - expect(state.appState.getState().sort).toEqual([['order_date', 'desc']]); - stopSync(); + const { state } = await getState('/#?_a=(sort:!(!(timestamp,desc)))', savedSearch); + const unsubscribe = state.actions.initializeAndSync(); + expect(state.appState.getState().sort).toEqual([['timestamp', 'desc']]); + unsubscribe(); }); - test('Empty sort in URL should use saved search sort for state', async () => { - history = createBrowserHistory(); - history.push('/#?_a=(sort:!())'); + test('Empty URL should use saved search sort for state', async () => { const nextSavedSearch = { ...savedSearchMock, ...{ sort: [['bytes', 'desc']] as SortOrder[] } }; - state = getDiscoverStateContainer({ - savedSearch: nextSavedSearch, - services: discoverServiceMock, - history, - }); - await state.appState.update({}, true); - const stopSync = startSync(state.appState); + const { state } = await getState('/', nextSavedSearch); + await state.actions.loadSavedSearch(savedSearchMock.id); + const unsubscribe = state.actions.initializeAndSync(); expect(state.appState.getState().sort).toEqual([['bytes', 'desc']]); - stopSync(); - }); - test('Empty sort in URL and saved search should sort by timestamp', async () => { - history = createBrowserHistory(); - history.push('/#?_a=(sort:!())'); - state = getDiscoverStateContainer({ - savedSearch: savedSearchMockWithTimeField, - services: discoverServiceMock, - history, - }); - await state.appState.update({}, true); - const stopSync = startSync(state.appState); - expect(state.appState.getState().sort).toEqual([['timestamp', 'desc']]); - stopSync(); + unsubscribe(); }); }); describe('Test discover state with legacy migration', () => { test('migration of legacy query ', async () => { - history = createBrowserHistory(); - history.push( - "/#?_a=(query:(query_string:(analyze_wildcard:!t,query:'type:nice%20name:%22yeah%22')))" + const { state } = await getState( + "/#?_a=(query:(query_string:(analyze_wildcard:!t,query:'type:nice%20name:%22yeah%22')))", + savedSearchMockWithTimeFieldNew ); - state = getDiscoverStateContainer({ - savedSearch: savedSearchMock, - services: discoverServiceMock, - history, - }); expect(state.appState.getState().query).toMatchInlineSnapshot(` Object { "language": "lucene", @@ -163,7 +171,7 @@ describe('Test discover state with legacy migration', () => { describe('createSearchSessionRestorationDataProvider', () => { let mockSavedSearch: SavedSearch = {} as unknown as SavedSearch; - history = createBrowserHistory(); + const history = createBrowserHistory(); const mockDataPlugin = dataPluginMock.createStartContract(); const searchSessionInfoProvider = createSearchSessionRestorationDataProvider({ data: mockDataPlugin, @@ -225,35 +233,320 @@ describe('createSearchSessionRestorationDataProvider', () => { }); }); }); +}); - describe('actions', () => { - beforeEach(async () => { - history = createBrowserHistory(); - state = getDiscoverStateContainer({ - services: discoverServiceMock, - history, - savedSearch: savedSearchMock, - }); +describe('actions', () => { + beforeEach(async () => { + discoverServiceMock.data.query.timefilter.timefilter.getTime = jest.fn(() => { + return { from: 'now-15d', to: 'now' }; + }); + discoverServiceMock.data.search.searchSource.create = jest + .fn() + .mockReturnValue(savedSearchMock.searchSource); + discoverServiceMock.core.savedObjects.client.resolve = jest.fn().mockReturnValue({ + saved_object: { + attributes: { + kibanaSavedObjectMeta: { + searchSourceJSON: + '{"query":{"query":"","language":"kuery"},"filter":[],"indexRefName":"kibanaSavedObjectMeta.searchSourceJSON.index"}', + }, + title: 'The saved search that will save the world', + sort: [], + columns: ['test123'], + description: 'description', + hideChart: false, + }, + id: 'the-saved-search-id', + type: 'search', + references: [ + { + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + id: 'the-data-view-id', + type: 'index-pattern', + }, + ], + namespaces: ['default'], + }, + outcome: 'exactMatch', }); + }); + + test('setDataView', async () => { + const { state } = await getState(''); + state.actions.setDataView(dataViewMock); + expect(state.internalState.getState().dataView).toBe(dataViewMock); + }); - test('setDataView', async () => { - state.actions.setDataView(dataViewMock); - expect(state.internalState.getState().dataView).toBe(dataViewMock); + test('fetchData', async () => { + const { state } = await getState('/'); + const dataState = state.dataState; + await state.actions.loadDataViewList(); + expect(dataState.data$.main$.value.fetchStatus).toBe(FetchStatus.LOADING); + await state.actions.loadSavedSearch(); + const unsubscribe = state.actions.initializeAndSync(); + state.actions.fetchData(); + await waitFor(() => { + expect(dataState.data$.documents$.value.fetchStatus).toBe(FetchStatus.COMPLETE); }); + unsubscribe(); + + expect(dataState.data$.totalHits$.value.result).toBe(0); + expect(dataState.data$.documents$.value.result).toEqual([]); + }); + test('loadDataViewList', async () => { + const { state } = await getState(''); + expect(state.internalState.getState().savedDataViews.length).toBe(3); + }); + test('loadSavedSearch with no id given an empty URL', async () => { + const { state, getCurrentUrl } = await getState(''); + await state.actions.loadDataViewList(); + const newSavedSearch = await state.actions.loadSavedSearch(); + expect(newSavedSearch?.id).toBeUndefined(); + const unsubscribe = state.actions.initializeAndSync(); + state.kbnUrlStateStorage.kbnUrlControls.flush(); + expect(getCurrentUrl()).toMatchInlineSnapshot( + `"/#?_g=(refreshInterval:(pause:!t,value:1000),time:(from:now-15d,to:now))&_a=(columns:!(default_column),index:the-data-view-id,interval:auto,sort:!())"` + ); + expect(state.savedSearchState.getHasChanged$().getValue()).toBe(false); + const { searchSource, ...savedSearch } = state.savedSearchState.getState(); + expect(savedSearch).toMatchInlineSnapshot(` + Object { + "columns": Array [ + "default_column", + ], + "refreshInterval": undefined, + "sort": Array [], + "timeRange": undefined, + "usesAdHocDataView": false, + } + `); + expect(searchSource.getField('index')?.id).toEqual('the-data-view-id'); + unsubscribe(); + }); + + test('loadNewSavedSearch given an empty URL using loadSavedSearch', async () => { + const { state, getCurrentUrl } = await getState('/'); - test('appendAdHocDataViews', async () => { - state.actions.appendAdHocDataViews(dataViewMock); - expect(state.internalState.getState().adHocDataViews).toEqual([dataViewMock]); + const newSavedSearch = await state.actions.loadSavedSearch(); + expect(newSavedSearch?.id).toBeUndefined(); + const unsubscribe = state.actions.initializeAndSync(); + state.kbnUrlStateStorage.kbnUrlControls.flush(); + expect(getCurrentUrl()).toMatchInlineSnapshot( + `"/#?_g=(refreshInterval:(pause:!t,value:1000),time:(from:now-15d,to:now))&_a=(columns:!(default_column),index:the-data-view-id,interval:auto,sort:!())"` + ); + expect(state.savedSearchState.getHasChanged$().getValue()).toBe(false); + unsubscribe(); + }); + test('loadNewSavedSearch with URL changing interval state', async () => { + const { state, getCurrentUrl } = await getState( + '/#?_a=(interval:month,columns:!(bytes))&_g=()' + ); + const newSavedSearch = await state.actions.loadSavedSearch(); + expect(newSavedSearch?.id).toBeUndefined(); + const unsubscribe = state.actions.initializeAndSync(); + state.kbnUrlStateStorage.kbnUrlControls.flush(); + expect(getCurrentUrl()).toMatchInlineSnapshot( + `"/#?_a=(columns:!(bytes),index:the-data-view-id,interval:month,sort:!())&_g=()"` + ); + expect(state.savedSearchState.getHasChanged$().getValue()).toBe(true); + unsubscribe(); + }); + test('loadSavedSearch with no id, given URL changes state', async () => { + const { state, getCurrentUrl } = await getState( + '/#?_a=(interval:month,columns:!(bytes))&_g=()' + ); + const newSavedSearch = await state.actions.loadSavedSearch(); + expect(newSavedSearch?.id).toBeUndefined(); + const unsubscribe = state.actions.initializeAndSync(); + state.kbnUrlStateStorage.kbnUrlControls.flush(); + expect(getCurrentUrl()).toMatchInlineSnapshot( + `"/#?_a=(columns:!(bytes),index:the-data-view-id,interval:month,sort:!())&_g=()"` + ); + expect(state.savedSearchState.getHasChanged$().getValue()).toBe(true); + unsubscribe(); + }); + test('loadSavedSearch given an empty URL, no state changes', async () => { + const { state, getCurrentUrl } = await getState('/', savedSearchMock); + const newSavedSearch = await state.actions.loadSavedSearch('the-saved-search-id'); + const unsubscribe = state.actions.initializeAndSync(); + state.kbnUrlStateStorage.kbnUrlControls.flush(); + expect(newSavedSearch?.id).toBe('the-saved-search-id'); + expect(getCurrentUrl()).toMatchInlineSnapshot( + `"/#?_g=(refreshInterval:(pause:!t,value:1000),time:(from:now-15d,to:now))&_a=(columns:!(default_column),index:the-data-view-id,interval:auto,sort:!())"` + ); + expect(state.savedSearchState.getHasChanged$().getValue()).toBe(false); + unsubscribe(); + }); + test('loadSavedSearch given a URL with different interval and columns modifying the state', async () => { + const url = '/#?_a=(interval:month,columns:!(message))&_g=()'; + const { state, getCurrentUrl } = await getState(url, savedSearchMock); + await state.actions.loadSavedSearch(savedSearchMock.id); + const unsubscribe = state.actions.initializeAndSync(); + state.kbnUrlStateStorage.kbnUrlControls.flush(); + expect(getCurrentUrl()).toMatchInlineSnapshot( + `"/#?_a=(columns:!(message),index:the-data-view-id,interval:month,sort:!())&_g=()"` + ); + expect(state.savedSearchState.getHasChanged$().getValue()).toBe(true); + unsubscribe(); + }); + test('loadSavedSearch data view handling', async () => { + const { state } = await getState('/', savedSearchMock); + await state.actions.loadSavedSearch(savedSearchMock.id); + expect(state.savedSearchState.getState().searchSource.getField('index')?.id).toBe( + 'the-data-view-id' + ); + state.savedSearchState.load = jest.fn().mockReturnValue(savedSearchMockWithTimeField); + await state.actions.loadSavedSearch('the-saved-search-id-with-timefield'); + expect(state.savedSearchState.getState().searchSource.getField('index')?.id).toBe( + 'index-pattern-with-timefield-id' + ); + }); + test('loadSavedSearch generating a new saved search, updated by ad-hoc data view', async () => { + const { state } = await getState('/'); + const dataViewSpecMock = { + id: 'mock-id', + title: 'mock-title', + timeFieldName: 'mock-time-field-name', + }; + const dataViewsCreateMock = discoverServiceMock.dataViews.create as jest.Mock; + dataViewsCreateMock.mockImplementation(() => ({ + ...dataViewMock, + ...dataViewSpecMock, + isPersisted: () => false, + })); + await state.actions.loadSavedSearch(undefined, undefined, dataViewSpecMock); + expect(state.savedSearchState.getInitial$().getValue().id).toEqual(undefined); + expect(state.savedSearchState.getCurrent$().getValue().id).toEqual(undefined); + expect( + state.savedSearchState.getInitial$().getValue().searchSource?.getField('index')?.id + ).toEqual(dataViewSpecMock.id); + expect( + state.savedSearchState.getCurrent$().getValue().searchSource?.getField('index')?.id + ).toEqual(dataViewSpecMock.id); + expect(state.savedSearchState.getHasChanged$().getValue()).toEqual(false); + expect(state.internalState.getState().adHocDataViews.length).toBe(1); + }); + + test('onChangeDataView', async () => { + const { state, getCurrentUrl } = await getState('/', savedSearchMock); + await state.actions.loadSavedSearch(savedSearchMock.id); + expect(state.savedSearchState.getState().searchSource.getField('index')!.id).toBe( + dataViewMock.id + ); + const unsubscribe = state.actions.initializeAndSync(); + state.kbnUrlStateStorage.kbnUrlControls.flush(); + expect(getCurrentUrl()).toMatchInlineSnapshot( + `"/#?_g=(refreshInterval:(pause:!t,value:1000),time:(from:now-15d,to:now))&_a=(columns:!(default_column),index:the-data-view-id,interval:auto,sort:!())"` + ); + await state.actions.onChangeDataView(dataViewComplexMock.id!); + await waitFor(() => { + expect(state.internalState.getState().dataView?.id).toBe(dataViewComplexMock.id); + }); + expect(state.appState.get().index).toBe(dataViewComplexMock.id); + expect(state.savedSearchState.getState().searchSource.getField('index')!.id).toBe( + dataViewComplexMock.id + ); + unsubscribe(); + }); + test('onDataViewCreated - persisted data view', async () => { + const { state } = await getState('/', savedSearchMock); + await state.actions.loadSavedSearch(savedSearchMock.id); + const unsubscribe = state.actions.initializeAndSync(); + await state.actions.onDataViewCreated(dataViewComplexMock); + await waitFor(() => { + expect(state.internalState.getState().dataView?.id).toBe(dataViewComplexMock.id); + }); + expect(state.appState.get().index).toBe(dataViewComplexMock.id); + expect(state.savedSearchState.getState().searchSource.getField('index')!.id).toBe( + dataViewComplexMock.id + ); + unsubscribe(); + }); + test('onDataViewCreated - ad-hoc data view', async () => { + const { state } = await getState('/', savedSearchMock); + await state.actions.loadSavedSearch(savedSearchMock.id); + const unsubscribe = state.actions.initializeAndSync(); + await state.actions.onDataViewCreated(dataViewAdHoc); + await waitFor(() => { + expect(state.internalState.getState().dataView?.id).toBe(dataViewAdHoc.id); + }); + expect(state.appState.get().index).toBe(dataViewAdHoc.id); + expect(state.savedSearchState.getState().searchSource.getField('index')!.id).toBe( + dataViewAdHoc.id + ); + unsubscribe(); + }); + test('onDataViewEdited - persisted data view', async () => { + const { state } = await getState('/', savedSearchMock); + await state.actions.loadSavedSearch(savedSearchMock.id); + const selectedDataView = state.internalState.getState().dataView; + await waitFor(() => { + expect(selectedDataView).toBe(dataViewMock); }); - test('removeAdHocDataViewById', async () => { - state.actions.appendAdHocDataViews(dataViewMock); - state.actions.removeAdHocDataViewById(dataViewMock.id!); - expect(state.internalState.getState().adHocDataViews).toEqual([]); + const unsubscribe = state.actions.initializeAndSync(); + await state.actions.onDataViewEdited(dataViewMock); + + await waitFor(() => { + expect(state.internalState.getState().dataView).not.toBe(selectedDataView); + }); + unsubscribe(); + }); + test('onDataViewEdited - ad-hoc data view', async () => { + const { state } = await getState('/', savedSearchMock); + const unsubscribe = state.actions.initializeAndSync(); + await state.actions.onDataViewCreated(dataViewAdHoc); + const previousId = dataViewAdHoc.id; + await state.actions.onDataViewEdited(dataViewAdHoc); + await waitFor(() => { + expect(state.internalState.getState().dataView?.id).not.toBe(previousId); }); - test('replaceAdHocDataViewWithId', async () => { - state.actions.appendAdHocDataViews(dataViewMock); - state.actions.replaceAdHocDataViewWithId(dataViewMock.id!, dataViewComplexMock); - expect(state.internalState.getState().adHocDataViews).toEqual([dataViewComplexMock]); + unsubscribe(); + }); + + test('onOpenSavedSearch - same target id', async () => { + const { state } = await getState('/', savedSearchMock); + const unsubscribe = state.actions.initializeAndSync(); + await state.actions.loadSavedSearch(savedSearchMock.id); + await state.savedSearchState.update({ nextState: { hideChart: true } }); + expect(state.savedSearchState.getState().hideChart).toBe(true); + await state.actions.onOpenSavedSearch(savedSearchMock.id!); + expect(state.savedSearchState.getState().hideChart).toBe(undefined); + unsubscribe(); + }); + + test('onCreateDefaultAdHocDataView', async () => { + discoverServiceMock.dataViews.create = jest.fn().mockReturnValue({ + ...dataViewMock, + isPersisted: () => false, + id: 'ad-hoc-id', + title: 'test', }); + const { state } = await getState('/', savedSearchMock); + await state.actions.loadSavedSearch(savedSearchMock.id); + const unsubscribe = state.actions.initializeAndSync(); + await state.actions.onCreateDefaultAdHocDataView('ad-hoc-test'); + expect(state.appState.getState().index).toBe('ad-hoc-id'); + expect(state.internalState.getState().adHocDataViews[0].id).toBe('ad-hoc-id'); + unsubscribe(); + }); + test('undoChanges', async () => { + const { state, getCurrentUrl } = await getState('/', savedSearchMock); + await state.actions.loadSavedSearch(savedSearchMock.id); + const unsubscribe = state.actions.initializeAndSync(); + state.kbnUrlStateStorage.kbnUrlControls.flush(); + expect(getCurrentUrl()).toMatchInlineSnapshot( + `"/#?_g=(refreshInterval:(pause:!t,value:1000),time:(from:now-15d,to:now))&_a=(columns:!(default_column),index:the-data-view-id,interval:auto,sort:!())"` + ); + await state.actions.onChangeDataView(dataViewComplexMock.id!); + state.kbnUrlStateStorage.kbnUrlControls.flush(); + expect(getCurrentUrl()).toMatchInlineSnapshot( + `"/#?_g=(refreshInterval:(pause:!t,value:1000),time:(from:now-15d,to:now))&_a=(columns:!(default_column),index:data-view-with-various-field-types-id,interval:auto,sort:!(!(data,desc)))"` + ); + await state.actions.undoChanges(); + state.kbnUrlStateStorage.kbnUrlControls.flush(); + expect(getCurrentUrl()).toMatchInlineSnapshot( + `"/#?_g=(refreshInterval:(pause:!t,value:1000),time:(from:now-15d,to:now))&_a=(columns:!(default_column),index:the-data-view-id,interval:auto,sort:!())"` + ); + unsubscribe(); }); }); diff --git a/src/plugins/discover/public/application/main/services/discover_state.ts b/src/plugins/discover/public/application/main/services/discover_state.ts index c283f09a48966..78027b52e62e9 100644 --- a/src/plugins/discover/public/application/main/services/discover_state.ts +++ b/src/plugins/discover/public/application/main/services/discover_state.ts @@ -16,12 +16,20 @@ import { } from '@kbn/kibana-utils-plugin/public'; import { DataPublicPluginStart, - FilterManager, QueryState, SearchSessionInfoProvider, } from '@kbn/data-plugin/public'; -import { DataView } from '@kbn/data-views-plugin/public'; -import { SavedSearch } from '@kbn/saved-search-plugin/public'; +import { DataView, DataViewSpec, DataViewType } from '@kbn/data-views-plugin/public'; +import type { SavedSearch } from '@kbn/saved-search-plugin/public'; +import { v4 as uuidv4 } from 'uuid'; +import { merge } from 'rxjs'; +import { AggregateQuery, Query, TimeRange } from '@kbn/es-query'; +import { FetchStatus } from '../../types'; +import { changeDataView } from '../hooks/utils/change_data_view'; +import { loadSavedSearch as loadNextSavedSearch } from './load_saved_search'; +import { buildStateSubscribe } from '../hooks/utils/build_state_subscribe'; +import { addLog } from '../../../utils/add_log'; +import { getUrlTracker } from '../../../kibana_services'; import { loadDataView, resolveDataView } from '../utils/resolve_data_view'; import { DiscoverDataStateContainer, getDataStateContainer } from './discover_data_state_container'; import { DiscoverSearchSessionManager } from './discover_search_session'; @@ -37,6 +45,13 @@ import { getInternalStateContainer, } from './discover_internal_state_container'; import { DiscoverServices } from '../../../build_services'; +import { + getDefaultAppState, + getSavedSearchContainer, + DiscoverSavedSearchContainer, +} from './discover_saved_search_container'; +import { updateFiltersReferences } from '../utils/update_filter_references'; + interface DiscoverStateContainerParams { /** * Browser history @@ -45,7 +60,7 @@ interface DiscoverStateContainerParams { /** * The current savedSearch */ - savedSearch: SavedSearch; + savedSearch?: string | SavedSearch; /** * core ui settings service */ @@ -53,81 +68,110 @@ interface DiscoverStateContainerParams { } export interface DiscoverStateContainer { - /** - * kbnUrlStateStorage - */ - kbnUrlStateStorage: IKbnUrlStateStorage; /** * App state, the _a part of the URL */ appState: DiscoverAppStateContainer; + /** + * Data fetching related state + **/ + dataState: DiscoverDataStateContainer; /** * Internal state that's used at several places in the UI */ internalState: DiscoverInternalStateContainer; + /** + * kbnUrlStateStorage - it keeps the state in sync with the URL + */ + kbnUrlStateStorage: IKbnUrlStateStorage; + /** + * State of saved search, the saved object of Discover + */ + savedSearchState: DiscoverSavedSearchContainer; /** * Service for handling search sessions */ searchSessionManager: DiscoverSearchSessionManager; /** - * Data fetching related state - **/ - dataState: DiscoverDataStateContainer; - /** - * functions executed by UI + * Complex functions to update multiple containers from UI */ actions: { /** - * Pause the auto refresh interval without pushing an entry to history + * Triggers fetching of new data from Elasticsearch + * If initial is true, then depending on the given configuration no fetch is triggered + * @param initial */ - pauseAutoRefreshInterval: () => Promise; + fetchData: (initial?: boolean) => void; /** - * Set the currently selected data view - */ - setDataView: (dataView: DataView) => void; - /** - * Load the data view of the given id - * A fallback data view is returned, given there's no match - * This is usually the default data view - * @param dataViewId - * @param savedSearch + * Initialize state with filters and query, start state syncing */ - loadAndResolveDataView: ( - dataViewId: string, - savedSearch: SavedSearch - ) => Promise<{ fallback: boolean; dataView: DataView }>; + initializeAndSync: () => () => void; /** * Load current list of data views, add them to internal state */ loadDataViewList: () => Promise; /** - * Set new adhoc data view list + * Load a saved search by id or create a new one that's not persisted yet + * @param savedSearchId + * @param dataView */ - setAdHocDataViews: (dataViews: DataView[]) => void; + loadSavedSearch: ( + savedSearchId?: string | undefined, + dataView?: DataView | undefined, + dataViewSpec?: DataViewSpec + ) => Promise; /** - * Append a given ad-hoc data views to the list of ad-hoc data view + * Create and select a ad-hoc data view by a given index pattern + * @param pattern */ - appendAdHocDataViews: (dataViews: DataView | DataView[]) => void; + onCreateDefaultAdHocDataView: (pattern: string) => Promise; /** - * Remove the ad-hoc data view of the given id from the list of ad-hoc data view - * @param id + * Triggered when a new data view is created + * @param dataView + */ + onDataViewCreated: (dataView: DataView) => Promise; + /** + * Triggered when a new data view is edited + * @param dataView + */ + onDataViewEdited: (dataView: DataView) => Promise; + /** + * Triggered when a saved search is opened in the savedObject finder + * @param savedSearchId */ - removeAdHocDataViewById: (id: string) => void; + onOpenSavedSearch: (savedSearchId: string) => void; /** - * Replace the data view of the given id with the given data view - * Used when the spec of a data view changed to prevent duplicates + * Triggered when the unified search bar query is updated + * @param payload + * @param isUpdate + */ + onUpdateQuery: ( + payload: { dateRange: TimeRange; query?: Query | AggregateQuery }, + isUpdate?: boolean + ) => void; + /** + * Triggered when the user selects a different data view in the data view picker * @param id + */ + onChangeDataView: (id: string) => Promise; + /** + * Triggered when an ad-hoc data view is persisted to allow sharing links and CSV * @param dataView */ - replaceAdHocDataViewWithId: (id: string, dataView: DataView) => void; + persistAdHocDataView: (dataView: DataView) => Promise; /** - * Initialize state with filters and query, start state syncing + * Set the currently selected data view */ - initializeAndSync: ( - dataView: DataView, - filterManager: FilterManager, - data: DataPublicPluginStart - ) => () => void; + setDataView: (dataView: DataView) => void; + /** + * Undo changes made to the saved search + */ + undoChanges: () => void; + /** + * When saving a saved search with an ad hoc data view, a new id needs to be generated for the data view + * This is to prevent duplicate ids messing with our system + */ + updateAdHocDataViewId: () => void; }; } @@ -137,7 +181,6 @@ export interface DiscoverStateContainer { */ export function getDiscoverStateContainer({ history, - savedSearch, services, }: DiscoverStateContainerParams): DiscoverStateContainer { const storeInSessionStorage = services.uiSettings.get('state:storeInSessionStorage'); @@ -155,79 +198,267 @@ export function getDiscoverStateContainer({ history, session: services.data.search.session, }); + const savedSearchContainer = getSavedSearchContainer({ + services, + }); + /** * App State Container, synced with URL */ - const appStateContainer = getDiscoverAppStateContainer({ stateStorage, savedSearch, services }); + const appStateContainer = getDiscoverAppStateContainer({ + stateStorage, + savedSearch: savedSearchContainer.getState(), + services, + }); const internalStateContainer = getInternalStateContainer(); - const pauseAutoRefreshInterval = async () => { - const state = stateStorage.get(GLOBAL_STATE_URL_KEY); - if (state?.refreshInterval && !state.refreshInterval.pause) { - await stateStorage.set( - GLOBAL_STATE_URL_KEY, - { ...state, refreshInterval: { ...state?.refreshInterval, pause: true } }, - { replace: true } - ); - } - }; - const dataStateContainer = getDataStateContainer({ services, searchSessionManager, getAppState: appStateContainer.getState, - getSavedSearch: () => { - // Simulating the behavior of the removed hook to always create a clean searchSource child that - // we then use to add query, filters, etc., will be removed soon. - return { ...savedSearch, searchSource: savedSearch.searchSource.createChild() }; - }, - appStateContainer, + getSavedSearch: savedSearchContainer.getState, }); + + const pauseAutoRefreshInterval = async (dataView: DataView) => { + if (dataView && (!dataView.isTimeBased() || dataView.type === DataViewType.ROLLUP)) { + const state = stateStorage.get(GLOBAL_STATE_URL_KEY); + if (state?.refreshInterval && !state.refreshInterval.pause) { + await stateStorage.set( + GLOBAL_STATE_URL_KEY, + { ...state, refreshInterval: { ...state?.refreshInterval, pause: true } }, + { replace: true } + ); + } + } + }; + const setDataView = (dataView: DataView) => { internalStateContainer.transitions.setDataView(dataView); + pauseAutoRefreshInterval(dataView); + savedSearchContainer.getState().searchSource.setField('index', dataView); }; - const setAdHocDataViews = (dataViews: DataView[]) => - internalStateContainer.transitions.setAdHocDataViews(dataViews); - const appendAdHocDataViews = (dataViews: DataView | DataView[]) => - internalStateContainer.transitions.appendAdHocDataViews(dataViews); - const replaceAdHocDataViewWithId = (id: string, dataView: DataView) => - internalStateContainer.transitions.replaceAdHocDataViewWithId(id, dataView); - const removeAdHocDataViewById = (id: string) => - internalStateContainer.transitions.removeAdHocDataViewById(id); const loadDataViewList = async () => { const dataViewList = await services.dataViews.getIdsWithTitle(true); internalStateContainer.transitions.setSavedDataViews(dataViewList); }; + /** + * Load the data view of the given id + * A fallback data view is returned, given there's no match + * This is usually the default data view + */ + const loadAndResolveDataView = async (id?: string, dataViewSpec?: DataViewSpec) => { + const { adHocDataViews, savedDataViews } = internalStateContainer.getState(); + const adHodDataView = adHocDataViews.find((dataView) => dataView.id === id); + if (adHodDataView) return { fallback: false, dataView: adHodDataView }; - const loadAndResolveDataView = async (id: string, actualSavedSearch: SavedSearch) => { - const nextDataViewData = await loadDataView(services.dataViews, services.uiSettings, id); - const nextDataView = resolveDataView( - nextDataViewData, - actualSavedSearch.searchSource, - services.toastNotifications - ); + const nextDataViewData = await loadDataView({ + services, + id, + dataViewSpec, + dataViewList: savedDataViews, + }); + const nextDataView = resolveDataView(nextDataViewData, services.toastNotifications); return { fallback: !nextDataViewData.stateValFound, dataView: nextDataView }; }; - const initializeAndSync = () => appStateContainer.initAndSync(savedSearch); + /** + * When saving a saved search with an ad hoc data view, a new id needs to be generated for the data view + * This is to prevent duplicate ids messing with our system + */ + const updateAdHocDataViewId = async () => { + const prevDataView = internalStateContainer.getState().dataView; + if (!prevDataView || prevDataView.isPersisted()) return; + const newDataView = await services.dataViews.create({ ...prevDataView.toSpec(), id: uuidv4() }); + services.dataViews.clearInstanceCache(prevDataView.id); + + updateFiltersReferences(prevDataView, newDataView); + + internalStateContainer.transitions.replaceAdHocDataViewWithId(prevDataView.id!, newDataView); + await appStateContainer.replaceUrlState({ index: newDataView.id }); + const trackingEnabled = Boolean(newDataView.isPersisted() || savedSearchContainer.getId()); + getUrlTracker().setTrackingEnabled(trackingEnabled); + + return newDataView; + }; + + const onOpenSavedSearch = async (newSavedSearchId: string) => { + addLog('[discoverState] onOpenSavedSearch', newSavedSearchId); + const currentSavedSearch = savedSearchContainer.getState(); + if (currentSavedSearch.id && currentSavedSearch.id === newSavedSearchId) { + addLog('[discoverState] undo changes since saved search did not change'); + await undoChanges(); + } else { + addLog('[discoverState] onOpenSavedSearch open view URL'); + history.push(`/view/${encodeURIComponent(newSavedSearchId)}`); + } + }; + + const onDataViewCreated = async (nextDataView: DataView) => { + if (!nextDataView.isPersisted()) { + internalStateContainer.transitions.appendAdHocDataViews(nextDataView); + } else { + await loadDataViewList(); + } + if (nextDataView.id) { + await onChangeDataView(nextDataView); + } + }; + + const onDataViewEdited = async (editedDataView: DataView) => { + if (editedDataView.isPersisted()) { + // Clear the current data view from the cache and create a new instance + // of it, ensuring we have a new object reference to trigger a re-render + services.dataViews.clearInstanceCache(editedDataView.id); + setDataView(await services.dataViews.create(editedDataView.toSpec(), true)); + } else { + await updateAdHocDataViewId(); + } + loadDataViewList(); + fetchData(); + }; + + const persistAdHocDataView = async (adHocDataView: DataView) => { + const persistedDataView = await services.dataViews.createAndSave({ + ...adHocDataView.toSpec(), + id: uuidv4(), + }); + services.dataViews.clearInstanceCache(adHocDataView.id); + updateFiltersReferences(adHocDataView, persistedDataView); + internalStateContainer.transitions.removeAdHocDataViewById(adHocDataView.id!); + await appStateContainer.update({ index: persistedDataView.id }, true); + return persistedDataView; + }; + + const loadSavedSearch = async ( + id?: string, + nextDataView?: DataView, + dataViewSpec?: DataViewSpec + ): Promise => { + const { dataView } = nextDataView + ? { dataView: nextDataView } + : await loadAndResolveDataView(appStateContainer.getState().index, dataViewSpec); + + const nextSavedSearch = await loadNextSavedSearch(id, dataView, { + appStateContainer, + savedSearchContainer, + }); + + const actualDataView = nextSavedSearch.searchSource.getField('index'); + if (actualDataView) { + setDataView(actualDataView); + if (!dataView.isPersisted()) { + internalStateContainer.transitions.appendAdHocDataViews(dataView); + } + } + dataStateContainer.reset(nextSavedSearch); + return nextSavedSearch; + }; + + const initializeAndSync = () => { + const unsubscribeData = dataStateContainer.subscribe(); + const appStateInitAndSyncUnsubscribe = appStateContainer.initAndSync( + savedSearchContainer.getState() + ); + + const appStateUnsubscribe = appStateContainer.subscribe( + buildStateSubscribe({ + appState: appStateContainer, + savedSearchState: savedSearchContainer, + dataState: dataStateContainer, + loadAndResolveDataView, + setDataView, + }) + ); + + const filterUnsubscribe = merge( + services.data.query.queryString.getUpdates$(), + services.filterManager.getFetches$() + ).subscribe(async () => { + await savedSearchContainer.update({ + nextDataView: internalStateContainer.getState().dataView, + nextState: appStateContainer.getState(), + filterAndQuery: true, + }); + fetchData(); + }); + + return () => { + unsubscribeData(); + appStateUnsubscribe(); + appStateInitAndSyncUnsubscribe(); + filterUnsubscribe.unsubscribe(); + }; + }; + + const onCreateDefaultAdHocDataView = async (pattern: string) => { + const newDataView = await services.dataViews.create({ + title: pattern, + }); + if (newDataView.fields.getByName('@timestamp')?.type === 'date') { + newDataView.timeFieldName = '@timestamp'; + } + internalStateContainer.transitions.appendAdHocDataViews(newDataView); + + await onChangeDataView(newDataView); + }; + + const onUpdateQuery = ( + payload: { dateRange: TimeRange; query?: Query | AggregateQuery }, + isUpdate?: boolean + ) => { + if (isUpdate === false) { + searchSessionManager.removeSearchSessionIdFromURL({ replace: false }); + dataStateContainer.fetch(); + } + }; + + /** + * Function triggered when user changes data view in the sidebar + */ + const onChangeDataView = async (id: string | DataView) => { + await changeDataView(id, { + services, + internalState: internalStateContainer, + appState: appStateContainer, + }); + }; + + const undoChanges = async () => { + const nextSavedSearch = savedSearchContainer.getInitial$().getValue(); + await savedSearchContainer.set(nextSavedSearch); + const newAppState = getDefaultAppState(nextSavedSearch, services); + await appStateContainer.replaceUrlState(newAppState); + return nextSavedSearch; + }; + const fetchData = (initial: boolean = false) => { + if (!initial || dataStateContainer.getInitialFetchStatus() === FetchStatus.LOADING) { + dataStateContainer.fetch(); + } + }; return { kbnUrlStateStorage: stateStorage, appState: appStateContainer, internalState: internalStateContainer, dataState: dataStateContainer, + savedSearchState: savedSearchContainer, searchSessionManager, actions: { - pauseAutoRefreshInterval, - setDataView, - loadAndResolveDataView, - loadDataViewList, - setAdHocDataViews, - appendAdHocDataViews, - replaceAdHocDataViewWithId, - removeAdHocDataViewById, initializeAndSync, + fetchData, + loadDataViewList, + loadSavedSearch, + onChangeDataView, + onCreateDefaultAdHocDataView, + onDataViewCreated, + onDataViewEdited, + onOpenSavedSearch, + onUpdateQuery, + persistAdHocDataView, + setDataView, + undoChanges, + updateAdHocDataViewId, }, }; } diff --git a/src/plugins/discover/public/application/main/services/discover_state_provider.tsx b/src/plugins/discover/public/application/main/services/discover_state_provider.tsx index 8cfdc45207ec7..70044385a1338 100644 --- a/src/plugins/discover/public/application/main/services/discover_state_provider.tsx +++ b/src/plugins/discover/public/application/main/services/discover_state_provider.tsx @@ -6,19 +6,42 @@ * Side Public License, v 1. */ -import React from 'react'; +import React, { useContext } from 'react'; +import useObservable from 'react-use/lib/useObservable'; +import { SavedSearch } from '@kbn/saved-search-plugin/public'; import { InternalStateProvider } from './discover_internal_state_container'; import { DiscoverAppStateProvider } from './discover_app_state_container'; import { DiscoverStateContainer } from './discover_state'; function createStateHelpers() { const context = React.createContext(null); + const useContainer = () => useContext(context); + const useSavedSearch = () => { + const container = useContainer(); + return useObservable( + container!.savedSearchState.getCurrent$(), + container!.savedSearchState.getCurrent$().getValue() + ); + }; + const useSavedSearchInitial = () => { + const container = useContainer(); + return useObservable( + container!.savedSearchState.getInitial$(), + container!.savedSearchState.getInitial$().getValue() + ); + }; return { Provider: context.Provider, + useSavedSearch, + useSavedSearchInitial, }; } -export const { Provider: DiscoverStateProvider } = createStateHelpers(); +export const { + Provider: DiscoverStateProvider, + useSavedSearchInitial, + useSavedSearch, +} = createStateHelpers(); export const DiscoverMainProvider = ({ value, diff --git a/src/plugins/discover/public/application/main/services/load_saved_search.ts b/src/plugins/discover/public/application/main/services/load_saved_search.ts new file mode 100644 index 0000000000000..93ca308d025ba --- /dev/null +++ b/src/plugins/discover/public/application/main/services/load_saved_search.ts @@ -0,0 +1,46 @@ +/* + * 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 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 or the Server + * Side Public License, v 1. + */ +import { DataView } from '@kbn/data-views-plugin/common'; +import { SavedSearch } from '@kbn/saved-search-plugin/public'; +import { DiscoverAppStateContainer } from './discover_app_state_container'; +import { DiscoverSavedSearchContainer } from './discover_saved_search_container'; +import { addLog } from '../../../utils/add_log'; + +export const loadSavedSearch = async ( + id: string | undefined, + dataView: DataView | undefined, + { + appStateContainer, + savedSearchContainer, + }: { + appStateContainer: DiscoverAppStateContainer; + savedSearchContainer: DiscoverSavedSearchContainer; + } +): Promise => { + addLog('[discoverState] loadSavedSearch'); + const isEmptyURL = appStateContainer.isEmptyURL(); + const isPersistedSearch = typeof id === 'string'; + if (isEmptyURL && isPersistedSearch) { + appStateContainer.set({}); + } + const appState = !isEmptyURL ? appStateContainer.getState() : undefined; + let nextSavedSearch = isPersistedSearch + ? await savedSearchContainer.load(id, dataView) + : await savedSearchContainer.new(dataView); + + if (appState) { + nextSavedSearch = savedSearchContainer.update({ + nextDataView: nextSavedSearch.searchSource.getField('index'), + nextState: appState, + }); + } + + await appStateContainer.resetWithSavedSearch(nextSavedSearch); + + return nextSavedSearch; +}; diff --git a/src/plugins/discover/public/application/main/utils/fetch_all.test.ts b/src/plugins/discover/public/application/main/utils/fetch_all.test.ts index 25e45f9470502..3d64177571ee1 100644 --- a/src/plugins/discover/public/application/main/utils/fetch_all.test.ts +++ b/src/plugins/discover/public/application/main/utils/fetch_all.test.ts @@ -11,7 +11,6 @@ import { reduce } from 'rxjs/operators'; import { SearchSource } from '@kbn/data-plugin/public'; import { RequestAdapter } from '@kbn/inspector-plugin/common'; import { savedSearchMock } from '../../../__mocks__/saved_search'; -import { ReduxLikeStateContainer } from '@kbn/kibana-utils-plugin/common'; import { discoverServiceMock } from '../../../__mocks__/services'; import { fetchAll } from './fetch_all'; import { @@ -26,8 +25,6 @@ import { fetchDocuments } from './fetch_documents'; import { fetchSql } from './fetch_sql'; import { buildDataTableRecord } from '../../../utils/build_data_record'; import { dataViewMock } from '../../../__mocks__/data_view'; -import { DiscoverAppState } from '../services/discover_app_state_container'; - jest.mock('./fetch_documents', () => ({ fetchDocuments: jest.fn().mockResolvedValue([]), })); @@ -54,7 +51,7 @@ const waitForNextTick = () => new Promise((resolve) => setTimeout(resolve, 0)); describe('test fetchAll', () => { let subjects: SavedSearchData; - let deps: Parameters[3]; + let deps: Parameters[2]; let searchSource: SearchSource; beforeEach(() => { subjects = { @@ -65,22 +62,21 @@ describe('test fetchAll', () => { fetchStatus: FetchStatus.UNINITIALIZED, }), }; + searchSource = savedSearchMock.searchSource.createChild(); + deps = { - appStateContainer: { - getState: () => { - return { interval: 'auto' }; - }, - } as ReduxLikeStateContainer, abortController: new AbortController(), - data: discoverServiceMock.data, inspectorAdapters: { requests: new RequestAdapter() }, + getAppState: () => ({}), searchSessionId: '123', initialFetchStatus: FetchStatus.UNINITIALIZED, useNewFieldsApi: true, - savedSearch: savedSearchMock, + savedSearch: { + ...savedSearchMock, + searchSource, + }, services: discoverServiceMock, }; - searchSource = savedSearchMock.searchSource.createChild(); mockFetchDocuments.mockReset().mockResolvedValue({ records: [] }); mockFetchSQL.mockReset().mockResolvedValue({ records: [] }); @@ -91,7 +87,7 @@ describe('test fetchAll', () => { subjects.main$.subscribe((value) => stateArr.push(value.fetchStatus)); - fetchAll(subjects, searchSource, false, deps); + fetchAll(subjects, false, deps); await waitForNextTick(); expect(stateArr).toEqual([ @@ -109,7 +105,7 @@ describe('test fetchAll', () => { ]; const documents = hits.map((hit) => buildDataTableRecord(hit, dataViewMock)); mockFetchDocuments.mockResolvedValue({ records: documents }); - fetchAll(subjects, searchSource, false, deps); + fetchAll(subjects, false, deps); await waitForNextTick(); expect(await collect()).toEqual([ { fetchStatus: FetchStatus.UNINITIALIZED }, @@ -136,7 +132,7 @@ describe('test fetchAll', () => { fetchStatus: FetchStatus.LOADING, recordRawType: RecordRawType.DOCUMENT, }); - fetchAll(subjects, searchSource, false, deps); + fetchAll(subjects, false, deps); await waitForNextTick(); subjects.totalHits$.next({ fetchStatus: FetchStatus.COMPLETE, @@ -159,7 +155,7 @@ describe('test fetchAll', () => { fetchStatus: FetchStatus.LOADING, recordRawType: RecordRawType.DOCUMENT, }); - fetchAll(subjects, searchSource, false, deps); + fetchAll(subjects, false, deps); await waitForNextTick(); subjects.totalHits$.next({ fetchStatus: FetchStatus.COMPLETE, @@ -186,7 +182,7 @@ describe('test fetchAll', () => { fetchStatus: FetchStatus.LOADING, recordRawType: RecordRawType.DOCUMENT, }); - fetchAll(subjects, searchSource, false, deps); + fetchAll(subjects, false, deps); await waitForNextTick(); subjects.totalHits$.next({ fetchStatus: FetchStatus.ERROR, @@ -221,7 +217,7 @@ describe('test fetchAll', () => { fetchStatus: FetchStatus.LOADING, recordRawType: RecordRawType.DOCUMENT, }); - fetchAll(subjects, searchSource, false, deps); + fetchAll(subjects, false, deps); await waitForNextTick(); subjects.totalHits$.next({ fetchStatus: FetchStatus.COMPLETE, @@ -254,21 +250,16 @@ describe('test fetchAll', () => { }); const query = { sql: 'SELECT * from foo' }; deps = { - appStateContainer: { - getState: () => { - return { interval: 'auto', query }; - }, - } as unknown as ReduxLikeStateContainer, abortController: new AbortController(), - data: discoverServiceMock.data, inspectorAdapters: { requests: new RequestAdapter() }, searchSessionId: '123', initialFetchStatus: FetchStatus.UNINITIALIZED, useNewFieldsApi: true, savedSearch: savedSearchMock, services: discoverServiceMock, + getAppState: () => ({ query }), }; - fetchAll(subjects, searchSource, false, deps); + fetchAll(subjects, false, deps); await waitForNextTick(); expect(await collect()).toEqual([ diff --git a/src/plugins/discover/public/application/main/utils/fetch_all.ts b/src/plugins/discover/public/application/main/utils/fetch_all.ts index 6ca474eb2dd41..f69399ab1d2bf 100644 --- a/src/plugins/discover/public/application/main/utils/fetch_all.ts +++ b/src/plugins/discover/public/application/main/utils/fetch_all.ts @@ -5,12 +5,11 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import { DataPublicPluginStart, ISearchSource } from '@kbn/data-plugin/public'; import { Adapters } from '@kbn/inspector-plugin/common'; -import { ReduxLikeStateContainer } from '@kbn/kibana-utils-plugin/common'; import type { SavedSearch, SortOrder } from '@kbn/saved-search-plugin/public'; import { BehaviorSubject, filter, firstValueFrom, map, merge, scan } from 'rxjs'; import { DiscoverAppState } from '../services/discover_app_state_container'; +import { updateVolatileSearchSource } from './update_search_source'; import { getRawRecordType } from './get_raw_record_type'; import { checkHitCount, @@ -20,7 +19,6 @@ import { sendLoadingMsg, sendResetMsg, } from '../hooks/use_saved_search_messages'; -import { updateSearchSource } from './update_search_source'; import { fetchDocuments } from './fetch_documents'; import { FetchStatus } from '../../types'; import { DataMsg, RecordRawType, SavedSearchData } from '../services/discover_data_state_container'; @@ -29,8 +27,7 @@ import { fetchSql } from './fetch_sql'; export interface FetchDeps { abortController: AbortController; - appStateContainer: ReduxLikeStateContainer; - data: DataPublicPluginStart; + getAppState: () => DiscoverAppState; initialFetchStatus: FetchStatus; inspectorAdapters: Adapters; savedSearch: SavedSearch; @@ -48,35 +45,28 @@ export interface FetchDeps { */ export function fetchAll( dataSubjects: SavedSearchData, - searchSource: ISearchSource, reset = false, fetchDeps: FetchDeps ): Promise { - const { - initialFetchStatus, - appStateContainer, - services, - useNewFieldsApi, - data, - inspectorAdapters, - } = fetchDeps; + const { initialFetchStatus, getAppState, services, inspectorAdapters, savedSearch } = fetchDeps; + const { data } = services; + const searchSource = savedSearch.searchSource.createChild(); try { const dataView = searchSource.getField('index')!; + const query = getAppState().query; + const recordRawType = getRawRecordType(query); if (reset) { - sendResetMsg(dataSubjects, initialFetchStatus); + sendResetMsg(dataSubjects, initialFetchStatus, recordRawType); } - const { sort, query } = appStateContainer.getState(); - const recordRawType = getRawRecordType(query); const useSql = recordRawType === RecordRawType.PLAIN; if (recordRawType === RecordRawType.DOCUMENT) { // Update the base searchSource, base for all child fetches - updateSearchSource(searchSource, false, { + updateVolatileSearchSource(searchSource, { dataView, services, - sort: sort as SortOrder[], - useNewFieldsApi, + sort: getAppState().sort as SortOrder[], }); } @@ -89,7 +79,7 @@ export function fetchAll( const response = useSql && query ? fetchSql(query, dataView, data, services.expressions, inspectorAdapters) - : fetchDocuments(searchSource.createCopy(), fetchDeps); + : fetchDocuments(searchSource, fetchDeps); // Handle results of the individual queries and forward the results to the corresponding dataSubjects response diff --git a/src/plugins/discover/public/application/main/utils/get_fetch_observable.ts b/src/plugins/discover/public/application/main/utils/get_fetch_observable.ts index 71490f05ac6ce..e19b17dad0489 100644 --- a/src/plugins/discover/public/application/main/utils/get_fetch_observable.ts +++ b/src/plugins/discover/public/application/main/utils/get_fetch_observable.ts @@ -35,10 +35,8 @@ export function getFetch$({ searchSource: ISearchSource; }) { const { timefilter } = data.query.timefilter; - const { filterManager } = data.query; return merge( refetch$, - filterManager.getFetches$(), timefilter.getFetch$(), timefilter.getAutoRefreshFetch$().pipe( tap((done) => { diff --git a/src/plugins/discover/public/application/main/utils/persist_saved_search.ts b/src/plugins/discover/public/application/main/utils/persist_saved_search.ts deleted file mode 100644 index ec26ad7d5880c..0000000000000 --- a/src/plugins/discover/public/application/main/utils/persist_saved_search.ts +++ /dev/null @@ -1,107 +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 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 or the Server - * Side Public License, v 1. - */ -import { isOfAggregateQueryType } from '@kbn/es-query'; -import { DataView } from '@kbn/data-views-plugin/public'; -import { SavedObjectSaveOpts } from '@kbn/saved-objects-plugin/public'; -import { SavedSearch, SortOrder, saveSavedSearch } from '@kbn/saved-search-plugin/public'; -import { DiscoverAppState } from '../services/discover_app_state_container'; -import { updateSearchSource } from './update_search_source'; -import { DiscoverServices } from '../../../build_services'; -/** - * Helper function to update and persist the given savedSearch - */ -export async function persistSavedSearch( - savedSearch: SavedSearch, - { - dataView, - onError, - onSuccess, - services, - saveOptions, - state, - }: { - dataView: DataView; - onError: (error: Error, savedSearch: SavedSearch) => void; - onSuccess: (id: string) => void; - saveOptions: SavedObjectSaveOpts; - services: DiscoverServices; - state: DiscoverAppState; - } -) { - updateSearchSource(savedSearch.searchSource, true, { - dataView, - services, - sort: state.sort as SortOrder[], - useNewFieldsApi: false, - }); - - savedSearch.columns = state.columns || []; - savedSearch.sort = (state.sort as SortOrder[]) || []; - if (state.grid) { - savedSearch.grid = state.grid; - } - if (typeof state.hideChart !== 'undefined') { - savedSearch.hideChart = state.hideChart; - } - if (typeof state.rowHeight !== 'undefined') { - savedSearch.rowHeight = state.rowHeight; - } - - if (state.viewMode) { - savedSearch.viewMode = state.viewMode; - } - - if (typeof state.breakdownField !== 'undefined') { - savedSearch.breakdownField = state.breakdownField; - } else if (savedSearch.breakdownField) { - savedSearch.breakdownField = ''; - } - - if (state.hideAggregatedPreview) { - savedSearch.hideAggregatedPreview = state.hideAggregatedPreview; - } - - // add a flag here to identify text based language queries - // these should be filtered out from the visualize editor - const isTextBasedQuery = state.query && isOfAggregateQueryType(state.query); - if (savedSearch.isTextBasedQuery || isTextBasedQuery) { - savedSearch.isTextBasedQuery = isTextBasedQuery; - } - - savedSearch.usesAdHocDataView = !dataView.isPersisted(); - - const { from, to } = services.timefilter.getTime(); - const refreshInterval = services.timefilter.getRefreshInterval(); - savedSearch.timeRange = - savedSearch.timeRestore || savedSearch.timeRange - ? { - from, - to, - } - : undefined; - savedSearch.refreshInterval = - savedSearch.timeRestore || savedSearch.refreshInterval - ? { value: refreshInterval.value, pause: refreshInterval.pause } - : undefined; - - try { - const id = await saveSavedSearch( - savedSearch, - saveOptions, - services.core.savedObjects.client, - services.savedObjectsTagging - ); - if (id) { - onSuccess(id); - } - return { id }; - } catch (saveError) { - onError(saveError, savedSearch); - return { error: saveError }; - } -} diff --git a/src/plugins/discover/public/application/main/utils/resolve_data_view.test.ts b/src/plugins/discover/public/application/main/utils/resolve_data_view.test.ts index f3db91d5ce40b..10e1780d233b8 100644 --- a/src/plugins/discover/public/application/main/utils/resolve_data_view.test.ts +++ b/src/plugins/discover/public/application/main/utils/resolve_data_view.test.ts @@ -7,23 +7,30 @@ */ import { loadDataView } from './resolve_data_view'; -import { dataViewsMock } from '../../../__mocks__/data_views'; import { dataViewMock } from '../../../__mocks__/data_view'; -import { configMock } from '../../../__mocks__/config'; +import { discoverServiceMock as services } from '../../../__mocks__/services'; describe('Resolve data view tests', () => { test('returns valid data for an existing data view', async () => { - const dataViewId = 'the-data-view-id'; - const result = await loadDataView(dataViewsMock, configMock, dataViewId); + const id = 'the-data-view-id'; + const result = await loadDataView({ + id, + services, + dataViewList: [], + }); expect(result.loaded).toEqual(dataViewMock); + expect(result.stateVal).toEqual(id); expect(result.stateValFound).toEqual(true); - expect(result.stateVal).toEqual(dataViewId); }); test('returns fallback data for an invalid data view', async () => { - const dataViewId = 'invalid-id'; - const result = await loadDataView(dataViewsMock, configMock, dataViewId); + const id = 'invalid-id'; + const result = await loadDataView({ + id, + services, + dataViewList: [], + }); expect(result.loaded).toEqual(dataViewMock); expect(result.stateValFound).toBe(false); - expect(result.stateVal).toBe(dataViewId); + expect(result.stateVal).toBe(id); }); }); diff --git a/src/plugins/discover/public/application/main/utils/resolve_data_view.ts b/src/plugins/discover/public/application/main/utils/resolve_data_view.ts index 2584d9b7c8279..c6fdd4e3f4a9a 100644 --- a/src/plugins/discover/public/application/main/utils/resolve_data_view.ts +++ b/src/plugins/discover/public/application/main/utils/resolve_data_view.ts @@ -7,14 +7,9 @@ */ import { i18n } from '@kbn/i18n'; -import type { - DataView, - DataViewListItem, - DataViewsContract, - DataViewSpec, -} from '@kbn/data-views-plugin/public'; -import type { ISearchSource } from '@kbn/data-plugin/public'; -import type { IUiSettingsClient, ToastsStart } from '@kbn/core/public'; +import type { DataView, DataViewListItem, DataViewSpec } from '@kbn/data-views-plugin/public'; +import type { ToastsStart } from '@kbn/core/public'; +import { DiscoverServices } from '../../../build_services'; interface DataViewData { /** * List of existing data views @@ -37,13 +32,19 @@ interface DataViewData { /** * Function to load the given data view by id, providing a fallback if it doesn't exist */ -export async function loadDataView( - dataViews: DataViewsContract, - config: IUiSettingsClient, - id?: string, - dataViewSpec?: DataViewSpec -): Promise { - const dataViewList = await dataViews.getIdsWithTitle(); +export async function loadDataView({ + id, + dataViewSpec, + services, + dataViewList, +}: { + id?: string; + dataViewSpec?: DataViewSpec; + services: DiscoverServices; + dataViewList: DataViewListItem[]; +}): Promise { + const { dataViews } = services; + let fetchId: string | undefined = id; /** @@ -97,7 +98,7 @@ export async function loadDataView( // we can be certain that the data view exists due to an earlier hasData check loaded: fetchedDataView || defaultDataView!, stateVal: fetchId, - stateValFound: !!fetchId && !!fetchedDataView, + stateValFound: Boolean(fetchId) && Boolean(fetchedDataView), }; } @@ -107,18 +108,11 @@ export async function loadDataView( */ export function resolveDataView( ip: DataViewData, - searchSource: ISearchSource, toastNotifications: ToastsStart, isTextBasedQuery?: boolean ) { const { loaded: loadedDataView, stateVal, stateValFound } = ip; - const ownDataView = searchSource.getOwnField('index'); - - if (ownDataView && !stateVal) { - return ownDataView; - } - if (stateVal && !stateValFound) { const warningTitle = i18n.translate('discover.valueIsNotConfiguredDataViewIDWarningTitle', { defaultMessage: '{stateVal} is not a configured data view ID', @@ -127,20 +121,6 @@ export function resolveDataView( }, }); - if (ownDataView) { - toastNotifications.addWarning({ - title: warningTitle, - text: i18n.translate('discover.showingSavedDataViewWarningDescription', { - defaultMessage: 'Showing the saved data view: "{ownDataViewTitle}" ({ownDataViewId})', - values: { - ownDataViewTitle: ownDataView.getIndexPattern(), - ownDataViewId: ownDataView.id, - }, - }), - 'data-test-subj': 'dscDataViewNotFoundShowSavedWarning', - }); - return ownDataView; - } if (!Boolean(isTextBasedQuery)) { toastNotifications.addWarning({ title: warningTitle, diff --git a/src/plugins/discover/public/application/main/utils/update_search_source.test.ts b/src/plugins/discover/public/application/main/utils/update_search_source.test.ts index 5ab1dd962652f..cc09732161890 100644 --- a/src/plugins/discover/public/application/main/utils/update_search_source.test.ts +++ b/src/plugins/discover/public/application/main/utils/update_search_source.test.ts @@ -6,55 +6,51 @@ * Side Public License, v 1. */ -import { updateSearchSource } from './update_search_source'; +import { updateVolatileSearchSource } from './update_search_source'; import { createSearchSourceMock } from '@kbn/data-plugin/common/search/search_source/mocks'; import { dataViewMock } from '../../../__mocks__/data_view'; import type { SortOrder } from '@kbn/saved-search-plugin/public'; import { discoverServiceMock } from '../../../__mocks__/services'; +import { IUiSettingsClient } from '@kbn/core-ui-settings-browser'; -describe('updateSearchSource', () => { +const getUiSettingsMock = (value: boolean) => { + return { + get: jest.fn(() => value), + } as unknown as IUiSettingsClient; +}; + +describe('updateVolatileSearchSource', () => { test('updates a given search source', async () => { - const persistentSearchSourceMock = createSearchSourceMock({}); - const volatileSearchSourceMock = createSearchSourceMock({}); - volatileSearchSourceMock.setParent(persistentSearchSourceMock); - updateSearchSource(volatileSearchSourceMock, false, { + const searchSource = createSearchSourceMock({}); + discoverServiceMock.uiSettings = getUiSettingsMock(true); + updateVolatileSearchSource(searchSource, { dataView: dataViewMock, services: discoverServiceMock, sort: [] as SortOrder[], - useNewFieldsApi: false, }); - expect(persistentSearchSourceMock.getField('index')).toEqual(dataViewMock); - expect(volatileSearchSourceMock.getField('fields')).toBe(undefined); + expect(searchSource.getField('fields')).toBe(undefined); }); test('updates a given search source with the usage of the new fields api', async () => { - const persistentSearchSourceMock = createSearchSourceMock({}); - const volatileSearchSourceMock = createSearchSourceMock({}); - volatileSearchSourceMock.setParent(persistentSearchSourceMock); - updateSearchSource(volatileSearchSourceMock, false, { + const searchSource = createSearchSourceMock({}); + discoverServiceMock.uiSettings = getUiSettingsMock(false); + updateVolatileSearchSource(searchSource, { dataView: dataViewMock, services: discoverServiceMock, sort: [] as SortOrder[], - useNewFieldsApi: true, }); - expect(persistentSearchSourceMock.getField('index')).toEqual(dataViewMock); - expect(volatileSearchSourceMock.getField('fields')).toEqual([ - { field: '*', include_unmapped: 'true' }, - ]); - expect(volatileSearchSourceMock.getField('fieldsFromSource')).toBe(undefined); + expect(searchSource.getField('fields')).toEqual([{ field: '*', include_unmapped: 'true' }]); + expect(searchSource.getField('fieldsFromSource')).toBe(undefined); }); test('updates a given search source when showUnmappedFields option is set to true', async () => { - const persistentSearchSourceMock = createSearchSourceMock({}); const volatileSearchSourceMock = createSearchSourceMock({}); - volatileSearchSourceMock.setParent(persistentSearchSourceMock); - updateSearchSource(volatileSearchSourceMock, false, { + discoverServiceMock.uiSettings = getUiSettingsMock(false); + updateVolatileSearchSource(volatileSearchSourceMock, { dataView: dataViewMock, services: discoverServiceMock, sort: [] as SortOrder[], - useNewFieldsApi: true, }); - expect(persistentSearchSourceMock.getField('index')).toEqual(dataViewMock); expect(volatileSearchSourceMock.getField('fields')).toEqual([ { field: '*', include_unmapped: 'true' }, ]); @@ -62,16 +58,13 @@ describe('updateSearchSource', () => { }); test('does not explicitly request fieldsFromSource when not using fields API', async () => { - const persistentSearchSourceMock = createSearchSourceMock({}); const volatileSearchSourceMock = createSearchSourceMock({}); - volatileSearchSourceMock.setParent(persistentSearchSourceMock); - updateSearchSource(volatileSearchSourceMock, false, { + discoverServiceMock.uiSettings = getUiSettingsMock(true); + updateVolatileSearchSource(volatileSearchSourceMock, { dataView: dataViewMock, services: discoverServiceMock, sort: [] as SortOrder[], - useNewFieldsApi: false, }); - expect(persistentSearchSourceMock.getField('index')).toEqual(dataViewMock); expect(volatileSearchSourceMock.getField('fields')).toEqual(undefined); expect(volatileSearchSourceMock.getField('fieldsFromSource')).toBe(undefined); }); diff --git a/src/plugins/discover/public/application/main/utils/update_search_source.ts b/src/plugins/discover/public/application/main/utils/update_search_source.ts index 4966a66cf9687..4152b57a34a0f 100644 --- a/src/plugins/discover/public/application/main/utils/update_search_source.ts +++ b/src/plugins/discover/public/application/main/utils/update_search_source.ts @@ -9,58 +9,47 @@ import { ISearchSource } from '@kbn/data-plugin/public'; import { DataViewType, DataView } from '@kbn/data-views-plugin/public'; import type { SortOrder } from '@kbn/saved-search-plugin/public'; -import { SORT_DEFAULT_ORDER_SETTING } from '../../../../common'; +import { SEARCH_FIELDS_FROM_SOURCE, SORT_DEFAULT_ORDER_SETTING } from '../../../../common'; import { DiscoverServices } from '../../../build_services'; import { getSortForSearchSource } from '../../../utils/sorting'; /** * Helper function to update the given searchSource before fetching/sharing/persisting */ -export function updateSearchSource( +export function updateVolatileSearchSource( searchSource: ISearchSource, - persist = true, { dataView, services, sort, - useNewFieldsApi, }: { dataView: DataView; services: DiscoverServices; - sort: SortOrder[]; - useNewFieldsApi: boolean; + sort?: SortOrder[]; } ) { const { uiSettings, data } = services; - const parentSearchSource = persist ? searchSource : searchSource.getParent()!; - - parentSearchSource - .setField('index', dataView) - .setField('query', data.query.queryString.getQuery() || null) - .setField('filter', data.query.filterManager.getFilters()); - - if (!persist) { - const usedSort = getSortForSearchSource( - sort, - dataView, - uiSettings.get(SORT_DEFAULT_ORDER_SETTING) - ); - searchSource.setField('trackTotalHits', true).setField('sort', usedSort); - - if (dataView.type !== DataViewType.ROLLUP) { - // Set the date range filter fields from timeFilter using the absolute format. Search sessions requires that it be converted from a relative range - searchSource.setField('filter', data.query.timefilter.timefilter.createFilter(dataView)); - } + const usedSort = getSortForSearchSource( + sort, + dataView, + uiSettings.get(SORT_DEFAULT_ORDER_SETTING) + ); + const useNewFieldsApi = !uiSettings.get(SEARCH_FIELDS_FROM_SOURCE); + searchSource.setField('trackTotalHits', true).setField('sort', usedSort); + + if (dataView.type !== DataViewType.ROLLUP) { + // Set the date range filter fields from timeFilter using the absolute format. Search sessions requires that it be converted from a relative range + searchSource.setField('filter', data.query.timefilter.timefilter.createFilter(dataView)); + } - if (useNewFieldsApi) { - searchSource.removeField('fieldsFromSource'); - const fields: Record = { field: '*' }; + if (useNewFieldsApi) { + searchSource.removeField('fieldsFromSource'); + const fields: Record = { field: '*' }; - fields.include_unmapped = 'true'; + fields.include_unmapped = 'true'; - searchSource.setField('fields', [fields]); - } else { - searchSource.removeField('fields'); - } + searchSource.setField('fields', [fields]); + } else { + searchSource.removeField('fields'); } } diff --git a/src/plugins/discover/public/hooks/use_confirm_persistence_prompt.ts b/src/plugins/discover/public/hooks/use_confirm_persistence_prompt.ts index d5ea19c4316b3..17aeb5bbeea37 100644 --- a/src/plugins/discover/public/hooks/use_confirm_persistence_prompt.ts +++ b/src/plugins/discover/public/hooks/use_confirm_persistence_prompt.ts @@ -7,15 +7,12 @@ */ import { useCallback } from 'react'; -import { v4 as uuidv4 } from 'uuid'; import { i18n } from '@kbn/i18n'; import type { DataView } from '@kbn/data-views-plugin/public'; import { SavedSearch } from '@kbn/saved-search-plugin/public'; import { useDiscoverServices } from './use_discover_services'; import { showConfirmPanel } from './show_confirm_panel'; -import { persistSavedSearch } from '../application/main/utils/persist_saved_search'; import { DiscoverStateContainer } from '../application/main/services/discover_state'; -import { updateFiltersReferences } from '../application/main/utils/update_filter_references'; export const useConfirmPersistencePrompt = (stateContainer: DiscoverStateContainer) => { const services = useDiscoverServices(); @@ -23,16 +20,7 @@ export const useConfirmPersistencePrompt = (stateContainer: DiscoverStateContain const persistDataView: (adHocDataView: DataView) => Promise = useCallback( async (adHocDataView) => { try { - const persistedDataView = await services.dataViews.createAndSave({ - ...adHocDataView.toSpec(), - id: uuidv4(), - }); - services.dataViews.clearInstanceCache(adHocDataView.id); - - updateFiltersReferences(adHocDataView, persistedDataView); - - stateContainer.actions.removeAdHocDataViewById(adHocDataView.id!); - await stateContainer.appState.update({ index: persistedDataView.id }, true); + const persistedDataView = await stateContainer.actions.persistAdHocDataView(adHocDataView); const message = i18n.translate('discover.dataViewPersist.message', { defaultMessage: "Saved '{dataViewName}'", @@ -50,7 +38,7 @@ export const useConfirmPersistencePrompt = (stateContainer: DiscoverStateContain throw new Error(error); } }, - [services.dataViews, services.toastNotifications, stateContainer] + [services.toastNotifications, stateContainer] ); const openConfirmSavePrompt: (dataView: DataView) => Promise = useCallback( @@ -99,17 +87,15 @@ export const useConfirmPersistencePrompt = (stateContainer: DiscoverStateContain ); const updateSavedSearch = useCallback( - ({ savedSearch, dataView, state }) => { - return persistSavedSearch(savedSearch, { - dataView, - onSuccess: () => onUpdateSuccess(savedSearch), - onError: (error) => onUpdateError(error, savedSearch), - state, - saveOptions: {}, - services, - }); + async ({ savedSearch }) => { + try { + await stateContainer.savedSearchState.persist(savedSearch); + onUpdateSuccess(savedSearch); + } catch (e) { + onUpdateError(e, savedSearch); + } }, - [onUpdateError, onUpdateSuccess, services] + [onUpdateError, onUpdateSuccess, stateContainer.savedSearchState] ); return { openConfirmSavePrompt, updateSavedSearch }; diff --git a/src/plugins/discover/public/services/doc_views/components/doc_viewer_table/table.tsx b/src/plugins/discover/public/services/doc_views/components/doc_viewer_table/table.tsx index 56232a31f9a0f..30e5586a9d452 100644 --- a/src/plugins/discover/public/services/doc_views/components/doc_viewer_table/table.tsx +++ b/src/plugins/discover/public/services/doc_views/components/doc_viewer_table/table.tsx @@ -404,7 +404,7 @@ export const DocViewerTable = ({ ) : ( - + {headers} {rowElements} diff --git a/src/plugins/discover/public/services/saved_searches/restore_from_saved_search.ts b/src/plugins/discover/public/services/saved_searches/restore_from_saved_search.ts index 550c36408977b..8e87d891aef1c 100644 --- a/src/plugins/discover/public/services/saved_searches/restore_from_saved_search.ts +++ b/src/plugins/discover/public/services/saved_searches/restore_from_saved_search.ts @@ -17,18 +17,14 @@ export const restoreStateFromSavedSearch = ({ savedSearch: SavedSearch; timefilter: TimefilterContract; }) => { - if (!savedSearch) { + if (!savedSearch || !savedSearch.timeRestore) { return; } - if (savedSearch.timeRestore && savedSearch.timeRange && isTimeRangeValid(savedSearch.timeRange)) { + if (savedSearch.timeRange && isTimeRangeValid(savedSearch.timeRange)) { timefilter.setTime(savedSearch.timeRange); } - if ( - savedSearch.timeRestore && - savedSearch.refreshInterval && - isRefreshIntervalValid(savedSearch.refreshInterval) - ) { + if (savedSearch.refreshInterval && isRefreshIntervalValid(savedSearch.refreshInterval)) { timefilter.setRefreshInterval(savedSearch.refreshInterval); } }; diff --git a/src/plugins/discover/public/utils/add_log.ts b/src/plugins/discover/public/utils/add_log.ts index fd2de53ab4e61..7b14f7f69b452 100644 --- a/src/plugins/discover/public/utils/add_log.ts +++ b/src/plugins/discover/public/utils/add_log.ts @@ -14,8 +14,15 @@ export const addLog = (message: string, payload?: unknown) => { // @ts-expect-error - if (window?.ELASTIC_DISCOVER_LOGGER) { - // eslint-disable-next-line no-console - console.log(`[Discover] ${message}`, payload); + const logger = window?.ELASTIC_DISCOVER_LOGGER; + + if (logger) { + if (logger === 'debug') { + // eslint-disable-next-line no-console + console.log(`[Discover] ${message}`, payload); + } else { + // eslint-disable-next-line no-console + console.log(`[Discover] ${message}`); + } } }; diff --git a/src/plugins/discover/public/utils/breadcrumbs.ts b/src/plugins/discover/public/utils/breadcrumbs.ts index ca968b43fdb2a..bf482587efe30 100644 --- a/src/plugins/discover/public/utils/breadcrumbs.ts +++ b/src/plugins/discover/public/utils/breadcrumbs.ts @@ -8,7 +8,6 @@ import { ChromeStart } from '@kbn/core/public'; import { i18n } from '@kbn/i18n'; -import { SavedSearch } from '@kbn/saved-search-plugin/public'; export function getRootBreadcrumbs(breadcrumb?: string) { return [ @@ -34,18 +33,18 @@ export function getSavedSearchBreadcrumbs(id: string) { * Helper function to set the Discover's breadcrumb * if there's an active savedSearch, its title is appended */ -export function setBreadcrumbsTitle(savedSearch: SavedSearch, chrome: ChromeStart) { +export function setBreadcrumbsTitle(title: string | undefined, chrome: ChromeStart) { const discoverBreadcrumbsTitle = i18n.translate('discover.discoverBreadcrumbTitle', { defaultMessage: 'Discover', }); - if (savedSearch.id && savedSearch.title) { + if (title) { chrome.setBreadcrumbs([ { text: discoverBreadcrumbsTitle, href: '#/', }, - { text: savedSearch.title }, + { text: title }, ]); } else { chrome.setBreadcrumbs([ diff --git a/src/plugins/discover/tsconfig.json b/src/plugins/discover/tsconfig.json index 546c1cd431b6a..826edbbbcd5ce 100644 --- a/src/plugins/discover/tsconfig.json +++ b/src/plugins/discover/tsconfig.json @@ -48,7 +48,6 @@ "@kbn/ui-theme", "@kbn/react-field", "@kbn/monaco", - "@kbn/core-notifications-browser", "@kbn/config-schema", "@kbn/storybook", "@kbn/shared-ux-router", diff --git a/test/functional/apps/discover/group1/_discover.ts b/test/functional/apps/discover/group1/_discover.ts index c3be725737470..7d7b18b053677 100644 --- a/test/functional/apps/discover/group1/_discover.ts +++ b/test/functional/apps/discover/group1/_discover.ts @@ -336,7 +336,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await retry.try(async () => { const currentHash = await browser.execute<[], string>('return window.location.hash'); expect(currentHash).to.be(originalHash); - expect(await testSubjects.exists('dscDataViewNotFoundShowSavedWarning')).to.be(true); + expect(await testSubjects.exists('dscDataViewNotFoundShowDefaultWarning')).to.be(true); }); }); }); diff --git a/test/functional/apps/discover/group1/_discover_histogram_breakdown.ts b/test/functional/apps/discover/group1/_discover_histogram_breakdown.ts index 805d59aee937d..ab8831ffee282 100644 --- a/test/functional/apps/discover/group1/_discover_histogram_breakdown.ts +++ b/test/functional/apps/discover/group1/_discover_histogram_breakdown.ts @@ -41,12 +41,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.clickLegendFilter('png', '+'); await PageObjects.header.waitUntilLoadingHasFinished(); expect(await filterBar.hasFilter('extension.raw', 'png')).to.be(true); + await filterBar.removeFilter('extension.raw'); }); it('should save breakdown field in saved search', async () => { - await filterBar.removeFilter('extension.raw'); + await PageObjects.discover.chooseBreakdownField('extension.raw'); await PageObjects.discover.saveSearch('with breakdown'); - await PageObjects.discover.clickNewSearchButton(); await PageObjects.header.waitUntilLoadingHasFinished(); const prevList = await PageObjects.discover.getHistogramLegendList(); diff --git a/test/functional/apps/discover/group2/_adhoc_data_views.ts b/test/functional/apps/discover/group2/_adhoc_data_views.ts index 9699dc36dd81c..d848f3af33335 100644 --- a/test/functional/apps/discover/group2/_adhoc_data_views.ts +++ b/test/functional/apps/discover/group2/_adhoc_data_views.ts @@ -11,7 +11,6 @@ import { FtrProviderContext } from '../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const dataGrid = getService('dataGrid'); - const toasts = getService('toasts'); const esArchiver = getService('esArchiver'); const filterBar = getService('filterBar'); const fieldEditor = getService('fieldEditor'); @@ -240,45 +239,5 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const newDataViewId = await PageObjects.discover.getCurrentDataViewId(); expect(prevDataViewId).not.to.equal(newDataViewId); }); - - it('should notify about invalid filter reffs', async () => { - await PageObjects.discover.createAdHocDataView('logstas', true); - await PageObjects.header.waitUntilLoadingHasFinished(); - - await filterBar.addFilter({ - field: 'nestedField.child', - operation: 'is', - value: 'nestedValue', - }); - await PageObjects.header.waitUntilLoadingHasFinished(); - - await filterBar.addFilter({ field: 'extension', operation: 'is', value: 'jpg' }); - await PageObjects.header.waitUntilLoadingHasFinished(); - - const first = await PageObjects.discover.getCurrentDataViewId(); - // trigger data view id update - await PageObjects.discover.addRuntimeField( - '_bytes-runtimefield', - `emit((doc["bytes"].value * 2).toString())` - ); - await PageObjects.header.waitUntilLoadingHasFinished(); - - const second = await PageObjects.discover.getCurrentDataViewId(); - expect(first).not.equal(second); - - await toasts.dismissAllToasts(); - - await browser.goBack(); - await PageObjects.header.waitUntilLoadingHasFinished(); - - const [firstToast, secondToast] = await toasts.getAllToastElements(); - - expect([await firstToast.getVisibleText(), await secondToast.getVisibleText()].sort()).to.eql( - [ - `"${first}" is not a configured data view ID\nShowing the saved data view: "logstas*" (${second})`, - `Different index references\nData view id references in some of the applied filters differ from the current data view.`, - ].sort() - ); - }); }); } diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 902dd7d88765a..f17f3949105d9 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -2178,7 +2178,6 @@ "discover.searchGenerationWithDescriptionGrid": "Tableau généré par la recherche {searchTitle} ({searchDescription})", "discover.selectedDocumentsNumber": "{nr} documents sélectionnés", "discover.showingDefaultDataViewWarningDescription": "Affichage de la vue de données par défaut : \"{loadedDataViewTitle}\" ({loadedDataViewId})", - "discover.showingSavedDataViewWarningDescription": "Affichage de la vue de données enregistrée : \"{ownDataViewTitle}\" ({ownDataViewId})", "discover.singleDocRoute.errorMessage": "Aucune vue de données correspondante pour l'ID {dataViewId}", "discover.topNav.optionsPopover.currentViewMode": "{viewModeLabel} : {currentViewMode}", "discover.utils.formatHit.moreFields": "et {count} {count, plural, one {champ} other {champs}} en plus", @@ -2391,8 +2390,6 @@ "discover.helpMenu.appName": "Découverte", "discover.inspectorRequestDataTitleDocuments": "Documents", "discover.inspectorRequestDescriptionDocument": "Cette requête interroge Elasticsearch afin de récupérer les documents.", - "discover.invalidFiltersWarnToast.description": "Les références d'ID de la vue de données dans certains filtres appliqués diffèrent de la vue de données actuelle.", - "discover.invalidFiltersWarnToast.title": "Références d'index différentes", "discover.json.codeEditorAriaLabel": "Affichage JSON en lecture seule d’un document Elasticsearch", "discover.json.copyToClipboardLabel": "Copier dans le presse-papiers", "discover.loadingDocuments": "Chargement des documents", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index e91efa4a1c273..d5ec0169732b3 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -2177,8 +2177,7 @@ "discover.searchGenerationWithDescription": "検索{searchTitle}で生成されたテーブル", "discover.searchGenerationWithDescriptionGrid": "検索{searchTitle}で生成されたテーブル({searchDescription})", "discover.selectedDocumentsNumber": "{nr}個のドキュメントが選択されました", - "discover.showingDefaultDataViewWarningDescription": "デフォルトのデータビューを表示しています:\"{loadedDataViewTitle}\"({loadedDataViewId})", - "discover.showingSavedDataViewWarningDescription": "保存されたデータビューを表示しています:\"{ownDataViewTitle}\"({ownDataViewId})", + "discover.showingDefaultDataViewWarningDescription": "デフォルトデータビューを表示しています:\"{loadedDataViewTitle}\" ({loadedDataViewId})", "discover.singleDocRoute.errorMessage": "ID {dataViewId}の一致するデータビューが見つかりません", "discover.topNav.optionsPopover.currentViewMode": "{viewModeLabel}:{currentViewMode}", "discover.utils.formatHit.moreFields": "およびその他{count}個の{count, plural, other {フィールド}}", @@ -2391,8 +2390,6 @@ "discover.helpMenu.appName": "Discover", "discover.inspectorRequestDataTitleDocuments": "ドキュメント", "discover.inspectorRequestDescriptionDocument": "このリクエストはElasticsearchにクエリをかけ、ドキュメントを取得します。", - "discover.invalidFiltersWarnToast.description": "一部の適用されたフィルターのデータビューID参照は、現在のデータビューとは異なります。", - "discover.invalidFiltersWarnToast.title": "別のインデックス参照", "discover.json.codeEditorAriaLabel": "Elasticsearch ドキュメントの JSON ビューのみを読み込む", "discover.json.copyToClipboardLabel": "クリップボードにコピー", "discover.loadingDocuments": "ドキュメントを読み込み中", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index b57b617a0ce26..8b34117e10e40 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -2177,8 +2177,7 @@ "discover.searchGenerationWithDescription": "搜索 {searchTitle} 生成的表", "discover.searchGenerationWithDescriptionGrid": "搜索 {searchTitle} 生成的表({searchDescription})", "discover.selectedDocumentsNumber": "{nr} 个文档已选择", - "discover.showingDefaultDataViewWarningDescription": "正在显示默认数据视图:“{loadedDataViewTitle}”({loadedDataViewId})", - "discover.showingSavedDataViewWarningDescription": "正在显示已保存数据视图:“{ownDataViewTitle}”({ownDataViewId})", + "discover.showingDefaultDataViewWarningDescription": "正在显示默认数据视图:“{loadedDataViewTitle}”({loadedDataViewId})", "discover.singleDocRoute.errorMessage": "没有与 ID {dataViewId} 相匹配的数据视图", "discover.topNav.optionsPopover.currentViewMode": "{viewModeLabel}{currentViewMode}", "discover.utils.formatHit.moreFields": "及另外 {count} 个 {count, plural, other {字段}}", @@ -2391,8 +2390,6 @@ "discover.helpMenu.appName": "Discover", "discover.inspectorRequestDataTitleDocuments": "文档", "discover.inspectorRequestDescriptionDocument": "此请求将查询 Elasticsearch 以获取文档。", - "discover.invalidFiltersWarnToast.description": "某些应用的筛选中的数据视图 ID 引用与当前数据视图不同。", - "discover.invalidFiltersWarnToast.title": "不同的索引引用", "discover.json.codeEditorAriaLabel": "Elasticsearch 文档的只读 JSON 视图", "discover.json.copyToClipboardLabel": "复制到剪贴板", "discover.loadingDocuments": "正在加载文档", From a28099a15f1170852e286ebbd13395f5c7ddd54e Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Tue, 28 Mar 2023 07:25:24 +0200 Subject: [PATCH 003/122] Undo changes to field_stats_table.tsx --- .../main/components/field_stats_table/field_stats_table.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/plugins/discover/public/application/main/components/field_stats_table/field_stats_table.tsx b/src/plugins/discover/public/application/main/components/field_stats_table/field_stats_table.tsx index 0aa6f24fba287..0bb6947dc7674 100644 --- a/src/plugins/discover/public/application/main/components/field_stats_table/field_stats_table.tsx +++ b/src/plugins/discover/public/application/main/components/field_stats_table/field_stats_table.tsx @@ -111,13 +111,13 @@ export interface FieldStatisticsTableProps { export const FieldStatisticsTable = (props: FieldStatisticsTableProps) => { const { dataView, + savedSearch, query, columns, filters, stateContainer, onAddFilter, trackUiMetric, - savedSearch, searchSessionId, } = props; const totalHits$ = stateContainer?.dataState.data$.totalHits$; @@ -185,6 +185,7 @@ export const FieldStatisticsTable = (props: FieldStatisticsTableProps) => { }, [ embeddable, dataView, + savedSearch, query, columns, filters, @@ -192,7 +193,6 @@ export const FieldStatisticsTable = (props: FieldStatisticsTableProps) => { searchSessionId, totalHits$, stateContainer, - savedSearch, ]); useEffect(() => { @@ -219,7 +219,7 @@ export const FieldStatisticsTable = (props: FieldStatisticsTableProps) => { const initializedEmbeddable = await factory.create({ id: 'discover_data_visualizer_grid', dataView, - savedSearch: stateContainer?.savedSearchState.getState(), + savedSearch, query, showPreviewByDefault, onAddFilter, From 10b7cd79bc8e51a25db25fff6eda6cb34a1ca874 Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Tue, 28 Mar 2023 08:48:04 +0200 Subject: [PATCH 004/122] Fix issue causing Inspector to show an empty title --- .../discover_saved_search_container.test.ts | 13 +++++++++++++ .../services/discover_saved_search_container.ts | 4 ++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/plugins/discover/public/application/main/services/discover_saved_search_container.test.ts b/src/plugins/discover/public/application/main/services/discover_saved_search_container.test.ts index 20d12eb15e058..394c01676a243 100644 --- a/src/plugins/discover/public/application/main/services/discover_saved_search_container.test.ts +++ b/src/plugins/discover/public/application/main/services/discover_saved_search_container.test.ts @@ -28,6 +28,19 @@ describe('DiscoverSavedSearchContainer', () => { const savedSearch = savedSearchMock; const services = discoverServiceMock; + describe('getTitle', () => { + it('returns undefined for new saved searches', () => { + const container = getSavedSearchContainer({ services }); + expect(container.getTitle()).toBe(undefined); + }); + + it('returns the title of a persisted saved searches', () => { + const container = getSavedSearchContainer({ services }); + container.set(savedSearch); + expect(container.getTitle()).toBe(savedSearch.title); + }); + }); + describe('set', () => { it('should update the current and initial state of the saved search', () => { const container = getSavedSearchContainer({ services }); diff --git a/src/plugins/discover/public/application/main/services/discover_saved_search_container.ts b/src/plugins/discover/public/application/main/services/discover_saved_search_container.ts index 71075f9a16f14..05447ada122f0 100644 --- a/src/plugins/discover/public/application/main/services/discover_saved_search_container.ts +++ b/src/plugins/discover/public/application/main/services/discover_saved_search_container.ts @@ -53,7 +53,7 @@ export interface DiscoverSavedSearchContainer { /** * Get the title of the current saved search */ - getTitle: () => string; + getTitle: () => string | undefined; /** * Get an BehaviorSubject containing the state if there have been changes to the initial state of the saved search * Can be used to track if the saved search has been modified and displayed in the UI @@ -117,7 +117,7 @@ export function getSavedSearchContainer({ const getInitial$ = () => savedSearchInitial$; const getCurrent$ = () => savedSearchCurrent$; const getHasChanged$ = () => hasChanged$; - const getTitle = () => savedSearchCurrent$.getValue().title ?? ''; + const getTitle = () => savedSearchCurrent$.getValue().title; const getId = () => savedSearchCurrent$.getValue().id; const newSavedSearch = async (nextDataView: DataView | undefined) => { From b36e2d81d38ea4c04b0e36ad3b02c5b1b27866ae Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Tue, 28 Mar 2023 09:02:57 +0200 Subject: [PATCH 005/122] Add title to mock for testing --- src/plugins/discover/public/__mocks__/saved_search.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/discover/public/__mocks__/saved_search.ts b/src/plugins/discover/public/__mocks__/saved_search.ts index 27b144a5aa081..98b0d60c18ac4 100644 --- a/src/plugins/discover/public/__mocks__/saved_search.ts +++ b/src/plugins/discover/public/__mocks__/saved_search.ts @@ -13,6 +13,7 @@ import { dataViewWithTimefieldMock } from './data_view_with_timefield'; export const savedSearchMock = { id: 'the-saved-search-id', + title: 'A saved search', searchSource: createSearchSourceMock({ index: dataViewMock }), } as unknown as SavedSearch; From 8ddefa4d5ee8e11b317fba63718af71e7ad785f3 Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Tue, 28 Mar 2023 10:31:36 +0200 Subject: [PATCH 006/122] Undo compressed property in table.tsx --- .../services/doc_views/components/doc_viewer_table/table.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/discover/public/services/doc_views/components/doc_viewer_table/table.tsx b/src/plugins/discover/public/services/doc_views/components/doc_viewer_table/table.tsx index 30e5586a9d452..56232a31f9a0f 100644 --- a/src/plugins/discover/public/services/doc_views/components/doc_viewer_table/table.tsx +++ b/src/plugins/discover/public/services/doc_views/components/doc_viewer_table/table.tsx @@ -404,7 +404,7 @@ export const DocViewerTable = ({ ) : ( - + {headers} {rowElements} From 5ba1e2b1ed786d5beb538ed87fcad92d7a4c21b5 Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Tue, 28 Mar 2023 10:47:04 +0200 Subject: [PATCH 007/122] Move URL listener for / to discover_main_route.tsx --- .../public/application/main/discover_main_app.tsx | 6 ------ .../public/application/main/discover_main_route.tsx | 13 +++++++++++++ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/plugins/discover/public/application/main/discover_main_app.tsx b/src/plugins/discover/public/application/main/discover_main_app.tsx index 9efc0c9d8a46e..bc833a13f4e32 100644 --- a/src/plugins/discover/public/application/main/discover_main_app.tsx +++ b/src/plugins/discover/public/application/main/discover_main_app.tsx @@ -13,7 +13,6 @@ import { DiscoverStateContainer } from './services/discover_state'; import { DiscoverLayout } from './components/layout'; import { setBreadcrumbsTitle } from '../../utils/breadcrumbs'; import { addHelpMenuToAppChrome } from '../../components/help_menu/help_menu_util'; -import { useUrl } from './hooks/use_url'; import { useDiscoverServices } from '../../hooks/use_discover_services'; import { useSavedSearchAliasMatchRedirect } from '../../hooks/saved_search_alias_match_redirect'; import { useSavedSearchInitial } from './services/discover_state_provider'; @@ -73,11 +72,6 @@ export function DiscoverMainApp(props: DiscoverMainProps) { return () => unsubscribe(); }, [stateContainer]); - /** - * Url / Routing logic - */ - useUrl({ history: usedHistory, stateContainer }); - /** * SavedSearch dependend initializing */ diff --git a/src/plugins/discover/public/application/main/discover_main_route.tsx b/src/plugins/discover/public/application/main/discover_main_route.tsx index 088cbcdcbe406..fc7bd9a55e213 100644 --- a/src/plugins/discover/public/application/main/discover_main_route.tsx +++ b/src/plugins/discover/public/application/main/discover_main_route.tsx @@ -195,9 +195,22 @@ export function DiscoverMainRoute(props: Props) { ); useEffect(() => { + // initial search + triggered when id changes loadSavedSearch(); }, [loadSavedSearch, id]); + useEffect(() => { + // this listener is waiting for such a path http://localhost:5601/app/discover#/ + // which could be set through pressing "New" button in top nav or go to "Discover" plugin from the sidebar + // to reload the page in a right way + const unlistenHistoryBasePath = history.listen(async ({ pathname, search, hash }) => { + if (!search && !hash && pathname === '/' && !stateContainer.savedSearchState.getState().id) { + await loadSavedSearch(); + } + }); + return () => unlistenHistoryBasePath(); + }, [history, stateContainer, loadSavedSearch]); + if (showNoDataPage) { const analyticsServices = { coreStart: core, From fb89c5917e98fad08b850a4997b4ad2e97081f2a Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Tue, 28 Mar 2023 20:58:10 +0200 Subject: [PATCH 008/122] Attempt to fix tests failing due to filters --- .../public/query/filter_manager/lib/map_filter.ts | 2 +- src/plugins/discover/public/__mocks__/data_view.ts | 1 - .../expression/search_source_expression_form.tsx | 11 +++-------- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/plugins/data/public/query/filter_manager/lib/map_filter.ts b/src/plugins/data/public/query/filter_manager/lib/map_filter.ts index 73951a5fc2954..eb428eb8d8af3 100644 --- a/src/plugins/data/public/query/filter_manager/lib/map_filter.ts +++ b/src/plugins/data/public/query/filter_manager/lib/map_filter.ts @@ -66,7 +66,7 @@ export function mapFilter(filter: Filter) { // Map the filter into an object with the key and value exposed so it's // easier to work with in the template - mappedFilter.meta = { ...filter.meta } || {}; + mappedFilter.meta = filter.meta || {}; mappedFilter.meta.type = mapped.type; mappedFilter.meta.key = mapped.key; // Display value or formatter function. diff --git a/src/plugins/discover/public/__mocks__/data_view.ts b/src/plugins/discover/public/__mocks__/data_view.ts index 847323a704912..67950cd9f5e15 100644 --- a/src/plugins/discover/public/__mocks__/data_view.ts +++ b/src/plugins/discover/public/__mocks__/data_view.ts @@ -113,7 +113,6 @@ export const buildDataViewMock = ({ getTimeField: () => { return dataViewFields.find((field) => field.name === timeFieldName); }, - toSpec: () => ({}), } as unknown as DataView; dataView.isTimeBased = () => !!timeFieldName; diff --git a/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/search_source_expression_form.tsx b/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/search_source_expression_form.tsx index f2a6ca3a84cee..1fed4e14ec4ba 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/search_source_expression_form.tsx +++ b/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/search_source_expression_form.tsx @@ -14,12 +14,7 @@ import { EuiSpacer, EuiTitle } from '@elastic/eui'; import { IErrorObject } from '@kbn/triggers-actions-ui-plugin/public'; import type { SearchBarProps } from '@kbn/unified-search-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public'; -import { - mapAndFlattenFilters, - getTime, - type SavedQuery, - type ISearchSource, -} from '@kbn/data-plugin/public'; +import { getTime, type SavedQuery, type ISearchSource } from '@kbn/data-plugin/public'; import { BUCKET_SELECTOR_FIELD, buildAggregation, @@ -100,7 +95,7 @@ export const SearchSourceExpressionForm = (props: SearchSourceExpressionFormProp { index: searchSource.getField('index'), query: searchSource.getField('query')! as Query, - filter: mapAndFlattenFilters(searchSource.getField('filter') as Filter[]), + filter: searchSource.getField('filter') as Filter[], threshold: ruleParams.threshold ?? DEFAULT_VALUES.THRESHOLD, thresholdComparator: ruleParams.thresholdComparator ?? DEFAULT_VALUES.THRESHOLD_COMPARATOR, timeWindowSize: ruleParams.timeWindowSize ?? DEFAULT_VALUES.TIME_WINDOW_SIZE, @@ -129,7 +124,7 @@ export const SearchSourceExpressionForm = (props: SearchSourceExpressionFormProp }, []); const onUpdateFilters = useCallback((newFilters) => { - dispatch({ type: 'filter', payload: mapAndFlattenFilters(newFilters) }); + dispatch({ type: 'filter', payload: newFilters }); }, []); const onChangeQuery = useCallback( From 5f70a9b565c7965be0671126435d79afdc9173cb Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Wed, 29 Mar 2023 06:13:34 +0200 Subject: [PATCH 009/122] Undo alerting changes --- .../expression/search_source_expression_form.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/search_source_expression_form.tsx b/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/search_source_expression_form.tsx index 1fed4e14ec4ba..f2a6ca3a84cee 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/search_source_expression_form.tsx +++ b/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/search_source_expression_form.tsx @@ -14,7 +14,12 @@ import { EuiSpacer, EuiTitle } from '@elastic/eui'; import { IErrorObject } from '@kbn/triggers-actions-ui-plugin/public'; import type { SearchBarProps } from '@kbn/unified-search-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public'; -import { getTime, type SavedQuery, type ISearchSource } from '@kbn/data-plugin/public'; +import { + mapAndFlattenFilters, + getTime, + type SavedQuery, + type ISearchSource, +} from '@kbn/data-plugin/public'; import { BUCKET_SELECTOR_FIELD, buildAggregation, @@ -95,7 +100,7 @@ export const SearchSourceExpressionForm = (props: SearchSourceExpressionFormProp { index: searchSource.getField('index'), query: searchSource.getField('query')! as Query, - filter: searchSource.getField('filter') as Filter[], + filter: mapAndFlattenFilters(searchSource.getField('filter') as Filter[]), threshold: ruleParams.threshold ?? DEFAULT_VALUES.THRESHOLD, thresholdComparator: ruleParams.thresholdComparator ?? DEFAULT_VALUES.THRESHOLD_COMPARATOR, timeWindowSize: ruleParams.timeWindowSize ?? DEFAULT_VALUES.TIME_WINDOW_SIZE, @@ -124,7 +129,7 @@ export const SearchSourceExpressionForm = (props: SearchSourceExpressionFormProp }, []); const onUpdateFilters = useCallback((newFilters) => { - dispatch({ type: 'filter', payload: newFilters }); + dispatch({ type: 'filter', payload: mapAndFlattenFilters(newFilters) }); }, []); const onChangeQuery = useCallback( From 46b8d91696e03cf9161714a5df15ec40b19454ad Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Wed, 29 Mar 2023 06:15:08 +0200 Subject: [PATCH 010/122] Improve useUrl usage --- .../application/main/discover_main_route.tsx | 16 ++++------------ .../application/main/hooks/use_url.test.ts | 10 ++++------ .../public/application/main/hooks/use_url.ts | 14 +++++++------- 3 files changed, 15 insertions(+), 25 deletions(-) diff --git a/src/plugins/discover/public/application/main/discover_main_route.tsx b/src/plugins/discover/public/application/main/discover_main_route.tsx index fc7bd9a55e213..470e73824a90c 100644 --- a/src/plugins/discover/public/application/main/discover_main_route.tsx +++ b/src/plugins/discover/public/application/main/discover_main_route.tsx @@ -16,6 +16,7 @@ import { } from '@kbn/shared-ux-page-analytics-no-data'; import { getSavedSearchFullPathUrl } from '@kbn/saved-search-plugin/public'; import useObservable from 'react-use/lib/useObservable'; +import { useUrl } from './hooks/use_url'; import { useSingleton } from './hooks/use_singleton'; import { MainHistoryLocationState } from '../../../common/locator'; import { DiscoverStateContainer, getDiscoverStateContainer } from './services/discover_state'; @@ -194,22 +195,13 @@ export function DiscoverMainRoute(props: Props) { [loadSavedSearch] ); + // primary fetch: on initial search + triggered when id changes useEffect(() => { - // initial search + triggered when id changes loadSavedSearch(); }, [loadSavedSearch, id]); - useEffect(() => { - // this listener is waiting for such a path http://localhost:5601/app/discover#/ - // which could be set through pressing "New" button in top nav or go to "Discover" plugin from the sidebar - // to reload the page in a right way - const unlistenHistoryBasePath = history.listen(async ({ pathname, search, hash }) => { - if (!search && !hash && pathname === '/' && !stateContainer.savedSearchState.getState().id) { - await loadSavedSearch(); - } - }); - return () => unlistenHistoryBasePath(); - }, [history, stateContainer, loadSavedSearch]); + // secondary fetch: in case URL is set to `/`, used to reset the 'new' state + useUrl({ history, savedSearchId: id, onNewUrl: () => loadSavedSearch() }); if (showNoDataPage) { const analyticsServices = { diff --git a/src/plugins/discover/public/application/main/hooks/use_url.test.ts b/src/plugins/discover/public/application/main/hooks/use_url.test.ts index e4774ac9a435c..a9cc1bc838fe9 100644 --- a/src/plugins/discover/public/application/main/hooks/use_url.test.ts +++ b/src/plugins/discover/public/application/main/hooks/use_url.test.ts @@ -8,7 +8,6 @@ import { renderHook } from '@testing-library/react-hooks'; import { createSearchSessionMock } from '../../../__mocks__/search_session'; import { useUrl } from './use_url'; -import { getDiscoverStateMock } from '../../../__mocks__/discover_state.mock'; import { savedSearchMockWithTimeField, savedSearchMockWithTimeFieldNew, @@ -17,18 +16,17 @@ import { SavedSearch } from '@kbn/saved-search-plugin/public'; function prepareTest(savedSearch: SavedSearch, path: string) { const { history } = createSearchSessionMock(); - const stateContainer = getDiscoverStateMock({ isTimeBased: true }); - stateContainer.savedSearchState.set(savedSearch); - stateContainer.actions.loadSavedSearch = jest.fn(); + const onNewUrl = jest.fn(); renderHook(() => useUrl({ history, - stateContainer, + savedSearchId: savedSearch.id, + onNewUrl, }) ); history.push(path); - return { load: stateContainer.actions.loadSavedSearch }; + return { load: onNewUrl }; } describe('test useUrl when the url is changed to /', () => { test('loadSavedSearch is not triggered when the url is e.g. /new', () => { diff --git a/src/plugins/discover/public/application/main/hooks/use_url.ts b/src/plugins/discover/public/application/main/hooks/use_url.ts index c43c4769a724d..f8549f1d9db71 100644 --- a/src/plugins/discover/public/application/main/hooks/use_url.ts +++ b/src/plugins/discover/public/application/main/hooks/use_url.ts @@ -7,13 +7,14 @@ */ import { useEffect } from 'react'; import { History } from 'history'; -import { DiscoverStateContainer } from '../services/discover_state'; export function useUrl({ history, - stateContainer, + savedSearchId, + onNewUrl, }: { history: History; - stateContainer: DiscoverStateContainer; + savedSearchId: string | undefined; + onNewUrl: () => void; }) { /** * Url / Routing logic @@ -23,11 +24,10 @@ export function useUrl({ // which could be set through pressing "New" button in top nav or go to "Discover" plugin from the sidebar // to reload the page in a right way const unlistenHistoryBasePath = history.listen(async ({ pathname, search, hash }) => { - if (!search && !hash && pathname === '/' && !stateContainer.savedSearchState.getState().id) { - await stateContainer.actions.loadSavedSearch(); - stateContainer.actions.fetchData(true); + if (!search && !hash && pathname === '/' && !savedSearchId) { + onNewUrl(); } }); return () => unlistenHistoryBasePath(); - }, [history, stateContainer]); + }, [history, savedSearchId, onNewUrl]); } From 4f37e5d3b3a187dcedc9d6b963972a54d002b850 Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Wed, 29 Mar 2023 06:53:21 +0200 Subject: [PATCH 011/122] Fix saveDataSource to throw an error when needed --- .../application/main/components/top_nav/on_save_search.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/plugins/discover/public/application/main/components/top_nav/on_save_search.tsx b/src/plugins/discover/public/application/main/components/top_nav/on_save_search.tsx index 9c3d792605ab3..f452f47b44a33 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/on_save_search.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/on_save_search.tsx @@ -74,7 +74,7 @@ async function saveDataSource({ return nextSavedSearch; } catch (e) { onError(e); - return savedSearch; + throw e; } } @@ -137,7 +137,7 @@ export async function onSaveSearch({ const navigateOrReloadSavedSearch = !Boolean(onSaveCb); try { - const response = await saveDataSource({ + return await saveDataSource({ saveOptions, services, navigateTo, @@ -145,8 +145,6 @@ export async function onSaveSearch({ state, navigateOrReloadSavedSearch, }); - onSaveCb?.(); - return response; } catch (e) { // If the save wasn't successful, put the original values back. @@ -159,6 +157,7 @@ export async function onSaveSearch({ } state.appState.resetInitialState(); } + onSaveCb?.(); }; const saveModal = ( From 2cb0234f07cdf647797af3b9be580955a1278b14 Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Wed, 29 Mar 2023 07:00:46 +0200 Subject: [PATCH 012/122] Fix alerting UI exception --- .../main/services/discover_saved_search_container.ts | 7 ------- .../public/application/main/utils/update_saved_search.ts | 3 ++- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/plugins/discover/public/application/main/services/discover_saved_search_container.ts b/src/plugins/discover/public/application/main/services/discover_saved_search_container.ts index 05447ada122f0..e114ca5cb07a4 100644 --- a/src/plugins/discover/public/application/main/services/discover_saved_search_container.ts +++ b/src/plugins/discover/public/application/main/services/discover_saved_search_container.ts @@ -177,13 +177,6 @@ export function getSavedSearchContainer({ !filterAndQuery ); - nextSavedSearch.searchSource.setField('index', dataView); - if (nextState) { - nextSavedSearch.searchSource - .setField('query', nextState.query) - .setField('filter', nextState.filters); - } - const hasChanged = !isEqualSavedSearch(savedSearchInitial$.getValue(), nextSavedSearch); hasChanged$.next(hasChanged); savedSearchCurrent$.next(nextSavedSearch); diff --git a/src/plugins/discover/public/application/main/utils/update_saved_search.ts b/src/plugins/discover/public/application/main/utils/update_saved_search.ts index dbf05ebbb213b..e75ca7e019c9e 100644 --- a/src/plugins/discover/public/application/main/utils/update_saved_search.ts +++ b/src/plugins/discover/public/application/main/utils/update_saved_search.ts @@ -8,6 +8,7 @@ import { SavedSearch, SortOrder } from '@kbn/saved-search-plugin/public'; import { DataView } from '@kbn/data-views-plugin/common'; import { isOfAggregateQueryType } from '@kbn/es-query'; +import { cloneDeep } from 'lodash'; import { DiscoverAppState } from '../services/discover_app_state_container'; import { DiscoverServices } from '../../../build_services'; @@ -34,7 +35,7 @@ export function updateSavedSearch( savedSearch.searchSource .setField('index', dataView) .setField('query', state.query) - .setField('filter', state.filters); + .setField('filter', cloneDeep(state.filters)); } savedSearch.columns = state.columns || []; savedSearch.sort = (state.sort as SortOrder[]) || []; From e7a7d92e743592962352fc335cb1bac06afe72e8 Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Wed, 29 Mar 2023 21:25:50 +0200 Subject: [PATCH 013/122] Fix timeRestore handling when executing undoChanges --- .../main/services/discover_state.test.ts | 18 ++++++++++++++++++ .../main/services/discover_state.ts | 6 +++++- .../restore_from_saved_search.ts | 10 +++++++--- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/plugins/discover/public/application/main/services/discover_state.test.ts b/src/plugins/discover/public/application/main/services/discover_state.test.ts index 7b90df6c0f282..6a5c0d10cecb9 100644 --- a/src/plugins/discover/public/application/main/services/discover_state.test.ts +++ b/src/plugins/discover/public/application/main/services/discover_state.test.ts @@ -549,4 +549,22 @@ describe('actions', () => { ); unsubscribe(); }); + + test('undoChanges with timeRestore', async () => { + const { state } = await getState('/', { + ...savedSearchMock, + timeRestore: true, + refreshInterval: { pause: false, value: 1000 }, + timeRange: { from: 'now-15d', to: 'now-10d' }, + }); + const setTime = jest.fn(); + const setRefreshInterval = jest.fn(); + discoverServiceMock.data.query.timefilter.timefilter.setTime = setTime; + discoverServiceMock.data.query.timefilter.timefilter.setRefreshInterval = setRefreshInterval; + await state.actions.loadSavedSearch(savedSearchMock.id); + await state.actions.undoChanges(); + expect(setTime).toHaveBeenCalledTimes(1); + expect(setTime).toHaveBeenCalledWith({ from: 'now-15d', to: 'now-10d' }); + expect(setRefreshInterval).toHaveBeenCalledWith({ pause: false, value: 1000 }); + }); }); diff --git a/src/plugins/discover/public/application/main/services/discover_state.ts b/src/plugins/discover/public/application/main/services/discover_state.ts index b821c29e48f2b..e82e72b474884 100644 --- a/src/plugins/discover/public/application/main/services/discover_state.ts +++ b/src/plugins/discover/public/application/main/services/discover_state.ts @@ -24,6 +24,7 @@ import type { SavedSearch } from '@kbn/saved-search-plugin/public'; import { v4 as uuidv4 } from 'uuid'; import { merge } from 'rxjs'; import { AggregateQuery, Query, TimeRange } from '@kbn/es-query'; +import { restoreStateFromSavedSearch } from '../../../services/saved_searches/restore_from_saved_search'; import { FetchStatus } from '../../types'; import { changeDataView } from '../hooks/utils/change_data_view'; import { loadSavedSearch as loadNextSavedSearch } from './load_saved_search'; @@ -51,7 +52,6 @@ import { DiscoverSavedSearchContainer, } from './discover_saved_search_container'; import { updateFiltersReferences } from '../utils/update_filter_references'; - interface DiscoverStateContainerParams { /** * Browser history @@ -443,6 +443,10 @@ export function getDiscoverStateContainer({ const undoChanges = async () => { const nextSavedSearch = savedSearchContainer.getInitial$().getValue(); await savedSearchContainer.set(nextSavedSearch); + restoreStateFromSavedSearch({ + savedSearch: nextSavedSearch, + timefilter: services.timefilter, + }); const newAppState = getDefaultAppState(nextSavedSearch, services); await appStateContainer.replaceUrlState(newAppState); return nextSavedSearch; diff --git a/src/plugins/discover/public/services/saved_searches/restore_from_saved_search.ts b/src/plugins/discover/public/services/saved_searches/restore_from_saved_search.ts index 8e87d891aef1c..550c36408977b 100644 --- a/src/plugins/discover/public/services/saved_searches/restore_from_saved_search.ts +++ b/src/plugins/discover/public/services/saved_searches/restore_from_saved_search.ts @@ -17,14 +17,18 @@ export const restoreStateFromSavedSearch = ({ savedSearch: SavedSearch; timefilter: TimefilterContract; }) => { - if (!savedSearch || !savedSearch.timeRestore) { + if (!savedSearch) { return; } - if (savedSearch.timeRange && isTimeRangeValid(savedSearch.timeRange)) { + if (savedSearch.timeRestore && savedSearch.timeRange && isTimeRangeValid(savedSearch.timeRange)) { timefilter.setTime(savedSearch.timeRange); } - if (savedSearch.refreshInterval && isRefreshIntervalValid(savedSearch.refreshInterval)) { + if ( + savedSearch.timeRestore && + savedSearch.refreshInterval && + isRefreshIntervalValid(savedSearch.refreshInterval) + ) { timefilter.setRefreshInterval(savedSearch.refreshInterval); } }; From 8eeed8bcf662460b59204ca8f6ca67e96c3fe92b Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Thu, 30 Mar 2023 06:32:41 +0200 Subject: [PATCH 014/122] Revert some change when saving saved searches --- .../components/top_nav/on_save_search.tsx | 37 +++++++++---------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/src/plugins/discover/public/application/main/components/top_nav/on_save_search.tsx b/src/plugins/discover/public/application/main/components/top_nav/on_save_search.tsx index f452f47b44a33..b22ae245319a6 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/on_save_search.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/on_save_search.tsx @@ -67,14 +67,13 @@ async function saveDataSource({ } try { - const nextSavedSearch = await state.savedSearchState.persist(savedSearch, saveOptions); - if (nextSavedSearch) { - onSuccess(nextSavedSearch.id!); + const response = await state.savedSearchState.persist(savedSearch, saveOptions); + if (response?.id) { + onSuccess(response.id!); } - return nextSavedSearch; - } catch (e) { - onError(e); - throw e; + return response; + } catch (error) { + onError(error); } } @@ -136,18 +135,16 @@ export async function onSaveSearch({ } const navigateOrReloadSavedSearch = !Boolean(onSaveCb); - try { - return await saveDataSource({ - saveOptions, - services, - navigateTo, - savedSearch, - state, - navigateOrReloadSavedSearch, - }); - } catch (e) { - // If the save wasn't successful, put the original values back. - + const response = await saveDataSource({ + saveOptions, + services, + navigateTo, + savedSearch, + state, + navigateOrReloadSavedSearch, + }); + // If the save wasn't successful, put the original values back. + if (!response) { savedSearch.title = currentTitle; savedSearch.timeRestore = currentTimeRestore; savedSearch.rowsPerPage = currentRowsPerPage; @@ -155,9 +152,11 @@ export async function onSaveSearch({ if (savedObjectsTagging) { savedSearch.tags = currentTags; } + } else { state.appState.resetInitialState(); } onSaveCb?.(); + return response; }; const saveModal = ( From f66e8f3e1f5e261882c56e7820bcad02bfd0ef24 Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Thu, 30 Mar 2023 23:01:43 +0200 Subject: [PATCH 015/122] Persist timeRestore correctly --- .../discover_saved_search_container.test.ts | 24 +++++++ .../discover_saved_search_container.ts | 1 + .../main/utils/update_saved_search.ts | 70 ++++++++++--------- 3 files changed, 61 insertions(+), 34 deletions(-) diff --git a/src/plugins/discover/public/application/main/services/discover_saved_search_container.test.ts b/src/plugins/discover/public/application/main/services/discover_saved_search_container.test.ts index 394c01676a243..e08a85329d7f3 100644 --- a/src/plugins/discover/public/application/main/services/discover_saved_search_container.test.ts +++ b/src/plugins/discover/public/application/main/services/discover_saved_search_container.test.ts @@ -196,6 +196,30 @@ describe('DiscoverSavedSearchContainer', () => { 'My updated saved search' ); }); + + it('takes care of persisting timeRestore correctly ', async () => { + discoverServiceMock.timefilter.getTime = jest.fn(() => ({ from: 'now-15m', to: 'now' })); + discoverServiceMock.timefilter.getRefreshInterval = jest.fn(() => ({ + value: 0, + pause: true, + })); + const savedSearchContainer = getSavedSearchContainer({ + services: discoverServiceMock, + }); + const savedSearchToPersist = { + ...savedSearchMockWithTimeField, + title: 'My updated saved search', + timeRestore: true, + }; + await savedSearchContainer.persist(savedSearchToPersist, saveOptions); + expect(discoverServiceMock.timefilter.getTime).toHaveBeenCalled(); + expect(discoverServiceMock.timefilter.getRefreshInterval).toHaveBeenCalled(); + expect(savedSearchToPersist.timeRange).toEqual({ from: 'now-15m', to: 'now' }); + expect(savedSearchToPersist.refreshInterval).toEqual({ + value: 0, + pause: true, + }); + }); }); describe('update', () => { diff --git a/src/plugins/discover/public/application/main/services/discover_saved_search_container.ts b/src/plugins/discover/public/application/main/services/discover_saved_search_container.ts index e114ca5cb07a4..be7670d827c48 100644 --- a/src/plugins/discover/public/application/main/services/discover_saved_search_container.ts +++ b/src/plugins/discover/public/application/main/services/discover_saved_search_container.ts @@ -146,6 +146,7 @@ export function getSavedSearchContainer({ const persist = async (nextSavedSearch: SavedSearch, saveOptions?: SavedObjectSaveOpts) => { addLog('[savedSearch] persist', { nextSavedSearch, saveOptions }); + updateSavedSearch({ savedSearch: nextSavedSearch, services }); const id = await saveSavedSearch( nextSavedSearch, diff --git a/src/plugins/discover/public/application/main/utils/update_saved_search.ts b/src/plugins/discover/public/application/main/utils/update_saved_search.ts index e75ca7e019c9e..a5728e842968b 100644 --- a/src/plugins/discover/public/application/main/utils/update_saved_search.ts +++ b/src/plugins/discover/public/application/main/utils/update_saved_search.ts @@ -20,58 +20,60 @@ export function updateSavedSearch( services, }: { savedSearch: SavedSearch; - dataView: DataView; - state: DiscoverAppState; + dataView?: DataView; + state?: DiscoverAppState; services: DiscoverServices; }, initial: boolean = false ) { - if (!initial) { + if (dataView) { + savedSearch.searchSource.setField('index', dataView); + savedSearch.usesAdHocDataView = !dataView.isPersisted(); + } + if (!initial || !state) { savedSearch.searchSource - .setField('index', dataView) .setField('query', services.data.query.queryString.getQuery() || null) .setField('filter', services.data.query.filterManager.getFilters()); } else { savedSearch.searchSource - .setField('index', dataView) .setField('query', state.query) .setField('filter', cloneDeep(state.filters)); } - savedSearch.columns = state.columns || []; - savedSearch.sort = (state.sort as SortOrder[]) || []; - if (state.grid) { - savedSearch.grid = state.grid; - } - if (typeof state.hideChart !== 'undefined') { - savedSearch.hideChart = state.hideChart; - } - if (typeof state.rowHeight !== 'undefined') { - savedSearch.rowHeight = state.rowHeight; - } + if (state) { + savedSearch.columns = state.columns || []; + savedSearch.sort = (state.sort as SortOrder[]) || []; + if (state.grid) { + savedSearch.grid = state.grid; + } + if (typeof state.hideChart !== 'undefined') { + savedSearch.hideChart = state.hideChart; + } + if (typeof state.rowHeight !== 'undefined') { + savedSearch.rowHeight = state.rowHeight; + } - if (state.viewMode) { - savedSearch.viewMode = state.viewMode; - } + if (state.viewMode) { + savedSearch.viewMode = state.viewMode; + } - if (typeof state.breakdownField !== 'undefined') { - savedSearch.breakdownField = state.breakdownField; - } else if (savedSearch.breakdownField) { - savedSearch.breakdownField = ''; - } + if (typeof state.breakdownField !== 'undefined') { + savedSearch.breakdownField = state.breakdownField; + } else if (savedSearch.breakdownField) { + savedSearch.breakdownField = ''; + } - if (state.hideAggregatedPreview) { - savedSearch.hideAggregatedPreview = state.hideAggregatedPreview; - } + if (state.hideAggregatedPreview) { + savedSearch.hideAggregatedPreview = state.hideAggregatedPreview; + } - // add a flag here to identify text based language queries - // these should be filtered out from the visualize editor - const isTextBasedQuery = state.query && isOfAggregateQueryType(state.query); - if (savedSearch.isTextBasedQuery || isTextBasedQuery) { - savedSearch.isTextBasedQuery = isTextBasedQuery; + // add a flag here to identify text based language queries + // these should be filtered out from the visualize editor + const isTextBasedQuery = state.query && isOfAggregateQueryType(state.query); + if (savedSearch.isTextBasedQuery || isTextBasedQuery) { + savedSearch.isTextBasedQuery = isTextBasedQuery; + } } - savedSearch.usesAdHocDataView = !dataView.isPersisted(); - const { from, to } = services.timefilter.getTime(); const refreshInterval = services.timefilter.getRefreshInterval(); savedSearch.timeRange = From dc7826203ea807956845f250e66e7061080d439c Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Fri, 31 Mar 2023 06:17:59 +0200 Subject: [PATCH 016/122] Fix test --- .../discover_saved_search_container.test.ts | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/plugins/discover/public/application/main/services/discover_saved_search_container.test.ts b/src/plugins/discover/public/application/main/services/discover_saved_search_container.test.ts index e08a85329d7f3..fea276a4e1c86 100644 --- a/src/plugins/discover/public/application/main/services/discover_saved_search_container.test.ts +++ b/src/plugins/discover/public/application/main/services/discover_saved_search_container.test.ts @@ -175,28 +175,6 @@ describe('DiscoverSavedSearchContainer', () => { expect(savedSearchContainer.getHasChanged$().getValue()).toBe(false); }); - it('Error thrown on persistence layer bubbling up, no changes to the initial saved search ', async () => { - mockSaveSavedSearch.mockImplementation(() => { - throw new Error('oh-noes'); - }); - - const savedSearchContainer = getSavedSearchContainer({ - services: discoverServiceMock, - }); - savedSearchContainer.set(savedSearch); - savedSearchContainer.update({ nextState: { hideChart: true } }); - expect(savedSearchContainer.getHasChanged$().getValue()).toBe(true); - try { - await savedSearchContainer.persist(savedSearch, saveOptions); - } catch (e) { - // intentional error - } - expect(savedSearchContainer.getHasChanged$().getValue()).toBe(true); - expect(savedSearchContainer.getInitial$().getValue().title).not.toBe( - 'My updated saved search' - ); - }); - it('takes care of persisting timeRestore correctly ', async () => { discoverServiceMock.timefilter.getTime = jest.fn(() => ({ from: 'now-15m', to: 'now' })); discoverServiceMock.timefilter.getRefreshInterval = jest.fn(() => ({ @@ -220,6 +198,28 @@ describe('DiscoverSavedSearchContainer', () => { pause: true, }); }); + + it('Error thrown on persistence layer bubbling up, no changes to the initial saved search ', async () => { + mockSaveSavedSearch.mockImplementation(() => { + throw new Error('oh-noes'); + }); + + const savedSearchContainer = getSavedSearchContainer({ + services: discoverServiceMock, + }); + savedSearchContainer.set(savedSearch); + savedSearchContainer.update({ nextState: { hideChart: true } }); + expect(savedSearchContainer.getHasChanged$().getValue()).toBe(true); + try { + await savedSearchContainer.persist(savedSearch, saveOptions); + } catch (e) { + // intentional error + } + expect(savedSearchContainer.getHasChanged$().getValue()).toBe(true); + expect(savedSearchContainer.getInitial$().getValue().title).not.toBe( + 'My updated saved search' + ); + }); }); describe('update', () => { From b2a08f521503c6df977f723b3f0ae4b08a39986c Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Fri, 31 Mar 2023 12:25:58 +0200 Subject: [PATCH 017/122] Refactor saved search loading --- .../application/main/discover_main_route.tsx | 22 ++++-- .../discover_data_state_container.test.ts | 2 +- .../main/services/discover_state.test.ts | 46 ++++++++----- .../main/services/discover_state.ts | 69 +++++++++---------- .../main/services/load_saved_search.ts | 46 +++++++------ 5 files changed, 102 insertions(+), 83 deletions(-) diff --git a/src/plugins/discover/public/application/main/discover_main_route.tsx b/src/plugins/discover/public/application/main/discover_main_route.tsx index 470e73824a90c..e20f73e9376d4 100644 --- a/src/plugins/discover/public/application/main/discover_main_route.tsx +++ b/src/plugins/discover/public/application/main/discover_main_route.tsx @@ -125,11 +125,21 @@ export function DiscoverMainRoute(props: Props) { } try { await stateContainer.actions.loadDataViewList(); - const currentSavedSearch = await stateContainer.actions.loadSavedSearch( - id, - nextDataView, - historyLocationState?.dataViewSpec - ); + // reset appState in case a saved search with id is loaded and the url is empty + // so the saved search is loaded in a clean state + // else it might be updated by the previous app state + const isEmptyURL = stateContainer.appState.isEmptyURL(); + const isPersistedSearch = typeof id === 'string'; + if (isEmptyURL && isPersistedSearch) { + stateContainer.appState.set({}); + } + const useAppState = !isEmptyURL || !isPersistedSearch; + const currentSavedSearch = await stateContainer.actions.loadSavedSearch({ + savedSearchId: id, + dataView: nextDataView, + dataViewSpec: historyLocationState?.dataViewSpec, + useAppState, + }); if (currentSavedSearch?.id) { chrome.recentlyAccessed.add( getSavedSearchFullPathUrl(currentSavedSearch.id), @@ -171,7 +181,7 @@ export function DiscoverMainRoute(props: Props) { }, [ checkData, - stateContainer.actions, + stateContainer, id, historyLocationState?.dataViewSpec, chrome, diff --git a/src/plugins/discover/public/application/main/services/discover_data_state_container.test.ts b/src/plugins/discover/public/application/main/services/discover_data_state_container.test.ts index 860ff1c096c4a..ccb3f4d39a877 100644 --- a/src/plugins/discover/public/application/main/services/discover_data_state_container.test.ts +++ b/src/plugins/discover/public/application/main/services/discover_data_state_container.test.ts @@ -80,7 +80,7 @@ describe('test getDataStateContainer', () => { savedSearch: savedSearchMockWithSQL, }); stateContainer.savedSearchState.load = jest.fn().mockResolvedValue(savedSearchMockWithSQL); - await stateContainer.actions.loadSavedSearch(savedSearchMockWithSQL.id); + await stateContainer.actions.loadSavedSearch({ savedSearchId: savedSearchMockWithSQL.id }); expect(stateContainer.dataState.data$.main$.getValue().recordRawType).toBe(RecordRawType.PLAIN); }); diff --git a/src/plugins/discover/public/application/main/services/discover_state.test.ts b/src/plugins/discover/public/application/main/services/discover_state.test.ts index 6a5c0d10cecb9..8a4df16003175 100644 --- a/src/plugins/discover/public/application/main/services/discover_state.test.ts +++ b/src/plugins/discover/public/application/main/services/discover_state.test.ts @@ -142,7 +142,7 @@ describe('Test discover initial state sort handling', () => { test('Empty URL should use saved search sort for state', async () => { const nextSavedSearch = { ...savedSearchMock, ...{ sort: [['bytes', 'desc']] as SortOrder[] } }; const { state } = await getState('/', nextSavedSearch); - await state.actions.loadSavedSearch(savedSearchMock.id); + await state.actions.loadSavedSearch({ savedSearchId: savedSearchMock.id }); const unsubscribe = state.actions.initializeAndSync(); expect(state.appState.getState().sort).toEqual([['bytes', 'desc']]); unsubscribe(); @@ -341,7 +341,7 @@ describe('actions', () => { const { state, getCurrentUrl } = await getState( '/#?_a=(interval:month,columns:!(bytes))&_g=()' ); - const newSavedSearch = await state.actions.loadSavedSearch(); + const newSavedSearch = await state.actions.loadSavedSearch({ useAppState: true }); expect(newSavedSearch?.id).toBeUndefined(); const unsubscribe = state.actions.initializeAndSync(); state.kbnUrlStateStorage.kbnUrlControls.flush(); @@ -355,7 +355,7 @@ describe('actions', () => { const { state, getCurrentUrl } = await getState( '/#?_a=(interval:month,columns:!(bytes))&_g=()' ); - const newSavedSearch = await state.actions.loadSavedSearch(); + const newSavedSearch = await state.actions.loadSavedSearch({ useAppState: true }); expect(newSavedSearch?.id).toBeUndefined(); const unsubscribe = state.actions.initializeAndSync(); state.kbnUrlStateStorage.kbnUrlControls.flush(); @@ -367,7 +367,9 @@ describe('actions', () => { }); test('loadSavedSearch given an empty URL, no state changes', async () => { const { state, getCurrentUrl } = await getState('/', savedSearchMock); - const newSavedSearch = await state.actions.loadSavedSearch('the-saved-search-id'); + const newSavedSearch = await state.actions.loadSavedSearch({ + savedSearchId: 'the-saved-search-id', + }); const unsubscribe = state.actions.initializeAndSync(); state.kbnUrlStateStorage.kbnUrlControls.flush(); expect(newSavedSearch?.id).toBe('the-saved-search-id'); @@ -380,7 +382,7 @@ describe('actions', () => { test('loadSavedSearch given a URL with different interval and columns modifying the state', async () => { const url = '/#?_a=(interval:month,columns:!(message))&_g=()'; const { state, getCurrentUrl } = await getState(url, savedSearchMock); - await state.actions.loadSavedSearch(savedSearchMock.id); + await state.actions.loadSavedSearch({ savedSearchId: savedSearchMock.id, useAppState: true }); const unsubscribe = state.actions.initializeAndSync(); state.kbnUrlStateStorage.kbnUrlControls.flush(); expect(getCurrentUrl()).toMatchInlineSnapshot( @@ -391,15 +393,27 @@ describe('actions', () => { }); test('loadSavedSearch data view handling', async () => { const { state } = await getState('/', savedSearchMock); - await state.actions.loadSavedSearch(savedSearchMock.id); + await state.actions.loadSavedSearch({ savedSearchId: savedSearchMock.id }); expect(state.savedSearchState.getState().searchSource.getField('index')?.id).toBe( 'the-data-view-id' ); + expect(state.savedSearchState.getHasChanged$().getValue()).toBe(false); + state.savedSearchState.load = jest.fn().mockReturnValue(savedSearchMockWithTimeField); - await state.actions.loadSavedSearch('the-saved-search-id-with-timefield'); + // unsetting the previous index else this is considered as update to the persisted saved search + state.appState.set({ index: undefined }); + await state.actions.loadSavedSearch({ savedSearchId: 'the-saved-search-id-with-timefield' }); expect(state.savedSearchState.getState().searchSource.getField('index')?.id).toBe( 'index-pattern-with-timefield-id' ); + expect(state.savedSearchState.getHasChanged$().getValue()).toBe(false); + + // switch back to the previous savedSearch, but not cleaning up appState index, so it's considered as update to the persisted saved search + await state.actions.loadSavedSearch({ savedSearchId: savedSearchMock.id, useAppState: true }); + expect(state.savedSearchState.getState().searchSource.getField('index')?.id).toBe( + 'index-pattern-with-timefield-id' + ); + expect(state.savedSearchState.getHasChanged$().getValue()).toBe(true); }); test('loadSavedSearch generating a new saved search, updated by ad-hoc data view', async () => { const { state } = await getState('/'); @@ -414,7 +428,7 @@ describe('actions', () => { ...dataViewSpecMock, isPersisted: () => false, })); - await state.actions.loadSavedSearch(undefined, undefined, dataViewSpecMock); + await state.actions.loadSavedSearch({ dataViewSpec: dataViewSpecMock }); expect(state.savedSearchState.getInitial$().getValue().id).toEqual(undefined); expect(state.savedSearchState.getCurrent$().getValue().id).toEqual(undefined); expect( @@ -429,7 +443,7 @@ describe('actions', () => { test('onChangeDataView', async () => { const { state, getCurrentUrl } = await getState('/', savedSearchMock); - await state.actions.loadSavedSearch(savedSearchMock.id); + await state.actions.loadSavedSearch({ savedSearchId: savedSearchMock.id }); expect(state.savedSearchState.getState().searchSource.getField('index')!.id).toBe( dataViewMock.id ); @@ -450,7 +464,7 @@ describe('actions', () => { }); test('onDataViewCreated - persisted data view', async () => { const { state } = await getState('/', savedSearchMock); - await state.actions.loadSavedSearch(savedSearchMock.id); + await state.actions.loadSavedSearch({ savedSearchId: savedSearchMock.id }); const unsubscribe = state.actions.initializeAndSync(); await state.actions.onDataViewCreated(dataViewComplexMock); await waitFor(() => { @@ -464,7 +478,7 @@ describe('actions', () => { }); test('onDataViewCreated - ad-hoc data view', async () => { const { state } = await getState('/', savedSearchMock); - await state.actions.loadSavedSearch(savedSearchMock.id); + await state.actions.loadSavedSearch({ savedSearchId: savedSearchMock.id }); const unsubscribe = state.actions.initializeAndSync(); await state.actions.onDataViewCreated(dataViewAdHoc); await waitFor(() => { @@ -478,7 +492,7 @@ describe('actions', () => { }); test('onDataViewEdited - persisted data view', async () => { const { state } = await getState('/', savedSearchMock); - await state.actions.loadSavedSearch(savedSearchMock.id); + await state.actions.loadSavedSearch({ savedSearchId: savedSearchMock.id }); const selectedDataView = state.internalState.getState().dataView; await waitFor(() => { expect(selectedDataView).toBe(dataViewMock); @@ -506,7 +520,7 @@ describe('actions', () => { test('onOpenSavedSearch - same target id', async () => { const { state } = await getState('/', savedSearchMock); const unsubscribe = state.actions.initializeAndSync(); - await state.actions.loadSavedSearch(savedSearchMock.id); + await state.actions.loadSavedSearch({ savedSearchId: savedSearchMock.id }); await state.savedSearchState.update({ nextState: { hideChart: true } }); expect(state.savedSearchState.getState().hideChart).toBe(true); await state.actions.onOpenSavedSearch(savedSearchMock.id!); @@ -522,7 +536,7 @@ describe('actions', () => { title: 'test', }); const { state } = await getState('/', savedSearchMock); - await state.actions.loadSavedSearch(savedSearchMock.id); + await state.actions.loadSavedSearch({ savedSearchId: savedSearchMock.id }); const unsubscribe = state.actions.initializeAndSync(); await state.actions.onCreateDefaultAdHocDataView('ad-hoc-test'); expect(state.appState.getState().index).toBe('ad-hoc-id'); @@ -531,7 +545,7 @@ describe('actions', () => { }); test('undoChanges', async () => { const { state, getCurrentUrl } = await getState('/', savedSearchMock); - await state.actions.loadSavedSearch(savedSearchMock.id); + await state.actions.loadSavedSearch({ savedSearchId: savedSearchMock.id }); const unsubscribe = state.actions.initializeAndSync(); state.kbnUrlStateStorage.kbnUrlControls.flush(); expect(getCurrentUrl()).toMatchInlineSnapshot( @@ -561,7 +575,7 @@ describe('actions', () => { const setRefreshInterval = jest.fn(); discoverServiceMock.data.query.timefilter.timefilter.setTime = setTime; discoverServiceMock.data.query.timefilter.timefilter.setRefreshInterval = setRefreshInterval; - await state.actions.loadSavedSearch(savedSearchMock.id); + await state.actions.loadSavedSearch({ savedSearchId: savedSearchMock.id }); await state.actions.undoChanges(); expect(setTime).toHaveBeenCalledTimes(1); expect(setTime).toHaveBeenCalledWith({ from: 'now-15d', to: 'now-10d' }); diff --git a/src/plugins/discover/public/application/main/services/discover_state.ts b/src/plugins/discover/public/application/main/services/discover_state.ts index e82e72b474884..0dabc9c8c4570 100644 --- a/src/plugins/discover/public/application/main/services/discover_state.ts +++ b/src/plugins/discover/public/application/main/services/discover_state.ts @@ -67,6 +67,13 @@ interface DiscoverStateContainerParams { services: DiscoverServices; } +export interface LoadParams { + savedSearchId?: string; + dataView?: DataView; + dataViewSpec?: DataViewSpec; + useAppState?: boolean; +} + export interface DiscoverStateContainer { /** * App state, the _a part of the URL @@ -115,11 +122,7 @@ export interface DiscoverStateContainer { * @param savedSearchId * @param dataView */ - loadSavedSearch: ( - savedSearchId?: string | undefined, - dataView?: DataView | undefined, - dataViewSpec?: DataViewSpec - ) => Promise; + loadSavedSearch: (param?: LoadParams) => Promise; /** * Create and select a ad-hoc data view by a given index pattern * @param pattern @@ -330,53 +333,42 @@ export function getDiscoverStateContainer({ return persistedDataView; }; - const loadSavedSearch = async ( - id?: string, - nextDataView?: DataView, - dataViewSpec?: DataViewSpec - ): Promise => { - const { dataView } = nextDataView - ? { dataView: nextDataView } - : id - ? { dataView: undefined } - : await loadAndResolveDataView(appStateContainer.getState().index, dataViewSpec); - - const nextSavedSearch = await loadNextSavedSearch(id, dataView, { - appStateContainer, + const loadSavedSearch = async (params?: LoadParams): Promise => { + const { savedSearchId, dataView, dataViewSpec, useAppState } = params ?? {}; + const appState = useAppState ? appStateContainer.getState() : undefined; + const actualDataView = dataView + ? dataView + : (await loadAndResolveDataView(appState?.index, dataViewSpec)).dataView; + + const nextSavedSearch = await loadNextSavedSearch({ + id: savedSearchId, + dataView: actualDataView, + appState, savedSearchContainer, }); + await appStateContainer.resetWithSavedSearch(nextSavedSearch); - if ( - id && - appStateContainer.getState().index && - appStateContainer.getState().index !== nextSavedSearch.searchSource.getField('index') - ) { - const { dataView: appStateDataView } = await loadAndResolveDataView( - appStateContainer.getState().index, - dataViewSpec - ); - if (appStateDataView) { - nextSavedSearch.searchSource.setField('index', appStateDataView); - } - } - - const actualDataView = nextSavedSearch.searchSource.getField('index'); - if (actualDataView) { - setDataView(actualDataView); + const savedSearchDataView = nextSavedSearch.searchSource.getField('index'); + if (savedSearchDataView) { + setDataView(savedSearchDataView); if (!actualDataView.isPersisted()) { - internalStateContainer.transitions.appendAdHocDataViews(actualDataView); + internalStateContainer.transitions.appendAdHocDataViews(savedSearchDataView); } } + dataStateContainer.reset(nextSavedSearch); return nextSavedSearch; }; const initializeAndSync = () => { + /** + * state containers initializing and starting to notify each other about changes + */ const unsubscribeData = dataStateContainer.subscribe(); const appStateInitAndSyncUnsubscribe = appStateContainer.initAndSync( savedSearchContainer.getState() ); - + // updates saved search when app state changes, triggers data fetching if required const appStateUnsubscribe = appStateContainer.subscribe( buildStateSubscribe({ appState: appStateContainer, @@ -386,7 +378,7 @@ export function getDiscoverStateContainer({ setDataView, }) ); - + // updates saved search when query or filters change, triggers data fetching const filterUnsubscribe = merge( services.data.query.queryString.getUpdates$(), services.filterManager.getFetches$() @@ -424,6 +416,7 @@ export function getDiscoverStateContainer({ isUpdate?: boolean ) => { if (isUpdate === false) { + // remove the search session if the given query is not just updated searchSessionManager.removeSearchSessionIdFromURL({ replace: false }); dataStateContainer.fetch(); } diff --git a/src/plugins/discover/public/application/main/services/load_saved_search.ts b/src/plugins/discover/public/application/main/services/load_saved_search.ts index 93ca308d025ba..fb1eeb05d1cd7 100644 --- a/src/plugins/discover/public/application/main/services/load_saved_search.ts +++ b/src/plugins/discover/public/application/main/services/load_saved_search.ts @@ -7,40 +7,42 @@ */ import { DataView } from '@kbn/data-views-plugin/common'; import { SavedSearch } from '@kbn/saved-search-plugin/public'; -import { DiscoverAppStateContainer } from './discover_app_state_container'; +import { DiscoverAppState } from './discover_app_state_container'; import { DiscoverSavedSearchContainer } from './discover_saved_search_container'; import { addLog } from '../../../utils/add_log'; -export const loadSavedSearch = async ( - id: string | undefined, - dataView: DataView | undefined, - { - appStateContainer, - savedSearchContainer, - }: { - appStateContainer: DiscoverAppStateContainer; - savedSearchContainer: DiscoverSavedSearchContainer; - } -): Promise => { +export const loadSavedSearch = async ({ + id, + dataView, + appState, + savedSearchContainer, +}: { + id: string | undefined; + dataView: DataView | undefined; + appState: DiscoverAppState | undefined; + savedSearchContainer: DiscoverSavedSearchContainer; +}): Promise => { addLog('[discoverState] loadSavedSearch'); - const isEmptyURL = appStateContainer.isEmptyURL(); - const isPersistedSearch = typeof id === 'string'; - if (isEmptyURL && isPersistedSearch) { - appStateContainer.set({}); - } - const appState = !isEmptyURL ? appStateContainer.getState() : undefined; - let nextSavedSearch = isPersistedSearch - ? await savedSearchContainer.load(id, dataView) + let nextSavedSearch = id + ? await savedSearchContainer.load(id) : await savedSearchContainer.new(dataView); if (appState) { + if ( + id && + dataView && + appState.index && + appState.index !== nextSavedSearch.searchSource.getField('index')?.id + ) { + // given a saved search is loaded, but the state in URL has a different data view selected + nextSavedSearch.searchSource.setField('index', dataView); + } + nextSavedSearch = savedSearchContainer.update({ nextDataView: nextSavedSearch.searchSource.getField('index'), nextState: appState, }); } - await appStateContainer.resetWithSavedSearch(nextSavedSearch); - return nextSavedSearch; }; From 93ed9c907ccf3dff28e4862ca20c326cd9265647 Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Fri, 31 Mar 2023 18:11:03 +0200 Subject: [PATCH 018/122] Fix functional tests --- .../discover/public/application/main/discover_main_route.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/discover/public/application/main/discover_main_route.tsx b/src/plugins/discover/public/application/main/discover_main_route.tsx index e20f73e9376d4..b6d0025aff9cd 100644 --- a/src/plugins/discover/public/application/main/discover_main_route.tsx +++ b/src/plugins/discover/public/application/main/discover_main_route.tsx @@ -130,7 +130,7 @@ export function DiscoverMainRoute(props: Props) { // else it might be updated by the previous app state const isEmptyURL = stateContainer.appState.isEmptyURL(); const isPersistedSearch = typeof id === 'string'; - if (isEmptyURL && isPersistedSearch) { + if (isEmptyURL) { stateContainer.appState.set({}); } const useAppState = !isEmptyURL || !isPersistedSearch; From e8b40b21a1c16d4b1407c844fd0bf182cf85ff63 Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Fri, 31 Mar 2023 23:11:43 +0200 Subject: [PATCH 019/122] Refactor unit tests --- .../application/main/discover_main_route.tsx | 3 --- .../services/discover_app_state_container.ts | 21 ++++--------------- .../main/services/discover_state.test.ts | 9 ++++++++ .../main/services/discover_state.ts | 6 ++++-- 4 files changed, 17 insertions(+), 22 deletions(-) diff --git a/src/plugins/discover/public/application/main/discover_main_route.tsx b/src/plugins/discover/public/application/main/discover_main_route.tsx index b6d0025aff9cd..550b5b4762e6b 100644 --- a/src/plugins/discover/public/application/main/discover_main_route.tsx +++ b/src/plugins/discover/public/application/main/discover_main_route.tsx @@ -130,9 +130,6 @@ export function DiscoverMainRoute(props: Props) { // else it might be updated by the previous app state const isEmptyURL = stateContainer.appState.isEmptyURL(); const isPersistedSearch = typeof id === 'string'; - if (isEmptyURL) { - stateContainer.appState.set({}); - } const useAppState = !isEmptyURL || !isPersistedSearch; const currentSavedSearch = await stateContainer.actions.loadSavedSearch({ savedSearchId: id, diff --git a/src/plugins/discover/public/application/main/services/discover_app_state_container.ts b/src/plugins/discover/public/application/main/services/discover_app_state_container.ts index ba5c38862da80..c01f54ab802b9 100644 --- a/src/plugins/discover/public/application/main/services/discover_app_state_container.ts +++ b/src/plugins/discover/public/application/main/services/discover_app_state_container.ts @@ -56,11 +56,6 @@ export interface DiscoverAppStateContainer extends ReduxLikeStateContainer void; - /** - * Resets the state by the given saved search - * @param savedSearch - */ - resetWithSavedSearch: (savedSearch: SavedSearch) => void; /** * Resets the current state to the initial state */ @@ -248,12 +243,6 @@ export const getDiscoverAppStateContainer = ({ }; }; - const resetWithSavedSearch = (nextSavedSearch: SavedSearch) => { - addLog('[appState] reset to saved search', { nextSavedSearch }); - const nextAppState = getInitialState(stateStorage, nextSavedSearch, services); - appStateContainer.set(nextAppState); - }; - const update = (newPartial: DiscoverAppState, replace = false) => { addLog('[appState] update', { newPartial, replace }); if (replace) { @@ -276,7 +265,6 @@ export const getDiscoverAppStateContainer = ({ getPrevious, hasChanged, initAndSync: initializeAndSync, - resetWithSavedSearch, resetInitialState, replaceUrlState, syncState: startAppStateUrlSync, @@ -293,13 +281,12 @@ export interface AppStateUrl extends Omit { export const GLOBAL_STATE_URL_KEY = '_g'; -function getInitialState( - stateStorage: IKbnUrlStateStorage, +export function getInitialState( + stateStorage: IKbnUrlStateStorage | undefined, savedSearch: SavedSearch, services: DiscoverServices ) { - const stateStorageURL = stateStorage.get(APP_STATE_URL_KEY) as AppStateUrl; - const appStateFromUrl = cleanupUrlState(stateStorageURL); + const stateStorageURL = stateStorage?.get(APP_STATE_URL_KEY) as AppStateUrl; const defaultAppState = getStateDefaults({ savedSearch, services, @@ -309,7 +296,7 @@ function getInitialState( ? defaultAppState : { ...defaultAppState, - ...appStateFromUrl, + ...cleanupUrlState(stateStorageURL), }, services.uiSettings ); diff --git a/src/plugins/discover/public/application/main/services/discover_state.test.ts b/src/plugins/discover/public/application/main/services/discover_state.test.ts index 8a4df16003175..86e126dd422db 100644 --- a/src/plugins/discover/public/application/main/services/discover_state.test.ts +++ b/src/plugins/discover/public/application/main/services/discover_state.test.ts @@ -391,6 +391,15 @@ describe('actions', () => { expect(state.savedSearchState.getHasChanged$().getValue()).toBe(true); unsubscribe(); }); + + test('loadSavedSearch ignoring hideChart in URL', async () => { + const url = '/#?_a=(hideChart:true,columns:!(message))&_g=()'; + const { state } = await getState(url, savedSearchMock); + await state.actions.loadSavedSearch(); + expect(state.savedSearchState.getState().hideChart).toBe(undefined); + expect(state.appState.getState().hideChart).toBe(undefined); + }); + test('loadSavedSearch data view handling', async () => { const { state } = await getState('/', savedSearchMock); await state.actions.loadSavedSearch({ savedSearchId: savedSearchMock.id }); diff --git a/src/plugins/discover/public/application/main/services/discover_state.ts b/src/plugins/discover/public/application/main/services/discover_state.ts index 0dabc9c8c4570..54fcad53efa7b 100644 --- a/src/plugins/discover/public/application/main/services/discover_state.ts +++ b/src/plugins/discover/public/application/main/services/discover_state.ts @@ -39,6 +39,7 @@ import { DiscoverAppState, DiscoverAppStateContainer, getDiscoverAppStateContainer, + getInitialState, GLOBAL_STATE_URL_KEY, } from './discover_app_state_container'; import { @@ -120,7 +121,7 @@ export interface DiscoverStateContainer { /** * Load a saved search by id or create a new one that's not persisted yet * @param savedSearchId - * @param dataView + * @param LoadParams - optional parameters to load a saved search */ loadSavedSearch: (param?: LoadParams) => Promise; /** @@ -346,7 +347,8 @@ export function getDiscoverStateContainer({ appState, savedSearchContainer, }); - await appStateContainer.resetWithSavedSearch(nextSavedSearch); + const nextAppState = getInitialState(undefined, nextSavedSearch, services); + appStateContainer.set(appState ? { ...nextAppState, ...appState } : nextAppState); const savedSearchDataView = nextSavedSearch.searchSource.getField('index'); if (savedSearchDataView) { From eb331e61ea923b5f327a3ba0077b858376a69411 Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Sat, 1 Apr 2023 01:41:30 +0200 Subject: [PATCH 020/122] Let's see what functionals say --- .../application/main/discover_main_route.tsx | 4 +--- .../services/discover_app_state_container.ts | 3 ++- .../discover_saved_search_container.ts | 15 +++++++++++--- .../main/services/discover_state.test.ts | 11 ++++++++++ .../main/services/discover_state.ts | 20 ++++++++++++++++++- .../main/utils/update_saved_search.ts | 8 ++++---- 6 files changed, 49 insertions(+), 12 deletions(-) diff --git a/src/plugins/discover/public/application/main/discover_main_route.tsx b/src/plugins/discover/public/application/main/discover_main_route.tsx index 550b5b4762e6b..1fcca3fe57981 100644 --- a/src/plugins/discover/public/application/main/discover_main_route.tsx +++ b/src/plugins/discover/public/application/main/discover_main_route.tsx @@ -128,9 +128,7 @@ export function DiscoverMainRoute(props: Props) { // reset appState in case a saved search with id is loaded and the url is empty // so the saved search is loaded in a clean state // else it might be updated by the previous app state - const isEmptyURL = stateContainer.appState.isEmptyURL(); - const isPersistedSearch = typeof id === 'string'; - const useAppState = !isEmptyURL || !isPersistedSearch; + const useAppState = !stateContainer.appState.isEmptyURL(); const currentSavedSearch = await stateContainer.actions.loadSavedSearch({ savedSearchId: id, dataView: nextDataView, diff --git a/src/plugins/discover/public/application/main/services/discover_app_state_container.ts b/src/plugins/discover/public/application/main/services/discover_app_state_container.ts index c01f54ab802b9..fe070ae56db4d 100644 --- a/src/plugins/discover/public/application/main/services/discover_app_state_container.ts +++ b/src/plugins/discover/public/application/main/services/discover_app_state_container.ts @@ -254,7 +254,8 @@ export const getDiscoverAppStateContainer = ({ }; const isEmptyURL = () => { - return stateStorage.get(APP_STATE_URL_KEY) === null; + const urlValue = stateStorage.get(APP_STATE_URL_KEY); + return urlValue === undefined || urlValue === null; }; const getPrevious = () => previousState; diff --git a/src/plugins/discover/public/application/main/services/discover_saved_search_container.ts b/src/plugins/discover/public/application/main/services/discover_saved_search_container.ts index be7670d827c48..ebb49ad75486d 100644 --- a/src/plugins/discover/public/application/main/services/discover_saved_search_container.ts +++ b/src/plugins/discover/public/application/main/services/discover_saved_search_container.ts @@ -25,9 +25,18 @@ import { DiscoverServices } from '../../../build_services'; import { getStateDefaults } from '../utils/get_state_defaults'; export interface UpdateParams { + /** + * The next data view to be used + */ nextDataView?: DataView | undefined; + /** + * The next AppState that should be used for updating the saved search + */ nextState?: DiscoverAppState | undefined; - filterAndQuery?: boolean; + /** + * use filter and query services to update the saved search + */ + updateByFilterAndQuery?: boolean; } /** @@ -160,7 +169,7 @@ export function getSavedSearchContainer({ } return { id }; }; - const update = ({ nextDataView, nextState, filterAndQuery }: UpdateParams) => { + const update = ({ nextDataView, nextState, updateByFilterAndQuery }: UpdateParams) => { addLog('[savedSearch] update', { nextDataView, nextState }); const previousSavedSearch = getState(); @@ -175,7 +184,7 @@ export function getSavedSearchContainer({ state: nextState || {}, services, }, - !filterAndQuery + updateByFilterAndQuery ); const hasChanged = !isEqualSavedSearch(savedSearchInitial$.getValue(), nextSavedSearch); diff --git a/src/plugins/discover/public/application/main/services/discover_state.test.ts b/src/plugins/discover/public/application/main/services/discover_state.test.ts index 86e126dd422db..544f1fd2b9027 100644 --- a/src/plugins/discover/public/application/main/services/discover_state.test.ts +++ b/src/plugins/discover/public/application/main/services/discover_state.test.ts @@ -537,6 +537,17 @@ describe('actions', () => { unsubscribe(); }); + test('onOpenSavedSearch - cleanup of previous filter', async () => { + const { state } = await getState( + "/#?_g=(filters:!(),refreshInterval:(pause:!t,value:60000),time:(from:now-15m,to:now))&_a=(columns:!(customer_first_name),filters:!(('$state':(store:appState),meta:(alias:!n,disabled:!f,index:ff959d40-b880-11e8-a6d9-e546fe2bba5f,key:customer_first_name,negate:!f,params:(query:Mary),type:phrase),query:(match_phrase:(customer_first_name:Mary)))),hideChart:!f,index:ff959d40-b880-11e8-a6d9-e546fe2bba5f,interval:auto,query:(language:kuery,query:''),sort:!())", + savedSearchMock + ); + await state.actions.loadSavedSearch({ savedSearchId: savedSearchMock.id, useAppState: true }); + expect(state.appState.get().filters).toHaveLength(1); + await state.actions.loadSavedSearch({ useAppState: false }); + expect(state.appState.get().filters).toHaveLength(0); + }); + test('onCreateDefaultAdHocDataView', async () => { discoverServiceMock.dataViews.create = jest.fn().mockReturnValue({ ...dataViewMock, diff --git a/src/plugins/discover/public/application/main/services/discover_state.ts b/src/plugins/discover/public/application/main/services/discover_state.ts index 54fcad53efa7b..6093bd1c30f53 100644 --- a/src/plugins/discover/public/application/main/services/discover_state.ts +++ b/src/plugins/discover/public/application/main/services/discover_state.ts @@ -69,9 +69,22 @@ interface DiscoverStateContainerParams { } export interface LoadParams { + /** + * the id of the saved search to load, if undefined, a new saved search will be created + */ savedSearchId?: string; + /** + * the data view to use, if undefined, the saved search's data view will be used + */ dataView?: DataView; + /** + * the data view spec to use, if undefined, the saved search's data view will be used + */ dataViewSpec?: DataViewSpec; + /** + * determins if AppState should be used to update the saved search + * URL is overwriting savedSearch params in this case + */ useAppState?: boolean; } @@ -337,6 +350,11 @@ export function getDiscoverStateContainer({ const loadSavedSearch = async (params?: LoadParams): Promise => { const { savedSearchId, dataView, dataViewSpec, useAppState } = params ?? {}; const appState = useAppState ? appStateContainer.getState() : undefined; + if (!useAppState) { + appStateContainer.set({}); + services.filterManager.setAppFilters([]); + services.data.query.queryString.clearQuery(); + } const actualDataView = dataView ? dataView : (await loadAndResolveDataView(appState?.index, dataViewSpec)).dataView; @@ -388,7 +406,7 @@ export function getDiscoverStateContainer({ await savedSearchContainer.update({ nextDataView: internalStateContainer.getState().dataView, nextState: appStateContainer.getState(), - filterAndQuery: true, + updateByFilterAndQuery: true, }); fetchData(); }); diff --git a/src/plugins/discover/public/application/main/utils/update_saved_search.ts b/src/plugins/discover/public/application/main/utils/update_saved_search.ts index a5728e842968b..3ec6a05d53bfb 100644 --- a/src/plugins/discover/public/application/main/utils/update_saved_search.ts +++ b/src/plugins/discover/public/application/main/utils/update_saved_search.ts @@ -24,20 +24,20 @@ export function updateSavedSearch( state?: DiscoverAppState; services: DiscoverServices; }, - initial: boolean = false + updateByFilterAndQuery: boolean = false ) { if (dataView) { savedSearch.searchSource.setField('index', dataView); savedSearch.usesAdHocDataView = !dataView.isPersisted(); } - if (!initial || !state) { + if (updateByFilterAndQuery || !state) { savedSearch.searchSource .setField('query', services.data.query.queryString.getQuery() || null) - .setField('filter', services.data.query.filterManager.getFilters()); + .setField('filter', services.data.query.filterManager.getFilters() || []); } else { savedSearch.searchSource .setField('query', state.query) - .setField('filter', cloneDeep(state.filters)); + .setField('filter', cloneDeep(state.filters) || []); } if (state) { savedSearch.columns = state.columns || []; From ddc5af15a51e145874faf9545357d01e71d5d50f Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Sat, 1 Apr 2023 10:04:46 +0200 Subject: [PATCH 021/122] Fix sorting --- .../public/application/main/services/discover_state.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/plugins/discover/public/application/main/services/discover_state.ts b/src/plugins/discover/public/application/main/services/discover_state.ts index 6093bd1c30f53..7650bd51d32a5 100644 --- a/src/plugins/discover/public/application/main/services/discover_state.ts +++ b/src/plugins/discover/public/application/main/services/discover_state.ts @@ -24,6 +24,7 @@ import type { SavedSearch } from '@kbn/saved-search-plugin/public'; import { v4 as uuidv4 } from 'uuid'; import { merge } from 'rxjs'; import { AggregateQuery, Query, TimeRange } from '@kbn/es-query'; +import { cleanupUrlState } from '../utils/cleanup_url_state'; import { restoreStateFromSavedSearch } from '../../../services/saved_searches/restore_from_saved_search'; import { FetchStatus } from '../../types'; import { changeDataView } from '../hooks/utils/change_data_view'; @@ -366,7 +367,9 @@ export function getDiscoverStateContainer({ savedSearchContainer, }); const nextAppState = getInitialState(undefined, nextSavedSearch, services); - appStateContainer.set(appState ? { ...nextAppState, ...appState } : nextAppState); + appStateContainer.set( + appState ? { ...nextAppState, ...cleanupUrlState({ ...appState }) } : nextAppState + ); const savedSearchDataView = nextSavedSearch.searchSource.getField('index'); if (savedSearchDataView) { From 4392bbd2327d653ca6f6691bc05c3f5164097bba Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Tue, 11 Apr 2023 11:23:19 +0200 Subject: [PATCH 022/122] Update src/plugins/discover/public/application/main/discover_main_app.tsx Co-authored-by: Julia Rechkunova --- .../discover/public/application/main/discover_main_app.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/plugins/discover/public/application/main/discover_main_app.tsx b/src/plugins/discover/public/application/main/discover_main_app.tsx index bc833a13f4e32..648ab0fce7029 100644 --- a/src/plugins/discover/public/application/main/discover_main_app.tsx +++ b/src/plugins/discover/public/application/main/discover_main_app.tsx @@ -83,11 +83,14 @@ export function DiscoverMainApp(props: DiscoverMainProps) { useEffect(() => { addHelpMenuToAppChrome(chrome, docLinks); + }, [chrome, docLinks]); + + useEffect(() => { return () => { // clear session when navigating away from discover main data.search.session.clear(); }; - }, [data.search.session, chrome, docLinks]); + }, [data.search.session]); useSavedSearchAliasMatchRedirect({ savedSearch, spaces, history }); From 0d153f0750cb41246b46fb2a261500dc9f75db67 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 11 Apr 2023 09:27:53 +0000 Subject: [PATCH 023/122] [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' --- .../discover/public/application/main/discover_main_app.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/discover/public/application/main/discover_main_app.tsx b/src/plugins/discover/public/application/main/discover_main_app.tsx index 648ab0fce7029..a3dc2d361df7d 100644 --- a/src/plugins/discover/public/application/main/discover_main_app.tsx +++ b/src/plugins/discover/public/application/main/discover_main_app.tsx @@ -84,7 +84,7 @@ export function DiscoverMainApp(props: DiscoverMainProps) { useEffect(() => { addHelpMenuToAppChrome(chrome, docLinks); }, [chrome, docLinks]); - + useEffect(() => { return () => { // clear session when navigating away from discover main From 98efa69830cb63ad307bee3943b2eba9f14949b2 Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Wed, 12 Apr 2023 22:04:50 +0200 Subject: [PATCH 024/122] Attempt to fix an existing query triggering a request if FetchStatus.UNINITIALIZED is given --- .../discover/public/application/main/services/discover_state.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/discover/public/application/main/services/discover_state.ts b/src/plugins/discover/public/application/main/services/discover_state.ts index 7650bd51d32a5..e28aa4351e1f2 100644 --- a/src/plugins/discover/public/application/main/services/discover_state.ts +++ b/src/plugins/discover/public/application/main/services/discover_state.ts @@ -387,10 +387,10 @@ export function getDiscoverStateContainer({ /** * state containers initializing and starting to notify each other about changes */ - const unsubscribeData = dataStateContainer.subscribe(); const appStateInitAndSyncUnsubscribe = appStateContainer.initAndSync( savedSearchContainer.getState() ); + const unsubscribeData = dataStateContainer.subscribe(); // updates saved search when app state changes, triggers data fetching if required const appStateUnsubscribe = appStateContainer.subscribe( buildStateSubscribe({ From 365bc526c529d726c18eea21f0308208e626ac46 Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Thu, 13 Apr 2023 09:10:17 +0200 Subject: [PATCH 025/122] Refactor setting data service query and filters to be taken care when loading the saved search --- .../services/discover_app_state_container.ts | 29 +++--------------- .../discover_saved_search_container.ts | 5 +++- .../main/services/discover_state.test.ts | 21 +++++++++++++ .../main/services/discover_state.ts | 30 +++++++++++++++++-- 4 files changed, 56 insertions(+), 29 deletions(-) diff --git a/src/plugins/discover/public/application/main/services/discover_app_state_container.ts b/src/plugins/discover/public/application/main/services/discover_app_state_container.ts index fe070ae56db4d..ba189bec26a1b 100644 --- a/src/plugins/discover/public/application/main/services/discover_app_state_container.ts +++ b/src/plugins/discover/public/application/main/services/discover_app_state_container.ts @@ -21,11 +21,10 @@ import { } from '@kbn/es-query'; import { SavedSearch, VIEW_MODE } from '@kbn/saved-search-plugin/public'; import { IKbnUrlStateStorage, ISyncStateRef, syncState } from '@kbn/kibana-utils-plugin/public'; -import { cloneDeep, isEqual } from 'lodash'; +import { isEqual } from 'lodash'; import { connectToQueryState, syncGlobalQueryStateWithUrl } from '@kbn/data-plugin/public'; import { DiscoverServices } from '../../../build_services'; import { addLog } from '../../../utils/add_log'; -import { getValidFilters } from '../../../utils/get_valid_filters'; import { cleanupUrlState } from '../utils/cleanup_url_state'; import { getStateDefaults } from '../utils/get_state_defaults'; import { handleSourceColumnState } from '../../../utils/state_helpers'; @@ -189,26 +188,14 @@ export const getDiscoverAppStateContainer = ({ const initializeAndSync = (currentSavedSearch: SavedSearch) => { addLog('[appState] initialize state and sync with URL', currentSavedSearch); - const { filterManager, data } = services; - - // searchsource is the source of truth + const { data } = services; const dataView = currentSavedSearch.searchSource.getField('index'); - const filters = currentSavedSearch.searchSource.getField('filter'); - const query = currentSavedSearch.searchSource.getField('query'); + if (appStateContainer.getState().index !== dataView?.id) { // used data view is different from the given by url/state which is invalid setState(appStateContainer, { index: dataView?.id }); } - // sync initial app filters from state to filterManager - if (Array.isArray(filters) && filters.length) { - filterManager.setAppFilters(cloneDeep(filters)); - } else { - filterManager.setAppFilters([]); - } - if (query) { - data.query.queryString.setQuery(query); - } - + // syncs `_a` portion of url with query services const stopSyncingQueryAppStateWithStateContainer = connectToQueryState( data.query, appStateContainer, @@ -224,14 +211,6 @@ export const getDiscoverAppStateContainer = ({ stateStorage ); - // some filters may not be valid for this context, so update - // the filter manager with a modified list of valid filters - const currentFilters = filterManager.getFilters(); - const validFilters = getValidFilters(dataView!, currentFilters); - if (!isEqual(currentFilters, validFilters)) { - filterManager.setFilters(validFilters); - } - const { start, stop } = startAppStateUrlSync(); // current state need to be pushed to url replaceUrlState({}).then(() => start()); diff --git a/src/plugins/discover/public/application/main/services/discover_saved_search_container.ts b/src/plugins/discover/public/application/main/services/discover_saved_search_container.ts index ebb49ad75486d..6acb551212d6b 100644 --- a/src/plugins/discover/public/application/main/services/discover_saved_search_container.ts +++ b/src/plugins/discover/public/application/main/services/discover_saved_search_container.ts @@ -227,8 +227,11 @@ export function getSavedSearchContainer({ }; } +/** + * Copies a saved search object, due to the stateful nature of searchSource it has to be copied with a dedicated function + * @param savedSearch + */ export function copySavedSearch(savedSearch: SavedSearch): SavedSearch { - // due to the stateful nature of searchSource it has to be copied separately return { ...savedSearch, ...{ searchSource: savedSearch.searchSource.createCopy() }, diff --git a/src/plugins/discover/public/application/main/services/discover_state.test.ts b/src/plugins/discover/public/application/main/services/discover_state.test.ts index 544f1fd2b9027..bf241ba48c2e1 100644 --- a/src/plugins/discover/public/application/main/services/discover_state.test.ts +++ b/src/plugins/discover/public/application/main/services/discover_state.test.ts @@ -450,6 +450,27 @@ describe('actions', () => { expect(state.internalState.getState().adHocDataViews.length).toBe(1); }); + test('loadSavedSearch resetting query & filters of data service', async () => { + const { state } = await getState('/', savedSearchMock); + await state.actions.loadSavedSearch({ savedSearchId: savedSearchMock.id }); + expect(discoverServiceMock.data.query.queryString.clearQuery()).toHaveBeenCalled(); + expect(discoverServiceMock.data.query.filterManager.setAppFilters).toHaveBeenCalledWith([]); + }); + + test('loadSavedSearch setting query & filters of data service if query and filters are persisted', async () => { + const savedSearchWithQueryAndFilters = copySavedSearch(savedSearchMock); + const query = { query: "foo: 'bar'", language: 'kql' }; + const filters = [{ meta: { index: 'the-data-view-id' }, query: { match_all: {} } }]; + savedSearchWithQueryAndFilters.searchSource.setField('query', query); + savedSearchWithQueryAndFilters.searchSource.setField('filter', filters); + const { state } = await getState('/', savedSearchWithQueryAndFilters); + await state.actions.loadSavedSearch({ savedSearchId: savedSearchMock.id }); + expect(discoverServiceMock.data.query.queryString.setQuery).toHaveBeenCalledWith(query); + expect(discoverServiceMock.data.query.filterManager.setAppFilters).toHaveBeenCalledWith( + filters + ); + }); + test('onChangeDataView', async () => { const { state, getCurrentUrl } = await getState('/', savedSearchMock); await state.actions.loadSavedSearch({ savedSearchId: savedSearchMock.id }); diff --git a/src/plugins/discover/public/application/main/services/discover_state.ts b/src/plugins/discover/public/application/main/services/discover_state.ts index e28aa4351e1f2..ef88550e43386 100644 --- a/src/plugins/discover/public/application/main/services/discover_state.ts +++ b/src/plugins/discover/public/application/main/services/discover_state.ts @@ -24,6 +24,8 @@ import type { SavedSearch } from '@kbn/saved-search-plugin/public'; import { v4 as uuidv4 } from 'uuid'; import { merge } from 'rxjs'; import { AggregateQuery, Query, TimeRange } from '@kbn/es-query'; +import { cloneDeep, isEqual } from 'lodash'; +import { getValidFilters } from '../../../utils/get_valid_filters'; import { cleanupUrlState } from '../utils/cleanup_url_state'; import { restoreStateFromSavedSearch } from '../../../services/saved_searches/restore_from_saved_search'; import { FetchStatus } from '../../types'; @@ -351,11 +353,13 @@ export function getDiscoverStateContainer({ const loadSavedSearch = async (params?: LoadParams): Promise => { const { savedSearchId, dataView, dataViewSpec, useAppState } = params ?? {}; const appState = useAppState ? appStateContainer.getState() : undefined; + // First let's clean up the previous state + services.filterManager.setAppFilters([]); + services.data.query.queryString.clearQuery(); if (!useAppState) { appStateContainer.set({}); - services.filterManager.setAppFilters([]); - services.data.query.queryString.clearQuery(); } + // Then, take care of data view and saved search loading, deriving the next state const actualDataView = dataView ? dataView : (await loadAndResolveDataView(appState?.index, dataViewSpec)).dataView; @@ -378,8 +382,28 @@ export function getDiscoverStateContainer({ internalStateContainer.transitions.appendAdHocDataViews(savedSearchDataView); } } - + // Finally notify dataStateContainer, data.query and filterManager about new derived state dataStateContainer.reset(nextSavedSearch); + + // set data service filters + const filters = nextSavedSearch.searchSource.getField('filter'); + + if (Array.isArray(filters) && filters.length) { + services.data.query.filterManager.setAppFilters(cloneDeep(filters)); + } + // some filters may not be valid for this context, so update + // the filter manager with a modified list of valid filters + const currentFilters = services.filterManager.getFilters(); + const validFilters = getValidFilters(actualDataView, currentFilters); + if (!isEqual(currentFilters, validFilters)) { + services.filterManager.setFilters(validFilters); + } + // set data service query + const query = nextSavedSearch.searchSource.getField('query'); + if (query) { + services.data.query.queryString.setQuery(query); + } + return nextSavedSearch; }; From c8ec5063ecb82fa4702448f04cec5d7a8632a486 Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Thu, 13 Apr 2023 12:02:22 +0200 Subject: [PATCH 026/122] Fix jest test --- .../public/application/main/services/discover_state.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/discover/public/application/main/services/discover_state.test.ts b/src/plugins/discover/public/application/main/services/discover_state.test.ts index bf241ba48c2e1..ba1fb76369fa6 100644 --- a/src/plugins/discover/public/application/main/services/discover_state.test.ts +++ b/src/plugins/discover/public/application/main/services/discover_state.test.ts @@ -453,7 +453,7 @@ describe('actions', () => { test('loadSavedSearch resetting query & filters of data service', async () => { const { state } = await getState('/', savedSearchMock); await state.actions.loadSavedSearch({ savedSearchId: savedSearchMock.id }); - expect(discoverServiceMock.data.query.queryString.clearQuery()).toHaveBeenCalled(); + expect(discoverServiceMock.data.query.queryString.clearQuery).toHaveBeenCalled(); expect(discoverServiceMock.data.query.filterManager.setAppFilters).toHaveBeenCalledWith([]); }); From b5a63c1d89a19316b36b18342b089b00df67f356 Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Tue, 18 Apr 2023 09:15:55 +0200 Subject: [PATCH 027/122] Add more documentation, refactor undoChanges to undoSavedSearchChanges so it's clearer --- .../layout/discover_histogram_layout.test.tsx | 4 +- .../layout/discover_histogram_layout.tsx | 2 +- .../components/top_nav/on_save_search.tsx | 2 +- .../main/services/discover_state.test.ts | 34 +++++++---- .../main/services/discover_state.ts | 59 ++++++++++++------- 5 files changed, 66 insertions(+), 35 deletions(-) diff --git a/src/plugins/discover/public/application/main/components/layout/discover_histogram_layout.test.tsx b/src/plugins/discover/public/application/main/components/layout/discover_histogram_layout.test.tsx index b780a4deb2467..beb09b473d2c5 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_histogram_layout.test.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_histogram_layout.test.tsx @@ -111,7 +111,7 @@ const mountComponent = async ({ const stateContainer = getStateContainer(savedSearch); stateContainer.dataState.data$ = savedSearchData$; - stateContainer.actions.undoChanges = jest.fn(); + stateContainer.actions.undoSavedSearchChanges = jest.fn(); const props: DiscoverHistogramLayoutProps = { isPlainRecord, @@ -179,7 +179,7 @@ describe('Discover histogram layout component', () => { const { component, stateContainer } = await mountComponent(); expect(component.find(ResetSearchButton).exists()).toBe(true); component.find(ResetSearchButton).find('button').simulate('click'); - expect(stateContainer.actions.undoChanges).toHaveBeenCalled(); + expect(stateContainer.actions.undoSavedSearchChanges).toHaveBeenCalled(); }); }); }); diff --git a/src/plugins/discover/public/application/main/components/layout/discover_histogram_layout.tsx b/src/plugins/discover/public/application/main/components/layout/discover_histogram_layout.tsx index ec8c010a0b3b0..d644455ae4529 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_histogram_layout.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_histogram_layout.tsx @@ -52,7 +52,7 @@ export const DiscoverHistogramLayout = ({ resizeRef={resizeRef} appendHitsCounter={ savedSearchState.getId() ? ( - + ) : undefined } css={histogramLayoutCss} diff --git a/src/plugins/discover/public/application/main/components/top_nav/on_save_search.tsx b/src/plugins/discover/public/application/main/components/top_nav/on_save_search.tsx index b22ae245319a6..838575184f7d8 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/on_save_search.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/on_save_search.tsx @@ -48,7 +48,7 @@ async function saveDataSource({ navigateTo(`/view/${encodeURIComponent(id)}`); } else { // Update defaults so that "reload saved query" functions correctly - state.actions.undoChanges(); + state.actions.undoSavedSearchChanges(); } } } diff --git a/src/plugins/discover/public/application/main/services/discover_state.test.ts b/src/plugins/discover/public/application/main/services/discover_state.test.ts index ba1fb76369fa6..811d1ad831c70 100644 --- a/src/plugins/discover/public/application/main/services/discover_state.test.ts +++ b/src/plugins/discover/public/application/main/services/discover_state.test.ts @@ -40,6 +40,7 @@ async function getState(url: string, savedSearch?: SavedSearch) { services: discoverServiceMock, history: nextHistory, }); + jest.spyOn(nextState.dataState, 'fetch'); await nextState.actions.loadDataViewList(); if (savedSearch) { nextState.savedSearchState.load = jest.fn(() => { @@ -584,28 +585,41 @@ describe('actions', () => { expect(state.internalState.getState().adHocDataViews[0].id).toBe('ad-hoc-id'); unsubscribe(); }); - test('undoChanges', async () => { + test('undoSavedSearchChanges - when changing data views', async () => { const { state, getCurrentUrl } = await getState('/', savedSearchMock); + // Load a given persisted saved search await state.actions.loadSavedSearch({ savedSearchId: savedSearchMock.id }); const unsubscribe = state.actions.initializeAndSync(); state.kbnUrlStateStorage.kbnUrlControls.flush(); - expect(getCurrentUrl()).toMatchInlineSnapshot( - `"/#?_g=(refreshInterval:(pause:!t,value:1000),time:(from:now-15d,to:now))&_a=(columns:!(default_column),index:the-data-view-id,interval:auto,sort:!())"` - ); + const initialUrlState = + '/#?_g=(refreshInterval:(pause:!t,value:1000),time:(from:now-15d,to:now))&_a=(columns:!(default_column),index:the-data-view-id,interval:auto,sort:!())'; + expect(getCurrentUrl()).toBe(initialUrlState); + expect(state.internalState.getState().dataView?.id).toBe(dataViewMock.id!); + + // Change the data view, this should change the URL and trigger a fetch await state.actions.onChangeDataView(dataViewComplexMock.id!); state.kbnUrlStateStorage.kbnUrlControls.flush(); expect(getCurrentUrl()).toMatchInlineSnapshot( `"/#?_g=(refreshInterval:(pause:!t,value:1000),time:(from:now-15d,to:now))&_a=(columns:!(default_column),index:data-view-with-various-field-types-id,interval:auto,sort:!(!(data,desc)))"` ); - await state.actions.undoChanges(); + await waitFor(() => { + expect(state.dataState.fetch).toHaveBeenCalledTimes(1); + }); + expect(state.internalState.getState().dataView?.id).toBe(dataViewComplexMock.id!); + + // Undo all changes to the saved search, this should trigger a fetch, again + await state.actions.undoSavedSearchChanges(); state.kbnUrlStateStorage.kbnUrlControls.flush(); - expect(getCurrentUrl()).toMatchInlineSnapshot( - `"/#?_g=(refreshInterval:(pause:!t,value:1000),time:(from:now-15d,to:now))&_a=(columns:!(default_column),index:the-data-view-id,interval:auto,sort:!())"` - ); + expect(getCurrentUrl()).toBe(initialUrlState); + await waitFor(() => { + expect(state.dataState.fetch).toHaveBeenCalledTimes(2); + }); + expect(state.internalState.getState().dataView?.id).toBe(dataViewMock.id!); + unsubscribe(); }); - test('undoChanges with timeRestore', async () => { + test('undoSavedSearchChanges with timeRestore', async () => { const { state } = await getState('/', { ...savedSearchMock, timeRestore: true, @@ -617,7 +631,7 @@ describe('actions', () => { discoverServiceMock.data.query.timefilter.timefilter.setTime = setTime; discoverServiceMock.data.query.timefilter.timefilter.setRefreshInterval = setRefreshInterval; await state.actions.loadSavedSearch({ savedSearchId: savedSearchMock.id }); - await state.actions.undoChanges(); + await state.actions.undoSavedSearchChanges(); expect(setTime).toHaveBeenCalledTimes(1); expect(setTime).toHaveBeenCalledWith({ from: 'now-15d', to: 'now-10d' }); expect(setRefreshInterval).toHaveBeenCalledWith({ pause: false, value: 1000 }); diff --git a/src/plugins/discover/public/application/main/services/discover_state.ts b/src/plugins/discover/public/application/main/services/discover_state.ts index ef88550e43386..bc4a221c20cd0 100644 --- a/src/plugins/discover/public/application/main/services/discover_state.ts +++ b/src/plugins/discover/public/application/main/services/discover_state.ts @@ -101,7 +101,7 @@ export interface DiscoverStateContainer { **/ dataState: DiscoverDataStateContainer; /** - * Internal state that's used at several places in the UI + * Internal shared state that's used at several places in the UI */ internalState: DiscoverInternalStateContainer; /** @@ -122,12 +122,12 @@ export interface DiscoverStateContainer { actions: { /** * Triggers fetching of new data from Elasticsearch - * If initial is true, then depending on the given configuration no fetch is triggered + * If initial is true, when SEARCH_ON_PAGE_LOAD_SETTING is set to false and it's a new saved search no fetch is triggered * @param initial */ fetchData: (initial?: boolean) => void; /** - * Initialize state with filters and query, start state syncing + * Initializing state containers and start subscribing to changes triggering e.g. data fetching */ initializeAndSync: () => () => void; /** @@ -136,12 +136,12 @@ export interface DiscoverStateContainer { loadDataViewList: () => Promise; /** * Load a saved search by id or create a new one that's not persisted yet - * @param savedSearchId * @param LoadParams - optional parameters to load a saved search */ loadSavedSearch: (param?: LoadParams) => Promise; /** - * Create and select a ad-hoc data view by a given index pattern + * Create and select a temporary/adhoc data view by a given index pattern + * Used by the Data View Picker * @param pattern */ onCreateDefaultAdHocDataView: (pattern: string) => Promise; @@ -171,7 +171,7 @@ export interface DiscoverStateContainer { ) => void; /** * Triggered when the user selects a different data view in the data view picker - * @param id + * @param id - id of the data view */ onChangeDataView: (id: string) => Promise; /** @@ -181,12 +181,13 @@ export interface DiscoverStateContainer { persistAdHocDataView: (dataView: DataView) => Promise; /** * Set the currently selected data view + * @param dataView */ setDataView: (dataView: DataView) => void; /** - * Undo changes made to the saved search + * Undo changes made to the saved search, e.g. when the user triggers the "Reset search" button */ - undoChanges: () => void; + undoSavedSearchChanges: () => void; /** * When saving a saved search with an ad hoc data view, a new id needs to be generated for the data view * This is to prevent duplicate ids messing with our system @@ -205,6 +206,9 @@ export function getDiscoverStateContainer({ }: DiscoverStateContainerParams): DiscoverStateContainer { const storeInSessionStorage = services.uiSettings.get('state:storeInSessionStorage'); const toasts = services.core.notifications.toasts; + /** + * state storage for state in the URL + */ const stateStorage = createKbnUrlStateStorage({ useHash: storeInSessionStorage, history, @@ -218,19 +222,24 @@ export function getDiscoverStateContainer({ history, session: services.data.search.session, }); + /** + * Saved Search State Container, the persisted saved object of Discover + */ const savedSearchContainer = getSavedSearchContainer({ services, }); /** - * App State Container, synced with URL + * App State Container, synced with the _a part URL */ const appStateContainer = getDiscoverAppStateContainer({ stateStorage, savedSearch: savedSearchContainer.getState(), services, }); - + /** + * Internal State Container, state that's not persisted and not part of the URL + */ const internalStateContainer = getInternalStateContainer(); const dataStateContainer = getDataStateContainer({ @@ -307,7 +316,7 @@ export function getDiscoverStateContainer({ const currentSavedSearch = savedSearchContainer.getState(); if (currentSavedSearch.id && currentSavedSearch.id === newSavedSearchId) { addLog('[discoverState] undo changes since saved search did not change'); - await undoChanges(); + await undoSavedSearchChanges(); } else { addLog('[discoverState] onOpenSavedSearch open view URL'); history.push(`/view/${encodeURIComponent(newSavedSearchId)}`); @@ -407,15 +416,15 @@ export function getDiscoverStateContainer({ return nextSavedSearch; }; + /** + * state containers initializing and subscribing to changes triggering e.g. data fetching + */ const initializeAndSync = () => { - /** - * state containers initializing and starting to notify each other about changes - */ + // initialize app state container, syncing with _g and _a part of the URL const appStateInitAndSyncUnsubscribe = appStateContainer.initAndSync( savedSearchContainer.getState() ); - const unsubscribeData = dataStateContainer.subscribe(); - // updates saved search when app state changes, triggers data fetching if required + // subscribing to state changes of appStateContainer, triggering data fetching const appStateUnsubscribe = appStateContainer.subscribe( buildStateSubscribe({ appState: appStateContainer, @@ -425,6 +434,9 @@ export function getDiscoverStateContainer({ setDataView, }) ); + // start subscribing to dataStateContainer, triggering data fetching + const unsubscribeData = dataStateContainer.subscribe(); + // updates saved search when query or filters change, triggers data fetching const filterUnsubscribe = merge( services.data.query.queryString.getUpdates$(), @@ -457,7 +469,9 @@ export function getDiscoverStateContainer({ await onChangeDataView(newDataView); }; - + /** + * Triggered when a user submits a query in the search bar + */ const onUpdateQuery = ( payload: { dateRange: TimeRange; query?: Query | AggregateQuery }, isUpdate?: boolean @@ -470,7 +484,7 @@ export function getDiscoverStateContainer({ }; /** - * Function triggered when user changes data view in the sidebar + * Function e.g. triggered when user changes data view in the sidebar */ const onChangeDataView = async (id: string | DataView) => { await changeDataView(id, { @@ -479,8 +493,11 @@ export function getDiscoverStateContainer({ appState: appStateContainer, }); }; - - const undoChanges = async () => { + /** + * Undo all changes to the current saved search + */ + const undoSavedSearchChanges = async () => { + addLog('undoSavedSearchChanges'); const nextSavedSearch = savedSearchContainer.getInitial$().getValue(); await savedSearchContainer.set(nextSavedSearch); restoreStateFromSavedSearch({ @@ -518,7 +535,7 @@ export function getDiscoverStateContainer({ onUpdateQuery, persistAdHocDataView, setDataView, - undoChanges, + undoSavedSearchChanges, updateAdHocDataViewId, }, }; From 566973b9c0eaaf550b6f263f74b4b5e459ddefdf Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Tue, 18 Apr 2023 09:31:40 +0200 Subject: [PATCH 028/122] Rename getEmptySavedSearch to getNewSavedSearch, add documentation --- .../discover_saved_search_container.ts | 5 ++-- src/plugins/saved_search/public/index.ts | 2 +- .../saved_searches/get_saved_searches.ts | 28 +++++++++++++------ .../public/services/saved_searches/index.ts | 2 +- 4 files changed, 24 insertions(+), 13 deletions(-) diff --git a/src/plugins/discover/public/application/main/services/discover_saved_search_container.ts b/src/plugins/discover/public/application/main/services/discover_saved_search_container.ts index 6acb551212d6b..d3b8e19057bcb 100644 --- a/src/plugins/discover/public/application/main/services/discover_saved_search_container.ts +++ b/src/plugins/discover/public/application/main/services/discover_saved_search_container.ts @@ -7,7 +7,7 @@ */ import { - getEmptySavedSearch, + getNewSavedSearch, getSavedSearch, SavedSearch, saveSavedSearch, @@ -111,7 +111,7 @@ export function getSavedSearchContainer({ }: { services: DiscoverServices; }): DiscoverSavedSearchContainer { - const initialSavedSearch = getEmptySavedSearch(services.data); + const initialSavedSearch = getNewSavedSearch(services.data); const savedSearchInitial$ = new BehaviorSubject(initialSavedSearch); const savedSearchCurrent$ = new BehaviorSubject(copySavedSearch(initialSavedSearch)); const hasChanged$ = new BehaviorSubject(false); @@ -196,6 +196,7 @@ export function getSavedSearchContainer({ }; const load = async (id: string, dataView: DataView | undefined): Promise => { + addLog('[savedSearch] load', { id, dataView }); const loadedSavedSearch = await getSavedSearch(id, { search: services.data.search, savedObjectsClient: services.core.savedObjects.client, diff --git a/src/plugins/saved_search/public/index.ts b/src/plugins/saved_search/public/index.ts index 0aebc8c8c60b4..4857b0fd153cf 100644 --- a/src/plugins/saved_search/public/index.ts +++ b/src/plugins/saved_search/public/index.ts @@ -15,7 +15,7 @@ export { getSavedSearchUrlConflictMessage, throwErrorOnSavedSearchUrlConflict, saveSavedSearch, - getEmptySavedSearch, + getNewSavedSearch, } from './services/saved_searches'; export { VIEW_MODE } from '../common'; diff --git a/src/plugins/saved_search/public/services/saved_searches/get_saved_searches.ts b/src/plugins/saved_search/public/services/saved_searches/get_saved_searches.ts index f8c5ee55bf1aa..d90bbed57e2f5 100644 --- a/src/plugins/saved_search/public/services/saved_searches/get_saved_searches.ts +++ b/src/plugins/saved_search/public/services/saved_searches/get_saved_searches.ts @@ -24,14 +24,6 @@ interface GetSavedSearchDependencies { savedObjectsTagging?: SavedObjectsTaggingApi; } -export const getEmptySavedSearch = ({ - search, -}: { - search: DataPublicPluginStart['search']; -}): SavedSearch => ({ - searchSource: search.searchSource.createEmpty(), -}); - const findSavedSearch = async ( savedSearchId: string, { search, savedObjectsClient, spaces, savedObjectsTagging }: GetSavedSearchDependencies @@ -82,11 +74,29 @@ const findSavedSearch = async ( }; /** @public **/ + +/** + * Returns a new saved search + * Used when e.g. Discover is opened without a saved search id + * @param search + */ +export const getNewSavedSearch = ({ + search, +}: { + search: DataPublicPluginStart['search']; +}): SavedSearch => ({ + searchSource: search.searchSource.createEmpty(), +}); +/** + * Returns a persisted or a new saved search + * @param savedSearchId - when undefined a new saved search is returned + * @param dependencies + */ export const getSavedSearch = async ( savedSearchId: string | undefined, dependencies: GetSavedSearchDependencies ) => { return savedSearchId ? findSavedSearch(savedSearchId, dependencies) - : getEmptySavedSearch(dependencies); + : getNewSavedSearch(dependencies); }; diff --git a/src/plugins/saved_search/public/services/saved_searches/index.ts b/src/plugins/saved_search/public/services/saved_searches/index.ts index 30e945c383f3f..53f486e79bb15 100644 --- a/src/plugins/saved_search/public/services/saved_searches/index.ts +++ b/src/plugins/saved_search/public/services/saved_searches/index.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -export { getSavedSearch, getEmptySavedSearch } from './get_saved_searches'; +export { getSavedSearch, getNewSavedSearch } from './get_saved_searches'; export { getSavedSearchUrl, getSavedSearchFullPathUrl, From c122a2a6fadf2b5800067b2995199f3b2642a0df Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Tue, 18 Apr 2023 15:09:36 +0200 Subject: [PATCH 029/122] Fix resetting the currently selected data view when 'New' is triggered --- .../public/application/main/discover_main_route.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/plugins/discover/public/application/main/discover_main_route.tsx b/src/plugins/discover/public/application/main/discover_main_route.tsx index 1fcca3fe57981..e4184ed4efcb8 100644 --- a/src/plugins/discover/public/application/main/discover_main_route.tsx +++ b/src/plugins/discover/public/application/main/discover_main_route.tsx @@ -205,8 +205,15 @@ export function DiscoverMainRoute(props: Props) { loadSavedSearch(); }, [loadSavedSearch, id]); - // secondary fetch: in case URL is set to `/`, used to reset the 'new' state - useUrl({ history, savedSearchId: id, onNewUrl: () => loadSavedSearch() }); + // secondary fetch: in case URL is set to `/`, used to reset to 'new' state, keeping the current data view + useUrl({ + history, + savedSearchId: id, + onNewUrl: () => { + const dataView = stateContainer.internalState.getState().dataView; + loadSavedSearch(dataView); + }, + }); if (showNoDataPage) { const analyticsServices = { From cdb695ce709ce439508c27ea3e3b08ac582cfe9f Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Tue, 18 Apr 2023 16:04:42 +0200 Subject: [PATCH 030/122] Fix temporary data view persisted in saved search not being displayed as temporary, and not being available in the data view list --- .../discover/public/application/main/services/discover_state.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/discover/public/application/main/services/discover_state.ts b/src/plugins/discover/public/application/main/services/discover_state.ts index bc4a221c20cd0..bddd8c38ca68e 100644 --- a/src/plugins/discover/public/application/main/services/discover_state.ts +++ b/src/plugins/discover/public/application/main/services/discover_state.ts @@ -387,7 +387,7 @@ export function getDiscoverStateContainer({ const savedSearchDataView = nextSavedSearch.searchSource.getField('index'); if (savedSearchDataView) { setDataView(savedSearchDataView); - if (!actualDataView.isPersisted()) { + if (!savedSearchDataView.isPersisted()) { internalStateContainer.transitions.appendAdHocDataViews(savedSearchDataView); } } From 4eb45d82468f84bd38c6e8be969f506bd40129dc Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Tue, 18 Apr 2023 16:41:41 +0200 Subject: [PATCH 031/122] Add test for adding an ad-hoc data view to the list of ad-hoc data views when loading a saved search with a such --- src/plugins/discover/public/__mocks__/saved_search.ts | 6 ++++++ .../application/main/services/discover_state.test.ts | 10 ++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/plugins/discover/public/__mocks__/saved_search.ts b/src/plugins/discover/public/__mocks__/saved_search.ts index 98b0d60c18ac4..c18c320063240 100644 --- a/src/plugins/discover/public/__mocks__/saved_search.ts +++ b/src/plugins/discover/public/__mocks__/saved_search.ts @@ -10,6 +10,7 @@ import { SavedSearch } from '@kbn/saved-search-plugin/public'; import { createSearchSourceMock } from '@kbn/data-plugin/public/mocks'; import { dataViewMock } from './data_view'; import { dataViewWithTimefieldMock } from './data_view_with_timefield'; +import { dataViewAdHoc } from './data_view_complex'; export const savedSearchMock = { id: 'the-saved-search-id', @@ -33,3 +34,8 @@ export const savedSearchMockWithSQL = { query: { sql: 'SELECT * FROM "the-saved-search-id-sql"' }, }), } as unknown as SavedSearch; + +export const savedSearchAdHoc = { + id: 'the-saved-search-with-ad-hoc', + searchSource: createSearchSourceMock({ index: dataViewAdHoc }), +} as unknown as SavedSearch; diff --git a/src/plugins/discover/public/application/main/services/discover_state.test.ts b/src/plugins/discover/public/application/main/services/discover_state.test.ts index 811d1ad831c70..f12328db7dc4f 100644 --- a/src/plugins/discover/public/application/main/services/discover_state.test.ts +++ b/src/plugins/discover/public/application/main/services/discover_state.test.ts @@ -15,6 +15,7 @@ import { createBrowserHistory, History } from 'history'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import type { SavedSearch, SortOrder } from '@kbn/saved-search-plugin/public'; import { + savedSearchAdHoc, savedSearchMock, savedSearchMockWithTimeField, savedSearchMockWithTimeFieldNew, @@ -472,6 +473,15 @@ describe('actions', () => { ); }); + test('loadSavedSearch with ad-hoc data view being added to internal state adHocDataViews', async () => { + const savedSearchAdHocCopy = copySavedSearch(savedSearchAdHoc); + const adHocDataViewId = savedSearchAdHoc.searchSource.getField('index')!.id; + const { state } = await getState('/', savedSearchAdHocCopy); + await state.actions.loadSavedSearch({ savedSearchId: savedSearchAdHoc.id }); + expect(state.appState.getState().index).toBe(adHocDataViewId); + expect(state.internalState.getState().adHocDataViews[0].id).toBe(adHocDataViewId); + }); + test('onChangeDataView', async () => { const { state, getCurrentUrl } = await getState('/', savedSearchMock); await state.actions.loadSavedSearch({ savedSearchId: savedSearchMock.id }); From 2ca49b2a4523200a2ff319e86188418edd8b1ee6 Mon Sep 17 00:00:00 2001 From: Davis McPhee Date: Tue, 4 Apr 2023 10:23:30 -0300 Subject: [PATCH 032/122] Create initial implementation of DiscoverExtensionRegistry --- src/plugins/discover/public/build_services.ts | 6 +- .../public/extensions/extension_registry.ts | 71 +++++++++++++++++++ .../discover/public/extensions/index.ts | 9 +++ src/plugins/discover/public/plugin.tsx | 10 ++- 4 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 src/plugins/discover/public/extensions/extension_registry.ts create mode 100644 src/plugins/discover/public/extensions/index.ts diff --git a/src/plugins/discover/public/build_services.ts b/src/plugins/discover/public/build_services.ts index 5975805522f45..7d524492ab342 100644 --- a/src/plugins/discover/public/build_services.ts +++ b/src/plugins/discover/public/build_services.ts @@ -52,6 +52,7 @@ import { DiscoverStartPlugins } from './plugin'; import { DiscoverContextAppLocator } from './application/context/services/locator'; import { DiscoverSingleDocLocator } from './application/doc/locator'; import { DiscoverAppLocator } from '../common'; +import type { DiscoverExtensionRegistry } from './extensions'; /** * Location state of internal Discover history instance @@ -99,6 +100,7 @@ export interface DiscoverServices { savedObjectsTagging?: SavedObjectsTaggingApi; unifiedSearch: UnifiedSearchPublicPluginStart; lens: LensPublicStart; + extensions: DiscoverExtensionRegistry; } export const buildServices = memoize(function ( @@ -107,7 +109,8 @@ export const buildServices = memoize(function ( context: PluginInitializerContext, locator: DiscoverAppLocator, contextLocator: DiscoverContextAppLocator, - singleDocLocator: DiscoverSingleDocLocator + singleDocLocator: DiscoverSingleDocLocator, + extensions: DiscoverExtensionRegistry ): DiscoverServices { const { usageCollection } = plugins; const storage = new Storage(localStorage); @@ -153,5 +156,6 @@ export const buildServices = memoize(function ( savedObjectsManagement: plugins.savedObjectsManagement, unifiedSearch: plugins.unifiedSearch, lens: plugins.lens, + extensions, }; }); diff --git a/src/plugins/discover/public/extensions/extension_registry.ts b/src/plugins/discover/public/extensions/extension_registry.ts new file mode 100644 index 0000000000000..39070a18626c2 --- /dev/null +++ b/src/plugins/discover/public/extensions/extension_registry.ts @@ -0,0 +1,71 @@ +/* + * 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 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 or the Server + * Side Public License, v 1. + */ + +import { filter, map, Observable, startWith, Subject } from 'rxjs'; + +export interface DiscoverExtension { + id: string; +} + +export type DiscoverExtensionId = DiscoverExtension['id']; + +export interface DiscoverExtensionRegistry { + set: (extension: DiscoverExtension) => void; + get$: ( + id: TExtensionId + ) => Observable | undefined>; + enable: (id: DiscoverExtensionId) => void; + disable: (id: DiscoverExtensionId) => void; +} + +interface DiscoverExtensionRegistration { + extension: DiscoverExtension; + enabled: boolean; +} + +export const createExtensionRegistry = (): DiscoverExtensionRegistry => { + const update$ = new Subject(); + const registrations = new Map(); + + return { + set: (extension) => { + const registration = registrations.get(extension.id); + registrations.set(extension.id, { extension, enabled: registration?.enabled ?? true }); + update$.next(extension.id); + }, + + get$: (id: TExtensionId) => { + return update$.pipe( + startWith(id), + filter((currentId) => currentId === id), + map(() => { + const registration = registrations.get(id); + if (registration && registration.enabled) { + return registration.extension as Extract; + } + }) + ); + }, + + enable: (id) => { + const registration = registrations.get(id); + if (registration && !registration.enabled) { + registration.enabled = true; + update$.next(registration.extension.id); + } + }, + + disable: (id) => { + const registration = registrations.get(id); + if (registration && registration.enabled) { + registration.enabled = false; + update$.next(registration.extension.id); + } + }, + }; +}; diff --git a/src/plugins/discover/public/extensions/index.ts b/src/plugins/discover/public/extensions/index.ts new file mode 100644 index 0000000000000..31c802a76d2c5 --- /dev/null +++ b/src/plugins/discover/public/extensions/index.ts @@ -0,0 +1,9 @@ +/* + * 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 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 or the Server + * Side Public License, v 1. + */ + +export * from './extension_registry'; diff --git a/src/plugins/discover/public/plugin.tsx b/src/plugins/discover/public/plugin.tsx index 1d106309641a3..a1e6c928d2397 100644 --- a/src/plugins/discover/public/plugin.tsx +++ b/src/plugins/discover/public/plugin.tsx @@ -71,6 +71,7 @@ import { DiscoverSingleDocLocatorDefinition, } from './application/doc/locator'; import { DiscoverAppLocator, DiscoverAppLocatorDefinition } from '../common'; +import { createExtensionRegistry, DiscoverExtensionRegistry } from './extensions'; const DocViewerLegacyTable = React.lazy( () => import('./services/doc_views/components/doc_viewer_table/legacy') @@ -155,6 +156,7 @@ export interface DiscoverStart { * ``` */ readonly locator: undefined | DiscoverAppLocator; + readonly extensions: DiscoverExtensionRegistry; } /** @@ -208,6 +210,7 @@ export class DiscoverPlugin private appStateUpdater = new BehaviorSubject(() => ({})); private docViewsRegistry: DocViewsRegistry | null = null; + private extensions = createExtensionRegistry(); private stopUrlTracking: (() => void) | undefined = undefined; private locator?: DiscoverAppLocator; private contextLocator?: DiscoverContextAppLocator; @@ -320,7 +323,8 @@ export class DiscoverPlugin this.initializerContext, this.locator!, this.contextLocator!, - this.singleDocLocator! + this.singleDocLocator!, + this.extensions ); // make sure the data view list is up to date @@ -391,6 +395,7 @@ export class DiscoverPlugin return { locator: this.locator, + extensions: this.extensions, }; } @@ -417,7 +422,8 @@ export class DiscoverPlugin this.initializerContext, this.locator!, this.contextLocator!, - this.singleDocLocator! + this.singleDocLocator!, + this.extensions ); }; From 3b097fa90d4f8e95bfdf79fe02e83b90dea5a7cb Mon Sep 17 00:00:00 2001 From: Davis McPhee Date: Wed, 5 Apr 2023 13:22:45 -0300 Subject: [PATCH 033/122] Add initial extension types to DiscoverExtension registry --- .../public/extensions/extension_registry.ts | 14 ++++++-- .../extension_types/data_grid_extension.ts | 24 +++++++++++++ .../field_popover_extension.ts | 15 ++++++++ .../extensions/extension_types/index.ts | 12 +++++++ .../extension_types/search_bar_extension.ts | 14 ++++++++ .../extension_types/top_nav_extension.ts | 35 +++++++++++++++++++ .../discover/public/extensions/index.ts | 1 + 7 files changed, 112 insertions(+), 3 deletions(-) create mode 100644 src/plugins/discover/public/extensions/extension_types/data_grid_extension.ts create mode 100644 src/plugins/discover/public/extensions/extension_types/field_popover_extension.ts create mode 100644 src/plugins/discover/public/extensions/extension_types/index.ts create mode 100644 src/plugins/discover/public/extensions/extension_types/search_bar_extension.ts create mode 100644 src/plugins/discover/public/extensions/extension_types/top_nav_extension.ts diff --git a/src/plugins/discover/public/extensions/extension_registry.ts b/src/plugins/discover/public/extensions/extension_registry.ts index 39070a18626c2..1a8f3335454eb 100644 --- a/src/plugins/discover/public/extensions/extension_registry.ts +++ b/src/plugins/discover/public/extensions/extension_registry.ts @@ -7,10 +7,18 @@ */ import { filter, map, Observable, startWith, Subject } from 'rxjs'; +import type { + DataGridExtension, + FieldPopoverExtension, + SearchBarExtension, + TopNavExtension, +} from './extension_types'; -export interface DiscoverExtension { - id: string; -} +export type DiscoverExtension = + | DataGridExtension + | FieldPopoverExtension + | SearchBarExtension + | TopNavExtension; export type DiscoverExtensionId = DiscoverExtension['id']; diff --git a/src/plugins/discover/public/extensions/extension_types/data_grid_extension.ts b/src/plugins/discover/public/extensions/extension_types/data_grid_extension.ts new file mode 100644 index 0000000000000..149f7789c354e --- /dev/null +++ b/src/plugins/discover/public/extensions/extension_types/data_grid_extension.ts @@ -0,0 +1,24 @@ +/* + * 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 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 or the Server + * Side Public License, v 1. + */ + +import type { EuiDataGridControlColumn } from '@elastic/eui'; + +export interface DefaultLeadingControlColumn { + disabled?: boolean; +} + +export interface DefaultLeadingControlColumns { + expand?: DefaultLeadingControlColumn; + select?: DefaultLeadingControlColumn; +} + +export interface DataGridExtension { + id: 'data_grid'; + defaultLeadingControlColumns?: DefaultLeadingControlColumns; + getLeadingControlColumns?: () => EuiDataGridControlColumn[]; +} diff --git a/src/plugins/discover/public/extensions/extension_types/field_popover_extension.ts b/src/plugins/discover/public/extensions/extension_types/field_popover_extension.ts new file mode 100644 index 0000000000000..18c1c168287fa --- /dev/null +++ b/src/plugins/discover/public/extensions/extension_types/field_popover_extension.ts @@ -0,0 +1,15 @@ +/* + * 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 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 or the Server + * Side Public License, v 1. + */ + +import type { ComponentType } from 'react'; + +export interface FieldPopoverExtension { + id: 'field_popover'; + disableDefaultBottomButton?: boolean; + CustomBottomButton?: ComponentType; +} diff --git a/src/plugins/discover/public/extensions/extension_types/index.ts b/src/plugins/discover/public/extensions/extension_types/index.ts new file mode 100644 index 0000000000000..e96917e27367b --- /dev/null +++ b/src/plugins/discover/public/extensions/extension_types/index.ts @@ -0,0 +1,12 @@ +/* + * 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 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 or the Server + * Side Public License, v 1. + */ + +export * from './data_grid_extension'; +export * from './field_popover_extension'; +export * from './search_bar_extension'; +export * from './top_nav_extension'; diff --git a/src/plugins/discover/public/extensions/extension_types/search_bar_extension.ts b/src/plugins/discover/public/extensions/extension_types/search_bar_extension.ts new file mode 100644 index 0000000000000..b923ef71eb9a3 --- /dev/null +++ b/src/plugins/discover/public/extensions/extension_types/search_bar_extension.ts @@ -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 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 or the Server + * Side Public License, v 1. + */ + +import type { ComponentType } from 'react'; + +export interface SearchBarExtension { + id: 'search_bar'; + CustomDataViewPicker?: ComponentType; +} diff --git a/src/plugins/discover/public/extensions/extension_types/top_nav_extension.ts b/src/plugins/discover/public/extensions/extension_types/top_nav_extension.ts new file mode 100644 index 0000000000000..a138316dd6f60 --- /dev/null +++ b/src/plugins/discover/public/extensions/extension_types/top_nav_extension.ts @@ -0,0 +1,35 @@ +/* + * 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 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 or the Server + * Side Public License, v 1. + */ + +import type { TopNavMenuData } from '@kbn/navigation-plugin/public'; + +export interface TopNavDefaultMenuItem { + disabled?: boolean; + order?: number; +} + +export interface TopNavDefaultMenu { + options?: TopNavDefaultMenuItem; + new?: TopNavDefaultMenuItem; + open?: TopNavDefaultMenuItem; + share?: TopNavDefaultMenuItem; + alerts?: TopNavDefaultMenuItem; + inspect?: TopNavDefaultMenuItem; + save?: TopNavDefaultMenuItem; +} + +export interface TopNavMenuItem { + data: TopNavMenuData; + order: number; +} + +export interface TopNavExtension { + id: 'top_nav'; + defaultMenu?: TopNavDefaultMenu; + getMenuItems?: () => TopNavMenuItem[]; +} diff --git a/src/plugins/discover/public/extensions/index.ts b/src/plugins/discover/public/extensions/index.ts index 31c802a76d2c5..ad125a998ba0d 100644 --- a/src/plugins/discover/public/extensions/index.ts +++ b/src/plugins/discover/public/extensions/index.ts @@ -6,4 +6,5 @@ * Side Public License, v 1. */ +export * from './extension_types'; export * from './extension_registry'; From 3e5b80947dfbd0e49d964a85a17db65858c5cc8e Mon Sep 17 00:00:00 2001 From: Davis McPhee Date: Wed, 5 Apr 2023 13:38:13 -0300 Subject: [PATCH 034/122] Implement data_grid extension --- .../components/layout/discover_documents.tsx | 18 +++++ .../discover_grid/discover_grid.tsx | 31 +++++++- .../discover_grid/discover_grid_columns.tsx | 10 ++- .../discover_grid_custom_control_column.tsx | 79 +++++++++++++++++++ 4 files changed, 134 insertions(+), 4 deletions(-) create mode 100644 src/plugins/discover/public/components/discover_grid/discover_grid_custom_control_column.tsx diff --git a/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx b/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx index ba6fc75b15f5d..4c9c95bcad5d6 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx @@ -16,6 +16,7 @@ import { import { FormattedMessage } from '@kbn/i18n-react'; import { DataView } from '@kbn/data-views-plugin/public'; import { SortOrder } from '@kbn/saved-search-plugin/public'; +import useObservable from 'react-use/lib/useObservable'; import { useInternalStateSelector } from '../../services/discover_internal_state_container'; import { useAppStateSelector } from '../../services/discover_app_state_container'; import { useDiscoverServices } from '../../../../hooks/use_discover_services'; @@ -167,6 +168,21 @@ function DiscoverDocumentsComponent({ [isPlainRecord, uiSettings, dataView.timeFieldName] ); + const dataGridExtension = useObservable(services.extensions.get$('data_grid')); + const defaultControlColumns = dataGridExtension?.defaultLeadingControlColumns; + + const controlColumnIds = useMemo(() => { + const ids: string[] = []; + if (!defaultControlColumns?.expand?.disabled) ids.push('openDetails'); + if (!defaultControlColumns?.select?.disabled) ids.push('select'); + return ids; + }, [defaultControlColumns?.expand?.disabled, defaultControlColumns?.select?.disabled]); + + const customControlColumns = useMemo( + () => dataGridExtension?.getLeadingControlColumns?.(), + [dataGridExtension] + ); + if (isDataViewLoading || (isEmptyDataResult && isDataLoading)) { return (
@@ -246,6 +262,8 @@ function DiscoverDocumentsComponent({ onFieldEdited={onFieldEdited} savedSearchId={savedSearch.id} DocumentView={DiscoverGridFlyout} + controlColumnIds={controlColumnIds} + customControlColumns={customControlColumns} services={services} />
diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid.tsx b/src/plugins/discover/public/components/discover_grid/discover_grid.tsx index d571de95f074d..6cbc8cb0bd14c 100644 --- a/src/plugins/discover/public/components/discover_grid/discover_grid.tsx +++ b/src/plugins/discover/public/components/discover_grid/discover_grid.tsx @@ -21,6 +21,7 @@ import { EuiIcon, EuiDataGridRefProps, EuiLink, + EuiDataGridControlColumn, } from '@elastic/eui'; import type { DataView } from '@kbn/data-views-plugin/public'; import type { SortOrder } from '@kbn/saved-search-plugin/public'; @@ -28,6 +29,7 @@ import { Filter } from '@kbn/es-query'; import { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; import { ToastsStart, IUiSettingsClient, HttpStart } from '@kbn/core/public'; import { DataViewFieldEditorStart } from '@kbn/data-view-field-editor-plugin/public'; +import { css } from '@emotion/react'; import { DocViewFilterFn } from '../../services/doc_views/doc_views_types'; import { getSchemaDetectors } from './discover_grid_schema'; import { DiscoverGridFlyout } from './discover_grid_flyout'; @@ -53,6 +55,7 @@ import type { DataTableRecord, ValueToStringConverter } from '../../types'; import { useRowHeightsOptions } from '../../hooks/use_row_heights_options'; import { convertValueToString } from '../../utils/convert_value_to_string'; import { getRowsPerPageOptions, getDefaultRowsPerPage } from '../../utils/rows_per_page'; +import { createCustomControlColumn } from './discover_grid_custom_control_column'; interface SortObj { id: string; @@ -157,6 +160,10 @@ export interface DiscoverGridProps { * List of used control columns (available: 'openDetails', 'select') */ controlColumnIds?: string[]; + /** + * Customize the displayed control columns + */ + customControlColumns?: EuiDataGridControlColumn[]; /** * Row height from state */ @@ -235,6 +242,7 @@ export const DiscoverGrid = ({ isSortEnabled = true, isPaginationEnabled = true, controlColumnIds = CONTROL_COLUMN_IDS_DEFAULT, + customControlColumns, className, rowHeightState, onUpdateRowHeight, @@ -474,8 +482,26 @@ export const DiscoverGrid = ({ const lead = useMemo( () => - getLeadControlColumns(canSetExpandedDoc).filter(({ id }) => controlColumnIds.includes(id)), - [controlColumnIds, canSetExpandedDoc] + getLeadControlColumns(canSetExpandedDoc) + .filter(({ id }) => controlColumnIds.includes(id)) + .concat((customControlColumns ?? []).map(createCustomControlColumn)), + [canSetExpandedDoc, customControlColumns, controlColumnIds] + ); + + const leadControlColumnsCss = useMemo( + () => + lead.map( + (_, index) => css` + .euiDataGridHeaderCell--controlColumn:nth-child(${index + 1}) { + ${index === lead.length - 1 ? '' : 'border-right: none;'} + } + + .euiDataGridRowCell--controlColumn:nth-child(${index + 1}) { + ${index === lead.length - 1 ? '' : 'border-right: none;'} + } + ` + ), + [lead] ); const additionalControls = useMemo( @@ -608,6 +634,7 @@ export const DiscoverGrid = ({ toolbarVisibility={toolbarVisibility} rowHeightsOptions={rowHeightsOptions} gridStyle={GRID_STYLE} + css={leadControlColumnsCss} /> {showDisclaimer && ( diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid_columns.tsx b/src/plugins/discover/public/components/discover_grid/discover_grid_columns.tsx index 6e4c0ec619e2b..779a30965f7dd 100644 --- a/src/plugins/discover/public/components/discover_grid/discover_grid_columns.tsx +++ b/src/plugins/discover/public/components/discover_grid/discover_grid_columns.tsx @@ -8,7 +8,13 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiDataGridColumn, EuiIcon, EuiScreenReaderOnly, EuiToolTip } from '@elastic/eui'; +import { + EuiDataGridColumn, + EuiDataGridControlColumn, + EuiIcon, + EuiScreenReaderOnly, + EuiToolTip, +} from '@elastic/eui'; import type { DataView } from '@kbn/data-views-plugin/public'; import { ToastsStart, IUiSettingsClient } from '@kbn/core/public'; import { DocViewFilterFn } from '../../services/doc_views/doc_views_types'; @@ -52,7 +58,7 @@ const select = { ), }; -export function getLeadControlColumns(canSetExpandedDoc: boolean) { +export function getLeadControlColumns(canSetExpandedDoc: boolean): EuiDataGridControlColumn[] { if (!canSetExpandedDoc) { return [select]; } diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid_custom_control_column.tsx b/src/plugins/discover/public/components/discover_grid/discover_grid_custom_control_column.tsx new file mode 100644 index 0000000000000..642234b7c2fb4 --- /dev/null +++ b/src/plugins/discover/public/components/discover_grid/discover_grid_custom_control_column.tsx @@ -0,0 +1,79 @@ +/* + * 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 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 or the Server + * Side Public License, v 1. + */ + +import type { + EuiDataGridCellValueElementProps, + EuiDataGridControlColumn, + EuiDataGridSetCellProps, +} from '@elastic/eui'; +import React, { + JSXElementConstructor, + useCallback, + useContext, + useEffect, + useMemo, + useRef, +} from 'react'; +import { euiDarkVars as themeDark, euiLightVars as themeLight } from '@kbn/ui-theme'; +import { merge } from 'lodash'; +import { DiscoverGridContext } from './discover_grid_context'; + +const wrapRowCellRenderer = (rowCellRender: EuiDataGridControlColumn['rowCellRender']) => { + // React is more permissible than the TS types indicate + const CellElement = rowCellRender as JSXElementConstructor; + + return ({ + rowIndex, + setCellProps: originalSetCellProps, + ...props + }: EuiDataGridCellValueElementProps) => { + const { expanded, rows, isDarkMode } = useContext(DiscoverGridContext); + const doc = useMemo(() => rows[rowIndex], [rows, rowIndex]); + const wrapperCellProps = useRef({}); + const customCellProps = useRef({}); + + const updateCellProps = useCallback(() => { + originalSetCellProps(merge({}, customCellProps.current, wrapperCellProps.current)); + }, [originalSetCellProps]); + + const setCellProps = useCallback( + (cellProps: EuiDataGridSetCellProps) => { + customCellProps.current = cellProps; + updateCellProps(); + }, + [updateCellProps] + ); + + useEffect(() => { + if (expanded && doc && expanded.id === doc.id) { + wrapperCellProps.current = { + style: { + backgroundColor: isDarkMode + ? themeDark.euiColorHighlight + : themeLight.euiColorHighlight, + }, + }; + } else { + wrapperCellProps.current = {}; + } + + updateCellProps(); + }, [doc, expanded, isDarkMode, updateCellProps]); + + return ; + }; +}; + +export const createCustomControlColumn = ( + column: EuiDataGridControlColumn +): EuiDataGridControlColumn => { + return { + ...column, + rowCellRender: wrapRowCellRenderer(column.rowCellRender), + }; +}; From e4577ace74492dcc72439f34aaae7e5013e9df34 Mon Sep 17 00:00:00 2001 From: Davis McPhee Date: Wed, 5 Apr 2023 13:40:21 -0300 Subject: [PATCH 035/122] Implement field_popover extension --- .../components/sidebar/discover_field.tsx | 43 ++++++++++++++----- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/src/plugins/discover/public/application/main/components/sidebar/discover_field.tsx b/src/plugins/discover/public/application/main/components/sidebar/discover_field.tsx index b26f1dae2164d..e02d27aeb369a 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/discover_field.tsx +++ b/src/plugins/discover/public/application/main/components/sidebar/discover_field.tsx @@ -10,6 +10,9 @@ import './discover_field.scss'; import React, { memo, useCallback, useMemo, useState } from 'react'; import { EuiSpacer, EuiTitle } from '@elastic/eui'; + EuiPopoverFooter, + EuiFlexItem, + EuiFlexGroup, import { i18n } from '@kbn/i18n'; import { UiCounterMetricType } from '@kbn/analytics'; import type { DataView, DataViewField } from '@kbn/data-views-plugin/public'; @@ -19,9 +22,10 @@ import { FieldPopover, FieldPopoverHeader, FieldPopoverHeaderProps, - FieldPopoverVisualize, + FieldVisualizeButton, } from '@kbn/unified-field-list-plugin/public'; import { DragDrop } from '@kbn/dom-drag-drop'; +import useObservable from 'react-use/lib/useObservable'; import { DiscoverFieldStats } from './discover_field_stats'; import { DiscoverFieldDetails } from './deprecated_stats/discover_field_details'; import { useDiscoverServices } from '../../../../hooks/use_discover_services'; @@ -271,6 +275,8 @@ function DiscoverFieldComponent({ [field.name] ); + const fieldPopoverExtension = useObservable(services.extensions.get$('field_popover')); + const renderPopover = () => { const showLegacyFieldStats = services.uiSettings.get(SHOW_LEGACY_FIELD_TOP_VALUES); @@ -307,15 +313,32 @@ function DiscoverFieldComponent({ )} - + {(!fieldPopoverExtension?.disableDefaultBottomButton || + fieldPopoverExtension.CustomBottomButton) && ( + + + {!fieldPopoverExtension?.disableDefaultBottomButton && ( + + + + )} + + {fieldPopoverExtension?.CustomBottomButton && ( + + + + )} + + + )} ); }; From d5657bd48539b8307061c487e1f5b3d7c54ec741 Mon Sep 17 00:00:00 2001 From: Davis McPhee Date: Wed, 5 Apr 2023 13:42:47 -0300 Subject: [PATCH 036/122] Implement search_bar extension --- .../main/components/top_nav/discover_topnav.tsx | 12 +++++++++++- .../public/query_string_input/query_bar_top_row.tsx | 4 +++- .../public/search_bar/create_search_bar.tsx | 1 + .../unified_search/public/search_bar/search_bar.tsx | 2 ++ 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx b/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx index 7e7a75eb3cefd..eb2a8fe0bd207 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx @@ -9,6 +9,7 @@ import React, { useCallback, useEffect, useMemo, useRef } from 'react'; import type { Query, TimeRange, AggregateQuery } from '@kbn/es-query'; import { DataViewType, type DataView } from '@kbn/data-views-plugin/public'; import type { DataViewPickerProps } from '@kbn/unified-search-plugin/public'; +import useObservable from 'react-use/lib/useObservable'; import { useInternalStateSelector } from '../../services/discover_internal_state_container'; import { ENABLE_SQL } from '../../../../../common'; import { useDiscoverServices } from '../../../../hooks/use_discover_services'; @@ -197,6 +198,8 @@ export const DiscoverTopNav = ({ [navigateTo, services, stateContainer] ); + const searchBarExtension = useObservable(services.extensions.get$('search_bar')); + return ( + ) : undefined + } + dataViewPickerComponentProps={ + searchBarExtension?.CustomDataViewPicker ? undefined : dataViewPickerProps + } displayStyle="detached" textBasedLanguageModeErrors={ textBasedLanguageModeErrors ? [textBasedLanguageModeErrors] : undefined diff --git a/src/plugins/unified_search/public/query_string_input/query_bar_top_row.tsx b/src/plugins/unified_search/public/query_string_input/query_bar_top_row.tsx index 8ca32108a2555..ee5048b95a086 100644 --- a/src/plugins/unified_search/public/query_string_input/query_bar_top_row.tsx +++ b/src/plugins/unified_search/public/query_string_input/query_bar_top_row.tsx @@ -8,7 +8,7 @@ import dateMath from '@kbn/datemath'; import classNames from 'classnames'; -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import React, { ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import deepEqual from 'fast-deep-equal'; import useObservable from 'react-use/lib/useObservable'; import type { Filter, TimeRange, Query, AggregateQuery } from '@kbn/es-query'; @@ -71,6 +71,7 @@ const SuperDatePicker = React.memo( // @internal export interface QueryBarTopRowProps { customSubmitButton?: any; + customDataViewPicker?: ReactNode; dataTestSubj?: string; dateRangeFrom?: string; dateRangeTo?: string; @@ -477,6 +478,7 @@ export const QueryBarTopRow = React.memo( } function renderDataViewsPicker() { + if (props.customDataViewPicker) return props.customDataViewPicker; if (!props.dataViewPickerComponentProps) return; let textBasedLanguage; if (Boolean(isQueryLangSelected)) { diff --git a/src/plugins/unified_search/public/search_bar/create_search_bar.tsx b/src/plugins/unified_search/public/search_bar/create_search_bar.tsx index 612b74fb204cb..27befb33c1fd1 100644 --- a/src/plugins/unified_search/public/search_bar/create_search_bar.tsx +++ b/src/plugins/unified_search/public/search_bar/create_search_bar.tsx @@ -227,6 +227,7 @@ export function createSearchBar({ iconType={props.iconType} nonKqlMode={props.nonKqlMode} customSubmitButton={props.customSubmitButton} + customDataViewPicker={props.customDataViewPicker} isClearable={props.isClearable} placeholder={props.placeholder} {...overrideDefaultBehaviors(props)} diff --git a/src/plugins/unified_search/public/search_bar/search_bar.tsx b/src/plugins/unified_search/public/search_bar/search_bar.tsx index 0a83a45de1d03..662e2dcd91518 100644 --- a/src/plugins/unified_search/public/search_bar/search_bar.tsx +++ b/src/plugins/unified_search/public/search_bar/search_bar.tsx @@ -45,6 +45,7 @@ export interface SearchBarOwnProps { indexPatterns?: DataView[]; isLoading?: boolean; customSubmitButton?: React.ReactNode; + customDataViewPicker?: React.ReactNode; screenTitle?: string; dataTestSubj?: string; // Togglers @@ -564,6 +565,7 @@ class SearchBarUI extends C customSubmitButton={ this.props.customSubmitButton ? this.props.customSubmitButton : undefined } + customDataViewPicker={this.props.customDataViewPicker} showSubmitButton={this.props.showSubmitButton} submitButtonStyle={this.props.submitButtonStyle} dataTestSubj={this.props.dataTestSubj} From 9989c0706570302bdb5fe2c348d6308d9577fbff Mon Sep 17 00:00:00 2001 From: Davis McPhee Date: Wed, 5 Apr 2023 13:45:04 -0300 Subject: [PATCH 037/122] Implement top_nav extension --- .../components/top_nav/discover_topnav.tsx | 4 ++ .../components/top_nav/get_top_nav_links.tsx | 51 ++++++++++++++----- 2 files changed, 43 insertions(+), 12 deletions(-) diff --git a/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx b/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx index eb2a8fe0bd207..8ec1bbcd9076f 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx @@ -109,6 +109,7 @@ export const DiscoverTopNav = ({ }); }, [dataViewEditor, stateContainer]); + const topNavExtension = useObservable(services.extensions.get$('top_nav')); const topNavMenu = useMemo( () => getTopNavLinks({ @@ -120,6 +121,7 @@ export const DiscoverTopNav = ({ isPlainRecord, adHocDataViews, persistDataView, + topNavExtension, }), [ dataView, @@ -129,7 +131,9 @@ export const DiscoverTopNav = ({ onOpenInspector, isPlainRecord, adHocDataViews, + updateDataViewList, persistDataView, + topNavExtension, ] ); diff --git a/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.tsx b/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.tsx index 3a0555925ed4b..2aa21fb02d1e8 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.tsx @@ -17,6 +17,7 @@ import { onSaveSearch } from './on_save_search'; import { DiscoverStateContainer } from '../../services/discover_state'; import { openOptionsPopover } from './open_options_popover'; import { openAlertsPopover } from './open_alerts_popover'; +import type { TopNavExtension } from '../../../../extensions'; /** * Helper function to build the top nav links @@ -30,6 +31,7 @@ export const getTopNavLinks = ({ isPlainRecord, persistDataView, adHocDataViews, + topNavExtension, }: { dataView: DataView; navigateTo: (url: string) => void; @@ -39,6 +41,7 @@ export const getTopNavLinks = ({ isPlainRecord: boolean; adHocDataViews: DataView[]; persistDataView: (dataView: DataView) => Promise; + topNavExtension: TopNavExtension | undefined; }): TopNavMenuData[] => { const options = { id: 'options', @@ -193,17 +196,41 @@ export const getTopNavLinks = ({ }, }; - return [ - ...(services.capabilities.advancedSettings.save ? [options] : []), - newSearch, - openSearch, - ...(!isPlainRecord ? [shareSearch] : []), - ...(services.triggersActionsUi && + const defaultMenu = topNavExtension?.defaultMenu; + const entries = topNavExtension?.getMenuItems?.() ?? []; + + if (services.capabilities.advancedSettings.save && !defaultMenu?.options?.disabled) { + entries.push({ data: options, order: defaultMenu?.options?.order ?? 100 }); + } + + if (!defaultMenu?.new?.disabled) { + entries.push({ data: newSearch, order: defaultMenu?.new?.order ?? 200 }); + } + + if (!defaultMenu?.open?.disabled) { + entries.push({ data: openSearch, order: defaultMenu?.open?.order ?? 300 }); + } + + if (!isPlainRecord && !defaultMenu?.share?.disabled) { + entries.push({ data: shareSearch, order: defaultMenu?.share?.order ?? 400 }); + } + + if ( + services.triggersActionsUi && services.capabilities.management?.insightsAndAlerting?.triggersActions && - !isPlainRecord - ? [alerts] - : []), - inspectSearch, - ...(services.capabilities.discover.save ? [saveSearch] : []), - ]; + !isPlainRecord && + !defaultMenu?.alerts?.disabled + ) { + entries.push({ data: alerts, order: defaultMenu?.alerts?.order ?? 500 }); + } + + if (!defaultMenu?.inspect?.disabled) { + entries.push({ data: inspectSearch, order: defaultMenu?.inspect?.order ?? 600 }); + } + + if (services.capabilities.discover.save && !defaultMenu?.save?.disabled) { + entries.push({ data: saveSearch, order: defaultMenu?.save?.order ?? 700 }); + } + + return entries.sort((a, b) => a.order - b.order).map((entry) => entry.data); }; From 43a61bd9333c298ff99813f1e489c8a46a14f6fd Mon Sep 17 00:00:00 2001 From: Davis McPhee Date: Wed, 5 Apr 2023 14:21:08 -0300 Subject: [PATCH 038/122] Create discover_extender example plugin --- examples/discover_extender/kibana.jsonc | 11 ++ examples/discover_extender/public/index.ts | 13 ++ examples/discover_extender/public/plugin.tsx | 138 +++++++++++++++++++ examples/discover_extender/tsconfig.json | 9 ++ package.json | 1 + tsconfig.base.json | 2 + yarn.lock | 4 + 7 files changed, 178 insertions(+) create mode 100644 examples/discover_extender/kibana.jsonc create mode 100644 examples/discover_extender/public/index.ts create mode 100644 examples/discover_extender/public/plugin.tsx create mode 100644 examples/discover_extender/tsconfig.json diff --git a/examples/discover_extender/kibana.jsonc b/examples/discover_extender/kibana.jsonc new file mode 100644 index 0000000000000..9a6acb70cd519 --- /dev/null +++ b/examples/discover_extender/kibana.jsonc @@ -0,0 +1,11 @@ +{ + "type": "plugin", + "id": "@kbn/discover-extender-plugin", + "owner": "@elastic/kibana-data-discovery", + "plugin": { + "id": "discoverExtender", + "server": false, + "browser": true, + "requiredPlugins": ["discover"] + } +} diff --git a/examples/discover_extender/public/index.ts b/examples/discover_extender/public/index.ts new file mode 100644 index 0000000000000..d164b53cae9d0 --- /dev/null +++ b/examples/discover_extender/public/index.ts @@ -0,0 +1,13 @@ +/* + * 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 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 or the Server + * Side Public License, v 1. + */ + +import { DiscoverExtenderPlugin } from './plugin'; + +export function plugin() { + return new DiscoverExtenderPlugin(); +} diff --git a/examples/discover_extender/public/plugin.tsx b/examples/discover_extender/public/plugin.tsx new file mode 100644 index 0000000000000..5e61a2968dd8a --- /dev/null +++ b/examples/discover_extender/public/plugin.tsx @@ -0,0 +1,138 @@ +/* + * 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 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 or the Server + * Side Public License, v 1. + */ + +import { + EuiButton, + EuiButtonIcon, + EuiDataGridCellValueElementProps, + EuiScreenReaderOnly, +} from '@elastic/eui'; +import type { CoreStart, Plugin } from '@kbn/core/public'; +import type { DiscoverStart } from '@kbn/discover-plugin/public'; +import React, { useEffect } from 'react'; + +export interface DiscoverExtenderStartPlugins { + discover: DiscoverStart; +} + +export class DiscoverExtenderPlugin implements Plugin { + setup() {} + + start(_: CoreStart, plugins: DiscoverExtenderStartPlugins) { + const { discover } = plugins; + + discover.extensions.set({ + id: 'top_nav', + defaultMenu: { + options: { disabled: true }, + new: { disabled: true }, + open: { disabled: true }, + share: { order: 200 }, + alerts: { disabled: true }, + inspect: { disabled: true }, + save: { order: 400 }, + }, + getMenuItems: () => [ + { + data: { + id: 'options', + label: 'Options', + iconType: 'arrowDown', + iconSide: 'right', + run: () => alert('Options menu opened'), + }, + order: 100, + }, + { + data: { + id: 'documentExplorer', + label: 'Document explorer', + iconType: 'discoverApp', + run: () => { + discover.extensions.disable('top_nav'); + discover.extensions.disable('search_bar'); + discover.extensions.disable('field_popover'); + discover.extensions.disable('data_grid'); + setTimeout(() => { + discover.extensions.enable('top_nav'); + discover.extensions.enable('search_bar'); + discover.extensions.enable('field_popover'); + discover.extensions.enable('data_grid'); + }, 5000); + }, + }, + order: 300, + }, + ], + }); + + discover.extensions.set({ + id: 'search_bar', + CustomDataViewPicker: () => ( + alert('Data view picker opened')} + > + [Logs] System + + ), + }); + + discover.extensions.set({ + id: 'field_popover', + CustomBottomButton: () => ( + alert('Categorize flyout opened')} + > + Categorize + + ), + }); + + const MoreMenuCell = ({ setCellProps }: EuiDataGridCellValueElementProps) => { + useEffect(() => { + setCellProps({ + style: { + padding: 0, + }, + }); + }, [setCellProps]); + + return ( + alert('More menu opened')} + /> + ); + }; + + discover.extensions.set({ + id: 'data_grid', + defaultLeadingControlColumns: { + select: { disabled: true }, + }, + getLeadingControlColumns: () => [ + { + id: 'moreMenu', + width: 24, + headerCellRender: () => ( + + More menu + + ), + rowCellRender: MoreMenuCell, + }, + ], + }); + } +} diff --git a/examples/discover_extender/tsconfig.json b/examples/discover_extender/tsconfig.json new file mode 100644 index 0000000000000..68052c509c6e0 --- /dev/null +++ b/examples/discover_extender/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types" + }, + "include": ["common/**/*", "public/**/*", "server/**/*", "../../typings/**/*"], + "kbn_references": [], + "exclude": ["target/**/*"] +} diff --git a/package.json b/package.json index 58b87303d4a02..f6ecec1fd1343 100644 --- a/package.json +++ b/package.json @@ -349,6 +349,7 @@ "@kbn/dev-tools-plugin": "link:src/plugins/dev_tools", "@kbn/developer-examples-plugin": "link:examples/developer_examples", "@kbn/discover-enhanced-plugin": "link:x-pack/plugins/discover_enhanced", + "@kbn/discover-extender-plugin": "link:examples/discover_extender", "@kbn/discover-plugin": "link:src/plugins/discover", "@kbn/doc-links": "link:packages/kbn-doc-links", "@kbn/dom-drag-drop": "link:packages/kbn-dom-drag-drop", diff --git a/tsconfig.base.json b/tsconfig.base.json index 6bede7391b146..d4a3cc5f3412a 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -612,6 +612,8 @@ "@kbn/developer-examples-plugin/*": ["examples/developer_examples/*"], "@kbn/discover-enhanced-plugin": ["x-pack/plugins/discover_enhanced"], "@kbn/discover-enhanced-plugin/*": ["x-pack/plugins/discover_enhanced/*"], + "@kbn/discover-extender-plugin": ["examples/discover_extender"], + "@kbn/discover-extender-plugin/*": ["examples/discover_extender/*"], "@kbn/discover-plugin": ["src/plugins/discover"], "@kbn/discover-plugin/*": ["src/plugins/discover/*"], "@kbn/doc-links": ["packages/kbn-doc-links"], diff --git a/yarn.lock b/yarn.lock index 5ab19eee75bd9..0bada4ad317f4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3961,6 +3961,10 @@ version "0.0.0" uid "" +"@kbn/discover-extender-plugin@link:examples/discover_extender": + version "0.0.0" + uid "" + "@kbn/discover-plugin@link:src/plugins/discover": version "0.0.0" uid "" From fca141ebddb35113d2700aa29549117e439216a2 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 5 Apr 2023 18:17:57 +0000 Subject: [PATCH 039/122] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- examples/discover_extender/tsconfig.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/discover_extender/tsconfig.json b/examples/discover_extender/tsconfig.json index 68052c509c6e0..e5dce4f00c56e 100644 --- a/examples/discover_extender/tsconfig.json +++ b/examples/discover_extender/tsconfig.json @@ -4,6 +4,9 @@ "outDir": "target/types" }, "include": ["common/**/*", "public/**/*", "server/**/*", "../../typings/**/*"], - "kbn_references": [], + "kbn_references": [ + "@kbn/core", + "@kbn/discover-plugin", + ], "exclude": ["target/**/*"] } From fbf10132b35d3f673c8f8abe0123f5add65f2044 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 5 Apr 2023 18:24:10 +0000 Subject: [PATCH 040/122] [CI] Auto-commit changed files from 'node scripts/generate codeowners' --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 270c2ce5d2b48..1c60e8be47eb3 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -309,6 +309,7 @@ src/plugins/dev_tools @elastic/platform-deployment-management packages/kbn-dev-utils @elastic/kibana-operations examples/developer_examples @elastic/appex-sharedux x-pack/plugins/discover_enhanced @elastic/kibana-data-discovery +examples/discover_extender @elastic/kibana-data-discovery src/plugins/discover @elastic/kibana-data-discovery packages/kbn-doc-links @elastic/kibana-docs packages/kbn-docs-utils @elastic/kibana-operations From ff6e6330cabb8ee5bf9e1f41701c27c715f8e93d Mon Sep 17 00:00:00 2001 From: Davis McPhee Date: Sat, 15 Apr 2023 16:51:50 -0300 Subject: [PATCH 041/122] Implement registerExtensions to pass Discover stateContainer to extensions --- examples/discover_extender/public/plugin.tsx | 206 +++++++++--------- .../public/application/discover_router.tsx | 19 +- .../discover/public/application/index.tsx | 15 +- .../components/layout/discover_documents.tsx | 4 +- .../components/sidebar/discover_field.tsx | 4 +- .../components/top_nav/discover_topnav.tsx | 15 +- .../application/main/discover_main_route.tsx | 20 +- src/plugins/discover/public/build_services.ts | 6 +- .../public/extensions/extension_provider.ts | 39 ++++ src/plugins/discover/public/plugin.tsx | 31 ++- 10 files changed, 224 insertions(+), 135 deletions(-) create mode 100644 src/plugins/discover/public/extensions/extension_provider.ts diff --git a/examples/discover_extender/public/plugin.tsx b/examples/discover_extender/public/plugin.tsx index 5e61a2968dd8a..8f168f2f31392 100644 --- a/examples/discover_extender/public/plugin.tsx +++ b/examples/discover_extender/public/plugin.tsx @@ -15,6 +15,7 @@ import { import type { CoreStart, Plugin } from '@kbn/core/public'; import type { DiscoverStart } from '@kbn/discover-plugin/public'; import React, { useEffect } from 'react'; +import useObservable from 'react-use/lib/useObservable'; export interface DiscoverExtenderStartPlugins { discover: DiscoverStart; @@ -26,113 +27,122 @@ export class DiscoverExtenderPlugin implements Plugin { start(_: CoreStart, plugins: DiscoverExtenderStartPlugins) { const { discover } = plugins; - discover.extensions.set({ - id: 'top_nav', - defaultMenu: { - options: { disabled: true }, - new: { disabled: true }, - open: { disabled: true }, - share: { order: 200 }, - alerts: { disabled: true }, - inspect: { disabled: true }, - save: { order: 400 }, - }, - getMenuItems: () => [ - { - data: { - id: 'options', - label: 'Options', - iconType: 'arrowDown', - iconSide: 'right', - run: () => alert('Options menu opened'), - }, - order: 100, + discover.registerExtensions(({ extensions, stateContainer }) => { + extensions.set({ + id: 'top_nav', + defaultMenu: { + options: { disabled: true }, + new: { disabled: true }, + open: { disabled: true }, + share: { order: 200 }, + alerts: { disabled: true }, + inspect: { disabled: true }, + save: { order: 400 }, }, - { - data: { - id: 'documentExplorer', - label: 'Document explorer', - iconType: 'discoverApp', - run: () => { - discover.extensions.disable('top_nav'); - discover.extensions.disable('search_bar'); - discover.extensions.disable('field_popover'); - discover.extensions.disable('data_grid'); - setTimeout(() => { - discover.extensions.enable('top_nav'); - discover.extensions.enable('search_bar'); - discover.extensions.enable('field_popover'); - discover.extensions.enable('data_grid'); - }, 5000); + getMenuItems: () => [ + { + data: { + id: 'options', + label: 'Options', + iconType: 'arrowDown', + iconSide: 'right', + run: () => alert('Options menu opened'), }, + order: 100, }, - order: 300, - }, - ], - }); + { + data: { + id: 'documentExplorer', + label: 'Document explorer', + iconType: 'discoverApp', + run: () => { + extensions.disable('top_nav'); + extensions.disable('search_bar'); + extensions.disable('field_popover'); + extensions.disable('data_grid'); + setTimeout(() => { + extensions.enable('top_nav'); + extensions.enable('search_bar'); + extensions.enable('field_popover'); + extensions.enable('data_grid'); + }, 5000); + }, + }, + order: 300, + }, + ], + }); - discover.extensions.set({ - id: 'search_bar', - CustomDataViewPicker: () => ( - alert('Data view picker opened')} - > - [Logs] System - - ), - }); + extensions.set({ + id: 'search_bar', + CustomDataViewPicker: () => { + const dataView = useObservable( + stateContainer.internalState.state$, + stateContainer.internalState.get() + ).dataView; - discover.extensions.set({ - id: 'field_popover', - CustomBottomButton: () => ( - alert('Categorize flyout opened')} - > - Categorize - - ), - }); + return ( + alert('Data view picker opened')} + > + {dataView?.name} + + ); + }, + }); - const MoreMenuCell = ({ setCellProps }: EuiDataGridCellValueElementProps) => { - useEffect(() => { - setCellProps({ - style: { - padding: 0, - }, - }); - }, [setCellProps]); + extensions.set({ + id: 'field_popover', + CustomBottomButton: () => ( + alert('Categorize flyout opened')} + > + Categorize + + ), + }); - return ( - alert('More menu opened')} - /> - ); - }; + const MoreMenuCell = ({ setCellProps }: EuiDataGridCellValueElementProps) => { + useEffect(() => { + setCellProps({ + style: { + padding: 0, + }, + }); + }, [setCellProps]); + + return ( + alert('More menu opened')} + /> + ); + }; - discover.extensions.set({ - id: 'data_grid', - defaultLeadingControlColumns: { - select: { disabled: true }, - }, - getLeadingControlColumns: () => [ - { - id: 'moreMenu', - width: 24, - headerCellRender: () => ( - - More menu - - ), - rowCellRender: MoreMenuCell, + extensions.set({ + id: 'data_grid', + defaultLeadingControlColumns: { + select: { disabled: true }, }, - ], + getLeadingControlColumns: () => [ + { + id: 'moreMenu', + width: 24, + headerCellRender: () => ( + + More menu + + ), + rowCellRender: MoreMenuCell, + }, + ], + }); }); } } diff --git a/src/plugins/discover/public/application/discover_router.tsx b/src/plugins/discover/public/application/discover_router.tsx index d2e03758e5c39..a69455d7e2912 100644 --- a/src/plugins/discover/public/application/discover_router.tsx +++ b/src/plugins/discover/public/application/discover_router.tsx @@ -18,8 +18,21 @@ import { DiscoverMainRoute } from './main'; import { NotFoundRoute } from './not_found'; import { DiscoverServices } from '../build_services'; import { ViewAlertRoute } from './view_alert'; +import type { RegisterExtensions } from '../plugin'; -export const discoverRouter = (services: DiscoverServices, history: History, isDev: boolean) => ( +export interface DiscoverRouterProps { + services: DiscoverServices; + registerExtensions: RegisterExtensions[]; + history: History; + isDev: boolean; +} + +export const discoverRouter = ({ + services, + registerExtensions, + history, + isDev, +}: DiscoverRouterProps) => ( @@ -40,10 +53,10 @@ export const discoverRouter = (services: DiscoverServices, history: History, isD - + - + diff --git a/src/plugins/discover/public/application/index.tsx b/src/plugins/discover/public/application/index.tsx index 5ae2ed76923b5..8b1ed6c4ed935 100644 --- a/src/plugins/discover/public/application/index.tsx +++ b/src/plugins/discover/public/application/index.tsx @@ -9,8 +9,16 @@ import { i18n } from '@kbn/i18n'; import { toMountPoint, wrapWithTheme } from '@kbn/kibana-react-plugin/public'; import { discoverRouter } from './discover_router'; import { DiscoverServices } from '../build_services'; +import type { RegisterExtensions } from '../plugin'; -export const renderApp = (element: HTMLElement, services: DiscoverServices, isDev: boolean) => { +export interface RenderAppProps { + element: HTMLElement; + services: DiscoverServices; + registerExtensions: RegisterExtensions[]; + isDev: boolean; +} + +export const renderApp = ({ element, services, registerExtensions, isDev }: RenderAppProps) => { const { history: getHistory, capabilities, chrome, data, core } = services; const history = getHistory(); @@ -26,7 +34,10 @@ export const renderApp = (element: HTMLElement, services: DiscoverServices, isDe }); } const unmount = toMountPoint( - wrapWithTheme(discoverRouter(services, history, isDev), core.theme.theme$) + wrapWithTheme( + discoverRouter({ services, registerExtensions, history, isDev }), + core.theme.theme$ + ) )(element); return () => { diff --git a/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx b/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx index 4c9c95bcad5d6..509bb28d2880a 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx @@ -16,7 +16,6 @@ import { import { FormattedMessage } from '@kbn/i18n-react'; import { DataView } from '@kbn/data-views-plugin/public'; import { SortOrder } from '@kbn/saved-search-plugin/public'; -import useObservable from 'react-use/lib/useObservable'; import { useInternalStateSelector } from '../../services/discover_internal_state_container'; import { useAppStateSelector } from '../../services/discover_app_state_container'; import { useDiscoverServices } from '../../../../hooks/use_discover_services'; @@ -42,6 +41,7 @@ import { DataTableRecord } from '../../../../types'; import { getRawRecordType } from '../../utils/get_raw_record_type'; import { DiscoverGridFlyout } from '../../../../components/discover_grid/discover_grid_flyout'; import { DocViewer } from '../../../../services/doc_views/components/doc_viewer'; +import { useDiscoverExtension } from '../../../../extensions/extension_provider'; const DocTableInfiniteMemoized = React.memo(DocTableInfinite); const DataGridMemoized = React.memo(DiscoverGrid); @@ -168,7 +168,7 @@ function DiscoverDocumentsComponent({ [isPlainRecord, uiSettings, dataView.timeFieldName] ); - const dataGridExtension = useObservable(services.extensions.get$('data_grid')); + const dataGridExtension = useDiscoverExtension('data_grid'); const defaultControlColumns = dataGridExtension?.defaultLeadingControlColumns; const controlColumnIds = useMemo(() => { diff --git a/src/plugins/discover/public/application/main/components/sidebar/discover_field.tsx b/src/plugins/discover/public/application/main/components/sidebar/discover_field.tsx index e02d27aeb369a..a4217702528ca 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/discover_field.tsx +++ b/src/plugins/discover/public/application/main/components/sidebar/discover_field.tsx @@ -25,13 +25,13 @@ import { FieldVisualizeButton, } from '@kbn/unified-field-list-plugin/public'; import { DragDrop } from '@kbn/dom-drag-drop'; -import useObservable from 'react-use/lib/useObservable'; import { DiscoverFieldStats } from './discover_field_stats'; import { DiscoverFieldDetails } from './deprecated_stats/discover_field_details'; import { useDiscoverServices } from '../../../../hooks/use_discover_services'; import { PLUGIN_ID, SHOW_LEGACY_FIELD_TOP_VALUES } from '../../../../../common'; import { getUiActions } from '../../../../kibana_services'; import { type DataDocuments$ } from '../../services/discover_data_state_container'; +import { useDiscoverExtension } from '../../../../extensions/extension_provider'; interface GetCommonFieldItemButtonPropsParams { field: DataViewField; @@ -275,7 +275,7 @@ function DiscoverFieldComponent({ [field.name] ); - const fieldPopoverExtension = useObservable(services.extensions.get$('field_popover')); + const fieldPopoverExtension = useDiscoverExtension('field_popover'); const renderPopover = () => { const showLegacyFieldStats = services.uiSettings.get(SHOW_LEGACY_FIELD_TOP_VALUES); diff --git a/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx b/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx index 8ec1bbcd9076f..42918f9f21878 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx @@ -9,7 +9,6 @@ import React, { useCallback, useEffect, useMemo, useRef } from 'react'; import type { Query, TimeRange, AggregateQuery } from '@kbn/es-query'; import { DataViewType, type DataView } from '@kbn/data-views-plugin/public'; import type { DataViewPickerProps } from '@kbn/unified-search-plugin/public'; -import useObservable from 'react-use/lib/useObservable'; import { useInternalStateSelector } from '../../services/discover_internal_state_container'; import { ENABLE_SQL } from '../../../../../common'; import { useDiscoverServices } from '../../../../hooks/use_discover_services'; @@ -18,6 +17,7 @@ import { getTopNavLinks } from './get_top_nav_links'; import { getHeaderActionMenuMounter } from '../../../../kibana_services'; import { DiscoverStateContainer } from '../../services/discover_state'; import { onSaveSearch } from './on_save_search'; +import { useDiscoverExtension } from '../../../../extensions/extension_provider'; export type DiscoverTopNavProps = Pick & { onOpenInspector: () => void; @@ -109,7 +109,7 @@ export const DiscoverTopNav = ({ }); }, [dataViewEditor, stateContainer]); - const topNavExtension = useObservable(services.extensions.get$('top_nav')); + const topNavExtension = useDiscoverExtension('top_nav'); const topNavMenu = useMemo( () => getTopNavLinks({ @@ -124,15 +124,14 @@ export const DiscoverTopNav = ({ topNavExtension, }), [ + adHocDataViews, dataView, + isPlainRecord, navigateTo, - services, - stateContainer, onOpenInspector, - isPlainRecord, - adHocDataViews, - updateDataViewList, persistDataView, + services, + stateContainer, topNavExtension, ] ); @@ -202,7 +201,7 @@ export const DiscoverTopNav = ({ [navigateTo, services, stateContainer] ); - const searchBarExtension = useObservable(services.extensions.get$('search_bar')); + const searchBarExtension = useDiscoverExtension('search_bar'); return ( (); const [loading, setLoading] = useState(true); const [hasESData, setHasESData] = useState(false); @@ -248,8 +254,10 @@ export function DiscoverMainRoute(props: Props) { } return ( - - - + + + + + ); } diff --git a/src/plugins/discover/public/build_services.ts b/src/plugins/discover/public/build_services.ts index 7d524492ab342..5975805522f45 100644 --- a/src/plugins/discover/public/build_services.ts +++ b/src/plugins/discover/public/build_services.ts @@ -52,7 +52,6 @@ import { DiscoverStartPlugins } from './plugin'; import { DiscoverContextAppLocator } from './application/context/services/locator'; import { DiscoverSingleDocLocator } from './application/doc/locator'; import { DiscoverAppLocator } from '../common'; -import type { DiscoverExtensionRegistry } from './extensions'; /** * Location state of internal Discover history instance @@ -100,7 +99,6 @@ export interface DiscoverServices { savedObjectsTagging?: SavedObjectsTaggingApi; unifiedSearch: UnifiedSearchPublicPluginStart; lens: LensPublicStart; - extensions: DiscoverExtensionRegistry; } export const buildServices = memoize(function ( @@ -109,8 +107,7 @@ export const buildServices = memoize(function ( context: PluginInitializerContext, locator: DiscoverAppLocator, contextLocator: DiscoverContextAppLocator, - singleDocLocator: DiscoverSingleDocLocator, - extensions: DiscoverExtensionRegistry + singleDocLocator: DiscoverSingleDocLocator ): DiscoverServices { const { usageCollection } = plugins; const storage = new Storage(localStorage); @@ -156,6 +153,5 @@ export const buildServices = memoize(function ( savedObjectsManagement: plugins.savedObjectsManagement, unifiedSearch: plugins.unifiedSearch, lens: plugins.lens, - extensions, }; }); diff --git a/src/plugins/discover/public/extensions/extension_provider.ts b/src/plugins/discover/public/extensions/extension_provider.ts new file mode 100644 index 0000000000000..f763eba327776 --- /dev/null +++ b/src/plugins/discover/public/extensions/extension_provider.ts @@ -0,0 +1,39 @@ +/* + * 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 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 or the Server + * Side Public License, v 1. + */ + +import { createContext, useContext, useState } from 'react'; +import useObservable from 'react-use/lib/useObservable'; +import type { RegisterExtensions } from '../plugin'; +import { createExtensionRegistry, DiscoverExtensionId, DiscoverExtensionRegistry } from '.'; +import type { DiscoverStateContainer } from '../application/main/services/discover_state'; + +const extensionContext = createContext(createExtensionRegistry()); + +export const DiscoverExtensionProvider = extensionContext.Provider; + +export const useDiscoverExtensionRegistry = ({ + registerExtensions, + stateContainer, +}: { + registerExtensions: RegisterExtensions[]; + stateContainer: DiscoverStateContainer; +}) => { + const [extensionRegistry] = useState(() => { + const extensions = createExtensionRegistry(); + registerExtensions.forEach((register) => register({ extensions, stateContainer })); + return extensions; + }); + + return extensionRegistry; +}; + +export const useDiscoverExtension$ = (id: TExtensionId) => + useContext(extensionContext).get$(id); + +export const useDiscoverExtension = (id: TExtensionId) => + useObservable(useDiscoverExtension$(id)); diff --git a/src/plugins/discover/public/plugin.tsx b/src/plugins/discover/public/plugin.tsx index a1e6c928d2397..f9e8b4dd37ba5 100644 --- a/src/plugins/discover/public/plugin.tsx +++ b/src/plugins/discover/public/plugin.tsx @@ -71,7 +71,8 @@ import { DiscoverSingleDocLocatorDefinition, } from './application/doc/locator'; import { DiscoverAppLocator, DiscoverAppLocatorDefinition } from '../common'; -import { createExtensionRegistry, DiscoverExtensionRegistry } from './extensions'; +import { DiscoverExtensionRegistry } from './extensions'; +import type { DiscoverStateContainer } from './application/main/services/discover_state'; const DocViewerLegacyTable = React.lazy( () => import('./services/doc_views/components/doc_viewer_table/legacy') @@ -79,6 +80,13 @@ const DocViewerLegacyTable = React.lazy( const DocViewerTable = React.lazy(() => import('./services/doc_views/components/doc_viewer_table')); const SourceViewer = React.lazy(() => import('./services/doc_views/components/doc_viewer_source')); +export interface RegisterExtensionsContext { + extensions: DiscoverExtensionRegistry; + stateContainer: DiscoverStateContainer; +} + +export type RegisterExtensions = (options: RegisterExtensionsContext) => void; + /** * @public */ @@ -156,7 +164,7 @@ export interface DiscoverStart { * ``` */ readonly locator: undefined | DiscoverAppLocator; - readonly extensions: DiscoverExtensionRegistry; + readonly registerExtensions: (register: RegisterExtensions) => void; } /** @@ -210,8 +218,8 @@ export class DiscoverPlugin private appStateUpdater = new BehaviorSubject(() => ({})); private docViewsRegistry: DocViewsRegistry | null = null; - private extensions = createExtensionRegistry(); private stopUrlTracking: (() => void) | undefined = undefined; + private registerExtensions: RegisterExtensions[] = []; private locator?: DiscoverAppLocator; private contextLocator?: DiscoverContextAppLocator; private singleDocLocator?: DiscoverSingleDocLocator; @@ -323,8 +331,7 @@ export class DiscoverPlugin this.initializerContext, this.locator!, this.contextLocator!, - this.singleDocLocator!, - this.extensions + this.singleDocLocator! ); // make sure the data view list is up to date @@ -334,7 +341,12 @@ export class DiscoverPlugin // FIXME: Temporarily hide overflow-y in Discover app when Field Stats table is shown // due to EUI bug https://github.com/elastic/eui/pull/5152 params.element.classList.add('dscAppWrapper'); - const unmount = renderApp(params.element, services, isDev); + const unmount = renderApp({ + element: params.element, + services, + registerExtensions: this.registerExtensions, + isDev, + }); return () => { unlistenParentHistory(); unmount(); @@ -395,7 +407,9 @@ export class DiscoverPlugin return { locator: this.locator, - extensions: this.extensions, + registerExtensions: (register: RegisterExtensions) => { + this.registerExtensions.push(register); + }, }; } @@ -422,8 +436,7 @@ export class DiscoverPlugin this.initializerContext, this.locator!, this.contextLocator!, - this.singleDocLocator!, - this.extensions + this.singleDocLocator! ); }; From 2e01f37a476664c1caddecc642bbab131fbdbaaf Mon Sep 17 00:00:00 2001 From: Davis McPhee Date: Sat, 15 Apr 2023 21:21:36 -0300 Subject: [PATCH 042/122] Update discover_extender to interact with DiscoverStateContainer --- examples/discover_extender/public/plugin.tsx | 159 ++++++++++++++++--- 1 file changed, 140 insertions(+), 19 deletions(-) diff --git a/examples/discover_extender/public/plugin.tsx b/examples/discover_extender/public/plugin.tsx index 8f168f2f31392..200379a72c261 100644 --- a/examples/discover_extender/public/plugin.tsx +++ b/examples/discover_extender/public/plugin.tsx @@ -9,12 +9,16 @@ import { EuiButton, EuiButtonIcon, + EuiContextMenu, EuiDataGridCellValueElementProps, + EuiPopover, EuiScreenReaderOnly, + EuiWrappingPopover, } from '@elastic/eui'; -import type { CoreStart, Plugin } from '@kbn/core/public'; +import type { CoreStart, Plugin, SimpleSavedObject } from '@kbn/core/public'; import type { DiscoverStart } from '@kbn/discover-plugin/public'; -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; +import ReactDOM from 'react-dom'; import useObservable from 'react-use/lib/useObservable'; export interface DiscoverExtenderStartPlugins { @@ -24,9 +28,17 @@ export interface DiscoverExtenderStartPlugins { export class DiscoverExtenderPlugin implements Plugin { setup() {} - start(_: CoreStart, plugins: DiscoverExtenderStartPlugins) { + start(core: CoreStart, plugins: DiscoverExtenderStartPlugins) { const { discover } = plugins; + let isOptionsOpen = false; + const optionsContainer = document.createElement('div'); + const closeOptionsPopover = () => { + ReactDOM.unmountComponentAtNode(optionsContainer); + document.body.removeChild(optionsContainer); + isOptionsOpen = false; + }; + discover.registerExtensions(({ extensions, stateContainer }) => { extensions.set({ id: 'top_nav', @@ -46,7 +58,54 @@ export class DiscoverExtenderPlugin implements Plugin { label: 'Options', iconType: 'arrowDown', iconSide: 'right', - run: () => alert('Options menu opened'), + run: (anchorElement: HTMLElement) => { + if (isOptionsOpen) { + closeOptionsPopover(); + return; + } + + isOptionsOpen = true; + document.body.appendChild(optionsContainer); + + const element = ( + + alert('Create new clicked'), + }, + { + name: 'Make a copy', + icon: 'copy', + onClick: () => alert('Make a copy clicked'), + }, + { + name: 'Manage saved searches', + icon: 'gear', + onClick: () => alert('Manage saved searches clicked'), + }, + ], + }, + ]} + /> + + ); + + ReactDOM.render(element, optionsContainer); + }, }, order: 100, }, @@ -76,19 +135,49 @@ export class DiscoverExtenderPlugin implements Plugin { extensions.set({ id: 'search_bar', CustomDataViewPicker: () => { - const dataView = useObservable( - stateContainer.internalState.state$, - stateContainer.internalState.get() - ).dataView; + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const togglePopover = () => setIsPopoverOpen((open) => !open); + const closePopover = () => setIsPopoverOpen(false); + const [savedSearches, setSavedSearches] = useState([]); + + useEffect(() => { + core.savedObjects.client.find({ type: 'search' }).then((response) => { + setSavedSearches(response.savedObjects); + }); + }, []); + + const currentSavedSearch = useObservable( + stateContainer.savedSearchState.getCurrent$(), + stateContainer.savedSearchState.getState() + ); return ( - alert('Data view picker opened')} + + {currentSavedSearch.title ?? 'None selected'} + + } + isOpen={isPopoverOpen} + panelPaddingSize="none" + closePopover={closePopover} > - {dataView?.name} - + ({ + name: savedSearch.get('title'), + onClick: () => stateContainer.actions.onOpenSavedSearch(savedSearch.id), + icon: savedSearch.id === currentSavedSearch.id ? 'check' : 'empty', + })), + }, + ]} + /> + ); }, }); @@ -108,6 +197,10 @@ export class DiscoverExtenderPlugin implements Plugin { }); const MoreMenuCell = ({ setCellProps }: EuiDataGridCellValueElementProps) => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const togglePopover = () => setIsPopoverOpen((open) => !open); + const closePopover = () => setIsPopoverOpen(false); + useEffect(() => { setCellProps({ style: { @@ -117,11 +210,39 @@ export class DiscoverExtenderPlugin implements Plugin { }, [setCellProps]); return ( - alert('More menu opened')} - /> + + } + isOpen={isPopoverOpen} + anchorPosition="rightCenter" + panelPaddingSize="none" + closePopover={closePopover} + > + alert('Show host details clicked'), + }, + { + name: 'Create rule', + onClick: () => alert('Create rule clicked'), + }, + { + name: 'Create SLO', + onClick: () => alert('Create SLO clicked'), + }, + ], + }, + ]} + /> + ); }; From f65cc98b654cb051f9460edcaa7b5cec03ca7208 Mon Sep 17 00:00:00 2001 From: Davis McPhee Date: Tue, 18 Apr 2023 10:52:58 -0300 Subject: [PATCH 043/122] Add extension cleanup function support --- examples/discover_extender/public/plugin.tsx | 4 ++++ .../public/extensions/extension_provider.ts | 17 +++++++++++++++-- src/plugins/discover/public/plugin.tsx | 2 +- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/examples/discover_extender/public/plugin.tsx b/examples/discover_extender/public/plugin.tsx index 200379a72c261..1c8aef8ecb680 100644 --- a/examples/discover_extender/public/plugin.tsx +++ b/examples/discover_extender/public/plugin.tsx @@ -264,6 +264,10 @@ export class DiscoverExtenderPlugin implements Plugin { }, ], }); + + return () => { + console.log('Cleaning up Discover extensions'); + }; }); } } diff --git a/src/plugins/discover/public/extensions/extension_provider.ts b/src/plugins/discover/public/extensions/extension_provider.ts index f763eba327776..8989bb3de73af 100644 --- a/src/plugins/discover/public/extensions/extension_provider.ts +++ b/src/plugins/discover/public/extensions/extension_provider.ts @@ -6,8 +6,9 @@ * Side Public License, v 1. */ -import { createContext, useContext, useState } from 'react'; +import { createContext, useContext, useEffect, useRef, useState } from 'react'; import useObservable from 'react-use/lib/useObservable'; +import { isFunction } from 'lodash'; import type { RegisterExtensions } from '../plugin'; import { createExtensionRegistry, DiscoverExtensionId, DiscoverExtensionRegistry } from '.'; import type { DiscoverStateContainer } from '../application/main/services/discover_state'; @@ -23,12 +24,24 @@ export const useDiscoverExtensionRegistry = ({ registerExtensions: RegisterExtensions[]; stateContainer: DiscoverStateContainer; }) => { + const cleanupExtensions = useRef void>>([]); + const [extensionRegistry] = useState(() => { const extensions = createExtensionRegistry(); - registerExtensions.forEach((register) => register({ extensions, stateContainer })); + + cleanupExtensions.current = registerExtensions + .map((register) => register({ extensions, stateContainer })) + .filter(isFunction); + return extensions; }); + useEffect(() => { + return () => { + cleanupExtensions.current.forEach((cleanup) => cleanup()); + }; + }, []); + return extensionRegistry; }; diff --git a/src/plugins/discover/public/plugin.tsx b/src/plugins/discover/public/plugin.tsx index f9e8b4dd37ba5..c62bdee33a0e6 100644 --- a/src/plugins/discover/public/plugin.tsx +++ b/src/plugins/discover/public/plugin.tsx @@ -85,7 +85,7 @@ export interface RegisterExtensionsContext { stateContainer: DiscoverStateContainer; } -export type RegisterExtensions = (options: RegisterExtensionsContext) => void; +export type RegisterExtensions = (options: RegisterExtensionsContext) => void | (() => void); /** * @public From 245dac4db43ed496dc01cd3a380337619653fe5a Mon Sep 17 00:00:00 2001 From: Davis McPhee Date: Thu, 20 Apr 2023 11:07:48 -0300 Subject: [PATCH 044/122] Fix discover_field EUI imports --- .../application/main/components/sidebar/discover_field.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/plugins/discover/public/application/main/components/sidebar/discover_field.tsx b/src/plugins/discover/public/application/main/components/sidebar/discover_field.tsx index a4217702528ca..8b85b6a521a25 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/discover_field.tsx +++ b/src/plugins/discover/public/application/main/components/sidebar/discover_field.tsx @@ -9,10 +9,7 @@ import './discover_field.scss'; import React, { memo, useCallback, useMemo, useState } from 'react'; -import { EuiSpacer, EuiTitle } from '@elastic/eui'; - EuiPopoverFooter, - EuiFlexItem, - EuiFlexGroup, +import { EuiSpacer, EuiTitle, EuiPopoverFooter, EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { UiCounterMetricType } from '@kbn/analytics'; import type { DataView, DataViewField } from '@kbn/data-views-plugin/public'; From a64dc079775eb16828c942d69735c4389afa1b2c Mon Sep 17 00:00:00 2001 From: Davis McPhee Date: Sun, 23 Apr 2023 22:29:09 -0300 Subject: [PATCH 045/122] Make Discover extension registrations async --- .../public/more_menu_cell.tsx | 63 +++++++++++++++++++ examples/discover_extender/public/plugin.tsx | 55 +--------------- .../application/main/discover_main_route.tsx | 2 +- .../public/extensions/extension_provider.ts | 27 ++++---- src/plugins/discover/public/plugin.tsx | 4 +- 5 files changed, 85 insertions(+), 66 deletions(-) create mode 100644 examples/discover_extender/public/more_menu_cell.tsx diff --git a/examples/discover_extender/public/more_menu_cell.tsx b/examples/discover_extender/public/more_menu_cell.tsx new file mode 100644 index 0000000000000..e497adc53f18d --- /dev/null +++ b/examples/discover_extender/public/more_menu_cell.tsx @@ -0,0 +1,63 @@ +/* + * 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 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 or the Server + * Side Public License, v 1. + */ + +import { + EuiButtonIcon, + EuiContextMenu, + EuiDataGridCellValueElementProps, + EuiPopover, +} from '@elastic/eui'; +import React, { useEffect, useState } from 'react'; + +export const MoreMenuCell = ({ setCellProps }: EuiDataGridCellValueElementProps) => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const togglePopover = () => setIsPopoverOpen((open) => !open); + const closePopover = () => setIsPopoverOpen(false); + + useEffect(() => { + setCellProps({ + style: { + padding: 0, + }, + }); + }, [setCellProps]); + + return ( + } + isOpen={isPopoverOpen} + anchorPosition="rightCenter" + panelPaddingSize="none" + closePopover={closePopover} + > + alert('Show host details clicked'), + }, + { + name: 'Create rule', + onClick: () => alert('Create rule clicked'), + }, + { + name: 'Create SLO', + onClick: () => alert('Create SLO clicked'), + }, + ], + }, + ]} + /> + + ); +}; diff --git a/examples/discover_extender/public/plugin.tsx b/examples/discover_extender/public/plugin.tsx index 1c8aef8ecb680..5542f225b5fa8 100644 --- a/examples/discover_extender/public/plugin.tsx +++ b/examples/discover_extender/public/plugin.tsx @@ -8,9 +8,7 @@ import { EuiButton, - EuiButtonIcon, EuiContextMenu, - EuiDataGridCellValueElementProps, EuiPopover, EuiScreenReaderOnly, EuiWrappingPopover, @@ -39,7 +37,7 @@ export class DiscoverExtenderPlugin implements Plugin { isOptionsOpen = false; }; - discover.registerExtensions(({ extensions, stateContainer }) => { + discover.registerExtensions(async ({ extensions, stateContainer }) => { extensions.set({ id: 'top_nav', defaultMenu: { @@ -196,55 +194,7 @@ export class DiscoverExtenderPlugin implements Plugin { ), }); - const MoreMenuCell = ({ setCellProps }: EuiDataGridCellValueElementProps) => { - const [isPopoverOpen, setIsPopoverOpen] = useState(false); - const togglePopover = () => setIsPopoverOpen((open) => !open); - const closePopover = () => setIsPopoverOpen(false); - - useEffect(() => { - setCellProps({ - style: { - padding: 0, - }, - }); - }, [setCellProps]); - - return ( - - } - isOpen={isPopoverOpen} - anchorPosition="rightCenter" - panelPaddingSize="none" - closePopover={closePopover} - > - alert('Show host details clicked'), - }, - { - name: 'Create rule', - onClick: () => alert('Create rule clicked'), - }, - { - name: 'Create SLO', - onClick: () => alert('Create SLO clicked'), - }, - ], - }, - ]} - /> - - ); - }; + const { MoreMenuCell } = await import('./more_menu_cell'); extensions.set({ id: 'data_grid', @@ -266,6 +216,7 @@ export class DiscoverExtenderPlugin implements Plugin { }); return () => { + // eslint-disable-next-line no-console console.log('Cleaning up Discover extensions'); }; }); diff --git a/src/plugins/discover/public/application/main/discover_main_route.tsx b/src/plugins/discover/public/application/main/discover_main_route.tsx index 9b07713030043..b7df45d62a852 100644 --- a/src/plugins/discover/public/application/main/discover_main_route.tsx +++ b/src/plugins/discover/public/application/main/discover_main_route.tsx @@ -249,7 +249,7 @@ export function DiscoverMainRoute({ registerExtensions, isDev }: MainRouteProps) return ; } - if (loading) { + if (loading || !extensionRegistry) { return ; } diff --git a/src/plugins/discover/public/extensions/extension_provider.ts b/src/plugins/discover/public/extensions/extension_provider.ts index 8989bb3de73af..42a689276a9ca 100644 --- a/src/plugins/discover/public/extensions/extension_provider.ts +++ b/src/plugins/discover/public/extensions/extension_provider.ts @@ -6,9 +6,10 @@ * Side Public License, v 1. */ -import { createContext, useContext, useEffect, useRef, useState } from 'react'; +import { createContext, useContext, useState } from 'react'; import useObservable from 'react-use/lib/useObservable'; import { isFunction } from 'lodash'; +import useEffectOnce from 'react-use/lib/useEffectOnce'; import type { RegisterExtensions } from '../plugin'; import { createExtensionRegistry, DiscoverExtensionId, DiscoverExtensionRegistry } from '.'; import type { DiscoverStateContainer } from '../application/main/services/discover_state'; @@ -24,23 +25,25 @@ export const useDiscoverExtensionRegistry = ({ registerExtensions: RegisterExtensions[]; stateContainer: DiscoverStateContainer; }) => { - const cleanupExtensions = useRef void>>([]); + const [extensionRegistry, setExtensionRegistry] = useState(); - const [extensionRegistry] = useState(() => { + useEffectOnce(() => { const extensions = createExtensionRegistry(); + const registrations = registerExtensions.map((register) => + Promise.resolve(register({ extensions, stateContainer })) + ); + const initialize = () => Promise.all(registrations).then((result) => result.filter(isFunction)); - cleanupExtensions.current = registerExtensions - .map((register) => register({ extensions, stateContainer })) - .filter(isFunction); + initialize().then(() => { + setExtensionRegistry(extensions); + }); - return extensions; - }); - - useEffect(() => { return () => { - cleanupExtensions.current.forEach((cleanup) => cleanup()); + initialize().then((cleanups) => { + cleanups.forEach((cleanup) => cleanup()); + }); }; - }, []); + }); return extensionRegistry; }; diff --git a/src/plugins/discover/public/plugin.tsx b/src/plugins/discover/public/plugin.tsx index c62bdee33a0e6..7950a55c38c62 100644 --- a/src/plugins/discover/public/plugin.tsx +++ b/src/plugins/discover/public/plugin.tsx @@ -85,7 +85,9 @@ export interface RegisterExtensionsContext { stateContainer: DiscoverStateContainer; } -export type RegisterExtensions = (options: RegisterExtensionsContext) => void | (() => void); +export type RegisterExtensions = ( + options: RegisterExtensionsContext +) => void | (() => void) | Promise void)>; /** * @public From 1aff224d991a61b8f40c9045ca07fb1a80e11f83 Mon Sep 17 00:00:00 2001 From: Davis McPhee Date: Sun, 30 Apr 2023 22:40:04 -0300 Subject: [PATCH 046/122] Add initial Discover custom routes --- .../application/discover_router.test.tsx | 4 +- .../public/application/discover_router.tsx | 80 ++++++++++++------- .../discover/public/application/index.tsx | 11 ++- 3 files changed, 64 insertions(+), 31 deletions(-) diff --git a/src/plugins/discover/public/application/discover_router.test.tsx b/src/plugins/discover/public/application/discover_router.test.tsx index e9fd30b3e1aea..51416eb5c728c 100644 --- a/src/plugins/discover/public/application/discover_router.test.tsx +++ b/src/plugins/discover/public/application/discover_router.test.tsx @@ -11,7 +11,7 @@ import { RouteProps } from 'react-router-dom'; import { Route } from '@kbn/shared-ux-router'; import { createSearchSessionMock } from '../__mocks__/search_session'; import { discoverServiceMock as mockDiscoverServices } from '../__mocks__/services'; -import { discoverRouter } from './discover_router'; +import { DiscoverRouter } from './discover_router'; import { DiscoverMainRoute } from './main'; import { SingleDocRoute } from './doc'; import { ContextAppRoute } from './context'; @@ -24,7 +24,7 @@ describe('Discover router', () => { }; beforeAll(() => { const { history } = createSearchSessionMock(); - const component = shallow(discoverRouter(mockDiscoverServices, history, props.isDev)); + const component = shallow(DiscoverRouter(mockDiscoverServices, history, props.isDev)); component.find(Route).forEach((route) => { const routeProps = route.props() as RouteProps; const path = routeProps.path; diff --git a/src/plugins/discover/public/application/discover_router.tsx b/src/plugins/discover/public/application/discover_router.tsx index a69455d7e2912..95ee3ff8c8f47 100644 --- a/src/plugins/discover/public/application/discover_router.tsx +++ b/src/plugins/discover/public/application/discover_router.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { Redirect, Router, Switch } from 'react-router-dom'; +import { Redirect, Router, Switch, useParams } from 'react-router-dom'; import { Route } from '@kbn/shared-ux-router'; import React from 'react'; import { History } from 'history'; @@ -27,38 +27,64 @@ export interface DiscoverRouterProps { isDev: boolean; } -export const discoverRouter = ({ - services, - registerExtensions, - history, - isDev, -}: DiscoverRouterProps) => ( +type DiscoverRoutesProps = Pick & { + prefix?: string; +}; + +const DiscoverRoutes = ({ prefix, ...mainRouteProps }: DiscoverRoutesProps) => { + const prefixPath = (path: string) => (prefix ? `/${prefix}/${path}` : `/${path}`); + + return ( + + + + + ( + + )} + /> + + + + + + + + + + + + + + + ); +}; + +const CustomDiscoverRoutes = (props: DiscoverRoutesProps) => { + const { profile } = useParams<{ profile: string }>(); + + if (profile === 'test') { + return ; + } + + return ; +}; + +export const DiscoverRouter = ({ services, history, ...routeProps }: DiscoverRouterProps) => ( - - - - ( - - )} - /> - - - - - - - - + + - - + + - diff --git a/src/plugins/discover/public/application/index.tsx b/src/plugins/discover/public/application/index.tsx index 8b1ed6c4ed935..5d9a3e30a7f1a 100644 --- a/src/plugins/discover/public/application/index.tsx +++ b/src/plugins/discover/public/application/index.tsx @@ -5,9 +5,11 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ + +import React from 'react'; import { i18n } from '@kbn/i18n'; import { toMountPoint, wrapWithTheme } from '@kbn/kibana-react-plugin/public'; -import { discoverRouter } from './discover_router'; +import { DiscoverRouter } from './discover_router'; import { DiscoverServices } from '../build_services'; import type { RegisterExtensions } from '../plugin'; @@ -35,7 +37,12 @@ export const renderApp = ({ element, services, registerExtensions, isDev }: Rend } const unmount = toMountPoint( wrapWithTheme( - discoverRouter({ services, registerExtensions, history, isDev }), + , core.theme.theme$ ) )(element); From 0ee856f84861d7dc18dac12a88fce43b4478279d Mon Sep 17 00:00:00 2001 From: Davis McPhee Date: Mon, 1 May 2023 11:16:00 -0300 Subject: [PATCH 047/122] Implement initial profile routes support --- examples/discover_extender/public/plugin.tsx | 48 ++++++++--- .../public/application/discover_router.tsx | 84 ++++++++++++------- .../discover/public/application/index.tsx | 6 +- src/plugins/discover/public/plugin.tsx | 12 +-- 4 files changed, 102 insertions(+), 48 deletions(-) diff --git a/examples/discover_extender/public/plugin.tsx b/examples/discover_extender/public/plugin.tsx index 5542f225b5fa8..6bfd27707fc46 100644 --- a/examples/discover_extender/public/plugin.tsx +++ b/examples/discover_extender/public/plugin.tsx @@ -29,6 +29,39 @@ export class DiscoverExtenderPlugin implements Plugin { start(core: CoreStart, plugins: DiscoverExtenderStartPlugins) { const { discover } = plugins; + discover.registerExtensions('default', ({ extensions, stateContainer }) => { + extensions.set({ + id: 'top_nav', + defaultMenu: { + options: { order: 100 }, + new: { order: 200 }, + open: { order: 300 }, + share: { order: 400 }, + alerts: { order: 500 }, + inspect: { order: 600 }, + save: { order: 800 }, + }, + getMenuItems: () => [ + { + data: { + id: 'logsExplorer', + label: 'Logs explorer', + iconType: 'logsApp', + run: () => { + // stateContainer.appState.update({ profile: 'extender' }); + }, + }, + order: 700, + }, + ], + }); + + return () => { + // eslint-disable-next-line no-console + console.log('Cleaning up Document explorer extensions'); + }; + }); + let isOptionsOpen = false; const optionsContainer = document.createElement('div'); const closeOptionsPopover = () => { @@ -37,7 +70,7 @@ export class DiscoverExtenderPlugin implements Plugin { isOptionsOpen = false; }; - discover.registerExtensions(async ({ extensions, stateContainer }) => { + discover.registerExtensions('extender', async ({ extensions, stateContainer }) => { extensions.set({ id: 'top_nav', defaultMenu: { @@ -113,16 +146,7 @@ export class DiscoverExtenderPlugin implements Plugin { label: 'Document explorer', iconType: 'discoverApp', run: () => { - extensions.disable('top_nav'); - extensions.disable('search_bar'); - extensions.disable('field_popover'); - extensions.disable('data_grid'); - setTimeout(() => { - extensions.enable('top_nav'); - extensions.enable('search_bar'); - extensions.enable('field_popover'); - extensions.enable('data_grid'); - }, 5000); + // stateContainer.appState.update({ profile: 'default' }); }, }, order: 300, @@ -217,7 +241,7 @@ export class DiscoverExtenderPlugin implements Plugin { return () => { // eslint-disable-next-line no-console - console.log('Cleaning up Discover extensions'); + console.log('Cleaning up Logs explorer extensions'); }; }); } diff --git a/src/plugins/discover/public/application/discover_router.tsx b/src/plugins/discover/public/application/discover_router.tsx index 95ee3ff8c8f47..884c359ef39d7 100644 --- a/src/plugins/discover/public/application/discover_router.tsx +++ b/src/plugins/discover/public/application/discover_router.tsx @@ -8,7 +8,7 @@ import { Redirect, Router, Switch, useParams } from 'react-router-dom'; import { Route } from '@kbn/shared-ux-router'; -import React from 'react'; +import React, { useCallback, useMemo } from 'react'; import { History } from 'history'; import { EuiErrorBoundary } from '@elastic/eui'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; @@ -20,19 +20,17 @@ import { DiscoverServices } from '../build_services'; import { ViewAlertRoute } from './view_alert'; import type { RegisterExtensions } from '../plugin'; -export interface DiscoverRouterProps { - services: DiscoverServices; +interface DiscoverRoutesProps { + prefix?: string; registerExtensions: RegisterExtensions[]; - history: History; isDev: boolean; } -type DiscoverRoutesProps = Pick & { - prefix?: string; -}; - const DiscoverRoutes = ({ prefix, ...mainRouteProps }: DiscoverRoutesProps) => { - const prefixPath = (path: string) => (prefix ? `/${prefix}/${path}` : `/${path}`); + const prefixPath = useCallback( + (path: string) => (prefix ? `${prefix}/${path}` : `/${path}`), + [prefix] + ); return ( @@ -64,29 +62,59 @@ const DiscoverRoutes = ({ prefix, ...mainRouteProps }: DiscoverRoutesProps) => { ); }; -const CustomDiscoverRoutes = (props: DiscoverRoutesProps) => { +interface CustomDiscoverRoutesProps { + registerExtensionsMap: Map; + isDev: boolean; +} + +const CustomDiscoverRoutes = ({ registerExtensionsMap, ...props }: CustomDiscoverRoutesProps) => { const { profile } = useParams<{ profile: string }>(); + const registerExtensions = useMemo( + () => registerExtensionsMap.get(profile), + [profile, registerExtensionsMap] + ); - if (profile === 'test') { - return ; + if (registerExtensions) { + return ( + + ); } return ; }; -export const DiscoverRouter = ({ services, history, ...routeProps }: DiscoverRouterProps) => ( - - - - - - - - - - - - - - -); +export interface DiscoverRouterProps { + services: DiscoverServices; + registerExtensionsMap: Map; + history: History; + isDev: boolean; +} + +export const DiscoverRouter = ({ + services, + history, + registerExtensionsMap, + ...routeProps +}: DiscoverRouterProps) => { + const registerDefaultExtensions = useMemo( + () => registerExtensionsMap.get('default') ?? [], + [registerExtensionsMap] + ); + + return ( + + + + + + + + + + + + + + + ); +}; diff --git a/src/plugins/discover/public/application/index.tsx b/src/plugins/discover/public/application/index.tsx index 5d9a3e30a7f1a..5d8463ce2004e 100644 --- a/src/plugins/discover/public/application/index.tsx +++ b/src/plugins/discover/public/application/index.tsx @@ -16,11 +16,11 @@ import type { RegisterExtensions } from '../plugin'; export interface RenderAppProps { element: HTMLElement; services: DiscoverServices; - registerExtensions: RegisterExtensions[]; + registerExtensionsMap: Map; isDev: boolean; } -export const renderApp = ({ element, services, registerExtensions, isDev }: RenderAppProps) => { +export const renderApp = ({ element, services, registerExtensionsMap, isDev }: RenderAppProps) => { const { history: getHistory, capabilities, chrome, data, core } = services; const history = getHistory(); @@ -39,7 +39,7 @@ export const renderApp = ({ element, services, registerExtensions, isDev }: Rend wrapWithTheme( , diff --git a/src/plugins/discover/public/plugin.tsx b/src/plugins/discover/public/plugin.tsx index 7950a55c38c62..46c5b6808e0d2 100644 --- a/src/plugins/discover/public/plugin.tsx +++ b/src/plugins/discover/public/plugin.tsx @@ -166,7 +166,7 @@ export interface DiscoverStart { * ``` */ readonly locator: undefined | DiscoverAppLocator; - readonly registerExtensions: (register: RegisterExtensions) => void; + readonly registerExtensions: (profile: string, register: RegisterExtensions) => void; } /** @@ -221,7 +221,7 @@ export class DiscoverPlugin private appStateUpdater = new BehaviorSubject(() => ({})); private docViewsRegistry: DocViewsRegistry | null = null; private stopUrlTracking: (() => void) | undefined = undefined; - private registerExtensions: RegisterExtensions[] = []; + private registerExtensionsMap = new Map(); private locator?: DiscoverAppLocator; private contextLocator?: DiscoverContextAppLocator; private singleDocLocator?: DiscoverSingleDocLocator; @@ -346,7 +346,7 @@ export class DiscoverPlugin const unmount = renderApp({ element: params.element, services, - registerExtensions: this.registerExtensions, + registerExtensionsMap: this.registerExtensionsMap, isDev, }); return () => { @@ -409,8 +409,10 @@ export class DiscoverPlugin return { locator: this.locator, - registerExtensions: (register: RegisterExtensions) => { - this.registerExtensions.push(register); + registerExtensions: (profile: string, register: RegisterExtensions) => { + const registerExtensions = this.registerExtensionsMap.get(profile) || []; + registerExtensions.push(register); + this.registerExtensionsMap.set(profile, registerExtensions); }, }; } From 904fb88e18efb4bf883f0bf3b0d411f5e86ac7a2 Mon Sep 17 00:00:00 2001 From: Davis McPhee Date: Mon, 1 May 2023 12:22:47 -0300 Subject: [PATCH 048/122] Add Discover profile registry --- .../public/application/discover_router.tsx | 21 ++++++------- .../discover/public/application/index.tsx | 8 ++--- .../application/main/discover_main_route.tsx | 2 +- .../public/extensions/extension_provider.ts | 2 +- .../public/extensions/profile_registry.ts | 28 +++++++++++++++++ .../discover/public/extensions/types.ts | 19 ++++++++++++ src/plugins/discover/public/plugin.tsx | 30 ++++++++----------- 7 files changed, 76 insertions(+), 34 deletions(-) create mode 100644 src/plugins/discover/public/extensions/profile_registry.ts create mode 100644 src/plugins/discover/public/extensions/types.ts diff --git a/src/plugins/discover/public/application/discover_router.tsx b/src/plugins/discover/public/application/discover_router.tsx index 884c359ef39d7..c7a87b4996f92 100644 --- a/src/plugins/discover/public/application/discover_router.tsx +++ b/src/plugins/discover/public/application/discover_router.tsx @@ -18,7 +18,8 @@ import { DiscoverMainRoute } from './main'; import { NotFoundRoute } from './not_found'; import { DiscoverServices } from '../build_services'; import { ViewAlertRoute } from './view_alert'; -import type { RegisterExtensions } from '../plugin'; +import type { RegisterExtensions } from '../extensions/types'; +import type { DiscoverProfileRegistry } from '../extensions/profile_registry'; interface DiscoverRoutesProps { prefix?: string; @@ -63,15 +64,15 @@ const DiscoverRoutes = ({ prefix, ...mainRouteProps }: DiscoverRoutesProps) => { }; interface CustomDiscoverRoutesProps { - registerExtensionsMap: Map; + profileRegistry: DiscoverProfileRegistry; isDev: boolean; } -const CustomDiscoverRoutes = ({ registerExtensionsMap, ...props }: CustomDiscoverRoutesProps) => { +const CustomDiscoverRoutes = ({ profileRegistry, ...props }: CustomDiscoverRoutesProps) => { const { profile } = useParams<{ profile: string }>(); const registerExtensions = useMemo( - () => registerExtensionsMap.get(profile), - [profile, registerExtensionsMap] + () => profileRegistry.get(profile)?.registerExtensions, + [profile, profileRegistry] ); if (registerExtensions) { @@ -85,7 +86,7 @@ const CustomDiscoverRoutes = ({ registerExtensionsMap, ...props }: CustomDiscove export interface DiscoverRouterProps { services: DiscoverServices; - registerExtensionsMap: Map; + profileRegistry: DiscoverProfileRegistry; history: History; isDev: boolean; } @@ -93,12 +94,12 @@ export interface DiscoverRouterProps { export const DiscoverRouter = ({ services, history, - registerExtensionsMap, + profileRegistry, ...routeProps }: DiscoverRouterProps) => { const registerDefaultExtensions = useMemo( - () => registerExtensionsMap.get('default') ?? [], - [registerExtensionsMap] + () => profileRegistry.get('default')?.registerExtensions ?? [], + [profileRegistry] ); return ( @@ -107,7 +108,7 @@ export const DiscoverRouter = ({ - + diff --git a/src/plugins/discover/public/application/index.tsx b/src/plugins/discover/public/application/index.tsx index 5d8463ce2004e..a4262593ccc5a 100644 --- a/src/plugins/discover/public/application/index.tsx +++ b/src/plugins/discover/public/application/index.tsx @@ -11,16 +11,16 @@ import { i18n } from '@kbn/i18n'; import { toMountPoint, wrapWithTheme } from '@kbn/kibana-react-plugin/public'; import { DiscoverRouter } from './discover_router'; import { DiscoverServices } from '../build_services'; -import type { RegisterExtensions } from '../plugin'; +import type { DiscoverProfileRegistry } from '../extensions/profile_registry'; export interface RenderAppProps { element: HTMLElement; services: DiscoverServices; - registerExtensionsMap: Map; + profileRegistry: DiscoverProfileRegistry; isDev: boolean; } -export const renderApp = ({ element, services, registerExtensionsMap, isDev }: RenderAppProps) => { +export const renderApp = ({ element, services, profileRegistry, isDev }: RenderAppProps) => { const { history: getHistory, capabilities, chrome, data, core } = services; const history = getHistory(); @@ -39,7 +39,7 @@ export const renderApp = ({ element, services, registerExtensionsMap, isDev }: R wrapWithTheme( , diff --git a/src/plugins/discover/public/application/main/discover_main_route.tsx b/src/plugins/discover/public/application/main/discover_main_route.tsx index b7df45d62a852..601899f4b6e32 100644 --- a/src/plugins/discover/public/application/main/discover_main_route.tsx +++ b/src/plugins/discover/public/application/main/discover_main_route.tsx @@ -32,7 +32,7 @@ import { DiscoverExtensionProvider, useDiscoverExtensionRegistry, } from '../../extensions/extension_provider'; -import type { RegisterExtensions } from '../../plugin'; +import type { RegisterExtensions } from '../../extensions/types'; const DiscoverMainAppMemoized = memo(DiscoverMainApp); diff --git a/src/plugins/discover/public/extensions/extension_provider.ts b/src/plugins/discover/public/extensions/extension_provider.ts index 42a689276a9ca..5fcaf1e20cf56 100644 --- a/src/plugins/discover/public/extensions/extension_provider.ts +++ b/src/plugins/discover/public/extensions/extension_provider.ts @@ -10,9 +10,9 @@ import { createContext, useContext, useState } from 'react'; import useObservable from 'react-use/lib/useObservable'; import { isFunction } from 'lodash'; import useEffectOnce from 'react-use/lib/useEffectOnce'; -import type { RegisterExtensions } from '../plugin'; import { createExtensionRegistry, DiscoverExtensionId, DiscoverExtensionRegistry } from '.'; import type { DiscoverStateContainer } from '../application/main/services/discover_state'; +import type { RegisterExtensions } from './types'; const extensionContext = createContext(createExtensionRegistry()); diff --git a/src/plugins/discover/public/extensions/profile_registry.ts b/src/plugins/discover/public/extensions/profile_registry.ts new file mode 100644 index 0000000000000..c1aa440d188e6 --- /dev/null +++ b/src/plugins/discover/public/extensions/profile_registry.ts @@ -0,0 +1,28 @@ +/* + * 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 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 or the Server + * Side Public License, v 1. + */ + +import type { RegisterExtensions } from './types'; + +export interface DiscoverProfile { + name: string; + registerExtensions: RegisterExtensions[]; +} + +export interface DiscoverProfileRegistry { + get(name: string): DiscoverProfile | undefined; + set(profile: DiscoverProfile): void; +} + +export const createProfileRegistry = (): DiscoverProfileRegistry => { + const profiles = new Map(); + + return { + get: (name) => profiles.get(name.toLowerCase()), + set: (profile) => profiles.set(profile.name.toLowerCase(), profile), + }; +}; diff --git a/src/plugins/discover/public/extensions/types.ts b/src/plugins/discover/public/extensions/types.ts new file mode 100644 index 0000000000000..e4ccd1fde8620 --- /dev/null +++ b/src/plugins/discover/public/extensions/types.ts @@ -0,0 +1,19 @@ +/* + * 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 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 or the Server + * Side Public License, v 1. + */ + +import type { DiscoverStateContainer } from '../application/main/services/discover_state'; +import type { DiscoverExtensionRegistry } from './extension_registry'; + +export interface RegisterExtensionsContext { + extensions: DiscoverExtensionRegistry; + stateContainer: DiscoverStateContainer; +} + +export type RegisterExtensions = ( + options: RegisterExtensionsContext +) => void | (() => void) | Promise void)>; diff --git a/src/plugins/discover/public/plugin.tsx b/src/plugins/discover/public/plugin.tsx index 46c5b6808e0d2..bf1a25e7d6a28 100644 --- a/src/plugins/discover/public/plugin.tsx +++ b/src/plugins/discover/public/plugin.tsx @@ -71,8 +71,8 @@ import { DiscoverSingleDocLocatorDefinition, } from './application/doc/locator'; import { DiscoverAppLocator, DiscoverAppLocatorDefinition } from '../common'; -import { DiscoverExtensionRegistry } from './extensions'; -import type { DiscoverStateContainer } from './application/main/services/discover_state'; +import type { RegisterExtensions } from './extensions/types'; +import { createProfileRegistry } from './extensions/profile_registry'; const DocViewerLegacyTable = React.lazy( () => import('./services/doc_views/components/doc_viewer_table/legacy') @@ -80,15 +80,6 @@ const DocViewerLegacyTable = React.lazy( const DocViewerTable = React.lazy(() => import('./services/doc_views/components/doc_viewer_table')); const SourceViewer = React.lazy(() => import('./services/doc_views/components/doc_viewer_source')); -export interface RegisterExtensionsContext { - extensions: DiscoverExtensionRegistry; - stateContainer: DiscoverStateContainer; -} - -export type RegisterExtensions = ( - options: RegisterExtensionsContext -) => void | (() => void) | Promise void)>; - /** * @public */ @@ -166,7 +157,7 @@ export interface DiscoverStart { * ``` */ readonly locator: undefined | DiscoverAppLocator; - readonly registerExtensions: (profile: string, register: RegisterExtensions) => void; + readonly registerExtensions: (profileName: string, register: RegisterExtensions) => void; } /** @@ -221,7 +212,7 @@ export class DiscoverPlugin private appStateUpdater = new BehaviorSubject(() => ({})); private docViewsRegistry: DocViewsRegistry | null = null; private stopUrlTracking: (() => void) | undefined = undefined; - private registerExtensionsMap = new Map(); + private profileRegistry = createProfileRegistry(); private locator?: DiscoverAppLocator; private contextLocator?: DiscoverContextAppLocator; private singleDocLocator?: DiscoverSingleDocLocator; @@ -346,7 +337,7 @@ export class DiscoverPlugin const unmount = renderApp({ element: params.element, services, - registerExtensionsMap: this.registerExtensionsMap, + profileRegistry: this.profileRegistry, isDev, }); return () => { @@ -409,10 +400,13 @@ export class DiscoverPlugin return { locator: this.locator, - registerExtensions: (profile: string, register: RegisterExtensions) => { - const registerExtensions = this.registerExtensionsMap.get(profile) || []; - registerExtensions.push(register); - this.registerExtensionsMap.set(profile, registerExtensions); + registerExtensions: (profileName: string, register: RegisterExtensions) => { + const profile = this.profileRegistry.get(profileName) ?? { + name: profileName, + registerExtensions: [], + }; + profile.registerExtensions.push(register); + this.profileRegistry.set(profile); }, }; } From a1ea3d96bd21c152548129a86313d0c790300d1b Mon Sep 17 00:00:00 2001 From: Davis McPhee Date: Mon, 1 May 2023 21:14:25 -0300 Subject: [PATCH 049/122] Adding profile aware locators --- src/plugins/discover/common/locator.ts | 13 +++++- .../application/context/services/locator.ts | 9 +++- .../public/application/doc/locator.ts | 9 +++- .../extensions/profile_aware_locator.ts | 41 +++++++++++++++++++ src/plugins/discover/public/plugin.tsx | 24 ++++++++--- 5 files changed, 87 insertions(+), 9 deletions(-) create mode 100644 src/plugins/discover/public/extensions/profile_aware_locator.ts diff --git a/src/plugins/discover/common/locator.ts b/src/plugins/discover/common/locator.ts index 77e501dbe40df..04347d93bbd6b 100644 --- a/src/plugins/discover/common/locator.ts +++ b/src/plugins/discover/common/locator.ts @@ -99,6 +99,10 @@ export interface DiscoverAppLocatorParams extends SerializableRecord { * Used when navigating to particular alert results */ isAlertResults?: boolean; + /** + * The Discover profile to use + */ + profile?: string; } export type DiscoverAppLocator = LocatorPublic; @@ -141,6 +145,7 @@ export class DiscoverAppLocatorDefinition implements LocatorDefinition('_g', queryState, { useHash }, path); path = this.deps.setStateToKbnUrl('_a', appState, { useHash }, path); diff --git a/src/plugins/discover/public/application/context/services/locator.ts b/src/plugins/discover/public/application/context/services/locator.ts index 29bb01e1af58a..c672718447ccd 100644 --- a/src/plugins/discover/public/application/context/services/locator.ts +++ b/src/plugins/discover/public/application/context/services/locator.ts @@ -20,6 +20,7 @@ export interface DiscoverContextAppLocatorParams extends SerializableRecord { columns?: string[]; filters?: Filter[]; referrer: string; // discover main view url + profile?: string; } export type DiscoverContextAppLocator = LocatorPublic; @@ -62,7 +63,13 @@ export class DiscoverContextAppLocatorDefinition dataViewId = index; } - let path = `#/context/${dataViewId}/${rowId}`; + let path = '#/'; + + if (params.profile) { + path = `${path}p/${params.profile}/`; + } + + path = `${path}context/${dataViewId}/${rowId}`; path = setStateToKbnUrl('_g', queryState, { useHash }, path); path = setStateToKbnUrl('_a', appState, { useHash }, path); diff --git a/src/plugins/discover/public/application/doc/locator.ts b/src/plugins/discover/public/application/doc/locator.ts index f3ee0c2942070..35f2bd52a6c25 100644 --- a/src/plugins/discover/public/application/doc/locator.ts +++ b/src/plugins/discover/public/application/doc/locator.ts @@ -17,6 +17,7 @@ export interface DiscoverSingleDocLocatorParams extends SerializableRecord { rowId: string; rowIndex: string; referrer: string; // discover main view url + profile?: string; } export type DiscoverSingleDocLocator = LocatorPublic; @@ -45,7 +46,13 @@ export class DiscoverSingleDocLocatorDefinition dataViewId = index; } - const path = `#/doc/${dataViewId}/${rowIndex}?id=${rowId}`; + let path = '#/'; + + if (params.profile) { + path = `${path}p/${params.profile}/`; + } + + path = `${path}doc/${dataViewId}/${rowIndex}?id=${rowId}`; return { app: 'discover', diff --git a/src/plugins/discover/public/extensions/profile_aware_locator.ts b/src/plugins/discover/public/extensions/profile_aware_locator.ts new file mode 100644 index 0000000000000..abf4a6436747d --- /dev/null +++ b/src/plugins/discover/public/extensions/profile_aware_locator.ts @@ -0,0 +1,41 @@ +/* + * 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 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 or the Server + * Side Public License, v 1. + */ + +import { LocatorDefinition } from '@kbn/share-plugin/public'; +import { matchPath } from 'react-router-dom'; +import { getHistory } from '../kibana_services'; + +export class ProfileAwareLocatorDefinition + implements LocatorDefinition +{ + public readonly id: string; + + constructor(private readonly definition: LocatorDefinition) { + this.id = definition.id; + } + + getLocation(params: T) { + if (params.profile) { + return this.definition.getLocation(params); + } + + const history = getHistory(); + const match = matchPath<{ profile: string }>(history.location.pathname, { + path: '/p/:profile', + }); + + if (match?.params.profile) { + params = { + ...params, + profile: match.params.profile, + }; + } + + return this.definition.getLocation(params); + } +} diff --git a/src/plugins/discover/public/plugin.tsx b/src/plugins/discover/public/plugin.tsx index bf1a25e7d6a28..662115632be70 100644 --- a/src/plugins/discover/public/plugin.tsx +++ b/src/plugins/discover/public/plugin.tsx @@ -73,6 +73,7 @@ import { import { DiscoverAppLocator, DiscoverAppLocatorDefinition } from '../common'; import type { RegisterExtensions } from './extensions/types'; import { createProfileRegistry } from './extensions/profile_registry'; +import { ProfileAwareLocatorDefinition } from './extensions/profile_aware_locator'; const DocViewerLegacyTable = React.lazy( () => import('./services/doc_views/components/doc_viewer_table/legacy') @@ -214,24 +215,35 @@ export class DiscoverPlugin private stopUrlTracking: (() => void) | undefined = undefined; private profileRegistry = createProfileRegistry(); private locator?: DiscoverAppLocator; + private profileAwareLocator?: DiscoverAppLocator; private contextLocator?: DiscoverContextAppLocator; private singleDocLocator?: DiscoverSingleDocLocator; setup(core: CoreSetup, plugins: DiscoverSetupPlugins) { const baseUrl = core.http.basePath.prepend('/app/discover'); const isDev = this.initializerContext.env.mode.dev; + if (plugins.share) { const useHash = core.uiSettings.get('state:storeInSessionStorage'); - this.locator = plugins.share.url.locators.create( - new DiscoverAppLocatorDefinition({ useHash, setStateToKbnUrl }) - ); + const appLocatorDefinition = new DiscoverAppLocatorDefinition({ useHash, setStateToKbnUrl }); + const contextLocatorDefinition = new DiscoverContextAppLocatorDefinition({ useHash }); + const singleDocLocatorDefinition = new DiscoverSingleDocLocatorDefinition(); + // Create profile-aware locators for internal use + this.profileAwareLocator = plugins.share.url.locators.create( + new ProfileAwareLocatorDefinition(appLocatorDefinition) + ); this.contextLocator = plugins.share.url.locators.create( - new DiscoverContextAppLocatorDefinition({ useHash }) + new ProfileAwareLocatorDefinition(contextLocatorDefinition) ); this.singleDocLocator = plugins.share.url.locators.create( - new DiscoverSingleDocLocatorDefinition() + new ProfileAwareLocatorDefinition(singleDocLocatorDefinition) ); + + // Recreate/re-register locators for external use without profile-awareness + this.locator = plugins.share.url.locators.create(appLocatorDefinition); + plugins.share.url.locators.create(contextLocatorDefinition); + plugins.share.url.locators.create(singleDocLocatorDefinition); } this.docViewsRegistry = new DocViewsRegistry(); @@ -322,7 +334,7 @@ export class DiscoverPlugin coreStart, discoverStartPlugins, this.initializerContext, - this.locator!, + this.profileAwareLocator!, this.contextLocator!, this.singleDocLocator! ); From 3c457d9388baacdcea7fcb39cde825d082dca55b Mon Sep 17 00:00:00 2001 From: Davis McPhee Date: Mon, 1 May 2023 22:21:42 -0300 Subject: [PATCH 050/122] Finish adding profile-aware locators --- .../extensions/profile_aware_locator.ts | 71 ++++++++++++++++--- src/plugins/discover/public/plugin.tsx | 27 ++++--- 2 files changed, 73 insertions(+), 25 deletions(-) diff --git a/src/plugins/discover/public/extensions/profile_aware_locator.ts b/src/plugins/discover/public/extensions/profile_aware_locator.ts index abf4a6436747d..cad6bd8eee56a 100644 --- a/src/plugins/discover/public/extensions/profile_aware_locator.ts +++ b/src/plugins/discover/public/extensions/profile_aware_locator.ts @@ -6,22 +6,33 @@ * Side Public License, v 1. */ -import { LocatorDefinition } from '@kbn/share-plugin/public'; +import type { SavedObjectReference } from '@kbn/core-saved-objects-api-server'; +import type { + MigrateFunctionsObject, + GetMigrationFunctionObjectFn, +} from '@kbn/kibana-utils-plugin/common'; +import type { + LocatorGetUrlParams, + FormatSearchParamsOptions, + LocatorNavigationParams, +} from '@kbn/share-plugin/common/url_service'; +import type { LocatorPublic } from '@kbn/share-plugin/public'; +import type { DependencyList } from 'react'; import { matchPath } from 'react-router-dom'; import { getHistory } from '../kibana_services'; -export class ProfileAwareLocatorDefinition - implements LocatorDefinition -{ - public readonly id: string; +export class ProfileAwareLocator implements LocatorPublic { + id: string; + migrations: MigrateFunctionsObject | GetMigrationFunctionObjectFn; - constructor(private readonly definition: LocatorDefinition) { - this.id = definition.id; + constructor(private readonly locator: LocatorPublic) { + this.id = locator.id; + this.migrations = locator.migrations; } - getLocation(params: T) { + private injectProfile(params: T) { if (params.profile) { - return this.definition.getLocation(params); + return params; } const history = getHistory(); @@ -36,6 +47,46 @@ export class ProfileAwareLocatorDefinition }; } - return this.definition.getLocation(params); + return params; + } + + getLocation(params: T) { + return this.locator.getLocation(this.injectProfile(params)); + } + + getUrl(params: T, getUrlParams?: LocatorGetUrlParams) { + return this.locator.getUrl(this.injectProfile(params), getUrlParams); + } + + getRedirectUrl(params: T, options?: FormatSearchParamsOptions) { + return this.locator.getRedirectUrl(this.injectProfile(params), options); + } + + navigate(params: T, navigationParams?: LocatorNavigationParams) { + return this.locator.navigate(this.injectProfile(params), navigationParams); + } + + navigateSync(params: T, navigationParams?: LocatorNavigationParams) { + return this.locator.navigateSync(this.injectProfile(params), navigationParams); + } + + useUrl( + params: T, + getUrlParams?: LocatorGetUrlParams | undefined, + deps?: DependencyList | undefined + ) { + return this.locator.useUrl(this.injectProfile(params), getUrlParams, deps); + } + + telemetry(state: T, stats: Record) { + return this.locator.telemetry(this.injectProfile(state), stats); + } + + inject(state: T, references: SavedObjectReference[]) { + return this.locator.inject(this.injectProfile(state), references); + } + + extract(state: T) { + return this.locator.extract(this.injectProfile(state)); } } diff --git a/src/plugins/discover/public/plugin.tsx b/src/plugins/discover/public/plugin.tsx index 662115632be70..150a378c2cf2c 100644 --- a/src/plugins/discover/public/plugin.tsx +++ b/src/plugins/discover/public/plugin.tsx @@ -73,7 +73,7 @@ import { import { DiscoverAppLocator, DiscoverAppLocatorDefinition } from '../common'; import type { RegisterExtensions } from './extensions/types'; import { createProfileRegistry } from './extensions/profile_registry'; -import { ProfileAwareLocatorDefinition } from './extensions/profile_aware_locator'; +import { ProfileAwareLocator } from './extensions/profile_aware_locator'; const DocViewerLegacyTable = React.lazy( () => import('./services/doc_views/components/doc_viewer_table/legacy') @@ -225,25 +225,22 @@ export class DiscoverPlugin if (plugins.share) { const useHash = core.uiSettings.get('state:storeInSessionStorage'); - const appLocatorDefinition = new DiscoverAppLocatorDefinition({ useHash, setStateToKbnUrl }); - const contextLocatorDefinition = new DiscoverContextAppLocatorDefinition({ useHash }); - const singleDocLocatorDefinition = new DiscoverSingleDocLocatorDefinition(); - // Create profile-aware locators for internal use - this.profileAwareLocator = plugins.share.url.locators.create( - new ProfileAwareLocatorDefinition(appLocatorDefinition) + // Create locators for external use without profile-awareness + this.locator = plugins.share.url.locators.create( + new DiscoverAppLocatorDefinition({ useHash, setStateToKbnUrl }) ); - this.contextLocator = plugins.share.url.locators.create( - new ProfileAwareLocatorDefinition(contextLocatorDefinition) + const contextLocator = plugins.share.url.locators.create( + new DiscoverContextAppLocatorDefinition({ useHash }) ); - this.singleDocLocator = plugins.share.url.locators.create( - new ProfileAwareLocatorDefinition(singleDocLocatorDefinition) + const singleDocLocator = plugins.share.url.locators.create( + new DiscoverSingleDocLocatorDefinition() ); - // Recreate/re-register locators for external use without profile-awareness - this.locator = plugins.share.url.locators.create(appLocatorDefinition); - plugins.share.url.locators.create(contextLocatorDefinition); - plugins.share.url.locators.create(singleDocLocatorDefinition); + // Create profile-aware locators for internal use + this.profileAwareLocator = new ProfileAwareLocator(this.locator); + this.contextLocator = new ProfileAwareLocator(contextLocator); + this.singleDocLocator = new ProfileAwareLocator(singleDocLocator); } this.docViewsRegistry = new DocViewsRegistry(); From 8000a776f287a9059eff444da2b04dd172a9068d Mon Sep 17 00:00:00 2001 From: Davis McPhee Date: Tue, 2 May 2023 00:16:38 -0300 Subject: [PATCH 051/122] Replace history push/replace with locator calls --- examples/discover_extender/public/plugin.tsx | 6 +++--- src/plugins/discover/common/locator.ts | 15 +++++++++++++-- .../application/context/services/locator.ts | 10 ++++++++-- .../layout/__stories__/get_layout_props.ts | 1 - .../main/components/layout/discover_layout.tsx | 8 +------- .../main/components/top_nav/discover_topnav.tsx | 11 +++-------- .../components/top_nav/get_top_nav_links.tsx | 5 +---- .../main/components/top_nav/on_save_search.tsx | 7 +------ .../application/main/discover_main_app.tsx | 17 +++-------------- .../public/application/main/hooks/use_url.ts | 7 ++++++- .../application/main/services/discover_state.ts | 4 +++- .../application/view_alert/view_alert_route.tsx | 4 +--- .../public/components/common/error_alert.tsx | 6 +++--- 13 files changed, 46 insertions(+), 55 deletions(-) diff --git a/examples/discover_extender/public/plugin.tsx b/examples/discover_extender/public/plugin.tsx index 6bfd27707fc46..ea8256a8bbc27 100644 --- a/examples/discover_extender/public/plugin.tsx +++ b/examples/discover_extender/public/plugin.tsx @@ -29,7 +29,7 @@ export class DiscoverExtenderPlugin implements Plugin { start(core: CoreStart, plugins: DiscoverExtenderStartPlugins) { const { discover } = plugins; - discover.registerExtensions('default', ({ extensions, stateContainer }) => { + discover.registerExtensions('default', ({ extensions }) => { extensions.set({ id: 'top_nav', defaultMenu: { @@ -48,7 +48,7 @@ export class DiscoverExtenderPlugin implements Plugin { label: 'Logs explorer', iconType: 'logsApp', run: () => { - // stateContainer.appState.update({ profile: 'extender' }); + discover.locator?.navigate({ profile: 'extender' }); }, }, order: 700, @@ -146,7 +146,7 @@ export class DiscoverExtenderPlugin implements Plugin { label: 'Document explorer', iconType: 'discoverApp', run: () => { - // stateContainer.appState.update({ profile: 'default' }); + discover.locator?.navigate({}); }, }, order: 300, diff --git a/src/plugins/discover/common/locator.ts b/src/plugins/discover/common/locator.ts index 04347d93bbd6b..48dba685d5fca 100644 --- a/src/plugins/discover/common/locator.ts +++ b/src/plugins/discover/common/locator.ts @@ -190,8 +190,19 @@ export class DiscoverAppLocatorDefinition implements LocatorDefinition('_g', queryState, { useHash }, path); - path = this.deps.setStateToKbnUrl('_a', appState, { useHash }, path); + + if (Object.keys(queryState).length) { + path = this.deps.setStateToKbnUrl( + '_g', + queryState, + { useHash }, + path + ); + } + + if (Object.keys(appState).length) { + path = this.deps.setStateToKbnUrl('_a', appState, { useHash }, path); + } if (searchSessionId) { path = `${path}&searchSessionId=${searchSessionId}`; diff --git a/src/plugins/discover/public/application/context/services/locator.ts b/src/plugins/discover/public/application/context/services/locator.ts index c672718447ccd..1e2b8b7a7b746 100644 --- a/src/plugins/discover/public/application/context/services/locator.ts +++ b/src/plugins/discover/public/application/context/services/locator.ts @@ -70,8 +70,14 @@ export class DiscoverContextAppLocatorDefinition } path = `${path}context/${dataViewId}/${rowId}`; - path = setStateToKbnUrl('_g', queryState, { useHash }, path); - path = setStateToKbnUrl('_a', appState, { useHash }, path); + + if (Object.keys(queryState).length) { + path = setStateToKbnUrl('_g', queryState, { useHash }, path); + } + + if (Object.keys(appState).length) { + path = setStateToKbnUrl('_a', appState, { useHash }, path); + } return { app: 'discover', diff --git a/src/plugins/discover/public/application/main/components/layout/__stories__/get_layout_props.ts b/src/plugins/discover/public/application/main/components/layout/__stories__/get_layout_props.ts index 0d41251ce4542..e63829c9bd098 100644 --- a/src/plugins/discover/public/application/main/components/layout/__stories__/get_layout_props.ts +++ b/src/plugins/discover/public/application/main/components/layout/__stories__/get_layout_props.ts @@ -84,7 +84,6 @@ const getCommonProps = () => { const savedSearchMock = {} as unknown as SavedSearch; return { inspectorAdapters: { requests: new RequestAdapter() }, - navigateTo: action('navigate to somewhere nice'), onChangeDataView: action('change the data view'), onUpdateQuery: action('update the query'), resetSavedSearch: action('reset the saved search the query'), diff --git a/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx b/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx index 776f2934be300..30f852f99c753 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx @@ -56,16 +56,11 @@ const SidebarMemoized = React.memo(DiscoverSidebarResponsive); const TopNavMemoized = React.memo(DiscoverTopNav); export interface DiscoverLayoutProps { - navigateTo: (url: string) => void; stateContainer: DiscoverStateContainer; persistDataView: (dataView: DataView) => Promise; } -export function DiscoverLayout({ - navigateTo, - stateContainer, - persistDataView, -}: DiscoverLayoutProps) { +export function DiscoverLayout({ stateContainer, persistDataView }: DiscoverLayoutProps) { const { trackUiMetric, capabilities, @@ -275,7 +270,6 @@ export function DiscoverLayout({ & { +export interface DiscoverTopNavProps { onOpenInspector: () => void; query?: Query | AggregateQuery; savedQuery?: string; @@ -32,7 +31,7 @@ export type DiscoverTopNavProps = Pick & { textBasedLanguageModeErrors?: Error; onFieldEdited: () => Promise; persistDataView: (dataView: DataView) => Promise; -}; +} export const DiscoverTopNav = ({ onOpenInspector, @@ -40,7 +39,6 @@ export const DiscoverTopNav = ({ savedQuery, stateContainer, updateQuery, - navigateTo, isPlainRecord, textBasedLanguageModeErrors, onFieldEdited, @@ -114,7 +112,6 @@ export const DiscoverTopNav = ({ () => getTopNavLinks({ dataView, - navigateTo, services, state: stateContainer, onOpenInspector, @@ -127,7 +124,6 @@ export const DiscoverTopNav = ({ adHocDataViews, dataView, isPlainRecord, - navigateTo, onOpenInspector, persistDataView, services, @@ -192,13 +188,12 @@ export const DiscoverTopNav = ({ onSaveSearch({ savedSearch: stateContainer.savedSearchState.getState(), services, - navigateTo, state: stateContainer, onClose: onCancel, onSaveCb: onSave, }); }, - [navigateTo, services, stateContainer] + [services, stateContainer] ); const searchBarExtension = useDiscoverExtension('search_bar'); diff --git a/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.tsx b/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.tsx index 2aa21fb02d1e8..31e4755a61343 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.tsx @@ -24,7 +24,6 @@ import type { TopNavExtension } from '../../../../extensions'; */ export const getTopNavLinks = ({ dataView, - navigateTo, services, state, onOpenInspector, @@ -34,7 +33,6 @@ export const getTopNavLinks = ({ topNavExtension, }: { dataView: DataView; - navigateTo: (url: string) => void; services: DiscoverServices; state: DiscoverStateContainer; onOpenInspector: () => void; @@ -90,7 +88,7 @@ export const getTopNavLinks = ({ description: i18n.translate('discover.localMenu.newSearchDescription', { defaultMessage: 'New Search', }), - run: () => navigateTo('/'), + run: () => services.locator.navigate({}), testId: 'discoverNewButton', }; @@ -109,7 +107,6 @@ export const getTopNavLinks = ({ onSaveSearch({ savedSearch: state.savedSearchState.getState(), services, - navigateTo, state, onClose: () => { anchorElement?.focus(); diff --git a/src/plugins/discover/public/application/main/components/top_nav/on_save_search.tsx b/src/plugins/discover/public/application/main/components/top_nav/on_save_search.tsx index 838575184f7d8..d63f9c9ef6d05 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/on_save_search.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/on_save_search.tsx @@ -17,14 +17,12 @@ import { DiscoverStateContainer } from '../../services/discover_state'; import { DOC_TABLE_LEGACY } from '../../../../../common'; async function saveDataSource({ - navigateTo, savedSearch, saveOptions, services, state, navigateOrReloadSavedSearch, }: { - navigateTo: (url: string) => void; savedSearch: SavedSearch; saveOptions: SaveSavedSearchOptions; services: DiscoverServices; @@ -45,7 +43,7 @@ async function saveDataSource({ }); if (navigateOrReloadSavedSearch) { if (id !== prevSavedSearchId) { - navigateTo(`/view/${encodeURIComponent(id)}`); + services.locator.navigate({ savedSearchId: id }); } else { // Update defaults so that "reload saved query" functions correctly state.actions.undoSavedSearchChanges(); @@ -78,14 +76,12 @@ async function saveDataSource({ } export async function onSaveSearch({ - navigateTo, savedSearch, services, state, onClose, onSaveCb, }: { - navigateTo: (path: string) => void; savedSearch: SavedSearch; services: DiscoverServices; state: DiscoverStateContainer; @@ -138,7 +134,6 @@ export async function onSaveSearch({ const response = await saveDataSource({ saveOptions, services, - navigateTo, savedSearch, state, navigateOrReloadSavedSearch, diff --git a/src/plugins/discover/public/application/main/discover_main_app.tsx b/src/plugins/discover/public/application/main/discover_main_app.tsx index f4dca7629a8e8..2f98c571cbfad 100644 --- a/src/plugins/discover/public/application/main/discover_main_app.tsx +++ b/src/plugins/discover/public/application/main/discover_main_app.tsx @@ -5,9 +5,9 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import React, { useCallback, useEffect } from 'react'; + +import React, { useEffect } from 'react'; import { RootDragDropProvider } from '@kbn/dom-drag-drop'; -import { useHistory } from 'react-router-dom'; import { useUrlTracking } from './hooks/use_url_tracking'; import { useSearchSession } from './hooks/use_search_session'; import { DiscoverStateContainer } from './services/discover_state'; @@ -34,13 +34,6 @@ export function DiscoverMainApp(props: DiscoverMainProps) { const savedSearch = useSavedSearchInitial(); const services = useDiscoverServices(); const { chrome, docLinks, data, spaces, history } = services; - const usedHistory = useHistory(); - const navigateTo = useCallback( - (path: string) => { - usedHistory.push(path); - }, - [usedHistory] - ); useUrlTracking(stateContainer.savedSearchState); @@ -97,11 +90,7 @@ export function DiscoverMainApp(props: DiscoverMainProps) { return ( - + ); } diff --git a/src/plugins/discover/public/application/main/hooks/use_url.ts b/src/plugins/discover/public/application/main/hooks/use_url.ts index f8549f1d9db71..cb38e2a7a6360 100644 --- a/src/plugins/discover/public/application/main/hooks/use_url.ts +++ b/src/plugins/discover/public/application/main/hooks/use_url.ts @@ -5,8 +5,11 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ + import { useEffect } from 'react'; import { History } from 'history'; +import { matchPath } from 'react-router-dom'; + export function useUrl({ history, savedSearchId, @@ -24,7 +27,9 @@ export function useUrl({ // which could be set through pressing "New" button in top nav or go to "Discover" plugin from the sidebar // to reload the page in a right way const unlistenHistoryBasePath = history.listen(async ({ pathname, search, hash }) => { - if (!search && !hash && pathname === '/' && !savedSearchId) { + const isProfileRoot = Boolean(matchPath(pathname, { path: '/p/:profile', exact: true })); + + if ((pathname === '/' || isProfileRoot) && !search && !hash && !savedSearchId) { onNewUrl(); } }); diff --git a/src/plugins/discover/public/application/main/services/discover_state.ts b/src/plugins/discover/public/application/main/services/discover_state.ts index bddd8c38ca68e..d10e4c59d9e8a 100644 --- a/src/plugins/discover/public/application/main/services/discover_state.ts +++ b/src/plugins/discover/public/application/main/services/discover_state.ts @@ -319,7 +319,9 @@ export function getDiscoverStateContainer({ await undoSavedSearchChanges(); } else { addLog('[discoverState] onOpenSavedSearch open view URL'); - history.push(`/view/${encodeURIComponent(newSavedSearchId)}`); + services.locator.navigate({ + savedSearchId: newSavedSearchId, + }); } }; diff --git a/src/plugins/discover/public/application/view_alert/view_alert_route.tsx b/src/plugins/discover/public/application/view_alert/view_alert_route.tsx index 84e65e8d672d6..48b7144b9115d 100644 --- a/src/plugins/discover/public/application/view_alert/view_alert_route.tsx +++ b/src/plugins/discover/public/application/view_alert/view_alert_route.tsx @@ -13,8 +13,6 @@ import { useDiscoverServices } from '../../hooks/use_discover_services'; import { displayPossibleDocsDiffInfoAlert } from '../main/hooks/use_alert_results_toast'; import { getAlertUtils, QueryParams } from './view_alert_utils'; -const DISCOVER_MAIN_ROUTE = '/'; - type NonNullableEntry = { [K in keyof T]: NonNullable }; const isActualAlert = (queryParams: QueryParams): queryParams is NonNullableEntry => { @@ -58,7 +56,7 @@ export function ViewAlertRoute() { locator.navigate(state); }; - const navigateToDiscoverRoot = () => history.push(DISCOVER_MAIN_ROUTE); + const navigateToDiscoverRoot = () => locator.navigate({}); fetchAlert(id) .then(fetchSearchSource) diff --git a/src/plugins/discover/public/components/common/error_alert.tsx b/src/plugins/discover/public/components/common/error_alert.tsx index 43b9ca6a31272..27eceb7e5ea53 100644 --- a/src/plugins/discover/public/components/common/error_alert.tsx +++ b/src/plugins/discover/public/components/common/error_alert.tsx @@ -10,13 +10,13 @@ import React from 'react'; import { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { useHistory } from 'react-router-dom'; +import { useDiscoverServices } from '../../hooks/use_discover_services'; export const DiscoverError = ({ error }: { error: Error }) => { - const history = useHistory(); + const { locator } = useDiscoverServices(); const goToMain = () => { - history.push('/'); + locator.navigate({}); }; return ( From 96e575966dc21e52f1f613943ee888fb9129409d Mon Sep 17 00:00:00 2001 From: Davis McPhee Date: Tue, 2 May 2023 21:56:04 -0300 Subject: [PATCH 052/122] Added breadcrumb support for profiles --- .../application/context/context_app.tsx | 4 +- .../public/application/doc/components/doc.tsx | 7 +-- .../application/main/discover_main_app.tsx | 4 +- .../application/main/discover_main_route.tsx | 5 ++- .../discover/public/utils/breadcrumbs.ts | 45 +++++++++++++++---- 5 files changed, 47 insertions(+), 18 deletions(-) diff --git a/src/plugins/discover/public/application/context/context_app.tsx b/src/plugins/discover/public/application/context/context_app.tsx index efd8992e48ca8..b3d7f299a0a21 100644 --- a/src/plugins/discover/public/application/context/context_app.tsx +++ b/src/plugins/discover/public/application/context/context_app.tsx @@ -74,14 +74,14 @@ export const ContextApp = ({ dataView, anchorId, referrer }: ContextAppProps) => useEffect(() => { services.chrome.setBreadcrumbs([ - ...getRootBreadcrumbs(referrer), + ...getRootBreadcrumbs({ breadcrumb: referrer, services }), { text: i18n.translate('discover.context.breadcrumb', { defaultMessage: 'Surrounding documents', }), }, ]); - }, [locator, referrer, services.chrome]); + }, [locator, referrer, services]); useExecutionContext(core.executionContext, { type: 'application', diff --git a/src/plugins/discover/public/application/doc/components/doc.tsx b/src/plugins/discover/public/application/doc/components/doc.tsx index f507d2794184a..2bf0e39113a4e 100644 --- a/src/plugins/discover/public/application/doc/components/doc.tsx +++ b/src/plugins/discover/public/application/doc/components/doc.tsx @@ -49,7 +49,8 @@ export interface DocProps { export function Doc(props: DocProps) { const { dataView } = props; const [reqState, hit] = useEsDocSearch(props); - const { locator, chrome, docLinks } = useDiscoverServices(); + const services = useDiscoverServices(); + const { locator, chrome, docLinks } = services; const indexExistsLink = docLinks.links.apis.indexExists; const singleDocTitle = useRef(null); @@ -59,10 +60,10 @@ export function Doc(props: DocProps) { useEffect(() => { chrome.setBreadcrumbs([ - ...getRootBreadcrumbs(props.referrer), + ...getRootBreadcrumbs({ breadcrumb: props.referrer, services }), { text: `${props.index}#${props.id}` }, ]); - }, [chrome, props.referrer, props.index, props.id, dataView, locator]); + }, [chrome, props.referrer, props.index, props.id, dataView, locator, services]); return ( diff --git a/src/plugins/discover/public/application/main/discover_main_app.tsx b/src/plugins/discover/public/application/main/discover_main_app.tsx index 2f98c571cbfad..bd62345e06f16 100644 --- a/src/plugins/discover/public/application/main/discover_main_app.tsx +++ b/src/plugins/discover/public/application/main/discover_main_app.tsx @@ -72,8 +72,8 @@ export function DiscoverMainApp(props: DiscoverMainProps) { useEffect(() => { const pageTitleSuffix = savedSearch.id && savedSearch.title ? `: ${savedSearch.title}` : ''; chrome.docTitle.change(`Discover${pageTitleSuffix}`); - setBreadcrumbsTitle(savedSearch.title, chrome); - }, [savedSearch.id, savedSearch.title, chrome, data]); + setBreadcrumbsTitle({ title: savedSearch.title, services }); + }, [savedSearch.id, savedSearch.title, chrome, data, services]); useEffect(() => { addHelpMenuToAppChrome(chrome, docLinks); diff --git a/src/plugins/discover/public/application/main/discover_main_route.tsx b/src/plugins/discover/public/application/main/discover_main_route.tsx index 601899f4b6e32..64ecfa006e553 100644 --- a/src/plugins/discover/public/application/main/discover_main_route.tsx +++ b/src/plugins/discover/public/application/main/discover_main_route.tsx @@ -151,8 +151,8 @@ export function DiscoverMainRoute({ registerExtensions, isDev }: MainRouteProps) chrome.setBreadcrumbs( currentSavedSearch && currentSavedSearch.title - ? getSavedSearchBreadcrumbs(currentSavedSearch.title) - : getRootBreadcrumbs() + ? getSavedSearchBreadcrumbs({ id: currentSavedSearch.title, services }) + : getRootBreadcrumbs({ services }) ); setLoading(false); @@ -186,6 +186,7 @@ export function DiscoverMainRoute({ registerExtensions, isDev }: MainRouteProps) id, historyLocationState?.dataViewSpec, chrome, + services, history, core.application.navigateToApp, core.theme, diff --git a/src/plugins/discover/public/utils/breadcrumbs.ts b/src/plugins/discover/public/utils/breadcrumbs.ts index bf482587efe30..b93330e68de7f 100644 --- a/src/plugins/discover/public/utils/breadcrumbs.ts +++ b/src/plugins/discover/public/utils/breadcrumbs.ts @@ -6,23 +6,44 @@ * Side Public License, v 1. */ -import { ChromeStart } from '@kbn/core/public'; import { i18n } from '@kbn/i18n'; +import { matchPath } from 'react-router-dom'; +import type { DiscoverServices } from '../build_services'; -export function getRootBreadcrumbs(breadcrumb?: string) { +const getRootPath = ({ history }: DiscoverServices) => { + const match = matchPath<{ profile?: string }>(history().location.pathname, { + path: '/p/:profile', + }); + + return match ? `#/p/${match.params.profile}/` : '#/'; +}; + +export function getRootBreadcrumbs({ + breadcrumb, + services, +}: { + breadcrumb?: string; + services: DiscoverServices; +}) { return [ { text: i18n.translate('discover.rootBreadcrumb', { defaultMessage: 'Discover', }), - href: breadcrumb || '#/', + href: breadcrumb || getRootPath(services), }, ]; } -export function getSavedSearchBreadcrumbs(id: string) { +export function getSavedSearchBreadcrumbs({ + id, + services, +}: { + id: string; + services: DiscoverServices; +}) { return [ - ...getRootBreadcrumbs(), + ...getRootBreadcrumbs({ services }), { text: id, }, @@ -33,21 +54,27 @@ export function getSavedSearchBreadcrumbs(id: string) { * Helper function to set the Discover's breadcrumb * if there's an active savedSearch, its title is appended */ -export function setBreadcrumbsTitle(title: string | undefined, chrome: ChromeStart) { +export function setBreadcrumbsTitle({ + title, + services, +}: { + title: string | undefined; + services: DiscoverServices; +}) { const discoverBreadcrumbsTitle = i18n.translate('discover.discoverBreadcrumbTitle', { defaultMessage: 'Discover', }); if (title) { - chrome.setBreadcrumbs([ + services.chrome.setBreadcrumbs([ { text: discoverBreadcrumbsTitle, - href: '#/', + href: getRootPath(services), }, { text: title }, ]); } else { - chrome.setBreadcrumbs([ + services.chrome.setBreadcrumbs([ { text: discoverBreadcrumbsTitle, }, From 4bd02889db8593396c27c1170e93c6a87e07dcb4 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 3 May 2023 01:19:16 +0000 Subject: [PATCH 053/122] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- src/plugins/discover/tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/discover/tsconfig.json b/src/plugins/discover/tsconfig.json index ffc0e2f316b48..29b29b2a436b0 100644 --- a/src/plugins/discover/tsconfig.json +++ b/src/plugins/discover/tsconfig.json @@ -52,6 +52,7 @@ "@kbn/storybook", "@kbn/shared-ux-router", "@kbn/dom-drag-drop", + "@kbn/core-saved-objects-api-server", ], "exclude": [ "target/**/*", From e84970ae3719623d83872c6fd2d85e547bdc18e2 Mon Sep 17 00:00:00 2001 From: Davis McPhee Date: Tue, 2 May 2023 22:50:48 -0300 Subject: [PATCH 054/122] Change from ExtensionRegistry to CustomizationService --- examples/discover_extender/public/plugin.tsx | 18 ++-- .../public/application/discover_router.tsx | 24 +++--- .../discover/public/application/index.tsx | 2 +- .../components/layout/discover_documents.tsx | 10 +-- .../components/sidebar/discover_field.tsx | 14 ++-- .../components/top_nav/discover_topnav.tsx | 16 ++-- .../components/top_nav/get_top_nav_links.tsx | 10 +-- .../application/main/discover_main_route.tsx | 23 +++--- .../customizations/customization_provider.ts | 63 ++++++++++++++ .../customizations/customization_service.ts | 82 +++++++++++++++++++ .../data_grid_customization.ts} | 2 +- .../field_popover_customization.ts} | 2 +- .../customization_types}/index.ts | 8 +- .../search_bar_customization.ts} | 2 +- .../top_nav_customization.ts} | 2 +- .../{extensions => customizations}/index.ts | 4 +- .../profile_aware_locator.ts | 0 .../profile_registry.ts | 4 +- .../{extensions => customizations}/types.ts | 10 +-- .../public/extensions/extension_provider.ts | 55 ------------- .../public/extensions/extension_registry.ts | 79 ------------------ src/plugins/discover/public/plugin.tsx | 14 ++-- 22 files changed, 231 insertions(+), 213 deletions(-) create mode 100644 src/plugins/discover/public/customizations/customization_provider.ts create mode 100644 src/plugins/discover/public/customizations/customization_service.ts rename src/plugins/discover/public/{extensions/extension_types/data_grid_extension.ts => customizations/customization_types/data_grid_customization.ts} (94%) rename src/plugins/discover/public/{extensions/extension_types/field_popover_extension.ts => customizations/customization_types/field_popover_customization.ts} (91%) rename src/plugins/discover/public/{extensions/extension_types => customizations/customization_types}/index.ts (66%) rename src/plugins/discover/public/{extensions/extension_types/search_bar_extension.ts => customizations/customization_types/search_bar_customization.ts} (91%) rename src/plugins/discover/public/{extensions/extension_types/top_nav_extension.ts => customizations/customization_types/top_nav_customization.ts} (96%) rename src/plugins/discover/public/{extensions => customizations}/index.ts (81%) rename src/plugins/discover/public/{extensions => customizations}/profile_aware_locator.ts (100%) rename src/plugins/discover/public/{extensions => customizations}/profile_registry.ts (88%) rename src/plugins/discover/public/{extensions => customizations}/types.ts (68%) delete mode 100644 src/plugins/discover/public/extensions/extension_provider.ts delete mode 100644 src/plugins/discover/public/extensions/extension_registry.ts diff --git a/examples/discover_extender/public/plugin.tsx b/examples/discover_extender/public/plugin.tsx index ea8256a8bbc27..9cd01e43c8dbb 100644 --- a/examples/discover_extender/public/plugin.tsx +++ b/examples/discover_extender/public/plugin.tsx @@ -29,8 +29,8 @@ export class DiscoverExtenderPlugin implements Plugin { start(core: CoreStart, plugins: DiscoverExtenderStartPlugins) { const { discover } = plugins; - discover.registerExtensions('default', ({ extensions }) => { - extensions.set({ + discover.customize('default', ({ customizations }) => { + customizations.set({ id: 'top_nav', defaultMenu: { options: { order: 100 }, @@ -58,7 +58,7 @@ export class DiscoverExtenderPlugin implements Plugin { return () => { // eslint-disable-next-line no-console - console.log('Cleaning up Document explorer extensions'); + console.log('Cleaning up Document explorer customizations'); }; }); @@ -70,8 +70,8 @@ export class DiscoverExtenderPlugin implements Plugin { isOptionsOpen = false; }; - discover.registerExtensions('extender', async ({ extensions, stateContainer }) => { - extensions.set({ + discover.customize('extender', async ({ customizations, stateContainer }) => { + customizations.set({ id: 'top_nav', defaultMenu: { options: { disabled: true }, @@ -154,7 +154,7 @@ export class DiscoverExtenderPlugin implements Plugin { ], }); - extensions.set({ + customizations.set({ id: 'search_bar', CustomDataViewPicker: () => { const [isPopoverOpen, setIsPopoverOpen] = useState(false); @@ -204,7 +204,7 @@ export class DiscoverExtenderPlugin implements Plugin { }, }); - extensions.set({ + customizations.set({ id: 'field_popover', CustomBottomButton: () => ( { // eslint-disable-next-line no-console - console.log('Cleaning up Logs explorer extensions'); + console.log('Cleaning up Logs explorer customizations'); }; }); } diff --git a/src/plugins/discover/public/application/discover_router.tsx b/src/plugins/discover/public/application/discover_router.tsx index c7a87b4996f92..8fafbed030937 100644 --- a/src/plugins/discover/public/application/discover_router.tsx +++ b/src/plugins/discover/public/application/discover_router.tsx @@ -18,12 +18,12 @@ import { DiscoverMainRoute } from './main'; import { NotFoundRoute } from './not_found'; import { DiscoverServices } from '../build_services'; import { ViewAlertRoute } from './view_alert'; -import type { RegisterExtensions } from '../extensions/types'; -import type { DiscoverProfileRegistry } from '../extensions/profile_registry'; +import type { CustomizationCallback } from '../customizations/types'; +import type { DiscoverProfileRegistry } from '../customizations/profile_registry'; interface DiscoverRoutesProps { prefix?: string; - registerExtensions: RegisterExtensions[]; + customizationCallbacks: CustomizationCallback[]; isDev: boolean; } @@ -70,14 +70,18 @@ interface CustomDiscoverRoutesProps { const CustomDiscoverRoutes = ({ profileRegistry, ...props }: CustomDiscoverRoutesProps) => { const { profile } = useParams<{ profile: string }>(); - const registerExtensions = useMemo( - () => profileRegistry.get(profile)?.registerExtensions, + const customizationCallbacks = useMemo( + () => profileRegistry.get(profile)?.customizationCallbacks, [profile, profileRegistry] ); - if (registerExtensions) { + if (customizationCallbacks) { return ( - + ); } @@ -97,8 +101,8 @@ export const DiscoverRouter = ({ profileRegistry, ...routeProps }: DiscoverRouterProps) => { - const registerDefaultExtensions = useMemo( - () => profileRegistry.get('default')?.registerExtensions ?? [], + const customizationCallbacks = useMemo( + () => profileRegistry.get('default')?.customizationCallbacks ?? [], [profileRegistry] ); @@ -111,7 +115,7 @@ export const DiscoverRouter = ({ - + diff --git a/src/plugins/discover/public/application/index.tsx b/src/plugins/discover/public/application/index.tsx index a4262593ccc5a..b8a24be2b2fcc 100644 --- a/src/plugins/discover/public/application/index.tsx +++ b/src/plugins/discover/public/application/index.tsx @@ -11,7 +11,7 @@ import { i18n } from '@kbn/i18n'; import { toMountPoint, wrapWithTheme } from '@kbn/kibana-react-plugin/public'; import { DiscoverRouter } from './discover_router'; import { DiscoverServices } from '../build_services'; -import type { DiscoverProfileRegistry } from '../extensions/profile_registry'; +import type { DiscoverProfileRegistry } from '../customizations/profile_registry'; export interface RenderAppProps { element: HTMLElement; diff --git a/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx b/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx index 509bb28d2880a..23aae105b5dc4 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx @@ -41,7 +41,7 @@ import { DataTableRecord } from '../../../../types'; import { getRawRecordType } from '../../utils/get_raw_record_type'; import { DiscoverGridFlyout } from '../../../../components/discover_grid/discover_grid_flyout'; import { DocViewer } from '../../../../services/doc_views/components/doc_viewer'; -import { useDiscoverExtension } from '../../../../extensions/extension_provider'; +import { useDiscoverCustomization } from '../../../../customizations/customization_provider'; const DocTableInfiniteMemoized = React.memo(DocTableInfinite); const DataGridMemoized = React.memo(DiscoverGrid); @@ -168,8 +168,8 @@ function DiscoverDocumentsComponent({ [isPlainRecord, uiSettings, dataView.timeFieldName] ); - const dataGridExtension = useDiscoverExtension('data_grid'); - const defaultControlColumns = dataGridExtension?.defaultLeadingControlColumns; + const dataGridCustomization = useDiscoverCustomization('data_grid'); + const defaultControlColumns = dataGridCustomization?.defaultLeadingControlColumns; const controlColumnIds = useMemo(() => { const ids: string[] = []; @@ -179,8 +179,8 @@ function DiscoverDocumentsComponent({ }, [defaultControlColumns?.expand?.disabled, defaultControlColumns?.select?.disabled]); const customControlColumns = useMemo( - () => dataGridExtension?.getLeadingControlColumns?.(), - [dataGridExtension] + () => dataGridCustomization?.getLeadingControlColumns?.(), + [dataGridCustomization] ); if (isDataViewLoading || (isEmptyDataResult && isDataLoading)) { diff --git a/src/plugins/discover/public/application/main/components/sidebar/discover_field.tsx b/src/plugins/discover/public/application/main/components/sidebar/discover_field.tsx index 8b85b6a521a25..52b3211ad03a7 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/discover_field.tsx +++ b/src/plugins/discover/public/application/main/components/sidebar/discover_field.tsx @@ -28,7 +28,7 @@ import { useDiscoverServices } from '../../../../hooks/use_discover_services'; import { PLUGIN_ID, SHOW_LEGACY_FIELD_TOP_VALUES } from '../../../../../common'; import { getUiActions } from '../../../../kibana_services'; import { type DataDocuments$ } from '../../services/discover_data_state_container'; -import { useDiscoverExtension } from '../../../../extensions/extension_provider'; +import { useDiscoverCustomization } from '../../../../customizations/customization_provider'; interface GetCommonFieldItemButtonPropsParams { field: DataViewField; @@ -272,7 +272,7 @@ function DiscoverFieldComponent({ [field.name] ); - const fieldPopoverExtension = useDiscoverExtension('field_popover'); + const fieldPopoverCustomization = useDiscoverCustomization('field_popover'); const renderPopover = () => { const showLegacyFieldStats = services.uiSettings.get(SHOW_LEGACY_FIELD_TOP_VALUES); @@ -310,11 +310,11 @@ function DiscoverFieldComponent({ )} - {(!fieldPopoverExtension?.disableDefaultBottomButton || - fieldPopoverExtension.CustomBottomButton) && ( + {(!fieldPopoverCustomization?.disableDefaultBottomButton || + fieldPopoverCustomization.CustomBottomButton) && ( - {!fieldPopoverExtension?.disableDefaultBottomButton && ( + {!fieldPopoverCustomization?.disableDefaultBottomButton && ( )} - {fieldPopoverExtension?.CustomBottomButton && ( + {fieldPopoverCustomization?.CustomBottomButton && ( - + )} diff --git a/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx b/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx index 647ea25fca3d8..2f5e9999bcff9 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx @@ -16,7 +16,7 @@ import { getTopNavLinks } from './get_top_nav_links'; import { getHeaderActionMenuMounter } from '../../../../kibana_services'; import { DiscoverStateContainer } from '../../services/discover_state'; import { onSaveSearch } from './on_save_search'; -import { useDiscoverExtension } from '../../../../extensions/extension_provider'; +import { useDiscoverCustomization } from '../../../../customizations/customization_provider'; export interface DiscoverTopNavProps { onOpenInspector: () => void; @@ -107,7 +107,7 @@ export const DiscoverTopNav = ({ }); }, [dataViewEditor, stateContainer]); - const topNavExtension = useDiscoverExtension('top_nav'); + const topNavCustomization = useDiscoverCustomization('top_nav'); const topNavMenu = useMemo( () => getTopNavLinks({ @@ -118,7 +118,7 @@ export const DiscoverTopNav = ({ isPlainRecord, adHocDataViews, persistDataView, - topNavExtension, + topNavCustomization, }), [ adHocDataViews, @@ -128,7 +128,7 @@ export const DiscoverTopNav = ({ persistDataView, services, stateContainer, - topNavExtension, + topNavCustomization, ] ); @@ -196,7 +196,7 @@ export const DiscoverTopNav = ({ [services, stateContainer] ); - const searchBarExtension = useDiscoverExtension('search_bar'); + const searchBarCustomization = useDiscoverCustomization('search_bar'); return ( + searchBarCustomization?.CustomDataViewPicker ? ( + ) : undefined } dataViewPickerComponentProps={ - searchBarExtension?.CustomDataViewPicker ? undefined : dataViewPickerProps + searchBarCustomization?.CustomDataViewPicker ? undefined : dataViewPickerProps } displayStyle="detached" textBasedLanguageModeErrors={ diff --git a/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.tsx b/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.tsx index 31e4755a61343..a0f4e2c957cb3 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.tsx @@ -17,7 +17,7 @@ import { onSaveSearch } from './on_save_search'; import { DiscoverStateContainer } from '../../services/discover_state'; import { openOptionsPopover } from './open_options_popover'; import { openAlertsPopover } from './open_alerts_popover'; -import type { TopNavExtension } from '../../../../extensions'; +import type { TopNavCustomization } from '../../../../customizations'; /** * Helper function to build the top nav links @@ -30,7 +30,7 @@ export const getTopNavLinks = ({ isPlainRecord, persistDataView, adHocDataViews, - topNavExtension, + topNavCustomization, }: { dataView: DataView; services: DiscoverServices; @@ -39,7 +39,7 @@ export const getTopNavLinks = ({ isPlainRecord: boolean; adHocDataViews: DataView[]; persistDataView: (dataView: DataView) => Promise; - topNavExtension: TopNavExtension | undefined; + topNavCustomization: TopNavCustomization | undefined; }): TopNavMenuData[] => { const options = { id: 'options', @@ -193,8 +193,8 @@ export const getTopNavLinks = ({ }, }; - const defaultMenu = topNavExtension?.defaultMenu; - const entries = topNavExtension?.getMenuItems?.() ?? []; + const defaultMenu = topNavCustomization?.defaultMenu; + const entries = topNavCustomization?.getMenuItems?.() ?? []; if (services.capabilities.advancedSettings.save && !defaultMenu?.options?.disabled) { entries.push({ data: options, order: defaultMenu?.options?.order ?? 100 }); diff --git a/src/plugins/discover/public/application/main/discover_main_route.tsx b/src/plugins/discover/public/application/main/discover_main_route.tsx index 64ecfa006e553..30f903dec14b0 100644 --- a/src/plugins/discover/public/application/main/discover_main_route.tsx +++ b/src/plugins/discover/public/application/main/discover_main_route.tsx @@ -29,10 +29,10 @@ import { getScopedHistory, getUrlTracker } from '../../kibana_services'; import { useAlertResultsToast } from './hooks/use_alert_results_toast'; import { DiscoverMainProvider } from './services/discover_state_provider'; import { - DiscoverExtensionProvider, - useDiscoverExtensionRegistry, -} from '../../extensions/extension_provider'; -import type { RegisterExtensions } from '../../extensions/types'; + DiscoverCustomizationProvider, + useDiscoverCustomizationService, +} from '../../customizations/customization_provider'; +import type { CustomizationCallback } from '../../customizations/types'; const DiscoverMainAppMemoized = memo(DiscoverMainApp); @@ -41,11 +41,11 @@ interface DiscoverLandingParams { } export interface MainRouteProps { - registerExtensions: RegisterExtensions[]; + customizationCallbacks: CustomizationCallback[]; isDev: boolean; } -export function DiscoverMainRoute({ registerExtensions, isDev }: MainRouteProps) { +export function DiscoverMainRoute({ customizationCallbacks, isDev }: MainRouteProps) { const history = useHistory(); const services = useDiscoverServices(); const { @@ -63,7 +63,10 @@ export function DiscoverMainRoute({ registerExtensions, isDev }: MainRouteProps) services, }) ); - const extensionRegistry = useDiscoverExtensionRegistry({ registerExtensions, stateContainer }); + const customizationService = useDiscoverCustomizationService({ + customizationCallbacks, + stateContainer, + }); const [error, setError] = useState(); const [loading, setLoading] = useState(true); const [hasESData, setHasESData] = useState(false); @@ -250,15 +253,15 @@ export function DiscoverMainRoute({ registerExtensions, isDev }: MainRouteProps) return ; } - if (loading || !extensionRegistry) { + if (loading || !customizationService) { return ; } return ( - + - + ); } diff --git a/src/plugins/discover/public/customizations/customization_provider.ts b/src/plugins/discover/public/customizations/customization_provider.ts new file mode 100644 index 0000000000000..95c21daa5f9b8 --- /dev/null +++ b/src/plugins/discover/public/customizations/customization_provider.ts @@ -0,0 +1,63 @@ +/* + * 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 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 or the Server + * Side Public License, v 1. + */ + +import { createContext, useContext, useState } from 'react'; +import useObservable from 'react-use/lib/useObservable'; +import { isFunction } from 'lodash'; +import useEffectOnce from 'react-use/lib/useEffectOnce'; +import { + createCustomizationService, + DiscoverCustomizationId, + DiscoverCustomizationService, +} from '.'; +import type { DiscoverStateContainer } from '../application/main/services/discover_state'; +import type { CustomizationCallback } from './types'; + +const customizationContext = createContext( + createCustomizationService() +); + +export const DiscoverCustomizationProvider = customizationContext.Provider; + +export const useDiscoverCustomizationService = ({ + customizationCallbacks, + stateContainer, +}: { + customizationCallbacks: CustomizationCallback[]; + stateContainer: DiscoverStateContainer; +}) => { + const [customizationService, setCustomizationService] = useState(); + + useEffectOnce(() => { + const customizations = createCustomizationService(); + const callbacks = customizationCallbacks.map((callback) => + Promise.resolve(callback({ customizations, stateContainer })) + ); + const initialize = () => Promise.all(callbacks).then((result) => result.filter(isFunction)); + + initialize().then(() => { + setCustomizationService(customizations); + }); + + return () => { + initialize().then((cleanups) => { + cleanups.forEach((cleanup) => cleanup()); + }); + }; + }); + + return customizationService; +}; + +export const useDiscoverCustomization$ = ( + id: TCustomizationId +) => useContext(customizationContext).get$(id); + +export const useDiscoverCustomization = ( + id: TCustomizationId +) => useObservable(useDiscoverCustomization$(id)); diff --git a/src/plugins/discover/public/customizations/customization_service.ts b/src/plugins/discover/public/customizations/customization_service.ts new file mode 100644 index 0000000000000..729eb070589cc --- /dev/null +++ b/src/plugins/discover/public/customizations/customization_service.ts @@ -0,0 +1,82 @@ +/* + * 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 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 or the Server + * Side Public License, v 1. + */ + +import { filter, map, Observable, startWith, Subject } from 'rxjs'; +import type { + DataGridCustomization, + FieldPopoverCustomization, + SearchBarCustomization, + TopNavCustomization, +} from './customization_types'; + +export type DiscoverCustomization = + | DataGridCustomization + | FieldPopoverCustomization + | SearchBarCustomization + | TopNavCustomization; + +export type DiscoverCustomizationId = DiscoverCustomization['id']; + +export interface DiscoverCustomizationService { + set: (customization: DiscoverCustomization) => void; + get$: ( + id: TCustomizationId + ) => Observable | undefined>; + enable: (id: DiscoverCustomizationId) => void; + disable: (id: DiscoverCustomizationId) => void; +} + +interface CustomizationEntry { + customization: DiscoverCustomization; + enabled: boolean; +} + +export const createCustomizationService = (): DiscoverCustomizationService => { + const update$ = new Subject(); + const customizations = new Map(); + + return { + set: (customization) => { + const entry = customizations.get(customization.id); + customizations.set(customization.id, { + customization, + enabled: entry?.enabled ?? true, + }); + update$.next(customization.id); + }, + + get$: (id: TCustomizationId) => { + return update$.pipe( + startWith(id), + filter((currentId) => currentId === id), + map(() => { + const entry = customizations.get(id); + if (entry && entry.enabled) { + return entry.customization as Extract; + } + }) + ); + }, + + enable: (id) => { + const entry = customizations.get(id); + if (entry && !entry.enabled) { + entry.enabled = true; + update$.next(entry.customization.id); + } + }, + + disable: (id) => { + const entry = customizations.get(id); + if (entry && entry.enabled) { + entry.enabled = false; + update$.next(entry.customization.id); + } + }, + }; +}; diff --git a/src/plugins/discover/public/extensions/extension_types/data_grid_extension.ts b/src/plugins/discover/public/customizations/customization_types/data_grid_customization.ts similarity index 94% rename from src/plugins/discover/public/extensions/extension_types/data_grid_extension.ts rename to src/plugins/discover/public/customizations/customization_types/data_grid_customization.ts index 149f7789c354e..a86be9fae142d 100644 --- a/src/plugins/discover/public/extensions/extension_types/data_grid_extension.ts +++ b/src/plugins/discover/public/customizations/customization_types/data_grid_customization.ts @@ -17,7 +17,7 @@ export interface DefaultLeadingControlColumns { select?: DefaultLeadingControlColumn; } -export interface DataGridExtension { +export interface DataGridCustomization { id: 'data_grid'; defaultLeadingControlColumns?: DefaultLeadingControlColumns; getLeadingControlColumns?: () => EuiDataGridControlColumn[]; diff --git a/src/plugins/discover/public/extensions/extension_types/field_popover_extension.ts b/src/plugins/discover/public/customizations/customization_types/field_popover_customization.ts similarity index 91% rename from src/plugins/discover/public/extensions/extension_types/field_popover_extension.ts rename to src/plugins/discover/public/customizations/customization_types/field_popover_customization.ts index 18c1c168287fa..fd65e2e120dac 100644 --- a/src/plugins/discover/public/extensions/extension_types/field_popover_extension.ts +++ b/src/plugins/discover/public/customizations/customization_types/field_popover_customization.ts @@ -8,7 +8,7 @@ import type { ComponentType } from 'react'; -export interface FieldPopoverExtension { +export interface FieldPopoverCustomization { id: 'field_popover'; disableDefaultBottomButton?: boolean; CustomBottomButton?: ComponentType; diff --git a/src/plugins/discover/public/extensions/extension_types/index.ts b/src/plugins/discover/public/customizations/customization_types/index.ts similarity index 66% rename from src/plugins/discover/public/extensions/extension_types/index.ts rename to src/plugins/discover/public/customizations/customization_types/index.ts index e96917e27367b..470344842c311 100644 --- a/src/plugins/discover/public/extensions/extension_types/index.ts +++ b/src/plugins/discover/public/customizations/customization_types/index.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -export * from './data_grid_extension'; -export * from './field_popover_extension'; -export * from './search_bar_extension'; -export * from './top_nav_extension'; +export * from './data_grid_customization'; +export * from './field_popover_customization'; +export * from './search_bar_customization'; +export * from './top_nav_customization'; diff --git a/src/plugins/discover/public/extensions/extension_types/search_bar_extension.ts b/src/plugins/discover/public/customizations/customization_types/search_bar_customization.ts similarity index 91% rename from src/plugins/discover/public/extensions/extension_types/search_bar_extension.ts rename to src/plugins/discover/public/customizations/customization_types/search_bar_customization.ts index b923ef71eb9a3..8117781a95c93 100644 --- a/src/plugins/discover/public/extensions/extension_types/search_bar_extension.ts +++ b/src/plugins/discover/public/customizations/customization_types/search_bar_customization.ts @@ -8,7 +8,7 @@ import type { ComponentType } from 'react'; -export interface SearchBarExtension { +export interface SearchBarCustomization { id: 'search_bar'; CustomDataViewPicker?: ComponentType; } diff --git a/src/plugins/discover/public/extensions/extension_types/top_nav_extension.ts b/src/plugins/discover/public/customizations/customization_types/top_nav_customization.ts similarity index 96% rename from src/plugins/discover/public/extensions/extension_types/top_nav_extension.ts rename to src/plugins/discover/public/customizations/customization_types/top_nav_customization.ts index a138316dd6f60..45288c6a248fb 100644 --- a/src/plugins/discover/public/extensions/extension_types/top_nav_extension.ts +++ b/src/plugins/discover/public/customizations/customization_types/top_nav_customization.ts @@ -28,7 +28,7 @@ export interface TopNavMenuItem { order: number; } -export interface TopNavExtension { +export interface TopNavCustomization { id: 'top_nav'; defaultMenu?: TopNavDefaultMenu; getMenuItems?: () => TopNavMenuItem[]; diff --git a/src/plugins/discover/public/extensions/index.ts b/src/plugins/discover/public/customizations/index.ts similarity index 81% rename from src/plugins/discover/public/extensions/index.ts rename to src/plugins/discover/public/customizations/index.ts index ad125a998ba0d..78f922c3a5eac 100644 --- a/src/plugins/discover/public/extensions/index.ts +++ b/src/plugins/discover/public/customizations/index.ts @@ -6,5 +6,5 @@ * Side Public License, v 1. */ -export * from './extension_types'; -export * from './extension_registry'; +export * from './customization_types'; +export * from './customization_service'; diff --git a/src/plugins/discover/public/extensions/profile_aware_locator.ts b/src/plugins/discover/public/customizations/profile_aware_locator.ts similarity index 100% rename from src/plugins/discover/public/extensions/profile_aware_locator.ts rename to src/plugins/discover/public/customizations/profile_aware_locator.ts diff --git a/src/plugins/discover/public/extensions/profile_registry.ts b/src/plugins/discover/public/customizations/profile_registry.ts similarity index 88% rename from src/plugins/discover/public/extensions/profile_registry.ts rename to src/plugins/discover/public/customizations/profile_registry.ts index c1aa440d188e6..236cd991f08b1 100644 --- a/src/plugins/discover/public/extensions/profile_registry.ts +++ b/src/plugins/discover/public/customizations/profile_registry.ts @@ -6,11 +6,11 @@ * Side Public License, v 1. */ -import type { RegisterExtensions } from './types'; +import type { CustomizationCallback } from './types'; export interface DiscoverProfile { name: string; - registerExtensions: RegisterExtensions[]; + customizationCallbacks: CustomizationCallback[]; } export interface DiscoverProfileRegistry { diff --git a/src/plugins/discover/public/extensions/types.ts b/src/plugins/discover/public/customizations/types.ts similarity index 68% rename from src/plugins/discover/public/extensions/types.ts rename to src/plugins/discover/public/customizations/types.ts index e4ccd1fde8620..7c7088306f585 100644 --- a/src/plugins/discover/public/extensions/types.ts +++ b/src/plugins/discover/public/customizations/types.ts @@ -7,13 +7,13 @@ */ import type { DiscoverStateContainer } from '../application/main/services/discover_state'; -import type { DiscoverExtensionRegistry } from './extension_registry'; +import type { DiscoverCustomizationService } from './customization_service'; -export interface RegisterExtensionsContext { - extensions: DiscoverExtensionRegistry; +export interface CustomizationCallbackContext { + customizations: DiscoverCustomizationService; stateContainer: DiscoverStateContainer; } -export type RegisterExtensions = ( - options: RegisterExtensionsContext +export type CustomizationCallback = ( + options: CustomizationCallbackContext ) => void | (() => void) | Promise void)>; diff --git a/src/plugins/discover/public/extensions/extension_provider.ts b/src/plugins/discover/public/extensions/extension_provider.ts deleted file mode 100644 index 5fcaf1e20cf56..0000000000000 --- a/src/plugins/discover/public/extensions/extension_provider.ts +++ /dev/null @@ -1,55 +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 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 or the Server - * Side Public License, v 1. - */ - -import { createContext, useContext, useState } from 'react'; -import useObservable from 'react-use/lib/useObservable'; -import { isFunction } from 'lodash'; -import useEffectOnce from 'react-use/lib/useEffectOnce'; -import { createExtensionRegistry, DiscoverExtensionId, DiscoverExtensionRegistry } from '.'; -import type { DiscoverStateContainer } from '../application/main/services/discover_state'; -import type { RegisterExtensions } from './types'; - -const extensionContext = createContext(createExtensionRegistry()); - -export const DiscoverExtensionProvider = extensionContext.Provider; - -export const useDiscoverExtensionRegistry = ({ - registerExtensions, - stateContainer, -}: { - registerExtensions: RegisterExtensions[]; - stateContainer: DiscoverStateContainer; -}) => { - const [extensionRegistry, setExtensionRegistry] = useState(); - - useEffectOnce(() => { - const extensions = createExtensionRegistry(); - const registrations = registerExtensions.map((register) => - Promise.resolve(register({ extensions, stateContainer })) - ); - const initialize = () => Promise.all(registrations).then((result) => result.filter(isFunction)); - - initialize().then(() => { - setExtensionRegistry(extensions); - }); - - return () => { - initialize().then((cleanups) => { - cleanups.forEach((cleanup) => cleanup()); - }); - }; - }); - - return extensionRegistry; -}; - -export const useDiscoverExtension$ = (id: TExtensionId) => - useContext(extensionContext).get$(id); - -export const useDiscoverExtension = (id: TExtensionId) => - useObservable(useDiscoverExtension$(id)); diff --git a/src/plugins/discover/public/extensions/extension_registry.ts b/src/plugins/discover/public/extensions/extension_registry.ts deleted file mode 100644 index 1a8f3335454eb..0000000000000 --- a/src/plugins/discover/public/extensions/extension_registry.ts +++ /dev/null @@ -1,79 +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 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 or the Server - * Side Public License, v 1. - */ - -import { filter, map, Observable, startWith, Subject } from 'rxjs'; -import type { - DataGridExtension, - FieldPopoverExtension, - SearchBarExtension, - TopNavExtension, -} from './extension_types'; - -export type DiscoverExtension = - | DataGridExtension - | FieldPopoverExtension - | SearchBarExtension - | TopNavExtension; - -export type DiscoverExtensionId = DiscoverExtension['id']; - -export interface DiscoverExtensionRegistry { - set: (extension: DiscoverExtension) => void; - get$: ( - id: TExtensionId - ) => Observable | undefined>; - enable: (id: DiscoverExtensionId) => void; - disable: (id: DiscoverExtensionId) => void; -} - -interface DiscoverExtensionRegistration { - extension: DiscoverExtension; - enabled: boolean; -} - -export const createExtensionRegistry = (): DiscoverExtensionRegistry => { - const update$ = new Subject(); - const registrations = new Map(); - - return { - set: (extension) => { - const registration = registrations.get(extension.id); - registrations.set(extension.id, { extension, enabled: registration?.enabled ?? true }); - update$.next(extension.id); - }, - - get$: (id: TExtensionId) => { - return update$.pipe( - startWith(id), - filter((currentId) => currentId === id), - map(() => { - const registration = registrations.get(id); - if (registration && registration.enabled) { - return registration.extension as Extract; - } - }) - ); - }, - - enable: (id) => { - const registration = registrations.get(id); - if (registration && !registration.enabled) { - registration.enabled = true; - update$.next(registration.extension.id); - } - }, - - disable: (id) => { - const registration = registrations.get(id); - if (registration && registration.enabled) { - registration.enabled = false; - update$.next(registration.extension.id); - } - }, - }; -}; diff --git a/src/plugins/discover/public/plugin.tsx b/src/plugins/discover/public/plugin.tsx index 150a378c2cf2c..f114578c8f4de 100644 --- a/src/plugins/discover/public/plugin.tsx +++ b/src/plugins/discover/public/plugin.tsx @@ -71,9 +71,9 @@ import { DiscoverSingleDocLocatorDefinition, } from './application/doc/locator'; import { DiscoverAppLocator, DiscoverAppLocatorDefinition } from '../common'; -import type { RegisterExtensions } from './extensions/types'; -import { createProfileRegistry } from './extensions/profile_registry'; -import { ProfileAwareLocator } from './extensions/profile_aware_locator'; +import type { CustomizationCallback } from './customizations/types'; +import { createProfileRegistry } from './customizations/profile_registry'; +import { ProfileAwareLocator } from './customizations/profile_aware_locator'; const DocViewerLegacyTable = React.lazy( () => import('./services/doc_views/components/doc_viewer_table/legacy') @@ -158,7 +158,7 @@ export interface DiscoverStart { * ``` */ readonly locator: undefined | DiscoverAppLocator; - readonly registerExtensions: (profileName: string, register: RegisterExtensions) => void; + readonly customize: (profileName: string, callback: CustomizationCallback) => void; } /** @@ -409,12 +409,12 @@ export class DiscoverPlugin return { locator: this.locator, - registerExtensions: (profileName: string, register: RegisterExtensions) => { + customize: (profileName: string, callback: CustomizationCallback) => { const profile = this.profileRegistry.get(profileName) ?? { name: profileName, - registerExtensions: [], + customizationCallbacks: [], }; - profile.registerExtensions.push(register); + profile.customizationCallbacks.push(callback); this.profileRegistry.set(profile); }, }; From 1d10e3d9dee6984f87b5ed0e9c32a93af0d5bb74 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Wed, 3 May 2023 11:33:40 +0200 Subject: [PATCH 055/122] feat(observability-logs): create plugin boilerplate --- x-pack/plugins/observability_logs/README.md | 3 + .../observability_logs/common/index.ts | 2 + .../plugins/observability_logs/jest.config.js | 17 +++ .../plugins/observability_logs/kibana.jsonc | 15 +++ .../observability_logs/public/application.tsx | 30 +++++ .../public/components/app.tsx | 119 ++++++++++++++++++ .../observability_logs/public/index.scss | 0 .../observability_logs/public/index.ts | 10 ++ .../observability_logs/public/plugin.ts | 54 ++++++++ .../observability_logs/public/types.ts | 11 ++ .../observability_logs/server/index.ts | 11 ++ .../observability_logs/server/plugin.ts | 37 ++++++ .../observability_logs/server/routes/index.ts | 17 +++ .../observability_logs/server/types.ts | 4 + .../plugins/observability_logs/tsconfig.json | 8 ++ 15 files changed, 338 insertions(+) create mode 100755 x-pack/plugins/observability_logs/README.md create mode 100644 x-pack/plugins/observability_logs/common/index.ts create mode 100644 x-pack/plugins/observability_logs/jest.config.js create mode 100644 x-pack/plugins/observability_logs/kibana.jsonc create mode 100644 x-pack/plugins/observability_logs/public/application.tsx create mode 100644 x-pack/plugins/observability_logs/public/components/app.tsx create mode 100644 x-pack/plugins/observability_logs/public/index.scss create mode 100644 x-pack/plugins/observability_logs/public/index.ts create mode 100644 x-pack/plugins/observability_logs/public/plugin.ts create mode 100644 x-pack/plugins/observability_logs/public/types.ts create mode 100644 x-pack/plugins/observability_logs/server/index.ts create mode 100644 x-pack/plugins/observability_logs/server/plugin.ts create mode 100644 x-pack/plugins/observability_logs/server/routes/index.ts create mode 100644 x-pack/plugins/observability_logs/server/types.ts create mode 100644 x-pack/plugins/observability_logs/tsconfig.json diff --git a/x-pack/plugins/observability_logs/README.md b/x-pack/plugins/observability_logs/README.md new file mode 100755 index 0000000000000..188d81b1ce4cc --- /dev/null +++ b/x-pack/plugins/observability_logs/README.md @@ -0,0 +1,3 @@ +# Observability Logs + +This plugin exposes and registers Logs+ features. diff --git a/x-pack/plugins/observability_logs/common/index.ts b/x-pack/plugins/observability_logs/common/index.ts new file mode 100644 index 0000000000000..3b5f47b0e78ab --- /dev/null +++ b/x-pack/plugins/observability_logs/common/index.ts @@ -0,0 +1,2 @@ +export const PLUGIN_ID = 'observabilityLogs'; +export const PLUGIN_NAME = 'observabilityLogs'; diff --git a/x-pack/plugins/observability_logs/jest.config.js b/x-pack/plugins/observability_logs/jest.config.js new file mode 100644 index 0000000000000..2c232f6a07547 --- /dev/null +++ b/x-pack/plugins/observability_logs/jest.config.js @@ -0,0 +1,17 @@ +/* + * 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. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/observability-logs'], + coverageDirectory: '/target/kibana-coverage/jest/x-pack/plugins/observability-logs', + coverageReporters: ['text', 'html'], + collectCoverageFrom: [ + '/x-pack/plugins/observability-logs/{common,public,server}/**/*.{ts,tsx}', + ], +}; diff --git a/x-pack/plugins/observability_logs/kibana.jsonc b/x-pack/plugins/observability_logs/kibana.jsonc new file mode 100644 index 0000000000000..2633cc71295b1 --- /dev/null +++ b/x-pack/plugins/observability_logs/kibana.jsonc @@ -0,0 +1,15 @@ +{ + "type": "plugin", + "id": "@kbn/observability-logs-plugin", + "owner": "@elastic/infra-monitoring-ui", + "description": "This plugin exposes and registers Logs+ features.", + "plugin": { + "id": "infra", + "server": true, + "browser": true, + "configPath": ["xpack", "obeservability_logs"], + "requiredPlugins": [], + "optionalPlugins": [], + "requiredBundles": [] + } +} diff --git a/x-pack/plugins/observability_logs/public/application.tsx b/x-pack/plugins/observability_logs/public/application.tsx new file mode 100644 index 0000000000000..bca48f45dafc1 --- /dev/null +++ b/x-pack/plugins/observability_logs/public/application.tsx @@ -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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import { AppMountParameters, CoreStart } from '@kbn/core/public'; +import { AppPluginStartDependencies } from './types'; +import { ObservabilityLogsApp } from './components/app'; + +export const renderApp = ( + { notifications, http }: CoreStart, + { navigation }: AppPluginStartDependencies, + { appBasePath, element }: AppMountParameters +) => { + ReactDOM.render( + , + element + ); + + return () => ReactDOM.unmountComponentAtNode(element); +}; diff --git a/x-pack/plugins/observability_logs/public/components/app.tsx b/x-pack/plugins/observability_logs/public/components/app.tsx new file mode 100644 index 0000000000000..5b874c722c505 --- /dev/null +++ b/x-pack/plugins/observability_logs/public/components/app.tsx @@ -0,0 +1,119 @@ +import React, { useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage, I18nProvider } from '@kbn/i18n-react'; +import { BrowserRouter as Router } from 'react-router-dom'; + +import { + EuiButton, + EuiHorizontalRule, + EuiPage, + EuiPageBody, + EuiPageContent_Deprecated as EuiPageContent, + EuiPageContentBody_Deprecated as EuiPageContentBody, + EuiPageContentHeader_Deprecated as EuiPageContentHeader, + EuiPageHeader, + EuiTitle, + EuiText, +} from '@elastic/eui'; + +import { CoreStart } from '../../../../../src/core/public'; +import { NavigationPublicPluginStart } from '../../../../../src/plugins/navigation/public'; + +import { PLUGIN_ID, PLUGIN_NAME } from '../../common'; + +interface ObservabilityLogsAppDeps { + basename: string; + notifications: CoreStart['notifications']; + http: CoreStart['http']; + navigation: NavigationPublicPluginStart; +} + +export const ObservabilityLogsApp = ({ + basename, + notifications, + http, + navigation, +}: ObservabilityLogsAppDeps) => { + // Use React hooks to manage state. + const [timestamp, setTimestamp] = useState(); + + const onClickHandler = () => { + // Use the core http service to make a response to the server API. + http.get('/api/observability_logs/example').then((res) => { + setTimestamp(res.time); + // Use the core notifications service to display a success message. + notifications.toasts.addSuccess( + i18n.translate('observabilityLogs.dataUpdated', { + defaultMessage: 'Data updated', + }) + ); + }); + }; + + // Render the application DOM. + // Note that `navigation.ui.TopNavMenu` is a stateful component exported on the `navigation` plugin's start contract. + return ( + + + <> + + + + + +

+ +

+
+
+ + + +

+ +

+
+
+ + +

+ +

+ +

+ +

+ + + +
+
+
+
+
+ +
+
+ ); +}; diff --git a/x-pack/plugins/observability_logs/public/index.scss b/x-pack/plugins/observability_logs/public/index.scss new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/x-pack/plugins/observability_logs/public/index.ts b/x-pack/plugins/observability_logs/public/index.ts new file mode 100644 index 0000000000000..5573d11fa6451 --- /dev/null +++ b/x-pack/plugins/observability_logs/public/index.ts @@ -0,0 +1,10 @@ +import './index.scss'; + +import { ObservabilityLogsPlugin } from './plugin'; + +// This exports static code and TypeScript types, +// as well as, Kibana Platform `plugin()` initializer. +export function plugin() { + return new ObservabilityLogsPlugin(); +} +export type { ObservabilityLogsPluginSetup, ObservabilityLogsPluginStart } from './types'; diff --git a/x-pack/plugins/observability_logs/public/plugin.ts b/x-pack/plugins/observability_logs/public/plugin.ts new file mode 100644 index 0000000000000..d3389c0a8d933 --- /dev/null +++ b/x-pack/plugins/observability_logs/public/plugin.ts @@ -0,0 +1,54 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { AppMountParameters, CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; +import { + ObservabilityLogsPluginSetup, + ObservabilityLogsPluginStart, + AppPluginStartDependencies, +} from './types'; +import { PLUGIN_NAME } from '../common'; + +export class ObservabilityLogsPlugin + implements Plugin +{ + public setup(core: CoreSetup): ObservabilityLogsPluginSetup { + // Register an application into the side navigation menu + core.application.register({ + id: 'observabilityLogs', + title: PLUGIN_NAME, + appRoute: '/app/observability-logs', + async mount(params: AppMountParameters) { + // Load application bundle + const { renderApp } = await import('./application'); + // Get start services as specified in kibana.json + const [coreStart, depsStart] = await core.getStartServices(); + // Render the application + return renderApp(coreStart, depsStart as AppPluginStartDependencies, params); + }, + }); + + // Return methods that should be available to other plugins + return { + getGreeting() { + return i18n.translate('observabilityLogs.greetingText', { + defaultMessage: 'Hello from {name}!', + values: { + name: PLUGIN_NAME, + }, + }); + }, + }; + } + + public start(core: CoreStart): ObservabilityLogsPluginStart { + return {}; + } + + public stop() {} +} diff --git a/x-pack/plugins/observability_logs/public/types.ts b/x-pack/plugins/observability_logs/public/types.ts new file mode 100644 index 0000000000000..decbc8f48e0fd --- /dev/null +++ b/x-pack/plugins/observability_logs/public/types.ts @@ -0,0 +1,11 @@ +import { NavigationPublicPluginStart } from '../../../../src/plugins/navigation/public'; + +export interface ObservabilityLogsPluginSetup { + getGreeting: () => string; +} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ObservabilityLogsPluginStart {} + +export interface AppPluginStartDependencies { + navigation: NavigationPublicPluginStart; +} diff --git a/x-pack/plugins/observability_logs/server/index.ts b/x-pack/plugins/observability_logs/server/index.ts new file mode 100644 index 0000000000000..3bba9da4211d2 --- /dev/null +++ b/x-pack/plugins/observability_logs/server/index.ts @@ -0,0 +1,11 @@ +import { PluginInitializerContext } from '../../../../src/core/server'; +import { ObservabilityLogsPlugin } from './plugin'; + +// This exports static code and TypeScript types, +// as well as, Kibana Platform `plugin()` initializer. + +export function plugin(initializerContext: PluginInitializerContext) { + return new ObservabilityLogsPlugin(initializerContext); +} + +export type { ObservabilityLogsPluginSetup, ObservabilityLogsPluginStart } from './types'; diff --git a/x-pack/plugins/observability_logs/server/plugin.ts b/x-pack/plugins/observability_logs/server/plugin.ts new file mode 100644 index 0000000000000..bf88675345af3 --- /dev/null +++ b/x-pack/plugins/observability_logs/server/plugin.ts @@ -0,0 +1,37 @@ +import { + PluginInitializerContext, + CoreSetup, + CoreStart, + Plugin, + Logger, +} from '../../../../src/core/server'; + +import { ObservabilityLogsPluginSetup, ObservabilityLogsPluginStart } from './types'; +import { defineRoutes } from './routes'; + +export class ObservabilityLogsPlugin + implements Plugin +{ + private readonly logger: Logger; + + constructor(initializerContext: PluginInitializerContext) { + this.logger = initializerContext.logger.get(); + } + + public setup(core: CoreSetup) { + this.logger.debug('observabilityLogs: Setup'); + const router = core.http.createRouter(); + + // Register server side APIs + defineRoutes(router); + + return {}; + } + + public start(core: CoreStart) { + this.logger.debug('observabilityLogs: Started'); + return {}; + } + + public stop() {} +} diff --git a/x-pack/plugins/observability_logs/server/routes/index.ts b/x-pack/plugins/observability_logs/server/routes/index.ts new file mode 100644 index 0000000000000..4ca98336c392d --- /dev/null +++ b/x-pack/plugins/observability_logs/server/routes/index.ts @@ -0,0 +1,17 @@ +import { IRouter } from '../../../../../src/core/server'; + +export function defineRoutes(router: IRouter) { + router.get( + { + path: '/api/observability_logs/example', + validate: false, + }, + async (context, request, response) => { + return response.ok({ + body: { + time: new Date().toISOString(), + }, + }); + } + ); +} diff --git a/x-pack/plugins/observability_logs/server/types.ts b/x-pack/plugins/observability_logs/server/types.ts new file mode 100644 index 0000000000000..74fdea00b2486 --- /dev/null +++ b/x-pack/plugins/observability_logs/server/types.ts @@ -0,0 +1,4 @@ +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ObservabilityLogsPluginSetup {} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ObservabilityLogsPluginStart {} diff --git a/x-pack/plugins/observability_logs/tsconfig.json b/x-pack/plugins/observability_logs/tsconfig.json new file mode 100644 index 0000000000000..ff350c9f3d85c --- /dev/null +++ b/x-pack/plugins/observability_logs/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types" + }, + "include": ["../../../typings/**/*", "common/**/*", "public/**/*", "server/**/*"], + "exclude": ["target/**/*"] +} From 90433a86e09ccb98f123a86e5af8122a3a3992c5 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 3 May 2023 10:10:39 +0000 Subject: [PATCH 056/122] [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' --- package.json | 1 + tsconfig.base.json | 2 ++ x-pack/plugins/observability_logs/common/index.ts | 7 +++++++ .../observability_logs/public/components/app.tsx | 11 +++++++++-- x-pack/plugins/observability_logs/public/index.ts | 7 +++++++ x-pack/plugins/observability_logs/public/types.ts | 9 ++++++++- x-pack/plugins/observability_logs/server/index.ts | 9 ++++++++- .../plugins/observability_logs/server/plugin.ts | 15 ++++++++------- .../observability_logs/server/routes/index.ts | 9 ++++++++- x-pack/plugins/observability_logs/server/types.ts | 7 +++++++ yarn.lock | 4 ++++ 11 files changed, 69 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index f6ecec1fd1343..bffc8e4553b0a 100644 --- a/package.json +++ b/package.json @@ -493,6 +493,7 @@ "@kbn/object-versioning": "link:packages/kbn-object-versioning", "@kbn/observability-alert-details": "link:x-pack/packages/observability/alert_details", "@kbn/observability-fixtures-plugin": "link:x-pack/test/cases_api_integration/common/plugins/observability", + "@kbn/observability-logs-plugin": "link:x-pack/plugins/observability_logs", "@kbn/observability-plugin": "link:x-pack/plugins/observability", "@kbn/observability-shared-plugin": "link:x-pack/plugins/observability_shared", "@kbn/oidc-provider-plugin": "link:x-pack/test/security_api_integration/plugins/oidc_provider", diff --git a/tsconfig.base.json b/tsconfig.base.json index d4a3cc5f3412a..24fad8fdf4d00 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -950,6 +950,8 @@ "@kbn/observability-alert-details/*": ["x-pack/packages/observability/alert_details/*"], "@kbn/observability-fixtures-plugin": ["x-pack/test/cases_api_integration/common/plugins/observability"], "@kbn/observability-fixtures-plugin/*": ["x-pack/test/cases_api_integration/common/plugins/observability/*"], + "@kbn/observability-logs-plugin": ["x-pack/plugins/observability_logs"], + "@kbn/observability-logs-plugin/*": ["x-pack/plugins/observability_logs/*"], "@kbn/observability-plugin": ["x-pack/plugins/observability"], "@kbn/observability-plugin/*": ["x-pack/plugins/observability/*"], "@kbn/observability-shared-plugin": ["x-pack/plugins/observability_shared"], diff --git a/x-pack/plugins/observability_logs/common/index.ts b/x-pack/plugins/observability_logs/common/index.ts index 3b5f47b0e78ab..2f2129469c8bb 100644 --- a/x-pack/plugins/observability_logs/common/index.ts +++ b/x-pack/plugins/observability_logs/common/index.ts @@ -1,2 +1,9 @@ +/* + * 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. + */ + export const PLUGIN_ID = 'observabilityLogs'; export const PLUGIN_NAME = 'observabilityLogs'; diff --git a/x-pack/plugins/observability_logs/public/components/app.tsx b/x-pack/plugins/observability_logs/public/components/app.tsx index 5b874c722c505..61e458d22e5cc 100644 --- a/x-pack/plugins/observability_logs/public/components/app.tsx +++ b/x-pack/plugins/observability_logs/public/components/app.tsx @@ -1,3 +1,10 @@ +/* + * 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 React, { useState } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage, I18nProvider } from '@kbn/i18n-react'; @@ -16,8 +23,8 @@ import { EuiText, } from '@elastic/eui'; -import { CoreStart } from '../../../../../src/core/public'; -import { NavigationPublicPluginStart } from '../../../../../src/plugins/navigation/public'; +import { CoreStart } from '@kbn/core/public'; +import { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public'; import { PLUGIN_ID, PLUGIN_NAME } from '../../common'; diff --git a/x-pack/plugins/observability_logs/public/index.ts b/x-pack/plugins/observability_logs/public/index.ts index 5573d11fa6451..c47891d99b30e 100644 --- a/x-pack/plugins/observability_logs/public/index.ts +++ b/x-pack/plugins/observability_logs/public/index.ts @@ -1,3 +1,10 @@ +/* + * 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 './index.scss'; import { ObservabilityLogsPlugin } from './plugin'; diff --git a/x-pack/plugins/observability_logs/public/types.ts b/x-pack/plugins/observability_logs/public/types.ts index decbc8f48e0fd..0b98854c84181 100644 --- a/x-pack/plugins/observability_logs/public/types.ts +++ b/x-pack/plugins/observability_logs/public/types.ts @@ -1,4 +1,11 @@ -import { NavigationPublicPluginStart } from '../../../../src/plugins/navigation/public'; +/* + * 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 { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public'; export interface ObservabilityLogsPluginSetup { getGreeting: () => string; diff --git a/x-pack/plugins/observability_logs/server/index.ts b/x-pack/plugins/observability_logs/server/index.ts index 3bba9da4211d2..aad18dee3f1a9 100644 --- a/x-pack/plugins/observability_logs/server/index.ts +++ b/x-pack/plugins/observability_logs/server/index.ts @@ -1,4 +1,11 @@ -import { PluginInitializerContext } from '../../../../src/core/server'; +/* + * 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 { PluginInitializerContext } from '@kbn/core/server'; import { ObservabilityLogsPlugin } from './plugin'; // This exports static code and TypeScript types, diff --git a/x-pack/plugins/observability_logs/server/plugin.ts b/x-pack/plugins/observability_logs/server/plugin.ts index bf88675345af3..a35ac28f3f91d 100644 --- a/x-pack/plugins/observability_logs/server/plugin.ts +++ b/x-pack/plugins/observability_logs/server/plugin.ts @@ -1,10 +1,11 @@ -import { - PluginInitializerContext, - CoreSetup, - CoreStart, - Plugin, - Logger, -} from '../../../../src/core/server'; +/* + * 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 { PluginInitializerContext, CoreSetup, CoreStart, Plugin, Logger } from '@kbn/core/server'; import { ObservabilityLogsPluginSetup, ObservabilityLogsPluginStart } from './types'; import { defineRoutes } from './routes'; diff --git a/x-pack/plugins/observability_logs/server/routes/index.ts b/x-pack/plugins/observability_logs/server/routes/index.ts index 4ca98336c392d..449d5ec992e4d 100644 --- a/x-pack/plugins/observability_logs/server/routes/index.ts +++ b/x-pack/plugins/observability_logs/server/routes/index.ts @@ -1,4 +1,11 @@ -import { IRouter } from '../../../../../src/core/server'; +/* + * 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 { IRouter } from '@kbn/core/server'; export function defineRoutes(router: IRouter) { router.get( diff --git a/x-pack/plugins/observability_logs/server/types.ts b/x-pack/plugins/observability_logs/server/types.ts index 74fdea00b2486..8f0148a170206 100644 --- a/x-pack/plugins/observability_logs/server/types.ts +++ b/x-pack/plugins/observability_logs/server/types.ts @@ -1,3 +1,10 @@ +/* + * 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. + */ + // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface ObservabilityLogsPluginSetup {} // eslint-disable-next-line @typescript-eslint/no-empty-interface diff --git a/yarn.lock b/yarn.lock index 0bada4ad317f4..0bc8f96a66559 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4637,6 +4637,10 @@ version "0.0.0" uid "" +"@kbn/observability-logs-plugin@link:x-pack/plugins/observability_logs": + version "0.0.0" + uid "" + "@kbn/observability-plugin@link:x-pack/plugins/observability": version "0.0.0" uid "" From 4d663767aa36190f0a7b70a51f12a8410415ac0a Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 3 May 2023 10:17:22 +0000 Subject: [PATCH 057/122] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- x-pack/plugins/observability_logs/tsconfig.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/observability_logs/tsconfig.json b/x-pack/plugins/observability_logs/tsconfig.json index ff350c9f3d85c..18457c5d1dfa0 100644 --- a/x-pack/plugins/observability_logs/tsconfig.json +++ b/x-pack/plugins/observability_logs/tsconfig.json @@ -4,5 +4,11 @@ "outDir": "target/types" }, "include": ["../../../typings/**/*", "common/**/*", "public/**/*", "server/**/*"], - "exclude": ["target/**/*"] + "exclude": ["target/**/*"], + "kbn_references": [ + "@kbn/core", + "@kbn/i18n", + "@kbn/i18n-react", + "@kbn/navigation-plugin", + ] } From e6855ff46eacc100efcfd96e318fc37a96f3aea0 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 3 May 2023 10:25:11 +0000 Subject: [PATCH 058/122] [CI] Auto-commit changed files from 'node scripts/generate codeowners' --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 1c60e8be47eb3..ce9fcdbbccfe0 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -478,6 +478,7 @@ x-pack/plugins/notifications @elastic/appex-sharedux packages/kbn-object-versioning @elastic/appex-sharedux x-pack/packages/observability/alert_details @elastic/actionable-observability x-pack/test/cases_api_integration/common/plugins/observability @elastic/response-ops +x-pack/plugins/observability_logs @elastic/infra-monitoring-ui x-pack/plugins/observability @elastic/actionable-observability x-pack/plugins/observability_shared @elastic/actionable-observability x-pack/test/security_api_integration/plugins/oidc_provider @elastic/kibana-security From 597a53a3fd0e8573e43853cab3a6886d2a4c1ce0 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 3 May 2023 10:31:39 +0000 Subject: [PATCH 059/122] [CI] Auto-commit changed files from 'node scripts/build_plugin_list_docs' --- docs/developer/plugin-list.asciidoc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 1755f65193276..2bc4381151a6a 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -583,6 +583,10 @@ Index Management by running this series of requests in Console: the infrastructure monitoring use-case within Kibana. +|{kib-repo}blob/{branch}/x-pack/plugins/observability_logs/README.md[infra] +|This plugin exposes and registers Logs+ features. + + |{kib-repo}blob/{branch}/x-pack/plugins/ingest_pipelines/README.md[ingestPipelines] |The ingest_pipelines plugin provides Kibana support for Elasticsearch's ingest pipelines. From 389778b70c62fa473923086e323220c6e309d401 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Wed, 3 May 2023 14:50:14 +0200 Subject: [PATCH 060/122] feat(observability-logs): try discover customization --- package.json | 1 + tsconfig.base.json | 2 + .../observability_logs/.storybook/main.js | 13 ++ .../observability_logs/.storybook/preview.js | 14 +++ .../plugins/observability_logs/kibana.jsonc | 6 +- .../observability_logs/public/application.tsx | 30 ----- .../public/components/app.tsx | 119 ------------------ .../observability_logs/public/index.scss | 0 .../observability_logs/public/index.ts | 10 +- .../observability_logs/public/plugin.ts | 54 -------- .../observability_logs/public/plugin.tsx | 38 ++++++ .../observability_logs/public/types.ts | 19 +-- .../observability_logs/server/routes/index.ts | 9 +- .../plugins/observability_logs/tsconfig.json | 4 + yarn.lock | 4 + 15 files changed, 105 insertions(+), 218 deletions(-) create mode 100644 x-pack/plugins/observability_logs/.storybook/main.js create mode 100644 x-pack/plugins/observability_logs/.storybook/preview.js delete mode 100644 x-pack/plugins/observability_logs/public/application.tsx delete mode 100644 x-pack/plugins/observability_logs/public/components/app.tsx delete mode 100644 x-pack/plugins/observability_logs/public/index.scss delete mode 100644 x-pack/plugins/observability_logs/public/plugin.ts create mode 100644 x-pack/plugins/observability_logs/public/plugin.tsx diff --git a/package.json b/package.json index f6ecec1fd1343..bffc8e4553b0a 100644 --- a/package.json +++ b/package.json @@ -493,6 +493,7 @@ "@kbn/object-versioning": "link:packages/kbn-object-versioning", "@kbn/observability-alert-details": "link:x-pack/packages/observability/alert_details", "@kbn/observability-fixtures-plugin": "link:x-pack/test/cases_api_integration/common/plugins/observability", + "@kbn/observability-logs-plugin": "link:x-pack/plugins/observability_logs", "@kbn/observability-plugin": "link:x-pack/plugins/observability", "@kbn/observability-shared-plugin": "link:x-pack/plugins/observability_shared", "@kbn/oidc-provider-plugin": "link:x-pack/test/security_api_integration/plugins/oidc_provider", diff --git a/tsconfig.base.json b/tsconfig.base.json index d4a3cc5f3412a..24fad8fdf4d00 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -950,6 +950,8 @@ "@kbn/observability-alert-details/*": ["x-pack/packages/observability/alert_details/*"], "@kbn/observability-fixtures-plugin": ["x-pack/test/cases_api_integration/common/plugins/observability"], "@kbn/observability-fixtures-plugin/*": ["x-pack/test/cases_api_integration/common/plugins/observability/*"], + "@kbn/observability-logs-plugin": ["x-pack/plugins/observability_logs"], + "@kbn/observability-logs-plugin/*": ["x-pack/plugins/observability_logs/*"], "@kbn/observability-plugin": ["x-pack/plugins/observability"], "@kbn/observability-plugin/*": ["x-pack/plugins/observability/*"], "@kbn/observability-shared-plugin": ["x-pack/plugins/observability_shared"], diff --git a/x-pack/plugins/observability_logs/.storybook/main.js b/x-pack/plugins/observability_logs/.storybook/main.js new file mode 100644 index 0000000000000..acfa6b43b2082 --- /dev/null +++ b/x-pack/plugins/observability_logs/.storybook/main.js @@ -0,0 +1,13 @@ +/* + * 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. + */ + +const defaultConfig = require('@kbn/storybook').defaultConfig; + +module.exports = { + ...defaultConfig, + stories: ['../**/*.stories.mdx', ...defaultConfig.stories], +}; diff --git a/x-pack/plugins/observability_logs/.storybook/preview.js b/x-pack/plugins/observability_logs/.storybook/preview.js new file mode 100644 index 0000000000000..59df773136b79 --- /dev/null +++ b/x-pack/plugins/observability_logs/.storybook/preview.js @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const parameters = { + docs: { + source: { + type: 'code', // without this, stories in mdx documents freeze the browser + }, + }, +}; diff --git a/x-pack/plugins/observability_logs/kibana.jsonc b/x-pack/plugins/observability_logs/kibana.jsonc index 2633cc71295b1..d98facb6ef657 100644 --- a/x-pack/plugins/observability_logs/kibana.jsonc +++ b/x-pack/plugins/observability_logs/kibana.jsonc @@ -4,11 +4,11 @@ "owner": "@elastic/infra-monitoring-ui", "description": "This plugin exposes and registers Logs+ features.", "plugin": { - "id": "infra", + "id": "observabilityLogs", "server": true, "browser": true, - "configPath": ["xpack", "obeservability_logs"], - "requiredPlugins": [], + "configPath": ["xpack", "observability_logs"], + "requiredPlugins": ["discover"], "optionalPlugins": [], "requiredBundles": [] } diff --git a/x-pack/plugins/observability_logs/public/application.tsx b/x-pack/plugins/observability_logs/public/application.tsx deleted file mode 100644 index bca48f45dafc1..0000000000000 --- a/x-pack/plugins/observability_logs/public/application.tsx +++ /dev/null @@ -1,30 +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 React from 'react'; -import ReactDOM from 'react-dom'; -import { AppMountParameters, CoreStart } from '@kbn/core/public'; -import { AppPluginStartDependencies } from './types'; -import { ObservabilityLogsApp } from './components/app'; - -export const renderApp = ( - { notifications, http }: CoreStart, - { navigation }: AppPluginStartDependencies, - { appBasePath, element }: AppMountParameters -) => { - ReactDOM.render( - , - element - ); - - return () => ReactDOM.unmountComponentAtNode(element); -}; diff --git a/x-pack/plugins/observability_logs/public/components/app.tsx b/x-pack/plugins/observability_logs/public/components/app.tsx deleted file mode 100644 index 5b874c722c505..0000000000000 --- a/x-pack/plugins/observability_logs/public/components/app.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import React, { useState } from 'react'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage, I18nProvider } from '@kbn/i18n-react'; -import { BrowserRouter as Router } from 'react-router-dom'; - -import { - EuiButton, - EuiHorizontalRule, - EuiPage, - EuiPageBody, - EuiPageContent_Deprecated as EuiPageContent, - EuiPageContentBody_Deprecated as EuiPageContentBody, - EuiPageContentHeader_Deprecated as EuiPageContentHeader, - EuiPageHeader, - EuiTitle, - EuiText, -} from '@elastic/eui'; - -import { CoreStart } from '../../../../../src/core/public'; -import { NavigationPublicPluginStart } from '../../../../../src/plugins/navigation/public'; - -import { PLUGIN_ID, PLUGIN_NAME } from '../../common'; - -interface ObservabilityLogsAppDeps { - basename: string; - notifications: CoreStart['notifications']; - http: CoreStart['http']; - navigation: NavigationPublicPluginStart; -} - -export const ObservabilityLogsApp = ({ - basename, - notifications, - http, - navigation, -}: ObservabilityLogsAppDeps) => { - // Use React hooks to manage state. - const [timestamp, setTimestamp] = useState(); - - const onClickHandler = () => { - // Use the core http service to make a response to the server API. - http.get('/api/observability_logs/example').then((res) => { - setTimestamp(res.time); - // Use the core notifications service to display a success message. - notifications.toasts.addSuccess( - i18n.translate('observabilityLogs.dataUpdated', { - defaultMessage: 'Data updated', - }) - ); - }); - }; - - // Render the application DOM. - // Note that `navigation.ui.TopNavMenu` is a stateful component exported on the `navigation` plugin's start contract. - return ( - - - <> - - - - - -

- -

-
-
- - - -

- -

-
-
- - -

- -

- -

- -

- - - -
-
-
-
-
- -
-
- ); -}; diff --git a/x-pack/plugins/observability_logs/public/index.scss b/x-pack/plugins/observability_logs/public/index.scss deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/x-pack/plugins/observability_logs/public/index.ts b/x-pack/plugins/observability_logs/public/index.ts index 5573d11fa6451..853e6e343e1e4 100644 --- a/x-pack/plugins/observability_logs/public/index.ts +++ b/x-pack/plugins/observability_logs/public/index.ts @@ -1,10 +1,14 @@ -import './index.scss'; +/* + * 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 { ObservabilityLogsPlugin } from './plugin'; -// This exports static code and TypeScript types, -// as well as, Kibana Platform `plugin()` initializer. export function plugin() { return new ObservabilityLogsPlugin(); } + export type { ObservabilityLogsPluginSetup, ObservabilityLogsPluginStart } from './types'; diff --git a/x-pack/plugins/observability_logs/public/plugin.ts b/x-pack/plugins/observability_logs/public/plugin.ts deleted file mode 100644 index d3389c0a8d933..0000000000000 --- a/x-pack/plugins/observability_logs/public/plugin.ts +++ /dev/null @@ -1,54 +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 { i18n } from '@kbn/i18n'; -import { AppMountParameters, CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; -import { - ObservabilityLogsPluginSetup, - ObservabilityLogsPluginStart, - AppPluginStartDependencies, -} from './types'; -import { PLUGIN_NAME } from '../common'; - -export class ObservabilityLogsPlugin - implements Plugin -{ - public setup(core: CoreSetup): ObservabilityLogsPluginSetup { - // Register an application into the side navigation menu - core.application.register({ - id: 'observabilityLogs', - title: PLUGIN_NAME, - appRoute: '/app/observability-logs', - async mount(params: AppMountParameters) { - // Load application bundle - const { renderApp } = await import('./application'); - // Get start services as specified in kibana.json - const [coreStart, depsStart] = await core.getStartServices(); - // Render the application - return renderApp(coreStart, depsStart as AppPluginStartDependencies, params); - }, - }); - - // Return methods that should be available to other plugins - return { - getGreeting() { - return i18n.translate('observabilityLogs.greetingText', { - defaultMessage: 'Hello from {name}!', - values: { - name: PLUGIN_NAME, - }, - }); - }, - }; - } - - public start(core: CoreStart): ObservabilityLogsPluginStart { - return {}; - } - - public stop() {} -} diff --git a/x-pack/plugins/observability_logs/public/plugin.tsx b/x-pack/plugins/observability_logs/public/plugin.tsx new file mode 100644 index 0000000000000..41b21a953951f --- /dev/null +++ b/x-pack/plugins/observability_logs/public/plugin.tsx @@ -0,0 +1,38 @@ +/* + * 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 { CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; +import React from 'react'; +import { + ObservabilityLogsPluginSetup, + ObservabilityLogsPluginStart, + ObservabilityLogsStartDeps, +} from './types'; + +export class ObservabilityLogsPlugin + implements Plugin +{ + public setup(core: CoreSetup): ObservabilityLogsPluginSetup {} + + public start(core: CoreStart, plugins: ObservabilityLogsStartDeps): ObservabilityLogsPluginStart { + const { discover } = plugins; + + discover.customize('log-data-stream-selector', async ({ customizations, stateContainer }) => { + customizations.set({ + id: 'search_bar', + CustomDataViewPicker: () => { + return

Test replace

; + }, + }); + + return () => { + // eslint-disable-next-line no-console + console.log('Cleaning up Logs explorer customizations'); + }; + }); + } +} diff --git a/x-pack/plugins/observability_logs/public/types.ts b/x-pack/plugins/observability_logs/public/types.ts index decbc8f48e0fd..9ef94cfdc7fce 100644 --- a/x-pack/plugins/observability_logs/public/types.ts +++ b/x-pack/plugins/observability_logs/public/types.ts @@ -1,11 +1,14 @@ -import { NavigationPublicPluginStart } from '../../../../src/plugins/navigation/public'; +/* + * 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 type { DiscoverStart } from '@kbn/discover-plugin/public'; -export interface ObservabilityLogsPluginSetup { - getGreeting: () => string; -} -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface ObservabilityLogsPluginStart {} +export type ObservabilityLogsPluginSetup = void; +export type ObservabilityLogsPluginStart = void; -export interface AppPluginStartDependencies { - navigation: NavigationPublicPluginStart; +export interface ObservabilityLogsStartDeps { + discover: DiscoverStart; } diff --git a/x-pack/plugins/observability_logs/server/routes/index.ts b/x-pack/plugins/observability_logs/server/routes/index.ts index 4ca98336c392d..449d5ec992e4d 100644 --- a/x-pack/plugins/observability_logs/server/routes/index.ts +++ b/x-pack/plugins/observability_logs/server/routes/index.ts @@ -1,4 +1,11 @@ -import { IRouter } from '../../../../../src/core/server'; +/* + * 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 { IRouter } from '@kbn/core/server'; export function defineRoutes(router: IRouter) { router.get( diff --git a/x-pack/plugins/observability_logs/tsconfig.json b/x-pack/plugins/observability_logs/tsconfig.json index ff350c9f3d85c..b77a32055d43f 100644 --- a/x-pack/plugins/observability_logs/tsconfig.json +++ b/x-pack/plugins/observability_logs/tsconfig.json @@ -4,5 +4,9 @@ "outDir": "target/types" }, "include": ["../../../typings/**/*", "common/**/*", "public/**/*", "server/**/*"], + "kbn_references": [ + "@kbn/core", + "@kbn/discover-plugin", + ], "exclude": ["target/**/*"] } diff --git a/yarn.lock b/yarn.lock index 0bada4ad317f4..0bc8f96a66559 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4637,6 +4637,10 @@ version "0.0.0" uid "" +"@kbn/observability-logs-plugin@link:x-pack/plugins/observability_logs": + version "0.0.0" + uid "" + "@kbn/observability-plugin@link:x-pack/plugins/observability": version "0.0.0" uid "" From ab4509d1989663aca68ae1f294ed45c9f6d9df88 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 3 May 2023 12:58:59 +0000 Subject: [PATCH 061/122] [CI] Auto-commit changed files from 'node scripts/build_plugin_list_docs' --- docs/developer/plugin-list.asciidoc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 2bc4381151a6a..520bc43fab9fe 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -583,10 +583,6 @@ Index Management by running this series of requests in Console: the infrastructure monitoring use-case within Kibana. -|{kib-repo}blob/{branch}/x-pack/plugins/observability_logs/README.md[infra] -|This plugin exposes and registers Logs+ features. - - |{kib-repo}blob/{branch}/x-pack/plugins/ingest_pipelines/README.md[ingestPipelines] |The ingest_pipelines plugin provides Kibana support for Elasticsearch's ingest pipelines. @@ -645,6 +641,10 @@ Elastic. |This plugin provides shared components and services for use across observability solutions, as well as the observability landing page UI. +|{kib-repo}blob/{branch}/x-pack/plugins/observability_logs/README.md[observabilityLogs] +|This plugin exposes and registers Logs+ features. + + |{kib-repo}blob/{branch}/x-pack/plugins/observability_shared/README.md[observabilityShared] |A plugin that contains components and utilities shared by all Observability plugins. From 592083945dc00084dcbf750137069c03a851c22d Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Thu, 4 May 2023 08:40:19 +0200 Subject: [PATCH 062/122] feat(observability-logs): update profileId --- x-pack/plugins/observability_logs/public/plugin.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/observability_logs/public/plugin.tsx b/x-pack/plugins/observability_logs/public/plugin.tsx index 41b21a953951f..bed6b5209c11e 100644 --- a/x-pack/plugins/observability_logs/public/plugin.tsx +++ b/x-pack/plugins/observability_logs/public/plugin.tsx @@ -21,7 +21,7 @@ export class ObservabilityLogsPlugin public start(core: CoreStart, plugins: ObservabilityLogsStartDeps): ObservabilityLogsPluginStart { const { discover } = plugins; - discover.customize('log-data-stream-selector', async ({ customizations, stateContainer }) => { + discover.customize('observability-logs', async ({ customizations, stateContainer }) => { customizations.set({ id: 'search_bar', CustomDataViewPicker: () => { From f064e25281332fc8fdecd91182891c4ae094a143 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Fri, 5 May 2023 17:08:16 +0200 Subject: [PATCH 063/122] feat(observability-logs): add lazy loading and wip component --- src/plugins/discover/public/index.ts | 1 + .../plugins/observability_logs/kibana.jsonc | 2 +- .../data_stream_selector/constants.ts | 27 +++ .../data_stream_selector.stories.mdx | 0 .../data_stream_selector.test.ts | 0 .../data_stream_selector.tsx | 170 ++++++++++++++++++ .../data_stream_selector.utils.ts | 12 ++ .../components/data_stream_selector/index.ts | 8 + .../custom_data_stream_selector.tsx | 28 +++ .../public/hooks/use_boolean.ts | 36 ++++ .../observability_logs/public/plugin.tsx | 11 +- .../internal_state_container_context.tsx | 13 ++ 12 files changed, 306 insertions(+), 2 deletions(-) create mode 100644 x-pack/plugins/observability_logs/public/components/data_stream_selector/constants.ts create mode 100644 x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.stories.mdx create mode 100644 x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.test.ts create mode 100644 x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx create mode 100644 x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.utils.ts create mode 100644 x-pack/plugins/observability_logs/public/components/data_stream_selector/index.ts create mode 100644 x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx create mode 100644 x-pack/plugins/observability_logs/public/hooks/use_boolean.ts create mode 100644 x-pack/plugins/observability_logs/public/utils/internal_state_container_context.tsx diff --git a/src/plugins/discover/public/index.ts b/src/plugins/discover/public/index.ts index 6b3dc0999410e..8e7ce1804618b 100644 --- a/src/plugins/discover/public/index.ts +++ b/src/plugins/discover/public/index.ts @@ -15,6 +15,7 @@ export function plugin(initializerContext: PluginInitializerContext) { } export type { ISearchEmbeddable, SearchInput } from './embeddable'; +export type { DiscoverStateContainer } from './application/main/services/discover_state'; export { SEARCH_EMBEDDABLE_TYPE } from './embeddable'; export { loadSharingDataHelpers } from './utils'; diff --git a/x-pack/plugins/observability_logs/kibana.jsonc b/x-pack/plugins/observability_logs/kibana.jsonc index d98facb6ef657..fb571771b609c 100644 --- a/x-pack/plugins/observability_logs/kibana.jsonc +++ b/x-pack/plugins/observability_logs/kibana.jsonc @@ -8,7 +8,7 @@ "server": true, "browser": true, "configPath": ["xpack", "observability_logs"], - "requiredPlugins": ["discover"], + "requiredPlugins": ["discover", "kibanaUtils"], "optionalPlugins": [], "requiredBundles": [] } diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/constants.ts b/x-pack/plugins/observability_logs/public/components/data_stream_selector/constants.ts new file mode 100644 index 0000000000000..9108eea476624 --- /dev/null +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/constants.ts @@ -0,0 +1,27 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const POPOVER_ID = 'data-stream-selector-popover'; + +export const DATA_VIEW_POPOVER_CONTENT_WIDTH = 300; + +export const selectViewLabel = i18n.translate( + 'xpack.observabilityLogs.dataStreamSelector.selectView', + { defaultMessage: 'Select view' } +); + +export const integrationsLabel = i18n.translate( + 'xpack.observabilityLogs.dataStreamSelector.integrations', + { defaultMessage: 'Integrations' } +); + +export const uncategorizedLabel = i18n.translate( + 'xpack.observabilityLogs.dataStreamSelector.uncategorized', + { defaultMessage: 'Uncategorized' } +); diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.stories.mdx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.stories.mdx new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.test.ts b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.test.ts new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx new file mode 100644 index 0000000000000..61855617a2e71 --- /dev/null +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx @@ -0,0 +1,170 @@ +/* + * 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 { + EuiButton, + EuiButtonProps, + EuiContextMenu, + EuiContextMenuItem, + EuiContextMenuPanel, + EuiHorizontalRule, + EuiIcon, + EuiPopover, + useIsWithinBreakpoints, +} from '@elastic/eui'; +import React from 'react'; +import { useBoolean } from '../../hooks/use_boolean'; +import { useInternalStateSelector } from '../../utils/internal_state_container_context'; +import { + DATA_VIEW_POPOVER_CONTENT_WIDTH, + integrationsLabel, + POPOVER_ID, + selectViewLabel, + uncategorizedLabel, +} from './constants'; +import { getPopoverButtonStyles } from './data_stream_selector.utils'; + +interface DataStreamSelectorProps { + title: string; + integrations: any[]; + uncategorizedStreams: any[]; + onUncategorizedClick: () => void; +} + +export function DataStreamSelector({ + title, + integrations, + uncategorizedStreams, + onUncategorizedClick, +}: DataStreamSelectorProps) { + const [isPopoverOpen, { off: closePopover, toggle: togglePopover }] = useBoolean(false); + + const panels = [ + { + id: 0, + width: DATA_VIEW_POPOVER_CONTENT_WIDTH, + items: [ + { + name: integrationsLabel, + panel: 1, + }, + { + name: uncategorizedLabel, + onClick: onUncategorizedClick, + panel: 2, + }, + ], + }, + { + id: 1, + title: integrationsLabel, + width: DATA_VIEW_POPOVER_CONTENT_WIDTH, + items: [ + { + name: 'PDF reports', + icon: 'user', + onClick: () => { + closePopover(); + }, + }, + { + name: 'Permalinks', + icon: 'user', + onClick: () => { + closePopover(); + }, + }, + ], + }, + { + id: 2, + title: uncategorizedLabel, + width: DATA_VIEW_POPOVER_CONTENT_WIDTH, + content:
, + }, + ]; + + const contextPanelItems = [ + , + , + , + ]; + + const button = ( + + {title} + + ); + + return ( + + + + ); +} + +interface DataStreamButtonProps extends EuiButtonProps { + isAdHocSelected: boolean; + onClick: () => void; +} + +const DataStreamButton = ({ + children, + isAdHocSelected = false, + ...props +}: DataStreamButtonProps) => { + const isMobile = useIsWithinBreakpoints(['xs', 's']); + + const buttonStyles = getPopoverButtonStyles({ fullWidth: isMobile }); + + return ( + + {isAdHocSelected && } + {children} + + ); +}; + +const SearchControls = () => { + return Here goes the search; + // return ( + // + // {search} + + // + // + // + // + // ); +}; diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.utils.ts b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.utils.ts new file mode 100644 index 0000000000000..3ea2a65b37cda --- /dev/null +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.utils.ts @@ -0,0 +1,12 @@ +/* + * 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 { DATA_VIEW_POPOVER_CONTENT_WIDTH } from './constants'; + +export const getPopoverButtonStyles = ({ fullWidth }: { fullWidth?: boolean }) => ({ + maxWidth: fullWidth ? undefined : DATA_VIEW_POPOVER_CONTENT_WIDTH, +}); diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/index.ts b/x-pack/plugins/observability_logs/public/components/data_stream_selector/index.ts new file mode 100644 index 0000000000000..ec33977320e7e --- /dev/null +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/index.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export * from './data_stream_selector'; diff --git a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx new file mode 100644 index 0000000000000..8be449dbd48dd --- /dev/null +++ b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx @@ -0,0 +1,28 @@ +/* + * 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 React from 'react'; +import { DiscoverStateContainer } from '@kbn/discover-plugin/public'; +import { DataStreamSelector } from '../components/data_stream_selector'; +import { useDataView } from '../utils/internal_state_container_context'; + +interface Props { + stateContainer: DiscoverStateContainer; +} + +export function CustomDataStreamSelector({ stateContainer }: Props) { + const dataView = useDataView(); + + return ( + console.log('fetch uncategorized streams')} + /> + ); +} diff --git a/x-pack/plugins/observability_logs/public/hooks/use_boolean.ts b/x-pack/plugins/observability_logs/public/hooks/use_boolean.ts new file mode 100644 index 0000000000000..321dddf627eda --- /dev/null +++ b/x-pack/plugins/observability_logs/public/hooks/use_boolean.ts @@ -0,0 +1,36 @@ +/* + * 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 { useMemo } from 'react'; +import useToggle from 'react-use/lib/useToggle'; + +export type VoidHandler = () => void; + +export type DispatchWithOptionalAction = (_arg?: Type | unknown) => void; + +export interface UseBooleanHandlers { + on: VoidHandler; + off: VoidHandler; + toggle: DispatchWithOptionalAction; +} + +export type UseBooleanResult = [boolean, UseBooleanHandlers]; + +export const useBoolean = (initialValue: boolean = false): UseBooleanResult => { + const [value, toggle] = useToggle(initialValue); + + const handlers = useMemo( + () => ({ + toggle, + on: () => toggle(true), + off: () => toggle(false), + }), + [toggle] + ); + + return [value, handlers]; +}; diff --git a/x-pack/plugins/observability_logs/public/plugin.tsx b/x-pack/plugins/observability_logs/public/plugin.tsx index bed6b5209c11e..d8cf2462f1a05 100644 --- a/x-pack/plugins/observability_logs/public/plugin.tsx +++ b/x-pack/plugins/observability_logs/public/plugin.tsx @@ -12,6 +12,7 @@ import { ObservabilityLogsPluginStart, ObservabilityLogsStartDeps, } from './types'; +import { InternalStateProvider } from './utils/internal_state_container_context'; export class ObservabilityLogsPlugin implements Plugin @@ -22,10 +23,18 @@ export class ObservabilityLogsPlugin const { discover } = plugins; discover.customize('observability-logs', async ({ customizations, stateContainer }) => { + const { CustomDataStreamSelector } = await import( + './customizations/custom_data_stream_selector' + ); + customizations.set({ id: 'search_bar', CustomDataViewPicker: () => { - return

Test replace

; + return ( + + + + ); }, }); diff --git a/x-pack/plugins/observability_logs/public/utils/internal_state_container_context.tsx b/x-pack/plugins/observability_logs/public/utils/internal_state_container_context.tsx new file mode 100644 index 0000000000000..d07daa1ebf14f --- /dev/null +++ b/x-pack/plugins/observability_logs/public/utils/internal_state_container_context.tsx @@ -0,0 +1,13 @@ +/* + * 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 { DiscoverStateContainer } from '@kbn/discover-plugin/public'; +import { createStateContainerReactHelpers } from '@kbn/kibana-utils-plugin/common'; + +export const { Provider: InternalStateProvider, useSelector: useInternalStateSelector } = + createStateContainerReactHelpers(); + +export const useDataView = () => useInternalStateSelector((state) => state.dataView!); From aada1ce76645592d467346643bb7650d5ed64c16 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Mon, 8 May 2023 09:19:16 +0200 Subject: [PATCH 064/122] feat(observability-logs): add story for data stream selector --- src/dev/storybook/aliases.ts | 1 + .../data_stream_selector.stories.mdx | 0 .../data_stream_selector.stories.tsx | 30 +++++++++++++++++++ .../data_stream_selector.tsx | 15 ++++------ 4 files changed, 36 insertions(+), 10 deletions(-) delete mode 100644 x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.stories.mdx create mode 100644 x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.stories.tsx diff --git a/src/dev/storybook/aliases.ts b/src/dev/storybook/aliases.ts index da762f8f55725..d9ff066807b1a 100644 --- a/src/dev/storybook/aliases.ts +++ b/src/dev/storybook/aliases.ts @@ -42,6 +42,7 @@ export const storybookAliases = { kibana_react: 'src/plugins/kibana_react/.storybook', lists: 'x-pack/plugins/lists/.storybook', observability: 'x-pack/plugins/observability/.storybook', + observability_logs: 'x-pack/plugins/observability_logs/.storybook', presentation: 'src/plugins/presentation_util/storybook', security_solution: 'x-pack/plugins/security_solution/.storybook', security_solution_packages: 'packages/security-solution/storybook/config', diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.stories.mdx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.stories.mdx deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.stories.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.stories.tsx new file mode 100644 index 0000000000000..22e49c4271106 --- /dev/null +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.stories.tsx @@ -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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { I18nProvider } from '@kbn/i18n-react'; +import { Story } from '@storybook/react'; +import React from 'react'; +import { DataStreamSelector, DataStreamSelectorProps } from './data_stream_selector'; + +export default { + component: DataStreamSelector, + title: 'observability_logs/DataStreamSelector', + decorators: [(wrappedStory) => {wrappedStory()}], +}; + +const DataStreamSelectorTemplate: Story = (args) => ( + +); + +export const Basic = DataStreamSelectorTemplate.bind({}); +Basic.args = { + title: 'Current stream name', + integrations: [], + uncategorizedStreams: [], + // eslint-disable-next-line no-console + onUncategorizedClick: () => console.log('Load uncategorized streams...'), +}; diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx index 61855617a2e71..5efd50cb45fba 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx @@ -28,7 +28,7 @@ import { } from './constants'; import { getPopoverButtonStyles } from './data_stream_selector.utils'; -interface DataStreamSelectorProps { +export interface DataStreamSelectorProps { title: string; integrations: any[]; uncategorizedStreams: any[]; @@ -41,6 +41,7 @@ export function DataStreamSelector({ uncategorizedStreams, onUncategorizedClick, }: DataStreamSelectorProps) { + const isMobile = useIsWithinBreakpoints(['xs', 's']); const [isPopoverOpen, { off: closePopover, toggle: togglePopover }] = useBoolean(false); const panels = [ @@ -95,7 +96,7 @@ export function DataStreamSelector({ ]; const button = ( - + {title} ); @@ -107,7 +108,7 @@ export function DataStreamSelector({ isOpen={isPopoverOpen} closePopover={closePopover} panelPaddingSize="none" - display="block" + {...(isMobile && { display: 'block' })} buffer={8} > @@ -130,13 +131,7 @@ const DataStreamButton = ({ const buttonStyles = getPopoverButtonStyles({ fullWidth: isMobile }); return ( - + {isAdHocSelected && } {children} From 47413ef98e4b3270462ccbb0a5af53a9f3ae6767 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Mon, 8 May 2023 16:11:10 +0200 Subject: [PATCH 065/122] feat(observability-logs): create nested panels --- x-pack/plugins/fleet/public/index.ts | 1 + .../plugins/observability_logs/kibana.jsonc | 2 +- .../data_stream_selector.stories.tsx | 42 ++++++++++- .../data_stream_selector.tsx | 72 ++++++++++++++----- .../custom_data_stream_selector.tsx | 48 ++++++++++++- 5 files changed, 143 insertions(+), 22 deletions(-) diff --git a/x-pack/plugins/fleet/public/index.ts b/x-pack/plugins/fleet/public/index.ts index a745c59319e8c..0dcfd592ae749 100644 --- a/x-pack/plugins/fleet/public/index.ts +++ b/x-pack/plugins/fleet/public/index.ts @@ -55,6 +55,7 @@ export { pagePathGetters } from './constants'; export { pkgKeyFromPackageInfo } from './services'; export type { CustomAssetsAccordionProps } from './components/custom_assets_accordion'; export { CustomAssetsAccordion } from './components/custom_assets_accordion'; +export { PackageIcon } from './components/package_icon'; // Export Package editor components for custom editors export { PackagePolicyEditorDatastreamPipelines } from './applications/fleet/sections/agent_policy/create_package_policy_page/components/datastream_pipelines'; export type { PackagePolicyEditorDatastreamPipelinesProps } from './applications/fleet/sections/agent_policy/create_package_policy_page/components/datastream_pipelines'; diff --git a/x-pack/plugins/observability_logs/kibana.jsonc b/x-pack/plugins/observability_logs/kibana.jsonc index fb571771b609c..8a69b3c92f8c1 100644 --- a/x-pack/plugins/observability_logs/kibana.jsonc +++ b/x-pack/plugins/observability_logs/kibana.jsonc @@ -8,7 +8,7 @@ "server": true, "browser": true, "configPath": ["xpack", "observability_logs"], - "requiredPlugins": ["discover", "kibanaUtils"], + "requiredPlugins": ["discover", "fleet","kibanaUtils"], "optionalPlugins": [], "requiredBundles": [] } diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.stories.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.stories.tsx index 22e49c4271106..b93fee4a129a5 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.stories.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.stories.tsx @@ -23,8 +23,48 @@ const DataStreamSelectorTemplate: Story = (args) => ( export const Basic = DataStreamSelectorTemplate.bind({}); Basic.args = { title: 'Current stream name', - integrations: [], + integrations: [ + { + name: 'atlassian_jira', + version: '1.8.0', + status: 'installed', + dataStreams: [ + { + name: 'Atlassian metrics stream', + title: 'metrics-*', + }, + { + name: 'Atlassian secondary', + title: 'metrics-*', + }, + ], + }, + { + name: 'docker', + version: '2.4.3', + status: 'installed', + dataStreams: [ + { + name: 'Docker stream', + title: 'metrics-*', + }, + ], + }, + { + name: 'system', + version: '1.27.1', + status: 'installed', + dataStreams: [ + { + name: 'System metrics logs', + title: 'metrics-*', + }, + ], + }, + ], uncategorizedStreams: [], // eslint-disable-next-line no-console + onStreamSelected: async (stream) => console.log('Create ad hoc view for stream: ', stream), + // eslint-disable-next-line no-console onUncategorizedClick: () => console.log('Load uncategorized streams...'), }; diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx index 5efd50cb45fba..8119690363925 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import React, { useMemo } from 'react'; import { EuiButton, EuiButtonProps, @@ -16,9 +17,10 @@ import { EuiPopover, useIsWithinBreakpoints, } from '@elastic/eui'; -import React from 'react'; +import { PackageIcon } from '@kbn/fleet-plugin/public'; + import { useBoolean } from '../../hooks/use_boolean'; -import { useInternalStateSelector } from '../../utils/internal_state_container_context'; + import { DATA_VIEW_POPOVER_CONTENT_WIDTH, integrationsLabel, @@ -32,6 +34,7 @@ export interface DataStreamSelectorProps { title: string; integrations: any[]; uncategorizedStreams: any[]; + onStreamSelected: (dataStream: any) => Promise; onUncategorizedClick: () => void; } @@ -39,11 +42,36 @@ export function DataStreamSelector({ title, integrations, uncategorizedStreams, + onStreamSelected, onUncategorizedClick, }: DataStreamSelectorProps) { const isMobile = useIsWithinBreakpoints(['xs', 's']); const [isPopoverOpen, { off: closePopover, toggle: togglePopover }] = useBoolean(false); + const handleStreamSelection = (dataStream) => { + onStreamSelected(dataStream).then(closePopover); + }; + + const { items: integrationItems, panels: integrationPanels } = useMemo( + () => + buildItemsTree({ + type: 'integration', + list: integrations, + onStreamSelected: handleStreamSelection, + }), + [integrations] + ); + + const { items: uncategorizedStreamsItems, panels: uncategorizedStreamsPanels } = useMemo( + () => + buildItemsTree({ + type: 'uncategorizedStreams', + list: uncategorizedStreams, + onStreamSelected: handleStreamSelection, + }), + [uncategorizedStreams] + ); + const panels = [ { id: 0, @@ -64,29 +92,16 @@ export function DataStreamSelector({ id: 1, title: integrationsLabel, width: DATA_VIEW_POPOVER_CONTENT_WIDTH, - items: [ - { - name: 'PDF reports', - icon: 'user', - onClick: () => { - closePopover(); - }, - }, - { - name: 'Permalinks', - icon: 'user', - onClick: () => { - closePopover(); - }, - }, - ], + items: integrationItems, }, { id: 2, title: uncategorizedLabel, width: DATA_VIEW_POPOVER_CONTENT_WIDTH, - content:
, + items: uncategorizedStreamsItems, }, + ...integrationPanels, + ...uncategorizedStreamsPanels, ]; const contextPanelItems = [ @@ -163,3 +178,22 @@ const SearchControls = () => { // // ); }; + +const buildItemsTree = ({ type, list, onStreamSelected }) => { + const items = list.map((entry) => ({ + name: entry.name, + icon: , + panel: `${type}-${entry.name}`, + })); + + const panels = list.map((entry) => ({ + id: `${type}-${entry.name}`, + title: entry.name, + items: entry.dataStreams.map((stream) => ({ + name: stream.name, + onClick: () => onStreamSelected(stream), + })), + })); + + return { items, panels }; +}; diff --git a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx index 8be449dbd48dd..2c9d1d2d4ea16 100644 --- a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx @@ -15,14 +15,60 @@ interface Props { } export function CustomDataStreamSelector({ stateContainer }: Props) { + // Container component, here goes all the state management and custom logic usage to keep the DataStreamSelector presentational. const dataView = useDataView(); + const handleStreamSelection = async (dataStream) => { + return stateContainer.actions.onCreateDefaultAdHocDataView(dataStream.title); + }; + return ( console.log('fetch uncategorized streams')} /> ); } + +const mockIntegrations = [ + { + name: 'atlassian_jira', + version: '1.8.0', + status: 'installed', + dataStreams: [ + { + name: 'Atlassian metrics stream', + title: 'metrics-*', + }, + { + name: 'Atlassian secondary', + title: 'metrics-*', + }, + ], + }, + { + name: 'docker', + version: '2.4.3', + status: 'installed', + dataStreams: [ + { + name: 'Docker stream', + title: 'metrics-*', + }, + ], + }, + { + name: 'system', + version: '1.27.1', + status: 'installed', + dataStreams: [ + { + name: 'System metrics logs', + title: 'metrics-*', + }, + ], + }, +]; From aa8b011180a058c580d0887f9e96e967634ae3e0 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 8 May 2023 14:48:30 +0000 Subject: [PATCH 066/122] [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' --- .../data_stream_selector/data_stream_selector.test.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.test.ts b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.test.ts index e69de29bb2d1d..1fec1c76430eb 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.test.ts +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.test.ts @@ -0,0 +1,6 @@ +/* + * 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. + */ From b5c99937d9534c3ce4d426d7499e667b5056a785 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 8 May 2023 14:54:04 +0000 Subject: [PATCH 067/122] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- x-pack/plugins/observability_logs/tsconfig.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/x-pack/plugins/observability_logs/tsconfig.json b/x-pack/plugins/observability_logs/tsconfig.json index b77a32055d43f..b576bc2260046 100644 --- a/x-pack/plugins/observability_logs/tsconfig.json +++ b/x-pack/plugins/observability_logs/tsconfig.json @@ -7,6 +7,10 @@ "kbn_references": [ "@kbn/core", "@kbn/discover-plugin", + "@kbn/i18n", + "@kbn/i18n-react", + "@kbn/fleet-plugin", + "@kbn/kibana-utils-plugin", ], "exclude": ["target/**/*"] } From 1ab9ae52d6ec770a4b8dc3e742138015d9265064 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Tue, 9 May 2023 12:02:59 +0200 Subject: [PATCH 068/122] feat(observability-logs): apply customization to hide discover controls --- .../plugins/observability_logs/public/plugin.tsx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/x-pack/plugins/observability_logs/public/plugin.tsx b/x-pack/plugins/observability_logs/public/plugin.tsx index d8cf2462f1a05..c94c92dd86f6e 100644 --- a/x-pack/plugins/observability_logs/public/plugin.tsx +++ b/x-pack/plugins/observability_logs/public/plugin.tsx @@ -22,6 +22,9 @@ export class ObservabilityLogsPlugin public start(core: CoreStart, plugins: ObservabilityLogsStartDeps): ObservabilityLogsPluginStart { const { discover } = plugins; + /** + * Replace the DataViewPicker with a custom DataStreamSelector to access only integrations streams + */ discover.customize('observability-logs', async ({ customizations, stateContainer }) => { const { CustomDataStreamSelector } = await import( './customizations/custom_data_stream_selector' @@ -38,6 +41,18 @@ export class ObservabilityLogsPlugin }, }); + /** + * Hide New, Open and Save settings to prevent working with saved views. + */ + customizations.set({ + id: 'top_nav', + defaultMenu: { + new: { disabled: true }, + open: { disabled: true }, + save: { disabled: true }, + }, + }); + return () => { // eslint-disable-next-line no-console console.log('Cleaning up Logs explorer customizations'); From 652c7c2e844c0f5969d85d290899c6e8ae381101 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Tue, 9 May 2023 12:13:15 +0200 Subject: [PATCH 069/122] feat(observability-logs): add integration types --- .../common/integrations/index.ts | 8 + .../common/integrations/types.ts | 144 ++++++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 x-pack/plugins/observability_logs/common/integrations/index.ts create mode 100644 x-pack/plugins/observability_logs/common/integrations/types.ts diff --git a/x-pack/plugins/observability_logs/common/integrations/index.ts b/x-pack/plugins/observability_logs/common/integrations/index.ts new file mode 100644 index 0000000000000..6cc0ccaa93a6d --- /dev/null +++ b/x-pack/plugins/observability_logs/common/integrations/index.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export * from './types'; diff --git a/x-pack/plugins/observability_logs/common/integrations/types.ts b/x-pack/plugins/observability_logs/common/integrations/types.ts new file mode 100644 index 0000000000000..6cd0af2ff6b3c --- /dev/null +++ b/x-pack/plugins/observability_logs/common/integrations/types.ts @@ -0,0 +1,144 @@ +/* + * 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 { indexPatternRt } from '@kbn/io-ts-utils'; +import * as rt from 'io-ts'; + +export const dataStreamRT = rt.type({ + name: rt.string, + title: indexPatternRt, +}); + +const integrationStatusRT = rt.union([ + rt.literal('installed'), + rt.literal('installing'), + rt.literal('install_failed'), +]); + +export const integrationRT = rt.type({ + name: rt.string, + status: integrationStatusRT, + version: rt.string, + dataStreams: rt.array(dataStreamRT), +}); + +export type DataStream = rt.TypeOf; +export type Integration = rt.TypeOf; + +// export interface LogViewsStaticConfig { +// messageFields: string[]; +// } + +// export const logViewOriginRT = rt.keyof({ +// stored: null, +// internal: null, +// inline: null, +// 'infra-source-stored': null, +// 'infra-source-internal': null, +// 'infra-source-fallback': null, +// }); +// export type LogViewOrigin = rt.TypeOf; + +// // Kibana data views +// export const logDataViewReferenceRT = rt.type({ +// type: rt.literal('data_view'), +// dataViewId: rt.string, +// }); + +// export type LogDataViewReference = rt.TypeOf; + +// // Index name +// export const logIndexNameReferenceRT = rt.type({ +// type: rt.literal('index_name'), +// indexName: rt.string, +// }); +// export type LogIndexNameReference = rt.TypeOf; + +// export const logIndexReferenceRT = rt.union([logDataViewReferenceRT, logIndexNameReferenceRT]); +// export type LogIndexReference = rt.TypeOf; + +// const logViewCommonColumnConfigurationRT = rt.strict({ +// id: rt.string, +// }); + +// const logViewTimestampColumnConfigurationRT = rt.strict({ +// timestampColumn: logViewCommonColumnConfigurationRT, +// }); + +// const logViewMessageColumnConfigurationRT = rt.strict({ +// messageColumn: logViewCommonColumnConfigurationRT, +// }); + +// export const logViewFieldColumnConfigurationRT = rt.strict({ +// fieldColumn: rt.intersection([ +// logViewCommonColumnConfigurationRT, +// rt.strict({ +// field: rt.string, +// }), +// ]), +// }); + +// export const logViewColumnConfigurationRT = rt.union([ +// logViewTimestampColumnConfigurationRT, +// logViewMessageColumnConfigurationRT, +// logViewFieldColumnConfigurationRT, +// ]); +// export type LogViewColumnConfiguration = rt.TypeOf; + +// export const logViewAttributesRT = rt.strict({ +// name: rt.string, +// description: rt.string, +// logIndices: logIndexReferenceRT, +// logColumns: rt.array(logViewColumnConfigurationRT), +// }); + +// export type LogViewAttributes = rt.TypeOf; + +// export const logViewRT = rt.exact( +// rt.intersection([ +// rt.type({ +// id: rt.string, +// origin: logViewOriginRT, +// attributes: logViewAttributesRT, +// }), +// rt.partial({ +// updatedAt: rt.number, +// version: rt.string, +// }), +// ]) +// ); +// export type LogView = rt.TypeOf; + +// export const logViewIndexStatusRT = rt.keyof({ +// available: null, +// empty: null, +// missing: null, +// unknown: null, +// }); +// export type LogViewIndexStatus = rt.TypeOf; + +// export const logViewStatusRT = rt.strict({ +// index: logViewIndexStatusRT, +// }); +// export type LogViewStatus = rt.TypeOf; + +// export const persistedLogViewReferenceRT = rt.type({ +// logViewId: rt.string, +// type: rt.literal('log-view-reference'), +// }); + +// export type PersistedLogViewReference = rt.TypeOf; + +// export const inlineLogViewReferenceRT = rt.type({ +// type: rt.literal('log-view-inline'), +// id: rt.string, +// attributes: logViewAttributesRT, +// }); + +// export const logViewReferenceRT = rt.union([persistedLogViewReferenceRT, inlineLogViewReferenceRT]); + +// export type LogViewReference = rt.TypeOf; From f1b4b7df15aa3cf81f75622440a5908f3c83cb48 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Tue, 9 May 2023 12:14:13 +0200 Subject: [PATCH 070/122] feat(observability-logs): update integrations tree --- .../data_stream_selector/constants.ts | 2 + .../data_stream_selector.stories.tsx | 2 +- .../data_stream_selector.tsx | 211 +++++++++++------- .../custom_data_stream_selector.tsx | 4 +- 4 files changed, 134 insertions(+), 85 deletions(-) diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/constants.ts b/x-pack/plugins/observability_logs/public/components/data_stream_selector/constants.ts index 9108eea476624..0c66cdd9c7ce3 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/constants.ts +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/constants.ts @@ -8,6 +8,8 @@ import { i18n } from '@kbn/i18n'; export const POPOVER_ID = 'data-stream-selector-popover'; +export const INTEGRATION_PANEL_ID = 'integrations_panel'; +export const UNCATEGORIZED_STREAMS_PANEL_ID = 'uncategorized_streams_panel'; export const DATA_VIEW_POPOVER_CONTENT_WIDTH = 300; diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.stories.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.stories.tsx index b93fee4a129a5..f89611c45a1c4 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.stories.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.stories.tsx @@ -62,7 +62,7 @@ Basic.args = { ], }, ], - uncategorizedStreams: [], + uncategorizedStreams: [{ name: 'metrics-*' }, { name: 'logs-*' }], // eslint-disable-next-line no-console onStreamSelected: async (stream) => console.log('Create ad hoc view for stream: ', stream), // eslint-disable-next-line no-console diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx index 8119690363925..6d85114159810 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx @@ -5,13 +5,19 @@ * 2.0. */ -import React, { useMemo } from 'react'; +import React, { useMemo, useState } from 'react'; import { EuiButton, + EuiButtonGroup, EuiButtonProps, EuiContextMenu, EuiContextMenuItem, EuiContextMenuPanel, + EuiContextMenuPanelDescriptor, + EuiContextMenuPanelItemDescriptor, + EuiFieldSearch, + EuiFlexGroup, + EuiFlexItem, EuiHorizontalRule, EuiIcon, EuiPopover, @@ -19,17 +25,22 @@ import { } from '@elastic/eui'; import { PackageIcon } from '@kbn/fleet-plugin/public'; -import { useBoolean } from '../../hooks/use_boolean'; +import { i18n } from '@kbn/i18n'; import { DATA_VIEW_POPOVER_CONTENT_WIDTH, integrationsLabel, + INTEGRATION_PANEL_ID, POPOVER_ID, selectViewLabel, uncategorizedLabel, + UNCATEGORIZED_STREAMS_PANEL_ID, } from './constants'; import { getPopoverButtonStyles } from './data_stream_selector.utils'; +import { useBoolean } from '../../hooks/use_boolean'; + +import type { DataStream, Integration } from '../../../common/integrations'; export interface DataStreamSelectorProps { title: string; integrations: any[]; @@ -38,6 +49,13 @@ export interface DataStreamSelectorProps { onUncategorizedClick: () => void; } +type CurrentPanelId = + | typeof INTEGRATION_PANEL_ID + | typeof UNCATEGORIZED_STREAMS_PANEL_ID + | `integration-${string}`; + +type StreamSelectionHandler = (stream: DataStream) => void; + export function DataStreamSelector({ title, integrations, @@ -48,66 +66,50 @@ export function DataStreamSelector({ const isMobile = useIsWithinBreakpoints(['xs', 's']); const [isPopoverOpen, { off: closePopover, toggle: togglePopover }] = useBoolean(false); - const handleStreamSelection = (dataStream) => { - onStreamSelected(dataStream).then(closePopover); - }; - - const { items: integrationItems, panels: integrationPanels } = useMemo( - () => - buildItemsTree({ - type: 'integration', - list: integrations, - onStreamSelected: handleStreamSelection, - }), - [integrations] - ); + const [currentPanel, setCurrentPanel] = useState(INTEGRATION_PANEL_ID); - const { items: uncategorizedStreamsItems, panels: uncategorizedStreamsPanels } = useMemo( - () => - buildItemsTree({ - type: 'uncategorizedStreams', - list: uncategorizedStreams, - onStreamSelected: handleStreamSelection, - }), - [uncategorizedStreams] - ); + const { items: integrationItems, panels: integrationPanels } = useMemo(() => { + const handleStreamSelection: StreamSelectionHandler = (dataStream) => { + onStreamSelected(dataStream).then(closePopover); + }; + + return buildIntegrationsTree({ + list: integrations, + onItemClick: setCurrentPanel, + onStreamSelected: handleStreamSelection, + }); + }, [closePopover, integrations, onStreamSelected]); const panels = [ { - id: 0, + id: INTEGRATION_PANEL_ID, + title: integrationsLabel, width: DATA_VIEW_POPOVER_CONTENT_WIDTH, items: [ - { - name: integrationsLabel, - panel: 1, - }, { name: uncategorizedLabel, onClick: onUncategorizedClick, - panel: 2, + panel: UNCATEGORIZED_STREAMS_PANEL_ID, }, + ...integrationItems, ], }, { - id: 1, - title: integrationsLabel, - width: DATA_VIEW_POPOVER_CONTENT_WIDTH, - items: integrationItems, - }, - { - id: 2, + id: UNCATEGORIZED_STREAMS_PANEL_ID, title: uncategorizedLabel, width: DATA_VIEW_POPOVER_CONTENT_WIDTH, - items: uncategorizedStreamsItems, + items: uncategorizedStreams.map((stream) => ({ + name: stream.name, + onClick: () => onStreamSelected(stream), + })), }, ...integrationPanels, - ...uncategorizedStreamsPanels, ]; const contextPanelItems = [ - , - , - , + // , + , + , ]; const button = ( @@ -153,47 +155,90 @@ const DataStreamButton = ({ ); }; -const SearchControls = () => { - return Here goes the search; - // return ( - // - // {search} - - // - // - // - // - // ); +type SearchStrategy = 'integrations' | 'integrationsStreams' | 'uncategorizedStreams'; + +interface SearchControlsProps { + strategy: SearchStrategy; +} + +const SearchControls = ({ strategy }: SearchControlsProps) => { + /** + * TODO: implement 3 different search strategies + * - Search integrations: API request + * - Search integrations streams: in memory sorting + * - Search uncategorized streams: API request + */ + const { search, searchByText, sortByDirection, isSearching } = useSearch(strategy); + + return ( + + + + + + + + + + + ); }; -const buildItemsTree = ({ type, list, onStreamSelected }) => { - const items = list.map((entry) => ({ - name: entry.name, - icon: , - panel: `${type}-${entry.name}`, - })); - - const panels = list.map((entry) => ({ - id: `${type}-${entry.name}`, - title: entry.name, - items: entry.dataStreams.map((stream) => ({ - name: stream.name, - onClick: () => onStreamSelected(stream), - })), - })); - - return { items, panels }; +interface IntegrationsTree { + items: EuiContextMenuPanelItemDescriptor[]; + panels: EuiContextMenuPanelDescriptor[]; +} + +interface IntegrationsTreeParams { + list: Integration[]; + onItemClick: (id: CurrentPanelId) => void; + onStreamSelected: StreamSelectionHandler; +} + +const buildIntegrationsTree = ({ list, onItemClick, onStreamSelected }: IntegrationsTreeParams) => { + return list.reduce( + (res: IntegrationsTree, entry) => { + const entryId: CurrentPanelId = `integration-${entry.name}`; + + res.items.push({ + name: entry.name, + onClick: () => onItemClick(entryId), + icon: , + panel: entryId, + }); + + res.panels.push({ + id: entryId, + title: entry.name, + width: DATA_VIEW_POPOVER_CONTENT_WIDTH, + items: entry.dataStreams.map((stream) => ({ + name: stream.name, + onClick: () => onStreamSelected(stream), + })), + }); + + return res; + }, + { items: [], panels: [] } + ); }; diff --git a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx index 2c9d1d2d4ea16..3811557d3096e 100644 --- a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx @@ -26,13 +26,15 @@ export function CustomDataStreamSelector({ stateContainer }: Props) { console.log('fetch uncategorized streams')} /> ); } +const mockUncategorized = [{ name: 'metrics-*' }, { name: 'logs-*' }]; + const mockIntegrations = [ { name: 'atlassian_jira', From 5e3156a7d5b8b80e6d96b233f443a3825e9aa5d7 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Tue, 9 May 2023 14:23:58 +0200 Subject: [PATCH 071/122] refactor(observability-logs): remove temporary index icon from data view selector --- .../data_stream_selector/constants.ts | 2 + .../data_stream_selector.tsx | 40 +++++++++---------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/constants.ts b/x-pack/plugins/observability_logs/public/components/data_stream_selector/constants.ts index 0c66cdd9c7ce3..fe62a939be3fd 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/constants.ts +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/constants.ts @@ -13,6 +13,8 @@ export const UNCATEGORIZED_STREAMS_PANEL_ID = 'uncategorized_streams_panel'; export const DATA_VIEW_POPOVER_CONTENT_WIDTH = 300; +export const contextMenuStyles = { maxHeight: 320 }; + export const selectViewLabel = i18n.translate( 'xpack.observabilityLogs.dataStreamSelector.selectView', { defaultMessage: 'Select view' } diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx index 6d85114159810..4ebc4ed8d1164 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx @@ -19,7 +19,6 @@ import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, - EuiIcon, EuiPopover, useIsWithinBreakpoints, } from '@elastic/eui'; @@ -28,6 +27,7 @@ import { PackageIcon } from '@kbn/fleet-plugin/public'; import { i18n } from '@kbn/i18n'; import { + contextMenuStyles, DATA_VIEW_POPOVER_CONTENT_WIDTH, integrationsLabel, INTEGRATION_PANEL_ID, @@ -107,13 +107,19 @@ export function DataStreamSelector({ ]; const contextPanelItems = [ - // , + , , - , + , ]; const button = ( - + {title} ); @@ -134,25 +140,15 @@ export function DataStreamSelector({ } interface DataStreamButtonProps extends EuiButtonProps { - isAdHocSelected: boolean; onClick: () => void; } -const DataStreamButton = ({ - children, - isAdHocSelected = false, - ...props -}: DataStreamButtonProps) => { +const DataStreamButton = (props: DataStreamButtonProps) => { const isMobile = useIsWithinBreakpoints(['xs', 's']); const buttonStyles = getPopoverButtonStyles({ fullWidth: isMobile }); - return ( - - {isAdHocSelected && } - {children} - - ); + return ; }; type SearchStrategy = 'integrations' | 'integrationsStreams' | 'uncategorizedStreams'; @@ -168,13 +164,17 @@ const SearchControls = ({ strategy }: SearchControlsProps) => { * - Search integrations streams: in memory sorting * - Search uncategorized streams: API request */ - const { search, searchByText, sortByDirection, isSearching } = useSearch(strategy); + // const { search, searchByText, sortByDirection, isSearching } = useSearch(strategy); return ( - + { legend={i18n.translate('xpack.observabilityLogs.dataStreamSelector.sortDirections', { defaultMessage: 'Sort directions', })} - idSelected={search.sortingDirection} - onChange={sortByDirection} + // idSelected={search.sortingDirection} + // onChange={sortByDirection} /> From e74f7b08dadf6c66a32b3cde2acadedafacbd5dd Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Tue, 9 May 2023 16:45:33 +0200 Subject: [PATCH 072/122] feat(observability-logs): create integrations state machine folder --- .../common/integrations/types.ts | 114 ------------------ .../state_machines/integrations/index.ts | 8 ++ .../state_machines/integrations/src/index.ts | 9 ++ .../integrations/src/state_machine.ts | 16 +++ .../state_machines/integrations/src/types.ts | 6 + 5 files changed, 39 insertions(+), 114 deletions(-) create mode 100644 x-pack/plugins/observability_logs/public/state_machines/integrations/index.ts create mode 100644 x-pack/plugins/observability_logs/public/state_machines/integrations/src/index.ts create mode 100644 x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts create mode 100644 x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts diff --git a/x-pack/plugins/observability_logs/common/integrations/types.ts b/x-pack/plugins/observability_logs/common/integrations/types.ts index 6cd0af2ff6b3c..a45ee7098a01f 100644 --- a/x-pack/plugins/observability_logs/common/integrations/types.ts +++ b/x-pack/plugins/observability_logs/common/integrations/types.ts @@ -28,117 +28,3 @@ export const integrationRT = rt.type({ export type DataStream = rt.TypeOf; export type Integration = rt.TypeOf; - -// export interface LogViewsStaticConfig { -// messageFields: string[]; -// } - -// export const logViewOriginRT = rt.keyof({ -// stored: null, -// internal: null, -// inline: null, -// 'infra-source-stored': null, -// 'infra-source-internal': null, -// 'infra-source-fallback': null, -// }); -// export type LogViewOrigin = rt.TypeOf; - -// // Kibana data views -// export const logDataViewReferenceRT = rt.type({ -// type: rt.literal('data_view'), -// dataViewId: rt.string, -// }); - -// export type LogDataViewReference = rt.TypeOf; - -// // Index name -// export const logIndexNameReferenceRT = rt.type({ -// type: rt.literal('index_name'), -// indexName: rt.string, -// }); -// export type LogIndexNameReference = rt.TypeOf; - -// export const logIndexReferenceRT = rt.union([logDataViewReferenceRT, logIndexNameReferenceRT]); -// export type LogIndexReference = rt.TypeOf; - -// const logViewCommonColumnConfigurationRT = rt.strict({ -// id: rt.string, -// }); - -// const logViewTimestampColumnConfigurationRT = rt.strict({ -// timestampColumn: logViewCommonColumnConfigurationRT, -// }); - -// const logViewMessageColumnConfigurationRT = rt.strict({ -// messageColumn: logViewCommonColumnConfigurationRT, -// }); - -// export const logViewFieldColumnConfigurationRT = rt.strict({ -// fieldColumn: rt.intersection([ -// logViewCommonColumnConfigurationRT, -// rt.strict({ -// field: rt.string, -// }), -// ]), -// }); - -// export const logViewColumnConfigurationRT = rt.union([ -// logViewTimestampColumnConfigurationRT, -// logViewMessageColumnConfigurationRT, -// logViewFieldColumnConfigurationRT, -// ]); -// export type LogViewColumnConfiguration = rt.TypeOf; - -// export const logViewAttributesRT = rt.strict({ -// name: rt.string, -// description: rt.string, -// logIndices: logIndexReferenceRT, -// logColumns: rt.array(logViewColumnConfigurationRT), -// }); - -// export type LogViewAttributes = rt.TypeOf; - -// export const logViewRT = rt.exact( -// rt.intersection([ -// rt.type({ -// id: rt.string, -// origin: logViewOriginRT, -// attributes: logViewAttributesRT, -// }), -// rt.partial({ -// updatedAt: rt.number, -// version: rt.string, -// }), -// ]) -// ); -// export type LogView = rt.TypeOf; - -// export const logViewIndexStatusRT = rt.keyof({ -// available: null, -// empty: null, -// missing: null, -// unknown: null, -// }); -// export type LogViewIndexStatus = rt.TypeOf; - -// export const logViewStatusRT = rt.strict({ -// index: logViewIndexStatusRT, -// }); -// export type LogViewStatus = rt.TypeOf; - -// export const persistedLogViewReferenceRT = rt.type({ -// logViewId: rt.string, -// type: rt.literal('log-view-reference'), -// }); - -// export type PersistedLogViewReference = rt.TypeOf; - -// export const inlineLogViewReferenceRT = rt.type({ -// type: rt.literal('log-view-inline'), -// id: rt.string, -// attributes: logViewAttributesRT, -// }); - -// export const logViewReferenceRT = rt.union([persistedLogViewReferenceRT, inlineLogViewReferenceRT]); - -// export type LogViewReference = rt.TypeOf; diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/index.ts b/x-pack/plugins/observability_logs/public/state_machines/integrations/index.ts new file mode 100644 index 0000000000000..3b2a320ae181f --- /dev/null +++ b/x-pack/plugins/observability_logs/public/state_machines/integrations/index.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export * from './src'; diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/index.ts b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/index.ts new file mode 100644 index 0000000000000..86a51fb2c0e16 --- /dev/null +++ b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/index.ts @@ -0,0 +1,9 @@ +/* + * 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. + */ + +export * from './state_machine'; +export * from './types'; diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts new file mode 100644 index 0000000000000..79399c510c86b --- /dev/null +++ b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts @@ -0,0 +1,16 @@ +/* + * 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 { createMachine } from 'xstate'; + +export const createPureIntegrationsStateMachine = (initialContext) => createMachine(); + +export const createIntegrationStateMachine = ({ initialContext }) => + createPureIntegrationsStateMachine(initialContext).withConfig({ + actions: {}, + services: {}, + }); diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts new file mode 100644 index 0000000000000..1fec1c76430eb --- /dev/null +++ b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts @@ -0,0 +1,6 @@ +/* + * 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. + */ From f51fd9c9c83c55fb0b9cb1aef416583990d1f1cc Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 9 May 2023 14:54:06 +0000 Subject: [PATCH 073/122] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- x-pack/plugins/observability_logs/tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/observability_logs/tsconfig.json b/x-pack/plugins/observability_logs/tsconfig.json index b576bc2260046..3ec013621799a 100644 --- a/x-pack/plugins/observability_logs/tsconfig.json +++ b/x-pack/plugins/observability_logs/tsconfig.json @@ -11,6 +11,7 @@ "@kbn/i18n-react", "@kbn/fleet-plugin", "@kbn/kibana-utils-plugin", + "@kbn/io-ts-utils", ], "exclude": ["target/**/*"] } From a1e344a441a5f93c731cbd5b648307cd698160e1 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Wed, 10 May 2023 10:23:39 +0200 Subject: [PATCH 074/122] refactor(observability-log): allow ad-hoc data view creation with specs --- .../main/services/discover_state.test.ts | 2 +- .../main/services/discover_state.ts | 8 ++--- .../dataview_picker/data_view_picker.tsx | 4 +-- .../dataview_picker/data_view_selector.tsx | 4 +-- .../explore_matching_button.tsx | 5 +-- .../lens/public/app_plugin/lens_top_nav.tsx | 8 ++--- .../data_stream_selector/constants.ts | 5 +++ .../data_stream_selector.tsx | 32 ++++++++----------- .../custom_data_stream_selector.tsx | 5 +-- .../components/data_view_select_popover.tsx | 6 ++-- 10 files changed, 38 insertions(+), 41 deletions(-) diff --git a/src/plugins/discover/public/application/main/services/discover_state.test.ts b/src/plugins/discover/public/application/main/services/discover_state.test.ts index f12328db7dc4f..25df9cc0ee660 100644 --- a/src/plugins/discover/public/application/main/services/discover_state.test.ts +++ b/src/plugins/discover/public/application/main/services/discover_state.test.ts @@ -590,7 +590,7 @@ describe('actions', () => { const { state } = await getState('/', savedSearchMock); await state.actions.loadSavedSearch({ savedSearchId: savedSearchMock.id }); const unsubscribe = state.actions.initializeAndSync(); - await state.actions.onCreateDefaultAdHocDataView('ad-hoc-test'); + await state.actions.onCreateDefaultAdHocDataView({ title: 'ad-hoc-test' }); expect(state.appState.getState().index).toBe('ad-hoc-id'); expect(state.internalState.getState().adHocDataViews[0].id).toBe('ad-hoc-id'); unsubscribe(); diff --git a/src/plugins/discover/public/application/main/services/discover_state.ts b/src/plugins/discover/public/application/main/services/discover_state.ts index d10e4c59d9e8a..b23e2cc98369f 100644 --- a/src/plugins/discover/public/application/main/services/discover_state.ts +++ b/src/plugins/discover/public/application/main/services/discover_state.ts @@ -144,7 +144,7 @@ export interface DiscoverStateContainer { * Used by the Data View Picker * @param pattern */ - onCreateDefaultAdHocDataView: (pattern: string) => Promise; + onCreateDefaultAdHocDataView: (dataViewSpec: DataViewSpec) => Promise; /** * Triggered when a new data view is created * @param dataView @@ -460,10 +460,8 @@ export function getDiscoverStateContainer({ }; }; - const onCreateDefaultAdHocDataView = async (pattern: string) => { - const newDataView = await services.dataViews.create({ - title: pattern, - }); + const onCreateDefaultAdHocDataView = async (dataViewSpec: DataViewSpec) => { + const newDataView = await services.dataViews.create(dataViewSpec); if (newDataView.fields.getByName('@timestamp')?.type === 'date') { newDataView.timeFieldName = '@timestamp'; } diff --git a/src/plugins/unified_search/public/dataview_picker/data_view_picker.tsx b/src/plugins/unified_search/public/dataview_picker/data_view_picker.tsx index abb9fe0dd7ca2..9bfda5f24c74e 100644 --- a/src/plugins/unified_search/public/dataview_picker/data_view_picker.tsx +++ b/src/plugins/unified_search/public/dataview_picker/data_view_picker.tsx @@ -8,7 +8,7 @@ import React from 'react'; import type { EuiButtonProps, EuiSelectableProps } from '@elastic/eui'; -import type { DataView, DataViewListItem } from '@kbn/data-views-plugin/public'; +import type { DataView, DataViewListItem, DataViewSpec } from '@kbn/data-views-plugin/public'; import type { AggregateQuery, Query } from '@kbn/es-query'; import { ChangeDataView } from './change_dataview'; @@ -73,7 +73,7 @@ export interface DataViewPickerProps { */ onDataViewCreated?: () => void; - onCreateDefaultAdHocDataView?: (pattern: string) => void; + onCreateDefaultAdHocDataView?: (dataViewSpec: DataViewSpec) => void; /** * List of the supported text based languages (SQL, ESQL) etc. * Defined per application, if not provided, no text based languages diff --git a/src/plugins/unified_search/public/dataview_picker/data_view_selector.tsx b/src/plugins/unified_search/public/dataview_picker/data_view_selector.tsx index e01128339ea18..eb70f8b0bc971 100644 --- a/src/plugins/unified_search/public/dataview_picker/data_view_selector.tsx +++ b/src/plugins/unified_search/public/dataview_picker/data_view_selector.tsx @@ -8,7 +8,7 @@ import React, { Fragment, useEffect, useRef, useState } from 'react'; import type { EuiSelectableProps } from '@elastic/eui'; -import type { DataViewListItem } from '@kbn/data-views-plugin/public'; +import type { DataViewListItem, DataViewSpec } from '@kbn/data-views-plugin/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { DataViewsList } from './dataview_list'; import { IUnifiedSearchPluginServices } from '../types'; @@ -22,7 +22,7 @@ export interface DataViewSelectorProps { isTextBasedLangSelected: boolean; setPopoverIsOpen: (isOpen: boolean) => void; onChangeDataView: (dataViewId: string) => void; - onCreateDefaultAdHocDataView?: (pattern: string) => void; + onCreateDefaultAdHocDataView?: (dataViewSpec: DataViewSpec) => void; } export const DataViewSelector = ({ diff --git a/src/plugins/unified_search/public/dataview_picker/explore_matching_button.tsx b/src/plugins/unified_search/public/dataview_picker/explore_matching_button.tsx index e63e456c10f76..77fe947061480 100644 --- a/src/plugins/unified_search/public/dataview_picker/explore_matching_button.tsx +++ b/src/plugins/unified_search/public/dataview_picker/explore_matching_button.tsx @@ -10,12 +10,13 @@ import React from 'react'; import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiSpacer, useEuiTheme } from '@elastic/eui'; import { css } from '@emotion/react'; import { i18n } from '@kbn/i18n'; +import { DataViewSpec } from '@kbn/data-views-plugin/common'; interface ExploreMatchingButtonProps { noDataViewMatches: boolean; indexMatches: number; dataViewSearchString: string; - onCreateDefaultAdHocDataView?: (pattern: string) => void; + onCreateDefaultAdHocDataView?: (dataViewSpec: DataViewSpec) => void; setPopoverIsOpen: (isOpen: boolean) => void; } @@ -47,7 +48,7 @@ export const ExploreMatchingButton = ({ data-test-subj="explore-matching-indices-button" onClick={() => { setPopoverIsOpen(false); - onCreateDefaultAdHocDataView(dataViewSearchString); + onCreateDefaultAdHocDataView({ title: dataViewSearchString }); }} > {i18n.translate('unifiedSearch.query.queryBar.indexPattern.createForMatchingIndices', { diff --git a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx index 50d70bb58ae75..3a6cc0b1c2e40 100644 --- a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx +++ b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx @@ -12,7 +12,7 @@ import { isOfAggregateQueryType } from '@kbn/es-query'; import { useStore } from 'react-redux'; import { TopNavMenuData } from '@kbn/navigation-plugin/public'; import { getEsQueryConfig } from '@kbn/data-plugin/public'; -import type { DataView } from '@kbn/data-views-plugin/public'; +import type { DataView, DataViewSpec } from '@kbn/data-views-plugin/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { DataViewPickerProps } from '@kbn/unified-search-plugin/public'; import moment from 'moment'; @@ -959,10 +959,8 @@ export const LensTopNavMenu = ({ ]); const onCreateDefaultAdHocDataView = useCallback( - async (pattern: string) => { - const dataView = await dataViewsService.create({ - title: pattern, - }); + async (dataViewSpec: DataViewSpec) => { + const dataView = await dataViewsService.create(dataViewSpec); if (dataView.fields.getByName('@timestamp')?.type === 'date') { dataView.timeFieldName = '@timestamp'; } diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/constants.ts b/x-pack/plugins/observability_logs/public/components/data_stream_selector/constants.ts index fe62a939be3fd..39753031962e1 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/constants.ts +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/constants.ts @@ -29,3 +29,8 @@ export const uncategorizedLabel = i18n.translate( 'xpack.observabilityLogs.dataStreamSelector.uncategorized', { defaultMessage: 'Uncategorized' } ); + +export const sortDirectionsLabel = i18n.translate( + 'xpack.observabilityLogs.dataStreamSelector.sortDirections', + { defaultMessage: 'Sort directions' } +); diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx index 4ebc4ed8d1164..0f66e11db2d68 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx @@ -33,6 +33,7 @@ import { INTEGRATION_PANEL_ID, POPOVER_ID, selectViewLabel, + sortDirectionsLabel, uncategorizedLabel, UNCATEGORIZED_STREAMS_PANEL_ID, } from './constants'; @@ -106,18 +107,6 @@ export function DataStreamSelector({ ...integrationPanels, ]; - const contextPanelItems = [ - , - , - , - ]; - const button = ( {title} @@ -134,7 +123,16 @@ export function DataStreamSelector({ {...(isMobile && { display: 'block' })} buffer={8} > - + + + + + ); } @@ -144,9 +142,7 @@ interface DataStreamButtonProps extends EuiButtonProps { } const DataStreamButton = (props: DataStreamButtonProps) => { - const isMobile = useIsWithinBreakpoints(['xs', 's']); - - const buttonStyles = getPopoverButtonStyles({ fullWidth: isMobile }); + const buttonStyles = getPopoverButtonStyles({ fullWidth: props.fullWidth }); return ; }; @@ -192,9 +188,7 @@ const SearchControls = ({ strategy }: SearchControlsProps) => { label: 'Descending', }, ]} - legend={i18n.translate('xpack.observabilityLogs.dataStreamSelector.sortDirections', { - defaultMessage: 'Sort directions', - })} + legend={sortDirectionsLabel} // idSelected={search.sortingDirection} // onChange={sortByDirection} /> diff --git a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx index 3811557d3096e..a553e951bc8b6 100644 --- a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { DiscoverStateContainer } from '@kbn/discover-plugin/public'; +import { DataStream } from '../../common/integrations'; import { DataStreamSelector } from '../components/data_stream_selector'; import { useDataView } from '../utils/internal_state_container_context'; @@ -18,8 +19,8 @@ export function CustomDataStreamSelector({ stateContainer }: Props) { // Container component, here goes all the state management and custom logic usage to keep the DataStreamSelector presentational. const dataView = useDataView(); - const handleStreamSelection = async (dataStream) => { - return stateContainer.actions.onCreateDefaultAdHocDataView(dataStream.title); + const handleStreamSelection = async (dataStream: DataStream) => { + return stateContainer.actions.onCreateDefaultAdHocDataView(dataStream); }; return ( diff --git a/x-pack/plugins/stack_alerts/public/rule_types/components/data_view_select_popover.tsx b/x-pack/plugins/stack_alerts/public/rule_types/components/data_view_select_popover.tsx index 7180db27afdc6..4bcf58967de22 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/components/data_view_select_popover.tsx +++ b/x-pack/plugins/stack_alerts/public/rule_types/components/data_view_select_popover.tsx @@ -20,7 +20,7 @@ import { EuiText, useEuiPaddingCSS, } from '@elastic/eui'; -import type { DataViewListItem, DataView } from '@kbn/data-views-plugin/public'; +import type { DataViewListItem, DataView, DataViewSpec } from '@kbn/data-views-plugin/public'; import { DataViewSelector } from '@kbn/unified-search-plugin/public'; import { useTriggerUiActionServices } from '../es_query/util'; import { EsQueryRuleMetaData } from '../es_query/types'; @@ -123,8 +123,8 @@ export const DataViewSelectPopover: React.FunctionComponent { - const newDataView = await dataViews.create({ title: pattern }); + async (dataViewSpec: DataViewSpec) => { + const newDataView = await dataViews.create(dataViewSpec); if (newDataView.fields.getByName('@timestamp')?.type === 'date') { newDataView.timeFieldName = '@timestamp'; } From 8514bfe7145576dfb6893217bbdcd65289398f23 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Wed, 10 May 2023 10:41:43 +0200 Subject: [PATCH 075/122] chore(observability-log): fix jest config --- x-pack/plugins/observability_logs/jest.config.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/observability_logs/jest.config.js b/x-pack/plugins/observability_logs/jest.config.js index 2c232f6a07547..e99928a12c80b 100644 --- a/x-pack/plugins/observability_logs/jest.config.js +++ b/x-pack/plugins/observability_logs/jest.config.js @@ -8,10 +8,10 @@ module.exports = { preset: '@kbn/test', rootDir: '../../..', - roots: ['/x-pack/plugins/observability-logs'], - coverageDirectory: '/target/kibana-coverage/jest/x-pack/plugins/observability-logs', + roots: ['/x-pack/plugins/observability_logs'], + coverageDirectory: '/target/kibana-coverage/jest/x-pack/plugins/observability_logs', coverageReporters: ['text', 'html'], collectCoverageFrom: [ - '/x-pack/plugins/observability-logs/{common,public,server}/**/*.{ts,tsx}', + '/x-pack/plugins/observability_logs/{common,public,server}/**/*.{ts,tsx}', ], }; From cad4802e5123c96cd9d1aa79636041874413d2f7 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Wed, 10 May 2023 11:08:42 +0200 Subject: [PATCH 076/122] chore(observability-log): add optimizer bundle limit --- packages/kbn-optimizer/limits.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 93d5507c53a1e..f951e2f1181f8 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -94,6 +94,7 @@ pageLoadAssetSize: navigation: 37269 newsfeed: 42228 observability: 95000 + observabilityLogs: 20000 observabilityShared: 21266 osquery: 107090 painlessLab: 179748 From e68eaa80ddc1f6f1fa5bb4cf76fff78c10e67d63 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 10 May 2023 09:39:12 +0000 Subject: [PATCH 077/122] [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' --- .../components/data_stream_selector/data_stream_selector.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx index 0f66e11db2d68..f8eea05776b19 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx @@ -24,8 +24,6 @@ import { } from '@elastic/eui'; import { PackageIcon } from '@kbn/fleet-plugin/public'; -import { i18n } from '@kbn/i18n'; - import { contextMenuStyles, DATA_VIEW_POPOVER_CONTENT_WIDTH, From 3b1ff49c65f3195e45bb6ffc02cca7c0d7e18584 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Wed, 10 May 2023 16:03:53 +0200 Subject: [PATCH 078/122] feat(observability-log): draft integrations state machine --- .../integrations/src/state_machine.ts | 138 +++++++++++++++++- .../state_machines/integrations/src/types.ts | 46 ++++++ 2 files changed, 180 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts index 79399c510c86b..9a9bf1979e52d 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts @@ -5,12 +5,142 @@ * 2.0. */ -import { createMachine } from 'xstate'; +import { catchError, from, map, of, throwError } from 'rxjs'; +import { actions, createMachine } from 'xstate'; +import { + DefaultIntegrationsContext, + IntegrationsContext, + IntegrationsEvent, + IntegrationTypestate, +} from './types'; -export const createPureIntegrationsStateMachine = (initialContext) => createMachine(); +const defaultContext = { + integrations: [], + error: null, + page: 0, +}; -export const createIntegrationStateMachine = ({ initialContext }) => +export const createPureIntegrationsStateMachine = ( + initialContext: DefaultIntegrationsContext = defaultContext +) => + /** @xstate-layout N4IgpgJg5mDOIC5QEkB2AXMUBOBDdAlgPaqwB0ArqgdYbgDYEBekAxANoAMAuoqAA5FYBQiT4gAHogCMAJgDsnMrICc8hbIBs0zgBZ5ADgA0IAJ4yAzCrLT5KgKybO92Z2cHNmgL5eTaTDj4xKRk9ES4EDRQrAAyAPIAggAiyAByAOIA+gDKAKoAwvkAokVJpVy8SCCCwqKo4lIIBioGZBb20va6mgYWnJ7yuibmCAoWZOqauhadKppdBl0+fhhYeHXkYRFRsYkpGZkAYgnIMeU84jUiwQ0y3bpknCrSuk-NHbYWw4gWv2SaFm6PUW80MKmWIH8ayCJE24UiqCgAFkiNgwLtkmksnlCiUykkKpchNcxFVGrILJobLpdB0LB4DINZC9vgh7M42jN6Z1NIZ2rJ7BCoYENqF4VEUWiMfsssdTudKgJiXVbggALT2R6aAWfAxyeyGZz2VlOKm2XRyTqyWSvAVC1Yi4LkWBgXDYADGAAsosL1k7pVicgViqUFUTajcyYgKWaaXSGUyWWYZG4yB4tFpXtpAep7QE-bCyC63V6fQ6C6QAwc5WcCRcqlcVVH1UoprTdB4ZgL5IMPKy1VM2pxGczBp4LQZZHnoaKthA2AAlIrxZKZNIAFSK6QXCXXyDiqWyhIbysjoEaBgMD0ZlM4WhenkvrN01rIKhmk+0Bk4L0W08dhZzmwK5JJkSJxEua6pJu267vuh7HkqEakue0aeBMfQ0umBrfsayYIPSVKvA40i2Ko9jtAY-4VnCERsNkRQJAu+QABJQTBO57geR71khJL1M2sh6soBp2BS8g4f0rKZm+pE0taLzvlM8jUTCIRzlEhy4AQ9CLsuezsVunHwTxirVKeKGSHcHb-D0t5ZjML7SV0abaBaqiKb+Pi+CAqBEPO8BVL6amBXxTaoeq342GReiXvqPZ4SMaqdNIabDuo5rjtIk6qaKVA0NcDDMJA4b8aqchWI8iwqNa758hYsistI1iuKoKgOFerhXrouVOmK2yIqV4VWQgpGMo8lrSACTj6L00mUv8cntBR8ieM4vWAeKiKSmAQ1niNaqqG+FpZgKz4WPIbSGDaNq-O0nDtBtITFh63qIsFGx7ZZjRqg10XqLFerdhJTWKMoepyLSnXDjST20fOEBfQJEVOLIZDdCO3SAg9tLSeNpFzIYjIqN0hgqT5H19RpiJaTpJUnshyMjVN7U2HZFEs-YDjSRa6NyS+zK6Ep+jeV4QA */ + createMachine( + { + context: initialContext, + preserveActionOrder: true, + predictableActionArguments: true, + id: 'Integrations', + initial: 'uninitialized', + states: { + uninitialized: { + always: { + target: 'loading', + }, + }, + loading: { + entry: 'notifyLoadingStarted', + invoke: { + src: 'loadIntegrations', + }, + on: { + LOADING_SUCCEEDED: { + target: 'loaded', + actions: 'storeIntegrations', + }, + LOADING_FAILED: { + target: 'loadingFailed', + actions: 'storeError', + }, + }, + }, + loadingMore: { + invoke: { + src: 'loadMoreIntegrations', + }, + on: { + LOADING_SUCCEEDED: { + target: 'loaded', + actions: 'appendIntegrations', + }, + LOADING_FAILED: { + target: 'loadingFailed', + actions: 'storeError', + }, + }, + }, + searchingIntegrations: { + invoke: { + src: 'searchIntegrations', + }, + on: { + LOADING_SUCCEEDED: { + target: 'loaded', + actions: 'storeIntegrations', + }, + LOADING_FAILED: { + target: 'loadingFailed', + actions: 'storeError', + }, + }, + }, + loaded: { + entry: 'notifyLoadingSucceeded', + on: { + RELOAD_INTEGRATIONS: 'loading', + LOAD_MORE_INTEGRATIONS: 'loadingMore', + SEARCH_INTEGRATIONS: 'searchingIntegrations', + }, + }, + loadingFailed: { + entry: 'notifyLoadingFailed', + on: { + RELOAD_INTEGRATIONS: { + target: 'loading', + }, + }, + }, + }, + }, + { + actions: { + notifyLoadingStarted: actions.pure(() => undefined), + notifyLoadingSucceeded: actions.pure(() => undefined), + notifyLoadingFailed: actions.pure(() => undefined), + }, + } + ); + +export interface IntegrationsStateMachineDependencies { + initialContext: IntegrationsContext; + logViews: IIntegrationsClient; +} + +export const createIntegrationStateMachine = ({ + initialContext, + integrations, +}: IntegrationsStateMachineDependencies) => createPureIntegrationsStateMachine(initialContext).withConfig({ actions: {}, - services: {}, + services: { + loadIntegrations: (context) => + from( + 'logViewReference' in context + ? integrations.getIntegrations() + : throwError(() => new Error('Failed to load integrations')) + ).pipe( + map( + (data): IntegrationsEvent => ({ + type: 'LOADING_SUCCEEDED', + integrations: data.items, + page: data.pageAfter, + }) + ), + catchError((error) => + of({ + type: 'LOADING_FAILED', + error, + }) + ) + ), + }, }); diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts index 1fec1c76430eb..b7b817a72fbd0 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts @@ -4,3 +4,49 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ + +import { Integration } from '../../../../common/integrations'; + +export interface DefaultIntegrationsContext { + integrations: Integration[]; + error: Error | null; + page: number; +} + +interface IntegrationsSearchParams { + name: string; + sortDirection: 'asc' | 'desc'; +} + +export type IntegrationTypestate = + | { + value: 'uninitialized'; + context: DefaultIntegrationsContext; + } + | { + value: 'loading'; + context: DefaultIntegrationsContext; + }; + +export type IntegrationsContext = IntegrationTypestate['context']; + +export type IntegrationsEvent = + | { + type: 'LOADING_SUCCEEDED'; + integrations: Integration[]; + page: number; + } + | { + type: 'LOADING_FAILED'; + error: Error; + } + | { + type: 'RELOAD_INTEGRATIONS'; + } + | { + type: 'LOAD_MORE_INTEGRATIONS'; + } + | { + type: 'SEARCH_INTEGRATIONS'; + search: IntegrationsSearchParams; + }; From 79160d0b4b5755b65face0b0e0a5d3ed0071c2e8 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Wed, 10 May 2023 16:41:31 +0200 Subject: [PATCH 079/122] feat(observability-log): adding integrations service --- .../observability_logs/common/index.ts | 6 + .../common/integrations/v1/common.ts | 13 ++ .../integrations/v1/find_integrations.ts | 36 ++++++ .../common/integrations/v1/index.ts | 9 ++ .../observability_logs/common/latest.ts | 8 ++ .../data_stream_selector.tsx | 112 +++++++++++++----- .../custom_data_stream_selector.tsx | 13 +- .../public/services/integrations/index.ts | 10 ++ .../integrations/integrations_client.mock.ts | 12 ++ .../integrations/integrations_client.ts | 36 ++++++ .../integrations/integrations_service.mock.ts | 16 +++ .../integrations/integrations_service.ts | 28 +++++ .../public/services/integrations/types.ts | 23 ++++ 13 files changed, 290 insertions(+), 32 deletions(-) create mode 100644 x-pack/plugins/observability_logs/common/integrations/v1/common.ts create mode 100644 x-pack/plugins/observability_logs/common/integrations/v1/find_integrations.ts create mode 100644 x-pack/plugins/observability_logs/common/integrations/v1/index.ts create mode 100644 x-pack/plugins/observability_logs/common/latest.ts create mode 100644 x-pack/plugins/observability_logs/public/services/integrations/index.ts create mode 100644 x-pack/plugins/observability_logs/public/services/integrations/integrations_client.mock.ts create mode 100644 x-pack/plugins/observability_logs/public/services/integrations/integrations_client.ts create mode 100644 x-pack/plugins/observability_logs/public/services/integrations/integrations_service.mock.ts create mode 100644 x-pack/plugins/observability_logs/public/services/integrations/integrations_service.ts create mode 100644 x-pack/plugins/observability_logs/public/services/integrations/types.ts diff --git a/x-pack/plugins/observability_logs/common/index.ts b/x-pack/plugins/observability_logs/common/index.ts index 2f2129469c8bb..75b4eca6d64dd 100644 --- a/x-pack/plugins/observability_logs/common/index.ts +++ b/x-pack/plugins/observability_logs/common/index.ts @@ -7,3 +7,9 @@ export const PLUGIN_ID = 'observabilityLogs'; export const PLUGIN_NAME = 'observabilityLogs'; + +/** + * Exporting versioned APIs types + */ +export * from './latest'; +export * as integrationsV1 from './integrations/v1'; diff --git a/x-pack/plugins/observability_logs/common/integrations/v1/common.ts b/x-pack/plugins/observability_logs/common/integrations/v1/common.ts new file mode 100644 index 0000000000000..d047eefbcf0a5 --- /dev/null +++ b/x-pack/plugins/observability_logs/common/integrations/v1/common.ts @@ -0,0 +1,13 @@ +/* + * 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. + */ + +export const INTEGRATIONS_URL = '/api/fleet/epm/packages/installed'; +export const getIntegrationsUrl = (search = {}) => { + const querySearch = new URLSearchParams(search).toString(); + + return [INTEGRATIONS_URL, querySearch].filter(Boolean).join('/'); +}; diff --git a/x-pack/plugins/observability_logs/common/integrations/v1/find_integrations.ts b/x-pack/plugins/observability_logs/common/integrations/v1/find_integrations.ts new file mode 100644 index 0000000000000..d46bbc3d47d8d --- /dev/null +++ b/x-pack/plugins/observability_logs/common/integrations/v1/find_integrations.ts @@ -0,0 +1,36 @@ +/* + * 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 * as rt from 'io-ts'; +import { integrationRT } from '../types'; + +const pageAfterRT = rt.array(rt.union([rt.number, rt.string])); + +const sortDirectionRT = rt.union([rt.literal('asc'), rt.literal('desc')]); + +export const findIntegrationsResponseRT = rt.exact( + rt.intersection([ + rt.type({ + items: rt.array(integrationRT), + total: rt.number, + }), + rt.partial({ + pageAfter: pageAfterRT, + }), + ]) +); + +export const findIntegrationsRequestQueryRT = rt.partial({ + nameQuery: rt.string, + pageSize: rt.number, + type: rt.literal('log'), + pageAfter: pageAfterRT, + sortDirection: sortDirectionRT, +}); + +export type FindIntegrationsRequestQuery = rt.TypeOf; +export type FindIntegrationsResponse = rt.TypeOf; diff --git a/x-pack/plugins/observability_logs/common/integrations/v1/index.ts b/x-pack/plugins/observability_logs/common/integrations/v1/index.ts new file mode 100644 index 0000000000000..792cf87991e40 --- /dev/null +++ b/x-pack/plugins/observability_logs/common/integrations/v1/index.ts @@ -0,0 +1,9 @@ +/* + * 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. + */ + +export * from './common'; +export * from './find_integrations'; diff --git a/x-pack/plugins/observability_logs/common/latest.ts b/x-pack/plugins/observability_logs/common/latest.ts new file mode 100644 index 0000000000000..af947c136f39f --- /dev/null +++ b/x-pack/plugins/observability_logs/common/latest.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export * from './integrations/v1'; diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx index 0f66e11db2d68..cb8a58fcbc114 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useMemo, useState } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { EuiButton, EuiButtonGroup, @@ -19,7 +19,12 @@ import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, + EuiLoadingSpinner, + EuiPanel, EuiPopover, + EuiSkeletonLoading, + EuiSkeletonText, + EuiSkeletonTitle, useIsWithinBreakpoints, } from '@elastic/eui'; import { PackageIcon } from '@kbn/fleet-plugin/public'; @@ -46,8 +51,15 @@ export interface DataStreamSelectorProps { title: string; integrations: any[]; uncategorizedStreams: any[]; - onStreamSelected: (dataStream: any) => Promise; + isSearching: boolean; + isLoadingIntegrations: boolean; + isLoadingUncategorizedStreams: boolean; + /* Triggered when a search or sorting is performed */ + onSearch: () => void; + /* Triggered when the uncategorized streams entry is selected */ onUncategorizedClick: () => void; + /* Triggered when a data stream entry is selected */ + onStreamSelected: (dataStream: any) => Promise; } type CurrentPanelId = @@ -61,6 +73,10 @@ export function DataStreamSelector({ title, integrations, uncategorizedStreams, + isSearching, + isLoadingIntegrations, + isLoadingUncategorizedStreams, + onSearch, onStreamSelected, onUncategorizedClick, }: DataStreamSelectorProps) { @@ -69,40 +85,50 @@ export function DataStreamSelector({ const [currentPanel, setCurrentPanel] = useState(INTEGRATION_PANEL_ID); - const { items: integrationItems, panels: integrationPanels } = useMemo(() => { - const handleStreamSelection: StreamSelectionHandler = (dataStream) => { - onStreamSelected(dataStream).then(closePopover); - }; + const handleStreamSelection = useCallback( + (dataStream) => { + onStreamSelected(dataStream); + closePopover(); + }, + [closePopover, onStreamSelected] + ); - return buildIntegrationsTree({ + const { items: integrationItems, panels: integrationPanels } = useMemo(() => { + const { items, panels } = buildIntegrationsTree({ list: integrations, onItemClick: setCurrentPanel, onStreamSelected: handleStreamSelection, }); - }, [closePopover, integrations, onStreamSelected]); + + const uncategorizedStreamsItem = { + name: uncategorizedLabel, + onClick: onUncategorizedClick, + panel: UNCATEGORIZED_STREAMS_PANEL_ID, + }; + + return { + items: [uncategorizedStreamsItem, ...items], + panels, + }; + }, [integrations, handleStreamSelection, onUncategorizedClick]); const panels = [ { id: INTEGRATION_PANEL_ID, title: integrationsLabel, width: DATA_VIEW_POPOVER_CONTENT_WIDTH, - items: [ - { - name: uncategorizedLabel, - onClick: onUncategorizedClick, - panel: UNCATEGORIZED_STREAMS_PANEL_ID, - }, - ...integrationItems, - ], + items: integrationItems, }, { id: UNCATEGORIZED_STREAMS_PANEL_ID, title: uncategorizedLabel, width: DATA_VIEW_POPOVER_CONTENT_WIDTH, - items: uncategorizedStreams.map((stream) => ({ - name: stream.name, - onClick: () => onStreamSelected(stream), - })), + items: isLoadingUncategorizedStreams + ? [] + : uncategorizedStreams.map((stream) => ({ + name: stream.name, + onClick: () => handleStreamSelection(stream), + })), }, ...integrationPanels, ]; @@ -123,15 +149,17 @@ export function DataStreamSelector({ {...(isMobile && { display: 'block' })} buffer={8} > - - + + - + + + ); @@ -150,10 +178,11 @@ const DataStreamButton = (props: DataStreamButtonProps) => { type SearchStrategy = 'integrations' | 'integrationsStreams' | 'uncategorizedStreams'; interface SearchControlsProps { - strategy: SearchStrategy; + isSearching: boolean; + onSearch: () => void; } -const SearchControls = ({ strategy }: SearchControlsProps) => { +const SearchControls = ({ isSearching, onSearch }: SearchControlsProps) => { /** * TODO: implement 3 different search strategies * - Search integrations: API request @@ -169,6 +198,7 @@ const SearchControls = ({ strategy }: SearchControlsProps) => { @@ -198,6 +228,28 @@ const SearchControls = ({ strategy }: SearchControlsProps) => { ); }; +const ContextMenuSkeleton = ({ children, isLoading }) => { + return ( + + + + + + + + + + + } + loadedContent={children} + /> + ); +}; + interface IntegrationsTree { items: EuiContextMenuPanelItemDescriptor[]; panels: EuiContextMenuPanelDescriptor[]; diff --git a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx index a553e951bc8b6..3db4f77281cac 100644 --- a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React from 'react'; +import React, { useState } from 'react'; import { DiscoverStateContainer } from '@kbn/discover-plugin/public'; import { DataStream } from '../../common/integrations'; import { DataStreamSelector } from '../components/data_stream_selector'; @@ -19,15 +19,24 @@ export function CustomDataStreamSelector({ stateContainer }: Props) { // Container component, here goes all the state management and custom logic usage to keep the DataStreamSelector presentational. const dataView = useDataView(); - const handleStreamSelection = async (dataStream: DataStream) => { + const handleStreamSelection = (dataStream: DataStream) => { return stateContainer.actions.onCreateDefaultAdHocDataView(dataStream); }; + const [flag, setFlag] = useState(true); + + React.useEffect(() => { + setTimeout(() => setFlag(false), 5000); + }, []); + return ( console.log('fetch uncategorized streams')} /> diff --git a/x-pack/plugins/observability_logs/public/services/integrations/index.ts b/x-pack/plugins/observability_logs/public/services/integrations/index.ts new file mode 100644 index 0000000000000..7ba3d4f86faf6 --- /dev/null +++ b/x-pack/plugins/observability_logs/public/services/integrations/index.ts @@ -0,0 +1,10 @@ +/* + * 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. + */ + +export * from './integrations_client'; +export * from './integrations_service'; +export * from './types'; diff --git a/x-pack/plugins/observability_logs/public/services/integrations/integrations_client.mock.ts b/x-pack/plugins/observability_logs/public/services/integrations/integrations_client.mock.ts new file mode 100644 index 0000000000000..99c10f780e9cc --- /dev/null +++ b/x-pack/plugins/observability_logs/public/services/integrations/integrations_client.mock.ts @@ -0,0 +1,12 @@ +/* + * 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 { IIntegrationsClient } from './types'; + +export const createIntegrationsClientMock = (): jest.Mocked => ({ + findIntegrations: jest.fn(), +}); diff --git a/x-pack/plugins/observability_logs/public/services/integrations/integrations_client.ts b/x-pack/plugins/observability_logs/public/services/integrations/integrations_client.ts new file mode 100644 index 0000000000000..7b79d202d5fb2 --- /dev/null +++ b/x-pack/plugins/observability_logs/public/services/integrations/integrations_client.ts @@ -0,0 +1,36 @@ +/* + * 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 { HttpStart } from '@kbn/core/public'; +import { decodeOrThrow } from '@kbn/infra-plugin/common/runtime_types'; +import { + FindIntegrationsRequestQuery, + FindIntegrationsResponse, + findIntegrationsResponseRT, + getIntegrationsUrl, +} from '../../../common'; + +import { IIntegrationsClient } from './types'; + +export class IntegrationsClient implements IIntegrationsClient { + constructor(private readonly http: HttpStart) {} + + public async findIntegrations( + params: FindIntegrationsRequestQuery + ): Promise { + const response = await this.http.get(getIntegrationsUrl(params)).catch((error) => { + throw new Error(`Failed to fetch integrations": ${error}`); + }); + + const data = decodeOrThrow( + findIntegrationsResponseRT, + (message: string) => new Error(`Failed to decode integrations response: ${message}"`) + )(response); + + return data; + } +} diff --git a/x-pack/plugins/observability_logs/public/services/integrations/integrations_service.mock.ts b/x-pack/plugins/observability_logs/public/services/integrations/integrations_service.mock.ts new file mode 100644 index 0000000000000..87e778e110cbd --- /dev/null +++ b/x-pack/plugins/observability_logs/public/services/integrations/integrations_service.mock.ts @@ -0,0 +1,16 @@ +/* + * 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 { createIntegrationsClientMock } from './integrations_client.mock'; +import { IntegrationsServiceStart } from './types'; + +export const createIntegrationsServiceStartMock = () => ({ + client: createIntegrationsClientMock(), +}); + +export const _ensureTypeCompatibility = (): IntegrationsServiceStart => + createIntegrationsServiceStartMock(); diff --git a/x-pack/plugins/observability_logs/public/services/integrations/integrations_service.ts b/x-pack/plugins/observability_logs/public/services/integrations/integrations_service.ts new file mode 100644 index 0000000000000..4ae669194fadd --- /dev/null +++ b/x-pack/plugins/observability_logs/public/services/integrations/integrations_service.ts @@ -0,0 +1,28 @@ +/* + * 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 { IntegrationsStaticConfig } from '../../../common/integrations'; +import { IntegrationsClient } from './integrations_client'; +import { + IntegrationsServiceStartDeps, + IntegrationsServiceSetup, + IntegrationsServiceStart, +} from './types'; + +export class IntegrationsService { + constructor(private readonly config: IntegrationsStaticConfig) {} + + public setup(): IntegrationsServiceSetup {} + + public start({ http }: IntegrationsServiceStartDeps): IntegrationsServiceStart { + const client = new IntegrationsClient(http); + + return { + client, + }; + } +} diff --git a/x-pack/plugins/observability_logs/public/services/integrations/types.ts b/x-pack/plugins/observability_logs/public/services/integrations/types.ts new file mode 100644 index 0000000000000..813e192eaab68 --- /dev/null +++ b/x-pack/plugins/observability_logs/public/services/integrations/types.ts @@ -0,0 +1,23 @@ +/* + * 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 { HttpStart } from '@kbn/core/public'; +import { FindIntegrationsRequestQuery, FindIntegrationsResponse } from '../../../common'; + +export type IntegrationsServiceSetup = void; + +export interface IntegrationsServiceStart { + client: IIntegrationsClient; +} + +export interface IntegrationsServiceStartDeps { + http: HttpStart; +} + +export interface IIntegrationsClient { + findIntegrations(params: FindIntegrationsRequestQuery): Promise; +} From 6078ab41a279b3ace2a37415ced8fa9404dd6c16 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 10 May 2023 14:48:29 +0000 Subject: [PATCH 080/122] [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' --- .../components/data_stream_selector/data_stream_selector.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx index ae879da7000ba..9b0fbad896354 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx @@ -19,7 +19,6 @@ import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, - EuiLoadingSpinner, EuiPanel, EuiPopover, EuiSkeletonLoading, From 0628dec505345f02f2d099b138455df99bbb7750 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 10 May 2023 14:55:10 +0000 Subject: [PATCH 081/122] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- x-pack/plugins/observability_logs/tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/observability_logs/tsconfig.json b/x-pack/plugins/observability_logs/tsconfig.json index 3ec013621799a..490d67bdd6632 100644 --- a/x-pack/plugins/observability_logs/tsconfig.json +++ b/x-pack/plugins/observability_logs/tsconfig.json @@ -12,6 +12,7 @@ "@kbn/fleet-plugin", "@kbn/kibana-utils-plugin", "@kbn/io-ts-utils", + "@kbn/infra-plugin", ], "exclude": ["target/**/*"] } From 68efde1ffb5835cd47e30633d60d0a29e8f07fee Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 10 May 2023 15:31:07 +0000 Subject: [PATCH 082/122] [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' --- x-pack/plugins/observability_logs/common/index.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/observability_logs/common/index.ts b/x-pack/plugins/observability_logs/common/index.ts index 75b4eca6d64dd..a93510cbc68f1 100644 --- a/x-pack/plugins/observability_logs/common/index.ts +++ b/x-pack/plugins/observability_logs/common/index.ts @@ -11,5 +11,11 @@ export const PLUGIN_NAME = 'observabilityLogs'; /** * Exporting versioned APIs types */ -export * from './latest'; +export type { FindIntegrationsRequestQuery, FindIntegrationsResponse } from './latest'; +export { + INTEGRATIONS_URL, + getIntegrationsUrl, + findIntegrationsResponseRT, + findIntegrationsRequestQueryRT, +} from './latest'; export * as integrationsV1 from './integrations/v1'; From eb6a35dd99631c0ac9778258c68115f8ffdee327 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Fri, 12 May 2023 15:12:30 +0200 Subject: [PATCH 083/122] feat(observability-log): use integration service --- .../integrations/v1/find_integrations.ts | 1 + .../common/runtime_types.ts | 54 +++++++++++++ .../plugins/observability_logs/kibana.jsonc | 2 +- .../data_stream_selector.tsx | 2 +- .../custom_data_stream_selector.tsx | 40 +++++++++- .../public/customizations/index.tsx | 31 ++++++++ .../public/hooks/use_integrations.ts | 76 +++++++++++++++++++ .../public/hooks/use_kibana.tsx | 46 +++++++++++ .../observability_logs/public/plugin.tsx | 34 ++++++--- .../integrations/integrations_client.ts | 12 ++- .../integrations/integrations_service.ts | 3 +- .../integrations/src/state_machine.ts | 34 +++------ .../state_machines/integrations/src/types.ts | 20 ++++- .../observability_logs/public/types.ts | 11 ++- 14 files changed, 317 insertions(+), 49 deletions(-) create mode 100644 x-pack/plugins/observability_logs/common/runtime_types.ts create mode 100644 x-pack/plugins/observability_logs/public/customizations/index.tsx create mode 100644 x-pack/plugins/observability_logs/public/hooks/use_integrations.ts create mode 100644 x-pack/plugins/observability_logs/public/hooks/use_kibana.tsx diff --git a/x-pack/plugins/observability_logs/common/integrations/v1/find_integrations.ts b/x-pack/plugins/observability_logs/common/integrations/v1/find_integrations.ts index d46bbc3d47d8d..3ec39b39b8c42 100644 --- a/x-pack/plugins/observability_logs/common/integrations/v1/find_integrations.ts +++ b/x-pack/plugins/observability_logs/common/integrations/v1/find_integrations.ts @@ -32,5 +32,6 @@ export const findIntegrationsRequestQueryRT = rt.partial({ sortDirection: sortDirectionRT, }); +export type PageAfter = rt.TypeOf; export type FindIntegrationsRequestQuery = rt.TypeOf; export type FindIntegrationsResponse = rt.TypeOf; diff --git a/x-pack/plugins/observability_logs/common/runtime_types.ts b/x-pack/plugins/observability_logs/common/runtime_types.ts new file mode 100644 index 0000000000000..8d747dd4c43f3 --- /dev/null +++ b/x-pack/plugins/observability_logs/common/runtime_types.ts @@ -0,0 +1,54 @@ +/* + * 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 { fold } from 'fp-ts/lib/Either'; +import { identity } from 'fp-ts/lib/function'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { Context, Errors, IntersectionType, Type, UnionType, ValidationError } from 'io-ts'; +import type { RouteValidationFunction } from '@kbn/core/server'; + +type ErrorFactory = (message: string) => Error; + +const getErrorPath = ([first, ...rest]: Context): string[] => { + if (typeof first === 'undefined') { + return []; + } else if (first.type instanceof IntersectionType) { + const [, ...next] = rest; + return getErrorPath(next); + } else if (first.type instanceof UnionType) { + const [, ...next] = rest; + return [first.key, ...getErrorPath(next)]; + } + + return [first.key, ...getErrorPath(rest)]; +}; + +const getErrorType = ({ context }: ValidationError) => + context[context.length - 1]?.type?.name ?? 'unknown'; + +const formatError = (error: ValidationError) => + error.message ?? + `in ${getErrorPath(error.context).join('/')}: ${JSON.stringify( + error.value + )} does not match expected type ${getErrorType(error)}`; + +export const formatErrors = (errors: ValidationError[]) => + `Failed to validate: \n${errors.map((error) => ` ${formatError(error)}`).join('\n')}`; + +export const createPlainError = (message: string) => new Error(message); + +export const throwErrors = (createError: ErrorFactory) => (errors: Errors) => { + throw createError(formatErrors(errors)); +}; + +export const decodeOrThrow = + ( + runtimeType: Type, + createError: ErrorFactory = createPlainError + ) => + (inputValue: InputValue) => + pipe(runtimeType.decode(inputValue), fold(throwErrors(createError), identity)); diff --git a/x-pack/plugins/observability_logs/kibana.jsonc b/x-pack/plugins/observability_logs/kibana.jsonc index 8a69b3c92f8c1..106515a333a78 100644 --- a/x-pack/plugins/observability_logs/kibana.jsonc +++ b/x-pack/plugins/observability_logs/kibana.jsonc @@ -8,7 +8,7 @@ "server": true, "browser": true, "configPath": ["xpack", "observability_logs"], - "requiredPlugins": ["discover", "fleet","kibanaUtils"], + "requiredPlugins": ["discover", "fleet", "kibanaReact", "kibanaUtils"], "optionalPlugins": [], "requiredBundles": [] } diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx index 9b0fbad896354..aab8433422ddd 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx @@ -266,7 +266,7 @@ const buildIntegrationsTree = ({ list, onItemClick, onStreamSelected }: Integrat res.items.push({ name: entry.name, onClick: () => onItemClick(entryId), - icon: , + icon: , panel: entryId, }); diff --git a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx index 3db4f77281cac..f9b162e6ac15b 100644 --- a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx @@ -9,15 +9,22 @@ import React, { useState } from 'react'; import { DiscoverStateContainer } from '@kbn/discover-plugin/public'; import { DataStream } from '../../common/integrations'; import { DataStreamSelector } from '../components/data_stream_selector'; -import { useDataView } from '../utils/internal_state_container_context'; +import { InternalStateProvider, useDataView } from '../utils/internal_state_container_context'; +import { IntegrationsProvider, useIntegrationsContext } from '../hooks/use_integrations'; +import { + ObservabilityLogsPluginProvider, + ObservabilityLogsPluginProviderProps, +} from '../hooks/use_kibana'; -interface Props { +interface CustomDataStreamSelectorProps { stateContainer: DiscoverStateContainer; } -export function CustomDataStreamSelector({ stateContainer }: Props) { +export const CustomDataStreamSelector = withProviders(({ stateContainer }) => { // Container component, here goes all the state management and custom logic usage to keep the DataStreamSelector presentational. const dataView = useDataView(); + const { integrations } = useIntegrationsContext(); + console.log({ integrations }); const handleStreamSelection = (dataStream: DataStream) => { return stateContainer.actions.onCreateDefaultAdHocDataView(dataStream); @@ -41,7 +48,10 @@ export function CustomDataStreamSelector({ stateContainer }: Props) { onUncategorizedClick={() => console.log('fetch uncategorized streams')} /> ); -} +}); + +// eslint-disable-next-line import/no-default-export +export default CustomDataStreamSelector; const mockUncategorized = [{ name: 'metrics-*' }, { name: 'logs-*' }]; @@ -84,3 +94,25 @@ const mockIntegrations = [ ], }, ]; + +export type CustomDataStreamSelectorBuilderProps = ObservabilityLogsPluginProviderProps & + CustomDataStreamSelectorProps; + +function withProviders(Component: React.FunctionComponent) { + return function ComponentWithProviders({ + core, + plugins, + pluginStart, + stateContainer, + }: CustomDataStreamSelectorBuilderProps) { + return ( + + + + + + + + ); + }; +} diff --git a/x-pack/plugins/observability_logs/public/customizations/index.tsx b/x-pack/plugins/observability_logs/public/customizations/index.tsx new file mode 100644 index 0000000000000..e7c9bc759ebb2 --- /dev/null +++ b/x-pack/plugins/observability_logs/public/customizations/index.tsx @@ -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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { lazy, Suspense } from 'react'; +import type { CustomDataStreamSelectorBuilderProps } from './custom_data_stream_selector'; + +const LazyCustomDataStreamSelector = lazy(() => import('./custom_data_stream_selector')); + +export function createLazyCustomDataStreamSelector({ + core, + plugins, + pluginStart, + stateContainer, +}: CustomDataStreamSelectorBuilderProps) { + return () => { + return ( + + + + ); + }; +} diff --git a/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts b/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts new file mode 100644 index 0000000000000..0a32a9de94f51 --- /dev/null +++ b/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts @@ -0,0 +1,76 @@ +/* + * 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 { useInterpret, useSelector } from '@xstate/react'; +import createContainer from 'constate'; +import { useCallback } from 'react'; +import { + createIntegrationStateMachine, + IntegrationsSearchParams, +} from '../state_machines/integrations'; +import { IIntegrationsClient } from '../services/integrations'; + +interface IntegrationsContextDeps { + integrationsClient: IIntegrationsClient; +} + +const useIntegrations = ({ integrationsClient }: IntegrationsContextDeps) => { + const integrationsStateService = useInterpret(() => + createIntegrationStateMachine({ + integrationsClient, + }) + ); + + const integrations = useSelector(integrationsStateService, (state) => + state.matches('loaded') ? state.context.integrations : undefined + ); + + const isUninitialized = useSelector(integrationsStateService, (state) => + state.matches('uninitialized') + ); + + const isLoading = useSelector(integrationsStateService, (state) => state.matches('loading')); + + const hasFailedLoading = useSelector(integrationsStateService, (state) => + state.matches('loadingFailed') + ); + + const search = useCallback( + (searchParams: IntegrationsSearchParams) => + integrationsStateService.send({ + type: 'SEARCH_INTEGRATIONS', + search: searchParams, + }), + [integrationsStateService] + ); + + const loadMore = useCallback( + () => integrationsStateService.send({ type: 'LOAD_MORE_INTEGRATIONS' }), + [integrationsStateService] + ); + + return { + // Underlying state machine + integrationsStateService, + + // Failure states + hasFailedLoading, + + // Loading states + isUninitialized, + isLoading, + + // Data + integrations, + + // Actions + search, + loadMore, + }; +}; + +export const [IntegrationsProvider, useIntegrationsContext] = createContainer(useIntegrations); diff --git a/x-pack/plugins/observability_logs/public/hooks/use_kibana.tsx b/x-pack/plugins/observability_logs/public/hooks/use_kibana.tsx new file mode 100644 index 0000000000000..282c9678729ef --- /dev/null +++ b/x-pack/plugins/observability_logs/public/hooks/use_kibana.tsx @@ -0,0 +1,46 @@ +/* + * 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 React, { PropsWithChildren } from 'react'; +import { CoreStart } from '@kbn/core/public'; +import { + createKibanaReactContext, + KibanaReactContextValue, + useKibana, +} from '@kbn/kibana-react-plugin/public'; +import { useMemo } from 'react'; +import { ObservabilityLogsPluginStart, ObservabilityLogsStartDeps } from '../types'; + +export type ObservabilityLogsContext = CoreStart & + ObservabilityLogsStartDeps & + ObservabilityLogsPluginStart; + +export const useObservabilityLogsPlugin = + useKibana as () => KibanaReactContextValue; + +export interface ObservabilityLogsPluginProviderProps { + core: CoreStart; + plugins: ObservabilityLogsStartDeps; + pluginStart: ObservabilityLogsPluginStart; + children: React.ReactNode; +} + +export const ObservabilityLogsPluginProvider = ({ + core, + plugins, + pluginStart, + children, +}: PropsWithChildren) => { + const context = useMemo( + () => ({ ...core, ...plugins, ...pluginStart }), + [core, pluginStart, plugins] + ); + + const { Provider } = createKibanaReactContext(context); + + return ; +}; diff --git a/x-pack/plugins/observability_logs/public/plugin.tsx b/x-pack/plugins/observability_logs/public/plugin.tsx index c94c92dd86f6e..123d7c97199df 100644 --- a/x-pack/plugins/observability_logs/public/plugin.tsx +++ b/x-pack/plugins/observability_logs/public/plugin.tsx @@ -5,23 +5,39 @@ * 2.0. */ -import { CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; +import { CoreSetup, CoreStart } from '@kbn/core/public'; import React from 'react'; +import { IntegrationsService } from './services/integrations'; import { + ObservabilityLogsClientPluginClass, ObservabilityLogsPluginSetup, ObservabilityLogsPluginStart, ObservabilityLogsStartDeps, } from './types'; import { InternalStateProvider } from './utils/internal_state_container_context'; -export class ObservabilityLogsPlugin - implements Plugin -{ - public setup(core: CoreSetup): ObservabilityLogsPluginSetup {} +export class ObservabilityLogsPlugin implements ObservabilityLogsClientPluginClass { + private integrationsService: IntegrationsService; - public start(core: CoreStart, plugins: ObservabilityLogsStartDeps): ObservabilityLogsPluginStart { + constructor() { + this.integrationsService = new IntegrationsService(); + } + + public setup() {} + + public start(core: CoreStart, plugins: ObservabilityLogsStartDeps) { const { discover } = plugins; + const getStartServices = () => ({ core, plugins, pluginStart }); + + const integrationsService = this.integrationsService.start({ + http: core.http, + }); + + const pluginStart = { + integrationsService, + }; + /** * Replace the DataViewPicker with a custom DataStreamSelector to access only integrations streams */ @@ -34,9 +50,7 @@ export class ObservabilityLogsPlugin id: 'search_bar', CustomDataViewPicker: () => { return ( - - - + ); }, }); @@ -58,5 +72,7 @@ export class ObservabilityLogsPlugin console.log('Cleaning up Logs explorer customizations'); }; }); + + return pluginStart; } } diff --git a/x-pack/plugins/observability_logs/public/services/integrations/integrations_client.ts b/x-pack/plugins/observability_logs/public/services/integrations/integrations_client.ts index 7b79d202d5fb2..3b1122f0cd89b 100644 --- a/x-pack/plugins/observability_logs/public/services/integrations/integrations_client.ts +++ b/x-pack/plugins/observability_logs/public/services/integrations/integrations_client.ts @@ -6,9 +6,10 @@ */ import { HttpStart } from '@kbn/core/public'; -import { decodeOrThrow } from '@kbn/infra-plugin/common/runtime_types'; +import { decodeOrThrow } from '../../../common/runtime_types'; import { FindIntegrationsRequestQuery, + findIntegrationsRequestQueryRT, FindIntegrationsResponse, findIntegrationsResponseRT, getIntegrationsUrl, @@ -20,9 +21,14 @@ export class IntegrationsClient implements IIntegrationsClient { constructor(private readonly http: HttpStart) {} public async findIntegrations( - params: FindIntegrationsRequestQuery + params: FindIntegrationsRequestQuery = {} ): Promise { - const response = await this.http.get(getIntegrationsUrl(params)).catch((error) => { + const search = decodeOrThrow( + findIntegrationsRequestQueryRT, + (message: string) => new Error(`Failed to decode integrations search param: ${message}"`) + )(params); + + const response = await this.http.get(getIntegrationsUrl(search)).catch((error) => { throw new Error(`Failed to fetch integrations": ${error}`); }); diff --git a/x-pack/plugins/observability_logs/public/services/integrations/integrations_service.ts b/x-pack/plugins/observability_logs/public/services/integrations/integrations_service.ts index 4ae669194fadd..d725ca185a7e9 100644 --- a/x-pack/plugins/observability_logs/public/services/integrations/integrations_service.ts +++ b/x-pack/plugins/observability_logs/public/services/integrations/integrations_service.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { IntegrationsStaticConfig } from '../../../common/integrations'; import { IntegrationsClient } from './integrations_client'; import { IntegrationsServiceStartDeps, @@ -14,7 +13,7 @@ import { } from './types'; export class IntegrationsService { - constructor(private readonly config: IntegrationsStaticConfig) {} + constructor() {} public setup(): IntegrationsServiceSetup {} diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts index 9a9bf1979e52d..12bb3e7cf90f9 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts @@ -7,6 +7,7 @@ import { catchError, from, map, of, throwError } from 'rxjs'; import { actions, createMachine } from 'xstate'; +import { IIntegrationsClient } from '../../../services/integrations'; import { DefaultIntegrationsContext, IntegrationsContext, @@ -17,13 +18,13 @@ import { const defaultContext = { integrations: [], error: null, - page: 0, + page: [], }; export const createPureIntegrationsStateMachine = ( initialContext: DefaultIntegrationsContext = defaultContext ) => - /** @xstate-layout N4IgpgJg5mDOIC5QEkB2AXMUBOBDdAlgPaqwB0ArqgdYbgDYEBekAxANoAMAuoqAA5FYBQiT4gAHogCMAJgDsnMrICc8hbIBs0zgBZ5ADgA0IAJ4yAzCrLT5KgKybO92Z2cHNmgL5eTaTDj4xKRk9ES4EDRQrAAyAPIAggAiyAByAOIA+gDKAKoAwvkAokVJpVy8SCCCwqKo4lIIBioGZBb20va6mgYWnJ7yuibmCAoWZOqauhadKppdBl0+fhhYeHXkYRFRsYkpGZkAYgnIMeU84jUiwQ0y3bpknCrSuk-NHbYWw4gWv2SaFm6PUW80MKmWIH8ayCJE24UiqCgAFkiNgwLtkmksnlCiUykkKpchNcxFVGrILJobLpdB0LB4DINZC9vgh7M42jN6Z1NIZ2rJ7BCoYENqF4VEUWiMfsssdTudKgJiXVbggALT2R6aAWfAxyeyGZz2VlOKm2XRyTqyWSvAVC1Yi4LkWBgXDYADGAAsosL1k7pVicgViqUFUTajcyYgKWaaXSGUyWWYZG4yB4tFpXtpAep7QE-bCyC63V6fQ6C6QAwc5WcCRcqlcVVH1UoprTdB4ZgL5IMPKy1VM2pxGczBp4LQZZHnoaKthA2AAlIrxZKZNIAFSK6QXCXXyDiqWyhIbysjoEaBgMD0ZlM4WhenkvrN01rIKhmk+0Bk4L0W08dhZzmwK5JJkSJxEua6pJu267vuh7HkqEakue0aeBMfQ0umBrfsayYIPSVKvA40i2Ko9jtAY-4VnCERsNkRQJAu+QABJQTBO57geR71khJL1M2sh6soBp2BS8g4f0rKZm+pE0taLzvlM8jUTCIRzlEhy4AQ9CLsuezsVunHwTxirVKeKGSHcHb-D0t5ZjML7SV0abaBaqiKb+Pi+CAqBEPO8BVL6amBXxTaoeq342GReiXvqPZ4SMaqdNIabDuo5rjtIk6qaKVA0NcDDMJA4b8aqchWI8iwqNa758hYsistI1iuKoKgOFerhXrouVOmK2yIqV4VWQgpGMo8lrSACTj6L00mUv8cntBR8ieM4vWAeKiKSmAQ1niNaqqG+FpZgKz4WPIbSGDaNq-O0nDtBtITFh63qIsFGx7ZZjRqg10XqLFerdhJTWKMoepyLSnXDjST20fOEBfQJEVOLIZDdCO3SAg9tLSeNpFzIYjIqN0hgqT5H19RpiJaTpJUnshyMjVN7U2HZFEs-YDjSRa6NyS+zK6Ep+jeV4QA */ + /** @xstate-layout N4IgpgJg5mDOIC5QEkB2AXMUBOBDdAlgPaqwB0ArqgdYbgDYEBekAxANoAMAuoqAA5FYBQiT4gAHogCMAJlmcyAFgCsAZiVrZa6QHY1ATk66ANCACeMgGxlOVjfq27ZKlbOkBfD2bSYc+YlIyeiJcCBooVgAZAHkAQQARZAA5AHEAfQBlAFUAYVyAUQKE4q5eJBBBYVFUcSkEAA4lBrIVY107dV0VBs5pM0sEaTayK2lODSUlCd1VYy8fDCw8GvIQsIjo+KS09IAxOOQo0p5xKpFAupklaSUyWSVuqwMrTgU1PoHEd0UVaQaetIrPZdN1VAsQL5lgESGtQuFUFAALJEbBgLaJFIZHL5IolBJlM5CC5iCr1FwGMhqFQvQzqexWFRWL4ILR3KyyBryUEGaS8gwPCFQ-yrYLwiIotEYnYZA5HE7lATEmpXBAAWhUdyM9hUuissw0bQMLOaalsPQmmlkBhpvKFSxFgThYTYACUCrFEukUgAVAqpV1xH3IGLJTKEirnFVkxANam2a167TuQEqFnWykNYb6BoGBr6Oa6e1+FZOsUuiDS9JImLu73JP0BoMhsMRpXVS4xoZA+56Pq9Xqgy3pzgqVqMrPSNQ6B4NAHF6Gi9YQNiZApxV25AAS9cbgeDofDp0jys7oHq+tkVKBnC5b0MALTFkQY3uBmn+asWZ5fQXjth5YIlAey4AQ9Buh62y7v6+4tkeiqVKepLnjID5kFyqgKNIU7yF+LJTtIZCzOoLjOO42HUl43ggKgRArvAFTCqWsJEh2yGSIgapyLoREAg8jKaLezgNCyXG8lSeazLotzPO4Ux-sxQRUDQFwMMwkCsSStRdthub3EoChclYUyzMyz7quMZq8h8n63jSKgKTCQTLhEmnRih3ZqDxQJcnqdj6Lysj4VOyg2hocZTACHQOdRTFOc6QGSmAblnhx6q6JSuh8UoAk2cJLI6HcsgcsMbjTJqzyOUu8IaSebHaR5rhXhyzRNBonAGI8QXmUCdycBMNI+cCxlKFVZYuYiIFgbV7ZaaqvWUmyfycnqbQiT1-xvuorhvO405vFRHhAA */ createMachine( { context: initialContext, @@ -68,27 +69,12 @@ export const createPureIntegrationsStateMachine = ( }, }, }, - searchingIntegrations: { - invoke: { - src: 'searchIntegrations', - }, - on: { - LOADING_SUCCEEDED: { - target: 'loaded', - actions: 'storeIntegrations', - }, - LOADING_FAILED: { - target: 'loadingFailed', - actions: 'storeError', - }, - }, - }, loaded: { entry: 'notifyLoadingSucceeded', on: { RELOAD_INTEGRATIONS: 'loading', LOAD_MORE_INTEGRATIONS: 'loadingMore', - SEARCH_INTEGRATIONS: 'searchingIntegrations', + SEARCH_INTEGRATIONS: 'loading', }, }, loadingFailed: { @@ -111,21 +97,21 @@ export const createPureIntegrationsStateMachine = ( ); export interface IntegrationsStateMachineDependencies { - initialContext: IntegrationsContext; - logViews: IIntegrationsClient; + initialContext?: IntegrationsContext; + integrationsClient: IIntegrationsClient; } export const createIntegrationStateMachine = ({ initialContext, - integrations, + integrationsClient, }: IntegrationsStateMachineDependencies) => createPureIntegrationsStateMachine(initialContext).withConfig({ actions: {}, services: { - loadIntegrations: (context) => + loadIntegrations: (_context, event) => from( - 'logViewReference' in context - ? integrations.getIntegrations() + 'search' in event + ? integrationsClient.findIntegrations(event.search) : throwError(() => new Error('Failed to load integrations')) ).pipe( map( diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts index b7b817a72fbd0..5328c6222a976 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts @@ -4,16 +4,16 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import { PageAfter } from '../../../../common/latest'; import { Integration } from '../../../../common/integrations'; export interface DefaultIntegrationsContext { integrations: Integration[]; error: Error | null; - page: number; + page: PageAfter; } -interface IntegrationsSearchParams { +export interface IntegrationsSearchParams { name: string; sortDirection: 'asc' | 'desc'; } @@ -26,6 +26,18 @@ export type IntegrationTypestate = | { value: 'loading'; context: DefaultIntegrationsContext; + } + | { + value: 'loaded'; + context: DefaultIntegrationsContext; + } + | { + value: 'loadingFailed'; + context: DefaultIntegrationsContext; + } + | { + value: 'loadingMore'; + context: DefaultIntegrationsContext; }; export type IntegrationsContext = IntegrationTypestate['context']; @@ -34,7 +46,7 @@ export type IntegrationsEvent = | { type: 'LOADING_SUCCEEDED'; integrations: Integration[]; - page: number; + page?: PageAfter; } | { type: 'LOADING_FAILED'; diff --git a/x-pack/plugins/observability_logs/public/types.ts b/x-pack/plugins/observability_logs/public/types.ts index 9ef94cfdc7fce..6129376dc9a62 100644 --- a/x-pack/plugins/observability_logs/public/types.ts +++ b/x-pack/plugins/observability_logs/public/types.ts @@ -4,11 +4,20 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import type { Plugin } from '@kbn/core/public'; import type { DiscoverStart } from '@kbn/discover-plugin/public'; +import { IntegrationsServiceStart } from './services/integrations'; export type ObservabilityLogsPluginSetup = void; -export type ObservabilityLogsPluginStart = void; +export interface ObservabilityLogsPluginStart { + integrationsService: IntegrationsServiceStart; +} export interface ObservabilityLogsStartDeps { discover: DiscoverStart; } + +export type ObservabilityLogsClientPluginClass = Plugin< + ObservabilityLogsPluginSetup, + ObservabilityLogsPluginStart +>; From 726d2b0b7a79a2883bdb42959ea704a3f3b718ba Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Fri, 12 May 2023 13:45:00 +0000 Subject: [PATCH 084/122] [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' --- .../plugins/observability_logs/common/runtime_types.ts | 1 - x-pack/plugins/observability_logs/public/plugin.tsx | 10 ++-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/observability_logs/common/runtime_types.ts b/x-pack/plugins/observability_logs/common/runtime_types.ts index 8d747dd4c43f3..d6d0336eafdd7 100644 --- a/x-pack/plugins/observability_logs/common/runtime_types.ts +++ b/x-pack/plugins/observability_logs/common/runtime_types.ts @@ -9,7 +9,6 @@ import { fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; import { pipe } from 'fp-ts/lib/pipeable'; import { Context, Errors, IntersectionType, Type, UnionType, ValidationError } from 'io-ts'; -import type { RouteValidationFunction } from '@kbn/core/server'; type ErrorFactory = (message: string) => Error; diff --git a/x-pack/plugins/observability_logs/public/plugin.tsx b/x-pack/plugins/observability_logs/public/plugin.tsx index 123d7c97199df..608458d622efb 100644 --- a/x-pack/plugins/observability_logs/public/plugin.tsx +++ b/x-pack/plugins/observability_logs/public/plugin.tsx @@ -5,16 +5,10 @@ * 2.0. */ -import { CoreSetup, CoreStart } from '@kbn/core/public'; +import { CoreStart } from '@kbn/core/public'; import React from 'react'; import { IntegrationsService } from './services/integrations'; -import { - ObservabilityLogsClientPluginClass, - ObservabilityLogsPluginSetup, - ObservabilityLogsPluginStart, - ObservabilityLogsStartDeps, -} from './types'; -import { InternalStateProvider } from './utils/internal_state_container_context'; +import { ObservabilityLogsClientPluginClass, ObservabilityLogsStartDeps } from './types'; export class ObservabilityLogsPlugin implements ObservabilityLogsClientPluginClass { private integrationsService: IntegrationsService; From 9ec6a43a096b976646d0297927b3bcdaed76eaf2 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Fri, 12 May 2023 13:52:07 +0000 Subject: [PATCH 085/122] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- x-pack/plugins/observability_logs/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/observability_logs/tsconfig.json b/x-pack/plugins/observability_logs/tsconfig.json index 490d67bdd6632..954cfb26a47de 100644 --- a/x-pack/plugins/observability_logs/tsconfig.json +++ b/x-pack/plugins/observability_logs/tsconfig.json @@ -12,7 +12,7 @@ "@kbn/fleet-plugin", "@kbn/kibana-utils-plugin", "@kbn/io-ts-utils", - "@kbn/infra-plugin", + "@kbn/kibana-react-plugin", ], "exclude": ["target/**/*"] } From ac2a0b4e959f493372ba475556c20b932577594f Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Mon, 15 May 2023 11:34:03 +0200 Subject: [PATCH 086/122] refactor(observability-log): rename integrations to data streams service --- .../{integrations => data_streams}/index.ts | 0 .../{integrations => data_streams}/types.ts | 14 ++++++--- .../v1/common.ts | 10 +++++++ .../data_streams/v1/find_data_streams.ts | 24 +++++++++++++++ .../v1/find_integrations.ts | 3 +- .../v1/index.ts | 1 + .../observability_logs/common/index.ts | 11 +++---- .../observability_logs/common/latest.ts | 2 +- .../data_stream_selector.tsx | 26 +++++++++++------ .../custom_data_stream_selector.tsx | 18 ++++-------- .../public/hooks/use_integrations.ts | 10 +++---- .../data_streams_client.mock.ts} | 5 ++-- .../data_streams_client.ts} | 29 +++++++++++++++++-- .../data_streams/data_streams_service.mock.ts | 16 ++++++++++ .../data_streams/data_streams_service.ts | 27 +++++++++++++++++ .../{integrations => data_streams}/index.ts | 4 +-- .../public/services/data_streams/types.ts | 29 +++++++++++++++++++ .../integrations/integrations_service.mock.ts | 16 ---------- .../integrations/integrations_service.ts | 27 ----------------- .../public/services/integrations/types.ts | 23 --------------- .../integrations/src/state_machine.ts | 7 ++--- .../state_machines/integrations/src/types.ts | 5 ++-- 22 files changed, 189 insertions(+), 118 deletions(-) rename x-pack/plugins/observability_logs/common/{integrations => data_streams}/index.ts (100%) rename x-pack/plugins/observability_logs/common/{integrations => data_streams}/types.ts (81%) rename x-pack/plugins/observability_logs/common/{integrations => data_streams}/v1/common.ts (58%) create mode 100644 x-pack/plugins/observability_logs/common/data_streams/v1/find_data_streams.ts rename x-pack/plugins/observability_logs/common/{integrations => data_streams}/v1/find_integrations.ts (93%) rename x-pack/plugins/observability_logs/common/{integrations => data_streams}/v1/index.ts (89%) rename x-pack/plugins/observability_logs/public/services/{integrations/integrations_client.mock.ts => data_streams/data_streams_client.mock.ts} (64%) rename x-pack/plugins/observability_logs/public/services/{integrations/integrations_client.ts => data_streams/data_streams_client.ts} (58%) create mode 100644 x-pack/plugins/observability_logs/public/services/data_streams/data_streams_service.mock.ts create mode 100644 x-pack/plugins/observability_logs/public/services/data_streams/data_streams_service.ts rename x-pack/plugins/observability_logs/public/services/{integrations => data_streams}/index.ts (77%) create mode 100644 x-pack/plugins/observability_logs/public/services/data_streams/types.ts delete mode 100644 x-pack/plugins/observability_logs/public/services/integrations/integrations_service.mock.ts delete mode 100644 x-pack/plugins/observability_logs/public/services/integrations/integrations_service.ts delete mode 100644 x-pack/plugins/observability_logs/public/services/integrations/types.ts diff --git a/x-pack/plugins/observability_logs/common/integrations/index.ts b/x-pack/plugins/observability_logs/common/data_streams/index.ts similarity index 100% rename from x-pack/plugins/observability_logs/common/integrations/index.ts rename to x-pack/plugins/observability_logs/common/data_streams/index.ts diff --git a/x-pack/plugins/observability_logs/common/integrations/types.ts b/x-pack/plugins/observability_logs/common/data_streams/types.ts similarity index 81% rename from x-pack/plugins/observability_logs/common/integrations/types.ts rename to x-pack/plugins/observability_logs/common/data_streams/types.ts index a45ee7098a01f..ecc755b34f79f 100644 --- a/x-pack/plugins/observability_logs/common/integrations/types.ts +++ b/x-pack/plugins/observability_logs/common/data_streams/types.ts @@ -8,10 +8,16 @@ import { indexPatternRt } from '@kbn/io-ts-utils'; import * as rt from 'io-ts'; -export const dataStreamRT = rt.type({ - name: rt.string, - title: indexPatternRt, -}); +export const dataStreamRT = rt.exact( + rt.intersection([ + rt.type({ + name: rt.string, + }), + rt.partial({ + title: indexPatternRt, + }), + ]) +); const integrationStatusRT = rt.union([ rt.literal('installed'), diff --git a/x-pack/plugins/observability_logs/common/integrations/v1/common.ts b/x-pack/plugins/observability_logs/common/data_streams/v1/common.ts similarity index 58% rename from x-pack/plugins/observability_logs/common/integrations/v1/common.ts rename to x-pack/plugins/observability_logs/common/data_streams/v1/common.ts index d047eefbcf0a5..c18a4933ee42d 100644 --- a/x-pack/plugins/observability_logs/common/integrations/v1/common.ts +++ b/x-pack/plugins/observability_logs/common/data_streams/v1/common.ts @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import * as rt from 'io-ts'; export const INTEGRATIONS_URL = '/api/fleet/epm/packages/installed'; export const getIntegrationsUrl = (search = {}) => { @@ -11,3 +12,12 @@ export const getIntegrationsUrl = (search = {}) => { return [INTEGRATIONS_URL, querySearch].filter(Boolean).join('/'); }; + +export const DATA_STREAMS_URL = '/api/fleet/epm/data_streams'; +export const getDataStreamsUrl = (search = {}) => { + const querySearch = new URLSearchParams(search).toString(); + + return [DATA_STREAMS_URL, querySearch].filter(Boolean).join('/'); +}; + +export const sortDirectionRT = rt.union([rt.literal('asc'), rt.literal('desc')]); diff --git a/x-pack/plugins/observability_logs/common/data_streams/v1/find_data_streams.ts b/x-pack/plugins/observability_logs/common/data_streams/v1/find_data_streams.ts new file mode 100644 index 0000000000000..cc6523fee1659 --- /dev/null +++ b/x-pack/plugins/observability_logs/common/data_streams/v1/find_data_streams.ts @@ -0,0 +1,24 @@ +/* + * 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 * as rt from 'io-ts'; +import { dataStreamRT } from '../types'; +import { sortDirectionRT } from './common'; + +export const findDataStreamsResponseRT = rt.type({ + items: rt.array(dataStreamRT), +}); + +export const findDataStreamsRequestQueryRT = rt.partial({ + datasetQuery: rt.string, + type: rt.literal('log'), + sortDirection: sortDirectionRT, + uncategorisedOnly: rt.boolean, +}); + +export type FindDataStreamsRequestQuery = rt.TypeOf; +export type FindDataStreamsResponse = rt.TypeOf; diff --git a/x-pack/plugins/observability_logs/common/integrations/v1/find_integrations.ts b/x-pack/plugins/observability_logs/common/data_streams/v1/find_integrations.ts similarity index 93% rename from x-pack/plugins/observability_logs/common/integrations/v1/find_integrations.ts rename to x-pack/plugins/observability_logs/common/data_streams/v1/find_integrations.ts index 3ec39b39b8c42..a2f0cf563ed6a 100644 --- a/x-pack/plugins/observability_logs/common/integrations/v1/find_integrations.ts +++ b/x-pack/plugins/observability_logs/common/data_streams/v1/find_integrations.ts @@ -7,11 +7,10 @@ import * as rt from 'io-ts'; import { integrationRT } from '../types'; +import { sortDirectionRT } from './common'; const pageAfterRT = rt.array(rt.union([rt.number, rt.string])); -const sortDirectionRT = rt.union([rt.literal('asc'), rt.literal('desc')]); - export const findIntegrationsResponseRT = rt.exact( rt.intersection([ rt.type({ diff --git a/x-pack/plugins/observability_logs/common/integrations/v1/index.ts b/x-pack/plugins/observability_logs/common/data_streams/v1/index.ts similarity index 89% rename from x-pack/plugins/observability_logs/common/integrations/v1/index.ts rename to x-pack/plugins/observability_logs/common/data_streams/v1/index.ts index 792cf87991e40..4b617363cee19 100644 --- a/x-pack/plugins/observability_logs/common/integrations/v1/index.ts +++ b/x-pack/plugins/observability_logs/common/data_streams/v1/index.ts @@ -6,4 +6,5 @@ */ export * from './common'; +export * from './find_data_streams'; export * from './find_integrations'; diff --git a/x-pack/plugins/observability_logs/common/index.ts b/x-pack/plugins/observability_logs/common/index.ts index a93510cbc68f1..98902101b37f6 100644 --- a/x-pack/plugins/observability_logs/common/index.ts +++ b/x-pack/plugins/observability_logs/common/index.ts @@ -5,6 +5,8 @@ * 2.0. */ +/* eslint-disable @kbn/eslint/no_export_all */ + export const PLUGIN_ID = 'observabilityLogs'; export const PLUGIN_NAME = 'observabilityLogs'; @@ -12,10 +14,5 @@ export const PLUGIN_NAME = 'observabilityLogs'; * Exporting versioned APIs types */ export type { FindIntegrationsRequestQuery, FindIntegrationsResponse } from './latest'; -export { - INTEGRATIONS_URL, - getIntegrationsUrl, - findIntegrationsResponseRT, - findIntegrationsRequestQueryRT, -} from './latest'; -export * as integrationsV1 from './integrations/v1'; +export * from './latest'; +export * as dataStreamsV1 from './data_streams/v1'; diff --git a/x-pack/plugins/observability_logs/common/latest.ts b/x-pack/plugins/observability_logs/common/latest.ts index af947c136f39f..8b7e484f21d35 100644 --- a/x-pack/plugins/observability_logs/common/latest.ts +++ b/x-pack/plugins/observability_logs/common/latest.ts @@ -5,4 +5,4 @@ * 2.0. */ -export * from './integrations/v1'; +export * from './data_streams/v1'; diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx index aab8433422ddd..0f128c5a6abb9 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx @@ -43,16 +43,17 @@ import { getPopoverButtonStyles } from './data_stream_selector.utils'; import { useBoolean } from '../../hooks/use_boolean'; -import type { DataStream, Integration } from '../../../common/integrations'; +import type { DataStream, Integration } from '../../../common/data_streams'; +import { SearchIntegrations } from '../../hooks/use_integrations'; export interface DataStreamSelectorProps { title: string; - integrations: any[]; + integrations: Integration[] | null; uncategorizedStreams: any[]; isSearching: boolean; isLoadingIntegrations: boolean; isLoadingUncategorizedStreams: boolean; /* Triggered when a search or sorting is performed */ - onSearch: () => void; + onSearch: SearchIntegrations; /* Triggered when the uncategorized streams entry is selected */ onUncategorizedClick: () => void; /* Triggered when a data stream entry is selected */ @@ -91,18 +92,25 @@ export function DataStreamSelector({ ); const { items: integrationItems, panels: integrationPanels } = useMemo(() => { - const { items, panels } = buildIntegrationsTree({ - list: integrations, - onItemClick: setCurrentPanel, - onStreamSelected: handleStreamSelection, - }); - const uncategorizedStreamsItem = { name: uncategorizedLabel, onClick: onUncategorizedClick, panel: UNCATEGORIZED_STREAMS_PANEL_ID, }; + if (!integrations) { + return { + items: [uncategorizedStreamsItem], + panels: [], + }; + } + + const { items, panels } = buildIntegrationsTree({ + list: integrations, + onItemClick: setCurrentPanel, + onStreamSelected: handleStreamSelection, + }); + return { items: [uncategorizedStreamsItem, ...items], panels, diff --git a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx index f9b162e6ac15b..5f8e71ac625f7 100644 --- a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx @@ -7,7 +7,7 @@ import React, { useState } from 'react'; import { DiscoverStateContainer } from '@kbn/discover-plugin/public'; -import { DataStream } from '../../common/integrations'; +import { DataStream } from '../../common/data_streams'; import { DataStreamSelector } from '../components/data_stream_selector'; import { InternalStateProvider, useDataView } from '../utils/internal_state_container_context'; import { IntegrationsProvider, useIntegrationsContext } from '../hooks/use_integrations'; @@ -23,27 +23,21 @@ interface CustomDataStreamSelectorProps { export const CustomDataStreamSelector = withProviders(({ stateContainer }) => { // Container component, here goes all the state management and custom logic usage to keep the DataStreamSelector presentational. const dataView = useDataView(); - const { integrations } = useIntegrationsContext(); - console.log({ integrations }); + const { integrations, isLoading, search } = useIntegrationsContext(); const handleStreamSelection = (dataStream: DataStream) => { return stateContainer.actions.onCreateDefaultAdHocDataView(dataStream); }; - const [flag, setFlag] = useState(true); - - React.useEffect(() => { - setTimeout(() => setFlag(false), 5000); - }, []); - return ( console.log('fetch uncategorized streams')} /> diff --git a/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts b/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts index 0a32a9de94f51..a038a3faa4847 100644 --- a/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts +++ b/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts @@ -18,6 +18,8 @@ interface IntegrationsContextDeps { integrationsClient: IIntegrationsClient; } +export type SearchIntegrations = (params: IntegrationsSearchParams) => void; + const useIntegrations = ({ integrationsClient }: IntegrationsContextDeps) => { const integrationsStateService = useInterpret(() => createIntegrationStateMachine({ @@ -25,9 +27,7 @@ const useIntegrations = ({ integrationsClient }: IntegrationsContextDeps) => { }) ); - const integrations = useSelector(integrationsStateService, (state) => - state.matches('loaded') ? state.context.integrations : undefined - ); + const integrations = useSelector(integrationsStateService, (state) => state.context.integrations); const isUninitialized = useSelector(integrationsStateService, (state) => state.matches('uninitialized') @@ -39,8 +39,8 @@ const useIntegrations = ({ integrationsClient }: IntegrationsContextDeps) => { state.matches('loadingFailed') ); - const search = useCallback( - (searchParams: IntegrationsSearchParams) => + const search: SearchIntegrations = useCallback( + (searchParams) => integrationsStateService.send({ type: 'SEARCH_INTEGRATIONS', search: searchParams, diff --git a/x-pack/plugins/observability_logs/public/services/integrations/integrations_client.mock.ts b/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_client.mock.ts similarity index 64% rename from x-pack/plugins/observability_logs/public/services/integrations/integrations_client.mock.ts rename to x-pack/plugins/observability_logs/public/services/data_streams/data_streams_client.mock.ts index 99c10f780e9cc..9fb92654d1841 100644 --- a/x-pack/plugins/observability_logs/public/services/integrations/integrations_client.mock.ts +++ b/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_client.mock.ts @@ -5,8 +5,9 @@ * 2.0. */ -import { IIntegrationsClient } from './types'; +import { IDataStreamsClient } from './types'; -export const createIntegrationsClientMock = (): jest.Mocked => ({ +export const createDataStreamsClientMock = (): jest.Mocked => ({ + findDataStreams: jest.fn(), findIntegrations: jest.fn(), }); diff --git a/x-pack/plugins/observability_logs/public/services/integrations/integrations_client.ts b/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_client.ts similarity index 58% rename from x-pack/plugins/observability_logs/public/services/integrations/integrations_client.ts rename to x-pack/plugins/observability_logs/public/services/data_streams/data_streams_client.ts index 3b1122f0cd89b..c2b91538a4981 100644 --- a/x-pack/plugins/observability_logs/public/services/integrations/integrations_client.ts +++ b/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_client.ts @@ -8,16 +8,21 @@ import { HttpStart } from '@kbn/core/public'; import { decodeOrThrow } from '../../../common/runtime_types'; import { + FindDataStreamsRequestQuery, + findDataStreamsRequestQueryRT, + FindDataStreamsResponse, + findDataStreamsResponseRT, FindIntegrationsRequestQuery, findIntegrationsRequestQueryRT, FindIntegrationsResponse, findIntegrationsResponseRT, + getDataStreamsUrl, getIntegrationsUrl, } from '../../../common'; -import { IIntegrationsClient } from './types'; +import { IDataStreamsClient } from './types'; -export class IntegrationsClient implements IIntegrationsClient { +export class DataStreamsClient implements IDataStreamsClient { constructor(private readonly http: HttpStart) {} public async findIntegrations( @@ -39,4 +44,24 @@ export class IntegrationsClient implements IIntegrationsClient { return data; } + + public async findDataStreams( + params: FindDataStreamsRequestQuery = {} + ): Promise { + const search = decodeOrThrow( + findDataStreamsRequestQueryRT, + (message: string) => new Error(`Failed to decode data streams search param: ${message}"`) + )(params); + + const response = await this.http.get(getDataStreamsUrl(search)).catch((error) => { + throw new Error(`Failed to fetch data streams": ${error}`); + }); + + const data = decodeOrThrow( + findDataStreamsResponseRT, + (message: string) => new Error(`Failed to decode data streams response: ${message}"`) + )(response); + + return data; + } } diff --git a/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_service.mock.ts b/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_service.mock.ts new file mode 100644 index 0000000000000..7ddf5035c260c --- /dev/null +++ b/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_service.mock.ts @@ -0,0 +1,16 @@ +/* + * 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 { createDataStreamsClientMock } from './data_streams_client.mock'; +import { DataStreamsServiceStart } from './types'; + +export const createDataStreamsServiceStartMock = () => ({ + client: createDataStreamsClientMock(), +}); + +export const _ensureTypeCompatibility = (): DataStreamsServiceStart => + createDataStreamsServiceStartMock(); diff --git a/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_service.ts b/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_service.ts new file mode 100644 index 0000000000000..95595494f41b2 --- /dev/null +++ b/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_service.ts @@ -0,0 +1,27 @@ +/* + * 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 { DataStreamsClient } from './data_streams_client'; +import { + DataStreamsServiceStartDeps, + DataStreamsServiceSetup, + DataStreamsServiceStart, +} from './types'; + +export class DataStreamsService { + constructor() {} + + public setup(): DataStreamsServiceSetup {} + + public start({ http }: DataStreamsServiceStartDeps): DataStreamsServiceStart { + const client = new DataStreamsClient(http); + + return { + client, + }; + } +} diff --git a/x-pack/plugins/observability_logs/public/services/integrations/index.ts b/x-pack/plugins/observability_logs/public/services/data_streams/index.ts similarity index 77% rename from x-pack/plugins/observability_logs/public/services/integrations/index.ts rename to x-pack/plugins/observability_logs/public/services/data_streams/index.ts index 7ba3d4f86faf6..7277c52204eab 100644 --- a/x-pack/plugins/observability_logs/public/services/integrations/index.ts +++ b/x-pack/plugins/observability_logs/public/services/data_streams/index.ts @@ -5,6 +5,6 @@ * 2.0. */ -export * from './integrations_client'; -export * from './integrations_service'; +export * from './data_streams_client'; +export * from './data_streams_service'; export * from './types'; diff --git a/x-pack/plugins/observability_logs/public/services/data_streams/types.ts b/x-pack/plugins/observability_logs/public/services/data_streams/types.ts new file mode 100644 index 0000000000000..f9f40976fa86e --- /dev/null +++ b/x-pack/plugins/observability_logs/public/services/data_streams/types.ts @@ -0,0 +1,29 @@ +/* + * 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 { HttpStart } from '@kbn/core/public'; +import { + FindDataStreamsRequestQuery, + FindDataStreamsResponse, + FindIntegrationsRequestQuery, + FindIntegrationsResponse, +} from '../../../common'; + +export type DataStreamsServiceSetup = void; + +export interface DataStreamsServiceStart { + client: IDataStreamsClient; +} + +export interface DataStreamsServiceStartDeps { + http: HttpStart; +} + +export interface IDataStreamsClient { + findDataStreams(params?: FindDataStreamsRequestQuery): Promise; + findIntegrations(params?: FindIntegrationsRequestQuery): Promise; +} diff --git a/x-pack/plugins/observability_logs/public/services/integrations/integrations_service.mock.ts b/x-pack/plugins/observability_logs/public/services/integrations/integrations_service.mock.ts deleted file mode 100644 index 87e778e110cbd..0000000000000 --- a/x-pack/plugins/observability_logs/public/services/integrations/integrations_service.mock.ts +++ /dev/null @@ -1,16 +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 { createIntegrationsClientMock } from './integrations_client.mock'; -import { IntegrationsServiceStart } from './types'; - -export const createIntegrationsServiceStartMock = () => ({ - client: createIntegrationsClientMock(), -}); - -export const _ensureTypeCompatibility = (): IntegrationsServiceStart => - createIntegrationsServiceStartMock(); diff --git a/x-pack/plugins/observability_logs/public/services/integrations/integrations_service.ts b/x-pack/plugins/observability_logs/public/services/integrations/integrations_service.ts deleted file mode 100644 index d725ca185a7e9..0000000000000 --- a/x-pack/plugins/observability_logs/public/services/integrations/integrations_service.ts +++ /dev/null @@ -1,27 +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 { IntegrationsClient } from './integrations_client'; -import { - IntegrationsServiceStartDeps, - IntegrationsServiceSetup, - IntegrationsServiceStart, -} from './types'; - -export class IntegrationsService { - constructor() {} - - public setup(): IntegrationsServiceSetup {} - - public start({ http }: IntegrationsServiceStartDeps): IntegrationsServiceStart { - const client = new IntegrationsClient(http); - - return { - client, - }; - } -} diff --git a/x-pack/plugins/observability_logs/public/services/integrations/types.ts b/x-pack/plugins/observability_logs/public/services/integrations/types.ts deleted file mode 100644 index 813e192eaab68..0000000000000 --- a/x-pack/plugins/observability_logs/public/services/integrations/types.ts +++ /dev/null @@ -1,23 +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 { HttpStart } from '@kbn/core/public'; -import { FindIntegrationsRequestQuery, FindIntegrationsResponse } from '../../../common'; - -export type IntegrationsServiceSetup = void; - -export interface IntegrationsServiceStart { - client: IIntegrationsClient; -} - -export interface IntegrationsServiceStartDeps { - http: HttpStart; -} - -export interface IIntegrationsClient { - findIntegrations(params: FindIntegrationsRequestQuery): Promise; -} diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts index 12bb3e7cf90f9..101fbf572964c 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { catchError, from, map, of, throwError } from 'rxjs'; +import { catchError, from, map, of } from 'rxjs'; import { actions, createMachine } from 'xstate'; import { IIntegrationsClient } from '../../../services/integrations'; import { @@ -16,7 +16,7 @@ import { } from './types'; const defaultContext = { - integrations: [], + integrations: null, error: null, page: [], }; @@ -72,7 +72,6 @@ export const createPureIntegrationsStateMachine = ( loaded: { entry: 'notifyLoadingSucceeded', on: { - RELOAD_INTEGRATIONS: 'loading', LOAD_MORE_INTEGRATIONS: 'loadingMore', SEARCH_INTEGRATIONS: 'loading', }, @@ -112,7 +111,7 @@ export const createIntegrationStateMachine = ({ from( 'search' in event ? integrationsClient.findIntegrations(event.search) - : throwError(() => new Error('Failed to load integrations')) + : integrationsClient.findIntegrations() ).pipe( map( (data): IntegrationsEvent => ({ diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts index 5328c6222a976..55e8ca48c857c 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts @@ -5,10 +5,10 @@ * 2.0. */ import { PageAfter } from '../../../../common/latest'; -import { Integration } from '../../../../common/integrations'; +import { Integration } from '../../../../common/data_streams'; export interface DefaultIntegrationsContext { - integrations: Integration[]; + integrations: Integration[] | null; error: Error | null; page: PageAfter; } @@ -54,6 +54,7 @@ export type IntegrationsEvent = } | { type: 'RELOAD_INTEGRATIONS'; + search: IntegrationsSearchParams; } | { type: 'LOAD_MORE_INTEGRATIONS'; From f93d3d5cb52e281eb8617d04361441766cce80b7 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 15 May 2023 10:16:20 +0000 Subject: [PATCH 087/122] [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' --- .../public/customizations/custom_data_stream_selector.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx index 5f8e71ac625f7..17caa7bbb010c 100644 --- a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useState } from 'react'; +import React from 'react'; import { DiscoverStateContainer } from '@kbn/discover-plugin/public'; import { DataStream } from '../../common/data_streams'; import { DataStreamSelector } from '../components/data_stream_selector'; From 18163a11fbb390afdf07dcf343b136b8e23d85b5 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Mon, 15 May 2023 12:46:58 +0200 Subject: [PATCH 088/122] refactor(observability-log): move state machine actions --- .../custom_data_stream_selector.tsx | 44 +--- .../public/customizations/index.tsx | 24 +-- .../public/hooks/use_integrations.ts | 11 +- .../public/hooks/use_kibana.tsx | 1 - .../observability_logs/public/plugin.tsx | 25 +-- .../data_streams/data_streams_client.ts | 192 +++++++++++++++++- .../integrations/src/state_machine.ts | 31 ++- .../state_machines/integrations/src/types.ts | 5 +- .../observability_logs/public/types.ts | 4 +- 9 files changed, 240 insertions(+), 97 deletions(-) diff --git a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx index 5f8e71ac625f7..1b7e21eba1bc7 100644 --- a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useState } from 'react'; +import React from 'react'; import { DiscoverStateContainer } from '@kbn/discover-plugin/public'; import { DataStream } from '../../common/data_streams'; import { DataStreamSelector } from '../components/data_stream_selector'; @@ -49,46 +49,6 @@ export default CustomDataStreamSelector; const mockUncategorized = [{ name: 'metrics-*' }, { name: 'logs-*' }]; -const mockIntegrations = [ - { - name: 'atlassian_jira', - version: '1.8.0', - status: 'installed', - dataStreams: [ - { - name: 'Atlassian metrics stream', - title: 'metrics-*', - }, - { - name: 'Atlassian secondary', - title: 'metrics-*', - }, - ], - }, - { - name: 'docker', - version: '2.4.3', - status: 'installed', - dataStreams: [ - { - name: 'Docker stream', - title: 'metrics-*', - }, - ], - }, - { - name: 'system', - version: '1.27.1', - status: 'installed', - dataStreams: [ - { - name: 'System metrics logs', - title: 'metrics-*', - }, - ], - }, -]; - export type CustomDataStreamSelectorBuilderProps = ObservabilityLogsPluginProviderProps & CustomDataStreamSelectorProps; @@ -102,7 +62,7 @@ function withProviders(Component: React.FunctionComponent - + diff --git a/x-pack/plugins/observability_logs/public/customizations/index.tsx b/x-pack/plugins/observability_logs/public/customizations/index.tsx index e7c9bc759ebb2..5fdc99e5d2f0a 100644 --- a/x-pack/plugins/observability_logs/public/customizations/index.tsx +++ b/x-pack/plugins/observability_logs/public/customizations/index.tsx @@ -10,22 +10,10 @@ import type { CustomDataStreamSelectorBuilderProps } from './custom_data_stream_ const LazyCustomDataStreamSelector = lazy(() => import('./custom_data_stream_selector')); -export function createLazyCustomDataStreamSelector({ - core, - plugins, - pluginStart, - stateContainer, -}: CustomDataStreamSelectorBuilderProps) { - return () => { - return ( - - - - ); - }; +export function createLazyCustomDataStreamSelector(props: CustomDataStreamSelectorBuilderProps) { + return () => ( + + + + ); } diff --git a/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts b/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts index a038a3faa4847..38c5ea42cc692 100644 --- a/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts +++ b/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts @@ -12,23 +12,25 @@ import { createIntegrationStateMachine, IntegrationsSearchParams, } from '../state_machines/integrations'; -import { IIntegrationsClient } from '../services/integrations'; +import { IDataStreamsClient } from '../services/data_streams'; interface IntegrationsContextDeps { - integrationsClient: IIntegrationsClient; + dataStreamsClient: IDataStreamsClient; } export type SearchIntegrations = (params: IntegrationsSearchParams) => void; -const useIntegrations = ({ integrationsClient }: IntegrationsContextDeps) => { +const useIntegrations = ({ dataStreamsClient }: IntegrationsContextDeps) => { const integrationsStateService = useInterpret(() => createIntegrationStateMachine({ - integrationsClient, + dataStreamsClient, }) ); const integrations = useSelector(integrationsStateService, (state) => state.context.integrations); + const error = useSelector(integrationsStateService, (state) => state.context.error); + const isUninitialized = useSelector(integrationsStateService, (state) => state.matches('uninitialized') ); @@ -58,6 +60,7 @@ const useIntegrations = ({ integrationsClient }: IntegrationsContextDeps) => { integrationsStateService, // Failure states + error, hasFailedLoading, // Loading states diff --git a/x-pack/plugins/observability_logs/public/hooks/use_kibana.tsx b/x-pack/plugins/observability_logs/public/hooks/use_kibana.tsx index 282c9678729ef..73d27c25b9d02 100644 --- a/x-pack/plugins/observability_logs/public/hooks/use_kibana.tsx +++ b/x-pack/plugins/observability_logs/public/hooks/use_kibana.tsx @@ -26,7 +26,6 @@ export interface ObservabilityLogsPluginProviderProps { core: CoreStart; plugins: ObservabilityLogsStartDeps; pluginStart: ObservabilityLogsPluginStart; - children: React.ReactNode; } export const ObservabilityLogsPluginProvider = ({ diff --git a/x-pack/plugins/observability_logs/public/plugin.tsx b/x-pack/plugins/observability_logs/public/plugin.tsx index 608458d622efb..9b1da6f2c7966 100644 --- a/x-pack/plugins/observability_logs/public/plugin.tsx +++ b/x-pack/plugins/observability_logs/public/plugin.tsx @@ -6,15 +6,15 @@ */ import { CoreStart } from '@kbn/core/public'; -import React from 'react'; -import { IntegrationsService } from './services/integrations'; +import { createLazyCustomDataStreamSelector } from './customizations'; +import { DataStreamsService } from './services/data_streams'; import { ObservabilityLogsClientPluginClass, ObservabilityLogsStartDeps } from './types'; export class ObservabilityLogsPlugin implements ObservabilityLogsClientPluginClass { - private integrationsService: IntegrationsService; + private dataStreamsService: DataStreamsService; constructor() { - this.integrationsService = new IntegrationsService(); + this.dataStreamsService = new DataStreamsService(); } public setup() {} @@ -24,29 +24,24 @@ export class ObservabilityLogsPlugin implements ObservabilityLogsClientPluginCla const getStartServices = () => ({ core, plugins, pluginStart }); - const integrationsService = this.integrationsService.start({ + const dataStreamsService = this.dataStreamsService.start({ http: core.http, }); const pluginStart = { - integrationsService, + dataStreamsService, }; /** * Replace the DataViewPicker with a custom DataStreamSelector to access only integrations streams */ discover.customize('observability-logs', async ({ customizations, stateContainer }) => { - const { CustomDataStreamSelector } = await import( - './customizations/custom_data_stream_selector' - ); - customizations.set({ id: 'search_bar', - CustomDataViewPicker: () => { - return ( - - ); - }, + CustomDataViewPicker: createLazyCustomDataStreamSelector({ + ...getStartServices(), + stateContainer, + }), }); /** diff --git a/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_client.ts b/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_client.ts index c2b91538a4981..aa7cff63b06f1 100644 --- a/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_client.ts +++ b/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_client.ts @@ -33,14 +33,14 @@ export class DataStreamsClient implements IDataStreamsClient { (message: string) => new Error(`Failed to decode integrations search param: ${message}"`) )(params); - const response = await this.http.get(getIntegrationsUrl(search)).catch((error) => { - throw new Error(`Failed to fetch integrations": ${error}`); - }); + // const response = await this.http.get(getIntegrationsUrl(search)).catch((error) => { + // throw new Error(`Failed to fetch integrations": ${error}`); + // }); const data = decodeOrThrow( findIntegrationsResponseRT, (message: string) => new Error(`Failed to decode integrations response: ${message}"`) - )(response); + )(mockIntegrationsResponse); // TODO: switch with response return data; } @@ -65,3 +65,187 @@ export class DataStreamsClient implements IDataStreamsClient { return data; } } + +const mockIntegrationsResponse = { + total: 11, + pageAfter: [], + items: [ + { + name: 'kubernetes', + version: '3.2.0', + status: 'installed', + dataStreams: [ + { + name: 'Kubernetes metrics stream', + title: 'k8s-metrics-*', + }, + { + name: 'Kubernetes logs stream', + title: 'k8s-logs-*', + }, + ], + }, + { + name: 'mysql', + version: '4.6.1', + status: 'installed', + dataStreams: [ + { + name: 'MySQL metrics stream', + title: 'mysql-metrics-*', + }, + { + name: 'MySQL slow logs stream', + title: 'mysql-slow-logs-*', + }, + { + name: 'MySQL error logs stream', + title: 'mysql-error-logs-*', + }, + ], + }, + { + name: 'apache', + version: '1.5.0', + status: 'installed', + dataStreams: [ + { + name: 'Apache metrics stream', + title: 'apache-metrics-*', + }, + { + name: 'Apache logs stream', + title: 'apache-logs-*', + }, + { + name: 'Apache error logs stream', + title: 'apache-error-logs-*', + }, + ], + }, + { + name: 'nginx', + version: '1.2.0', + status: 'installed', + dataStreams: [ + { + name: 'Nginx metrics stream', + title: 'nginx-metrics-*', + }, + { + name: 'Nginx access logs stream', + title: 'nginx-access-logs-*', + }, + ], + }, + { + name: 'postgresql', + version: '1.13.0', + status: 'installed', + dataStreams: [ + { + name: 'PostgreSQL metrics stream', + title: 'postgresql-metrics-*', + }, + { + name: 'PostgreSQL slow query logs stream', + title: 'postgresql-slow-query-logs-*', + }, + { + name: 'PostgreSQL error logs stream', + title: 'postgresql-error-logs-*', + }, + ], + }, + { + name: 'rabbitmq', + version: '3.0.1', + status: 'installed', + dataStreams: [ + { + name: 'RabbitMQ metrics stream', + title: 'rabbitmq-metrics-*', + }, + { + name: 'RabbitMQ queues stream', + title: 'rabbitmq-queues-*', + }, + { + name: 'RabbitMQ error logs stream', + title: 'rabbitmq-error-logs-*', + }, + ], + }, + { + name: 'redis', + version: '4.3.2', + status: 'installed', + dataStreams: [ + { + name: 'Redis metrics stream', + title: 'redis-metrics-*', + }, + { + name: 'Redis slow logs stream', + title: 'redis-slow-logs-*', + }, + ], + }, + { + name: 'elasticsearch', + version: '7.11.1', + status: 'installed', + dataStreams: [ + { + name: 'Elasticsearch metrics stream', + title: 'elasticsearch-metrics-*', + }, + { + name: 'Elasticsearch indices stream', + title: 'elasticsearch-indices-*', + }, + ], + }, + { + name: 'mongodb', + version: '3.6.1', + status: 'installed', + dataStreams: [ + { + name: 'MongoDB metrics stream', + title: 'mongodb-metrics-*', + }, + { + name: 'MongoDB slow query logs stream', + title: 'mongodb-slow-query-logs-*', + }, + ], + }, + { + name: 'prometheus', + version: '2.28.1', + status: 'installed', + dataStreams: [ + { + name: 'Prometheus metrics stream', + title: 'prometheus-metrics-*', + }, + ], + }, + { + name: 'haproxy', + version: '1.7.5', + status: 'installed', + dataStreams: [ + { + name: 'HAProxy metrics stream', + title: 'haproxy-metrics-*', + }, + { + name: 'HAProxy logs stream', + title: 'haproxy-logs-*', + }, + ], + }, + ], +}; diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts index 101fbf572964c..361822e875bb4 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts @@ -6,8 +6,8 @@ */ import { catchError, from, map, of } from 'rxjs'; -import { actions, createMachine } from 'xstate'; -import { IIntegrationsClient } from '../../../services/integrations'; +import { actions, assign, createMachine } from 'xstate'; +import { IDataStreamsClient } from '../../../services/data_streams'; import { DefaultIntegrationsContext, IntegrationsContext, @@ -91,18 +91,34 @@ export const createPureIntegrationsStateMachine = ( notifyLoadingStarted: actions.pure(() => undefined), notifyLoadingSucceeded: actions.pure(() => undefined), notifyLoadingFailed: actions.pure(() => undefined), + storeIntegrations: assign((context, event) => + 'data' in event + ? { + integrations: event.data.items, + total: event.data.total, + pageAfter: event.data.pageAfter, + } + : {} + ), + storeError: assign((_context, event) => + 'error' in event + ? { + error: event.error, + } + : {} + ), }, } ); export interface IntegrationsStateMachineDependencies { initialContext?: IntegrationsContext; - integrationsClient: IIntegrationsClient; + dataStreamsClient: IDataStreamsClient; } export const createIntegrationStateMachine = ({ initialContext, - integrationsClient, + dataStreamsClient, }: IntegrationsStateMachineDependencies) => createPureIntegrationsStateMachine(initialContext).withConfig({ actions: {}, @@ -110,14 +126,13 @@ export const createIntegrationStateMachine = ({ loadIntegrations: (_context, event) => from( 'search' in event - ? integrationsClient.findIntegrations(event.search) - : integrationsClient.findIntegrations() + ? dataStreamsClient.findIntegrations(event.search) + : dataStreamsClient.findIntegrations() ).pipe( map( (data): IntegrationsEvent => ({ type: 'LOADING_SUCCEEDED', - integrations: data.items, - page: data.pageAfter, + data, }) ), catchError((error) => diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts index 55e8ca48c857c..95e900fca7423 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { PageAfter } from '../../../../common/latest'; +import { PageAfter, FindIntegrationsResponse } from '../../../../common/latest'; import { Integration } from '../../../../common/data_streams'; export interface DefaultIntegrationsContext { @@ -45,8 +45,7 @@ export type IntegrationsContext = IntegrationTypestate['context']; export type IntegrationsEvent = | { type: 'LOADING_SUCCEEDED'; - integrations: Integration[]; - page?: PageAfter; + data: FindIntegrationsResponse; } | { type: 'LOADING_FAILED'; diff --git a/x-pack/plugins/observability_logs/public/types.ts b/x-pack/plugins/observability_logs/public/types.ts index 6129376dc9a62..70399a0a3284f 100644 --- a/x-pack/plugins/observability_logs/public/types.ts +++ b/x-pack/plugins/observability_logs/public/types.ts @@ -6,11 +6,11 @@ */ import type { Plugin } from '@kbn/core/public'; import type { DiscoverStart } from '@kbn/discover-plugin/public'; -import { IntegrationsServiceStart } from './services/integrations'; +import { DataStreamsServiceStart } from './services/data_streams'; export type ObservabilityLogsPluginSetup = void; export interface ObservabilityLogsPluginStart { - integrationsService: IntegrationsServiceStart; + dataStreamsService: DataStreamsServiceStart; } export interface ObservabilityLogsStartDeps { From a0dff35445924fdeb824c040f6d6ba109bd029dc Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 15 May 2023 11:18:48 +0000 Subject: [PATCH 089/122] [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' --- .../public/services/data_streams/data_streams_client.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_client.ts b/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_client.ts index aa7cff63b06f1..f8544817afa43 100644 --- a/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_client.ts +++ b/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_client.ts @@ -17,7 +17,6 @@ import { FindIntegrationsResponse, findIntegrationsResponseRT, getDataStreamsUrl, - getIntegrationsUrl, } from '../../../common'; import { IDataStreamsClient } from './types'; From f5397b080bd8b1a5bab6a774f9f2c488ff557bfb Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Mon, 15 May 2023 16:42:56 +0200 Subject: [PATCH 090/122] refactor(observability-log): load more feature --- .../data_stream_selector.tsx | 39 ++++++++++++++---- .../custom_data_stream_selector.tsx | 4 +- .../public/hooks/use_integrations.ts | 5 +++ .../integrations/src/state_machine.ts | 40 +++++++++++++++++-- .../state_machines/integrations/src/types.ts | 4 +- 5 files changed, 79 insertions(+), 13 deletions(-) diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx index 0f128c5a6abb9..8a58f4023afab 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback, useMemo, useState } from 'react'; +import React, { RefCallback, useCallback, useEffect, useMemo, useState } from 'react'; import { EuiButton, EuiButtonGroup, @@ -19,6 +19,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, + EuiLoadingSpinner, EuiPanel, EuiPopover, EuiSkeletonLoading, @@ -28,6 +29,7 @@ import { } from '@elastic/eui'; import { PackageIcon } from '@kbn/fleet-plugin/public'; +import useIntersection from 'react-use/lib/useIntersection'; import { contextMenuStyles, DATA_VIEW_POPOVER_CONTENT_WIDTH, @@ -44,16 +46,19 @@ import { getPopoverButtonStyles } from './data_stream_selector.utils'; import { useBoolean } from '../../hooks/use_boolean'; import type { DataStream, Integration } from '../../../common/data_streams'; -import { SearchIntegrations } from '../../hooks/use_integrations'; +import { LoadMoreIntegrations, SearchIntegrations } from '../../hooks/use_integrations'; export interface DataStreamSelectorProps { title: string; integrations: Integration[] | null; uncategorizedStreams: any[]; isSearching: boolean; isLoadingIntegrations: boolean; + isLoadingMoreIntegrations: boolean; isLoadingUncategorizedStreams: boolean; /* Triggered when a search or sorting is performed */ onSearch: SearchIntegrations; + /* Triggered when we reach the bottom of the integration list and want to load more */ + onLoadMore: LoadMoreIntegrations; /* Triggered when the uncategorized streams entry is selected */ onUncategorizedClick: () => void; /* Triggered when a data stream entry is selected */ @@ -73,16 +78,35 @@ export function DataStreamSelector({ uncategorizedStreams, isSearching, isLoadingIntegrations, + isLoadingMoreIntegrations, isLoadingUncategorizedStreams, onSearch, + onLoadMore, onStreamSelected, onUncategorizedClick, }: DataStreamSelectorProps) { const isMobile = useIsWithinBreakpoints(['xs', 's']); const [isPopoverOpen, { off: closePopover, toggle: togglePopover }] = useBoolean(false); + const [loadMoreSpyRef, setRef] = useState(null); + + const loadMoreSpyIntersection = useIntersection( + { current: loadMoreSpyRef }, + { root: null, threshold: 0.5 } + ); + + useEffect(() => { + if (loadMoreSpyIntersection?.isIntersecting) { + onLoadMore(); + } + }, [loadMoreSpyIntersection, onLoadMore]); + const [currentPanel, setCurrentPanel] = useState(INTEGRATION_PANEL_ID); + const handlePanelChange = ({ panelId }: { panelId: CurrentPanelId }) => { + setCurrentPanel(panelId); + }; + const handleStreamSelection = useCallback( (dataStream) => { onStreamSelected(dataStream); @@ -107,8 +131,8 @@ export function DataStreamSelector({ const { items, panels } = buildIntegrationsTree({ list: integrations, - onItemClick: setCurrentPanel, onStreamSelected: handleStreamSelection, + spyRef: setRef, }); return { @@ -161,6 +185,7 @@ export function DataStreamSelector({ @@ -262,20 +287,20 @@ interface IntegrationsTree { interface IntegrationsTreeParams { list: Integration[]; - onItemClick: (id: CurrentPanelId) => void; onStreamSelected: StreamSelectionHandler; + spyRef: RefCallback; } -const buildIntegrationsTree = ({ list, onItemClick, onStreamSelected }: IntegrationsTreeParams) => { +const buildIntegrationsTree = ({ list, onStreamSelected, spyRef }: IntegrationsTreeParams) => { return list.reduce( - (res: IntegrationsTree, entry) => { + (res: IntegrationsTree, entry, pos) => { const entryId: CurrentPanelId = `integration-${entry.name}`; res.items.push({ name: entry.name, - onClick: () => onItemClick(entryId), icon: , panel: entryId, + buttonRef: pos === list.length - 1 ? spyRef : undefined, }); res.panels.push({ diff --git a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx index 1b7e21eba1bc7..c3a72eab81c64 100644 --- a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx @@ -23,7 +23,7 @@ interface CustomDataStreamSelectorProps { export const CustomDataStreamSelector = withProviders(({ stateContainer }) => { // Container component, here goes all the state management and custom logic usage to keep the DataStreamSelector presentational. const dataView = useDataView(); - const { integrations, isLoading, search } = useIntegrationsContext(); + const { integrations, isLoading, isLoadingMore, loadMore, search } = useIntegrationsContext(); const handleStreamSelection = (dataStream: DataStream) => { return stateContainer.actions.onCreateDefaultAdHocDataView(dataStream); @@ -36,7 +36,9 @@ export const CustomDataStreamSelector = withProviders(({ stateContainer }) => { uncategorizedStreams={mockUncategorized} isSearching={false} isLoadingIntegrations={isLoading} + isLoadingMoreIntegrations={isLoadingMore} isLoadingUncategorizedStreams={false} + onLoadMore={loadMore} onSearch={search} onStreamSelected={handleStreamSelection} onUncategorizedClick={() => console.log('fetch uncategorized streams')} diff --git a/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts b/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts index 38c5ea42cc692..b84564b03af63 100644 --- a/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts +++ b/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts @@ -19,6 +19,7 @@ interface IntegrationsContextDeps { } export type SearchIntegrations = (params: IntegrationsSearchParams) => void; +export type LoadMoreIntegrations = () => void; const useIntegrations = ({ dataStreamsClient }: IntegrationsContextDeps) => { const integrationsStateService = useInterpret(() => @@ -36,6 +37,9 @@ const useIntegrations = ({ dataStreamsClient }: IntegrationsContextDeps) => { ); const isLoading = useSelector(integrationsStateService, (state) => state.matches('loading')); + const isLoadingMore = useSelector(integrationsStateService, (state) => + state.matches('loadingMore') + ); const hasFailedLoading = useSelector(integrationsStateService, (state) => state.matches('loadingFailed') @@ -66,6 +70,7 @@ const useIntegrations = ({ dataStreamsClient }: IntegrationsContextDeps) => { // Loading states isUninitialized, isLoading, + isLoadingMore, // Data integrations, diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts index 361822e875bb4..be83bd8d66de1 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { catchError, from, map, of } from 'rxjs'; +import { catchError, from, map, of, throwError } from 'rxjs'; import { actions, assign, createMachine } from 'xstate'; import { IDataStreamsClient } from '../../../services/data_streams'; import { @@ -24,7 +24,7 @@ const defaultContext = { export const createPureIntegrationsStateMachine = ( initialContext: DefaultIntegrationsContext = defaultContext ) => - /** @xstate-layout N4IgpgJg5mDOIC5QEkB2AXMUBOBDdAlgPaqwB0ArqgdYbgDYEBekAxANoAMAuoqAA5FYBQiT4gAHogCMAJlmcyAFgCsAZiVrZa6QHY1ATk66ANCACeMgGxlOVjfq27ZKlbOkBfD2bSYc+YlIyeiJcCBooVgAZAHkAQQARZAA5AHEAfQBlAFUAYVyAUQKE4q5eJBBBYVFUcSkEAA4lBrIVY107dV0VBs5pM0sEaTayK2lODSUlCd1VYy8fDCw8GvIQsIjo+KS09IAxOOQo0p5xKpFAupklaSUyWSVuqwMrTgU1PoHEd0UVaQaetIrPZdN1VAsQL5lgESGtQuFUFAALJEbBgLaJFIZHL5IolBJlM5CC5iCr1FwGMhqFQvQzqexWFRWL4ILR3KyyBryUEGaS8gwPCFQ-yrYLwiIotEYnYZA5HE7lATEmpXBAAWhUdyM9hUuissw0bQMLOaalsPQmmlkBhpvKFSxFgThYTYACUCrFEukUgAVAqpV1xH3IGLJTKEirnFVkxANam2a167TuQEqFnWykNYb6BoGBr6Oa6e1+FZOsUuiDS9JImLu73JP0BoMhsMRpXVS4xoZA+56Pq9Xqgy3pzgqVqMrPSNQ6B4NAHF6Gi9YQNiZApxV25AAS9cbgeDofDp0jys7oHq+tkVKBnC5b0MALTFkQY3uBmn+asWZ5fQXjth5YIlAey4AQ9Buh62y7v6+4tkeiqVKepLnjID5kFyqgKNIU7yF+LJTtIZCzOoLjOO42HUl43ggKgRArvAFTCqWsJEh2yGSIgapyLoREAg8jKaLezgNCyXG8lSeazLotzPO4Ux-sxQRUDQFwMMwkCsSStRdthub3EoChclYUyzMyz7quMZq8h8n63jSKgKTCQTLhEmnRih3ZqDxQJcnqdj6Lysj4VOyg2hocZTACHQOdRTFOc6QGSmAblnhx6q6JSuh8UoAk2cJLI6HcsgcsMbjTJqzyOUu8IaSebHaR5rhXhyzRNBonAGI8QXmUCdycBMNI+cCxlKFVZYuYiIFgbV7ZaaqvWUmyfycnqbQiT1-xvuorhvO405vFRHhAA */ + /** @xstate-layout N4IgpgJg5mDOIC5QEkB2AXMUBOBDdAlgPaqwB0ArqgdYbgDYEBekAxANoAMAuoqAA5FYBQiT4gAHogCMAJlmcyAFgCsAZiVrZa6QHY1ATk66ANCACeMgGxlOVjfq27ZKlbOkBfD2bSYc+YlIyeiJcCBooVgAZAHkAQQARZAA5AHEAfQBlAFUAYVyAUQKE4q5eJBBBYVFUcSkEAA4lBrIVY107dV0VBs5pM0sEaTayK2lODSUlCd1VYy8fDCw8GvIQsIjo+KS09IAxOOQo0p5xKpFAupklaSUyWSVuqwMrTgU1PoHEd0UVaQaetIrPZdN1VAsQL5lgESGtQuFUFAALJEbBgLaJFIZHL5IolBJlM5CC5iCr1FwGMhqFQvQzqexWFRWL4ILRqWy6Ma6JpTYydCFQ-yrYLwiIotEYnYZA5HE7lATEmpXBAAWhUdyM9hUnNmGjaBhZzXZnB6E00sgMNIMnm8kKWQsCcLCbFiiXSSJiACUCukUgAVAqpT1xP3IGLJTKEirnJVkmRA+56Pq9Xqgs0shQqVqMhrSaRqHQPBoAgX2laOkXOiCsTIFOKe3IACV9yQDQZDYYjUYV1UucYQVmcVKBnAaCm0BgBKhZY3uBgLDU5udBRhtiz85dhlYRUD2uAI9DY3tdCRbbeDofDkdO0cVfdA9Xzk7IY9UCjzWlkVgaLPz0jIszqC4zjuB+KheLaqBEBAcDiIKm6kESvakg+iAqnIdzUto2gNAWfT0iy6HWso+acAYBhKORSiDmopYbjCQRUDQFwMMwkBISStT9nmDSUg8ChjlYUyzMyFhoeM7LWh8i6vAClp0dCwrrDuHGxqhQz5roZBAmOnJ2Po1qyL++bKJaGi4VMAIdOBtrwQxTo7uKYCqfekhoZyVJuGyuEfMMaiiYMOiKKCwIKN+y5tFYCkOluynsbeyFcepriyKMRZNBoZGPEZYlDEJtgTDSOnAkJSjRQhDkRHuB7xT2nHKkClFUg8fyyDJbQ-rlPFzuorhvO4eGyBBHhAA */ createMachine( { context: initialContext, @@ -91,12 +91,25 @@ export const createPureIntegrationsStateMachine = ( notifyLoadingStarted: actions.pure(() => undefined), notifyLoadingSucceeded: actions.pure(() => undefined), notifyLoadingFailed: actions.pure(() => undefined), - storeIntegrations: assign((context, event) => + storeIntegrations: assign((_context, event) => 'data' in event ? { integrations: event.data.items, total: event.data.total, - pageAfter: event.data.pageAfter, + search: { + pageAfter: event.data.pageAfter, + }, + } + : {} + ), + appendIntegrations: assign((context, event) => + 'data' in event + ? { + integrations: context.integrations?.concat(event.data.items), + total: event.data.total, + search: { + pageAfter: event.data.pageAfter, + }, } : {} ), @@ -142,5 +155,24 @@ export const createIntegrationStateMachine = ({ }) ) ), + loadMoreIntegrations: (context) => + from( + 'search' in context + ? dataStreamsClient.findIntegrations(context.search) + : throwError(() => new Error('Failed to load more integration')) + ).pipe( + map( + (data): IntegrationsEvent => ({ + type: 'LOADING_SUCCEEDED', + data, + }) + ), + catchError((error) => + of({ + type: 'LOADING_FAILED', + error, + }) + ) + ), }, }); diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts index 95e900fca7423..6a77bd8d2056d 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts @@ -10,7 +10,9 @@ import { Integration } from '../../../../common/data_streams'; export interface DefaultIntegrationsContext { integrations: Integration[] | null; error: Error | null; - page: PageAfter; + search?: { + pageAfter?: PageAfter; + }; } export interface IntegrationsSearchParams { From 7b3f67ce477751c262e2173c312045c1e2d8a4e9 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 15 May 2023 15:20:22 +0000 Subject: [PATCH 091/122] [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' --- .../components/data_stream_selector/data_stream_selector.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx index 8a58f4023afab..0227dd844e55b 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx @@ -19,7 +19,6 @@ import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, - EuiLoadingSpinner, EuiPanel, EuiPopover, EuiSkeletonLoading, From 4a1566e9225708f24da8c62a008addcf8a71f9ba Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Tue, 16 May 2023 13:27:09 +0200 Subject: [PATCH 092/122] feat(infra): handle integrations search and load more guard --- .../common/data_streams/v1/common.ts | 2 +- .../data_streams/v1/find_data_streams.ts | 4 +- .../data_streams/v1/find_integrations.ts | 16 ++-- .../observability_logs/common/index.ts | 1 - .../data_stream_selector/constants.ts | 17 ++++- .../data_stream_selector.tsx | 66 +++++++---------- .../custom_data_stream_selector.tsx | 11 +-- .../public/hooks/use_integrations.ts | 11 ++- .../data_streams/data_streams_client.ts | 2 +- .../integrations/src/defaults.ts | 19 +++++ .../integrations/src/state_machine.ts | 74 +++++++++++++------ .../state_machines/integrations/src/types.ts | 20 +++-- 12 files changed, 153 insertions(+), 90 deletions(-) create mode 100644 x-pack/plugins/observability_logs/public/state_machines/integrations/src/defaults.ts diff --git a/x-pack/plugins/observability_logs/common/data_streams/v1/common.ts b/x-pack/plugins/observability_logs/common/data_streams/v1/common.ts index c18a4933ee42d..d8f05b386178c 100644 --- a/x-pack/plugins/observability_logs/common/data_streams/v1/common.ts +++ b/x-pack/plugins/observability_logs/common/data_streams/v1/common.ts @@ -20,4 +20,4 @@ export const getDataStreamsUrl = (search = {}) => { return [DATA_STREAMS_URL, querySearch].filter(Boolean).join('/'); }; -export const sortDirectionRT = rt.union([rt.literal('asc'), rt.literal('desc')]); +export const sortOrderRT = rt.union([rt.literal('asc'), rt.literal('desc')]); diff --git a/x-pack/plugins/observability_logs/common/data_streams/v1/find_data_streams.ts b/x-pack/plugins/observability_logs/common/data_streams/v1/find_data_streams.ts index cc6523fee1659..1070f756cc048 100644 --- a/x-pack/plugins/observability_logs/common/data_streams/v1/find_data_streams.ts +++ b/x-pack/plugins/observability_logs/common/data_streams/v1/find_data_streams.ts @@ -7,7 +7,7 @@ import * as rt from 'io-ts'; import { dataStreamRT } from '../types'; -import { sortDirectionRT } from './common'; +import { sortOrderRT } from './common'; export const findDataStreamsResponseRT = rt.type({ items: rt.array(dataStreamRT), @@ -16,7 +16,7 @@ export const findDataStreamsResponseRT = rt.type({ export const findDataStreamsRequestQueryRT = rt.partial({ datasetQuery: rt.string, type: rt.literal('log'), - sortDirection: sortDirectionRT, + sortOrder: sortOrderRT, uncategorisedOnly: rt.boolean, }); diff --git a/x-pack/plugins/observability_logs/common/data_streams/v1/find_integrations.ts b/x-pack/plugins/observability_logs/common/data_streams/v1/find_integrations.ts index a2f0cf563ed6a..0e83c1a6bd5ec 100644 --- a/x-pack/plugins/observability_logs/common/data_streams/v1/find_integrations.ts +++ b/x-pack/plugins/observability_logs/common/data_streams/v1/find_integrations.ts @@ -7,9 +7,9 @@ import * as rt from 'io-ts'; import { integrationRT } from '../types'; -import { sortDirectionRT } from './common'; +import { sortOrderRT } from './common'; -const pageAfterRT = rt.array(rt.union([rt.number, rt.string])); +const searchAfterRT = rt.array(rt.union([rt.number, rt.string])); export const findIntegrationsResponseRT = rt.exact( rt.intersection([ @@ -18,19 +18,19 @@ export const findIntegrationsResponseRT = rt.exact( total: rt.number, }), rt.partial({ - pageAfter: pageAfterRT, + searchAfter: searchAfterRT, }), ]) ); export const findIntegrationsRequestQueryRT = rt.partial({ nameQuery: rt.string, - pageSize: rt.number, - type: rt.literal('log'), - pageAfter: pageAfterRT, - sortDirection: sortDirectionRT, + perPage: rt.number, + dataStreamType: rt.literal('logs'), + searchAfter: searchAfterRT, + sortOrder: sortOrderRT, }); -export type PageAfter = rt.TypeOf; +export type SearchAfter = rt.TypeOf; export type FindIntegrationsRequestQuery = rt.TypeOf; export type FindIntegrationsResponse = rt.TypeOf; diff --git a/x-pack/plugins/observability_logs/common/index.ts b/x-pack/plugins/observability_logs/common/index.ts index 98902101b37f6..4549d55176308 100644 --- a/x-pack/plugins/observability_logs/common/index.ts +++ b/x-pack/plugins/observability_logs/common/index.ts @@ -13,6 +13,5 @@ export const PLUGIN_NAME = 'observabilityLogs'; /** * Exporting versioned APIs types */ -export type { FindIntegrationsRequestQuery, FindIntegrationsResponse } from './latest'; export * from './latest'; export * as dataStreamsV1 from './data_streams/v1'; diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/constants.ts b/x-pack/plugins/observability_logs/public/components/data_stream_selector/constants.ts index 39753031962e1..1d49c8d2d1b27 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/constants.ts +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/constants.ts @@ -30,7 +30,20 @@ export const uncategorizedLabel = i18n.translate( { defaultMessage: 'Uncategorized' } ); -export const sortDirectionsLabel = i18n.translate( - 'xpack.observabilityLogs.dataStreamSelector.sortDirections', +export const sortOrdersLabel = i18n.translate( + 'xpack.observabilityLogs.dataStreamSelector.sortOrders', { defaultMessage: 'Sort directions' } ); + +export const sortOptions = [ + { + id: 'asc', + iconType: 'sortAscending', + label: 'Ascending', + }, + { + id: 'desc', + iconType: 'sortDescending', + label: 'Descending', + }, +]; diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx index 0227dd844e55b..34425a70b5374 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx @@ -37,6 +37,7 @@ import { POPOVER_ID, selectViewLabel, sortDirectionsLabel, + sortOptions, uncategorizedLabel, UNCATEGORIZED_STREAMS_PANEL_ID, } from './constants'; @@ -46,16 +47,18 @@ import { useBoolean } from '../../hooks/use_boolean'; import type { DataStream, Integration } from '../../../common/data_streams'; import { LoadMoreIntegrations, SearchIntegrations } from '../../hooks/use_integrations'; +import { IntegrationsSearchParams } from '../../state_machines/integrations'; + export interface DataStreamSelectorProps { title: string; + search?: IntegrationsSearchParams; integrations: Integration[] | null; uncategorizedStreams: any[]; - isSearching: boolean; isLoadingIntegrations: boolean; isLoadingMoreIntegrations: boolean; isLoadingUncategorizedStreams: boolean; - /* Triggered when a search or sorting is performed */ - onSearch: SearchIntegrations; + /* Triggered when a search or sorting is performed on integrations */ + onIntegrationsSearch: SearchIntegrations; /* Triggered when we reach the bottom of the integration list and want to load more */ onLoadMore: LoadMoreIntegrations; /* Triggered when the uncategorized streams entry is selected */ @@ -69,20 +72,21 @@ type CurrentPanelId = | typeof UNCATEGORIZED_STREAMS_PANEL_ID | `integration-${string}`; +type SearchStrategy = 'integrations' | 'integrationsStreams' | 'uncategorizedStreams'; type StreamSelectionHandler = (stream: DataStream) => void; export function DataStreamSelector({ title, integrations, uncategorizedStreams, - isSearching, isLoadingIntegrations, isLoadingMoreIntegrations, isLoadingUncategorizedStreams, - onSearch, + onIntegrationsSearch, onLoadMore, onStreamSelected, onUncategorizedClick, + search, }: DataStreamSelectorProps) { const isMobile = useIsWithinBreakpoints(['xs', 's']); const [isPopoverOpen, { off: closePopover, toggle: togglePopover }] = useBoolean(false); @@ -167,6 +171,9 @@ export function DataStreamSelector({ ); + // TODO: Handle search strategy by current panel id + const handleSearch = onIntegrationsSearch; + return ( - + { return ; }; -type SearchStrategy = 'integrations' | 'integrationsStreams' | 'uncategorizedStreams'; - interface SearchControlsProps { - isSearching: boolean; - onSearch: () => void; + search: IntegrationsSearchParams; + onSearch: (params: searchParams) => void; } -const SearchControls = ({ isSearching, onSearch }: SearchControlsProps) => { - /** - * TODO: implement 3 different search strategies - * - Search integrations: API request - * - Search integrations streams: in memory sorting - * - Search uncategorized streams: API request - */ - // const { search, searchByText, sortByDirection, isSearching } = useSearch(strategy); +const SearchControls = ({ search, onSearch }: SearchControlsProps) => { + const handleQueryChange = (event) => { + const name = event.target.value; + onSearch({ ...search, name }); + }; + + const handleSortChange = (sortOrder: IntegrationsSearchParams['sortOrder']) => { + onSearch({ ...search, sortOrder }); + }; return ( - + diff --git a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx index c3a72eab81c64..d75d8dae25ead 100644 --- a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx @@ -23,7 +23,8 @@ interface CustomDataStreamSelectorProps { export const CustomDataStreamSelector = withProviders(({ stateContainer }) => { // Container component, here goes all the state management and custom logic usage to keep the DataStreamSelector presentational. const dataView = useDataView(); - const { integrations, isLoading, isLoadingMore, loadMore, search } = useIntegrationsContext(); + const { integrations, isLoading, isLoadingMore, loadMore, search, searchIntegrations } = + useIntegrationsContext(); const handleStreamSelection = (dataStream: DataStream) => { return stateContainer.actions.onCreateDefaultAdHocDataView(dataStream); @@ -31,17 +32,17 @@ export const CustomDataStreamSelector = withProviders(({ stateContainer }) => { return ( console.log('fetch uncategorized streams')} + search={search} + title={dataView.getName()} + uncategorizedStreams={mockUncategorized} /> ); }); diff --git a/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts b/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts index b84564b03af63..4948129620cd5 100644 --- a/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts +++ b/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts @@ -29,6 +29,7 @@ const useIntegrations = ({ dataStreamsClient }: IntegrationsContextDeps) => { ); const integrations = useSelector(integrationsStateService, (state) => state.context.integrations); + const search = useSelector(integrationsStateService, (state) => state.context.search); const error = useSelector(integrationsStateService, (state) => state.context.error); @@ -36,7 +37,10 @@ const useIntegrations = ({ dataStreamsClient }: IntegrationsContextDeps) => { state.matches('uninitialized') ); - const isLoading = useSelector(integrationsStateService, (state) => state.matches('loading')); + const isLoading = useSelector( + integrationsStateService, + (state) => state.matches('debouncingSearch') || state.matches('loading') + ); const isLoadingMore = useSelector(integrationsStateService, (state) => state.matches('loadingMore') ); @@ -45,7 +49,7 @@ const useIntegrations = ({ dataStreamsClient }: IntegrationsContextDeps) => { state.matches('loadingFailed') ); - const search: SearchIntegrations = useCallback( + const searchIntegrations: SearchIntegrations = useCallback( (searchParams) => integrationsStateService.send({ type: 'SEARCH_INTEGRATIONS', @@ -74,9 +78,10 @@ const useIntegrations = ({ dataStreamsClient }: IntegrationsContextDeps) => { // Data integrations, + search, // Actions - search, + searchIntegrations, loadMore, }; }; diff --git a/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_client.ts b/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_client.ts index f8544817afa43..ce218208c3867 100644 --- a/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_client.ts +++ b/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_client.ts @@ -67,7 +67,7 @@ export class DataStreamsClient implements IDataStreamsClient { const mockIntegrationsResponse = { total: 11, - pageAfter: [], + // searchAfter: [], items: [ { name: 'kubernetes', diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/defaults.ts b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/defaults.ts new file mode 100644 index 0000000000000..e36a74927b0e7 --- /dev/null +++ b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/defaults.ts @@ -0,0 +1,19 @@ +/* + * 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 { DefaultIntegrationsContext } from './types'; + +export const DEFAULT_DATA_STREAMS_TYPE = 'logs'; + +export const DEFAULT_CONTEXT: DefaultIntegrationsContext = { + integrations: null, + error: null, + search: { + sortOrder: 'asc', + dataStreamType: DEFAULT_DATA_STREAMS_TYPE, + }, +}; diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts index be83bd8d66de1..978439dfde0eb 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts @@ -8,6 +8,7 @@ import { catchError, from, map, of, throwError } from 'rxjs'; import { actions, assign, createMachine } from 'xstate'; import { IDataStreamsClient } from '../../../services/data_streams'; +import { DEFAULT_CONTEXT } from './defaults'; import { DefaultIntegrationsContext, IntegrationsContext, @@ -15,16 +16,10 @@ import { IntegrationTypestate, } from './types'; -const defaultContext = { - integrations: null, - error: null, - page: [], -}; - export const createPureIntegrationsStateMachine = ( - initialContext: DefaultIntegrationsContext = defaultContext + initialContext: DefaultIntegrationsContext = DEFAULT_CONTEXT ) => - /** @xstate-layout N4IgpgJg5mDOIC5QEkB2AXMUBOBDdAlgPaqwB0ArqgdYbgDYEBekAxANoAMAuoqAA5FYBQiT4gAHogCMAJlmcyAFgCsAZiVrZa6QHY1ATk66ANCACeMgGxlOVjfq27ZKlbOkBfD2bSYc+YlIyeiJcCBooVgAZAHkAQQARZAA5AHEAfQBlAFUAYVyAUQKE4q5eJBBBYVFUcSkEAA4lBrIVY107dV0VBs5pM0sEaTayK2lODSUlCd1VYy8fDCw8GvIQsIjo+KS09IAxOOQo0p5xKpFAupklaSUyWSVuqwMrTgU1PoHEd0UVaQaetIrPZdN1VAsQL5lgESGtQuFUFAALJEbBgLaJFIZHL5IolBJlM5CC5iCr1FwGMhqFQvQzqexWFRWL4ILRqWy6Ma6JpTYydCFQ-yrYLwiIotEYnYZA5HE7lATEmpXBAAWhUdyM9hUnNmGjaBhZzXZnB6E00sgMNIMnm8kKWQsCcLCbFiiXSSJiACUCukUgAVAqpT1xP3IGLJTKEirnJVkmRA+56Pq9Xqgs0shQqVqMhrSaRqHQPBoAgX2laOkXOiCsTIFOKe3IACV9yQDQZDYYjUYV1UucYQVmcVKBnAaCm0BgBKhZY3uBgLDU5udBRhtiz85dhlYRUD2uAI9DY3tdCRbbeDofDkdO0cVfdA9Xzk7IY9UCjzWlkVgaLPz0jIszqC4zjuB+KheLaqBEBAcDiIKm6kESvakg+iAqnIdzUto2gNAWfT0iy6HWso+acAYBhKORSiDmopYbjCQRUDQFwMMwkBISStT9nmDSUg8ChjlYUyzMyFhoeM7LWh8i6vAClp0dCwrrDuHGxqhQz5roZBAmOnJ2Po1qyL++bKJaGi4VMAIdOBtrwQxTo7uKYCqfekhoZyVJuGyuEfMMaiiYMOiKKCwIKN+y5tFYCkOluynsbeyFcepriyKMRZNBoZGPEZYlDEJtgTDSOnAkJSjRQhDkRHuB7xT2nHKkClFUg8fyyDJbQ-rlPFzuorhvO4eGyBBHhAA */ + /** @xstate-layout N4IgpgJg5mDOIC5QEkB2AXMUBOBDdAlgPaqwB0ArqgdYbgDYEBekAxANoAMAuoqAA5FYBQiT4gAHogCMAJlmcyAFgCsAZiVrZa6QHY1ATk66ANCACeMgGxlOVjfq27ZKlbOkBfD2bSYc+YlIyeiJcCBooVgAZAHkAQQARZAA5AHEAfQBlAFUAYVyAUQKE4q5eJBBBYVFUcSkEAA4lBrIVY107dV0VBs5pM0sEaTayK2lODSUlCd1VYy8fDCw8GvIQsIjo+KS09IAxOOQo0p5xKpFAupklaSUyWSVuqwMrTgU1PoHEd0UVaQaetIrPZdN1VAsQL5lgESGtQuFUFAALJEbBgLaJFIZHL5IolBJlM5CC5iCr1FwGMhqFQvQzqexWFRWL4ILRqWy6Ma6JpTYydCFQ-yrYLwiIotEYnYZA5HE7lATEmpXBAAWhUdyM9hUnNmGjaBhZzXZnB6E00sgMNIMnm8kKWQsCcLCbFiiXSSJiACUCukUgAVAqpT1xP3IGLJTKEirnJVkmRA+56Pq9Xqgs0shQqVqMhrSaRqHQPBoAgX2laOkXOiCsTIFOKe3IACV9yQDQZDYYjUYV1UucYQ6tkZHzRYasn0BgBKhZKvHrQaBiURf+8k4BgMpb85dhZAgYAARkQqABjCKZMC4bDHgAWNbrDeb-sDwdD4cjp2jir7oHq+bI+k4JQrAaboZz0Fo3A0NRuk3aFhT3Q8TzPC8r1vCRYHQfAwDIXAADNMGwAAKD5OAASlYQVtyCBCj1QU9EXPS8b27Sov1JH9vhpWxmhmAwLTUBdZBnSlHmaMddHzcZNE0WCHR3dYESgPZcAIeg2G9V0EhbNsX07d95VY3t2MkGRDBaMdVAUPMtFkYCWUk-9VC0bV5DzfMVC8W1UCIPd4AqSiYVIIkjNqfsVTkO5qW0bQGgLPp6RnWybDeKCYNtALhSoGgLgYZhIGCklQo4oZ-kpB4FDHKwplmZkLEQcKJjIa0PhA14AUtWSqKdRSCtjYrrN0YdgPHV4rAnOR7L-JRLQ0WKpgBDoPPSstAu6sVUTAXrvxM1VOSpSCYri4Y1FqwYdEUUFgQUYDc26OxOtWys9wgLbjPqVwhysUdpLXR4hLqoYqtsCYaSBBpgSqpQHvgg9aPoqBGNQ16ip2gSh1zJkpmtZ4miqsCgNsUH1CTaY11kaGKwUiJlNU-LPxC5UgWmqkHj+WRWraBpJpaC11FcN53Di8nPKAA */ createMachine( { context: initialContext, @@ -46,7 +41,7 @@ export const createPureIntegrationsStateMachine = ( on: { LOADING_SUCCEEDED: { target: 'loaded', - actions: 'storeIntegrations', + actions: ['storeSearch', 'storeIntegrations'], }, LOADING_FAILED: { target: 'loadingFailed', @@ -72,8 +67,35 @@ export const createPureIntegrationsStateMachine = ( loaded: { entry: 'notifyLoadingSucceeded', on: { - LOAD_MORE_INTEGRATIONS: 'loadingMore', - SEARCH_INTEGRATIONS: 'loading', + LOAD_MORE_INTEGRATIONS: 'checkingMoreIntegrationsAvailability', + SEARCH_INTEGRATIONS: { + target: 'debouncingSearch', + actions: 'storeSearch', + }, + }, + }, + checkingMoreIntegrationsAvailability: { + always: [ + { + cond: 'hasMoreIntegrations', + target: 'loadingMore', + }, + { + target: 'loaded', + }, + ], + }, + debouncingSearch: { + on: { + SEARCH_INTEGRATIONS: { + target: 'debouncingSearch', + actions: 'storeSearch', + }, + }, + after: { + 500: { + target: 'loading', + }, }, }, loadingFailed: { @@ -91,25 +113,30 @@ export const createPureIntegrationsStateMachine = ( notifyLoadingStarted: actions.pure(() => undefined), notifyLoadingSucceeded: actions.pure(() => undefined), notifyLoadingFailed: actions.pure(() => undefined), - storeIntegrations: assign((_context, event) => + storeSearch: assign((context, event) => ({ + // Store search from search event + ...('search' in event && { search: event.search }), + // Store search from response + ...('data' in event && { + search: { + ...context.search, + searchAfter: event.data.searchAfter, + }, + }), + })), + storeIntegrations: assign((context, event) => 'data' in event ? { integrations: event.data.items, total: event.data.total, - search: { - pageAfter: event.data.pageAfter, - }, } : {} ), appendIntegrations: assign((context, event) => 'data' in event ? { - integrations: context.integrations?.concat(event.data.items), + integrations: context.integrations?.concat(event.data.items) ?? [], total: event.data.total, - search: { - pageAfter: event.data.pageAfter, - }, } : {} ), @@ -121,6 +148,9 @@ export const createPureIntegrationsStateMachine = ( : {} ), }, + guards: { + hasMoreIntegrations: (context) => Boolean(context.search.searchAfter), + }, } ); @@ -136,11 +166,11 @@ export const createIntegrationStateMachine = ({ createPureIntegrationsStateMachine(initialContext).withConfig({ actions: {}, services: { - loadIntegrations: (_context, event) => + loadIntegrations: (context, event) => from( 'search' in event - ? dataStreamsClient.findIntegrations(event.search) - : dataStreamsClient.findIntegrations() + ? dataStreamsClient.findIntegrations({ ...context.search, ...event.search }) + : dataStreamsClient.findIntegrations(context.search) ).pipe( map( (data): IntegrationsEvent => ({ diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts index 6a77bd8d2056d..dd9e7527e1aaa 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts @@ -4,20 +4,19 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { PageAfter, FindIntegrationsResponse } from '../../../../common/latest'; +import { FindIntegrationsResponse, FindIntegrationsRequestQuery } from '../../../../common/latest'; import { Integration } from '../../../../common/data_streams'; export interface DefaultIntegrationsContext { integrations: Integration[] | null; error: Error | null; - search?: { - pageAfter?: PageAfter; - }; + search: FindIntegrationsRequestQuery; + total?: number; } export interface IntegrationsSearchParams { - name: string; - sortDirection: 'asc' | 'desc'; + name?: string; + sortOrder?: 'asc' | 'desc'; } export type IntegrationTypestate = @@ -40,6 +39,14 @@ export type IntegrationTypestate = | { value: 'loadingMore'; context: DefaultIntegrationsContext; + } + | { + value: 'debouncingSearch'; + context: DefaultIntegrationsContext; + } + | { + value: 'checkingMoreIntegrationsAvailability'; + context: DefaultIntegrationsContext; }; export type IntegrationsContext = IntegrationTypestate['context']; @@ -55,7 +62,6 @@ export type IntegrationsEvent = } | { type: 'RELOAD_INTEGRATIONS'; - search: IntegrationsSearchParams; } | { type: 'LOAD_MORE_INTEGRATIONS'; From a4d544e0432b7f3338d8ced35e0c983f7efd1590 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Tue, 16 May 2023 17:29:41 +0200 Subject: [PATCH 093/122] feat(infra): update url creator --- .../common/data_streams/v1/common.ts | 23 +++++++++++++++---- .../data_streams/data_streams_client.ts | 9 ++++---- .../integrations/src/state_machine.ts | 2 +- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/observability_logs/common/data_streams/v1/common.ts b/x-pack/plugins/observability_logs/common/data_streams/v1/common.ts index d8f05b386178c..ac684a4484c66 100644 --- a/x-pack/plugins/observability_logs/common/data_streams/v1/common.ts +++ b/x-pack/plugins/observability_logs/common/data_streams/v1/common.ts @@ -5,19 +5,34 @@ * 2.0. */ import * as rt from 'io-ts'; +import { isUndefined, mapValues, omitBy } from 'lodash'; +import { FindDataStreamsRequestQuery } from './find_data_streams'; +import { FindIntegrationsRequestQuery } from './find_integrations'; export const INTEGRATIONS_URL = '/api/fleet/epm/packages/installed'; export const getIntegrationsUrl = (search = {}) => { - const querySearch = new URLSearchParams(search).toString(); + const cleanSearch = stringifyByProp(omitBy(search, isUndefined), ['searchAfter']); + const querySearch = new URLSearchParams(cleanSearch).toString(); - return [INTEGRATIONS_URL, querySearch].filter(Boolean).join('/'); + return [INTEGRATIONS_URL, querySearch].filter(Boolean).join('?'); }; export const DATA_STREAMS_URL = '/api/fleet/epm/data_streams'; export const getDataStreamsUrl = (search = {}) => { - const querySearch = new URLSearchParams(search).toString(); + const cleanSearch = stringifyByProp(omitBy(search, isUndefined), ['searchAfter']); + const querySearch = new URLSearchParams(cleanSearch).toString(); - return [DATA_STREAMS_URL, querySearch].filter(Boolean).join('/'); + return [DATA_STREAMS_URL, querySearch].filter(Boolean).join('?'); }; export const sortOrderRT = rt.union([rt.literal('asc'), rt.literal('desc')]); + +/** + * Utils + */ +function stringifyByProp( + obj: FindIntegrationsRequestQuery | FindDataStreamsRequestQuery, + props: string[] +) { + return mapValues(obj, (val, key) => (props.includes(key) ? JSON.stringify(val) : val)); +} diff --git a/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_client.ts b/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_client.ts index ce218208c3867..3d62ab9af266f 100644 --- a/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_client.ts +++ b/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_client.ts @@ -17,6 +17,7 @@ import { FindIntegrationsResponse, findIntegrationsResponseRT, getDataStreamsUrl, + getIntegrationsUrl, } from '../../../common'; import { IDataStreamsClient } from './types'; @@ -32,9 +33,9 @@ export class DataStreamsClient implements IDataStreamsClient { (message: string) => new Error(`Failed to decode integrations search param: ${message}"`) )(params); - // const response = await this.http.get(getIntegrationsUrl(search)).catch((error) => { - // throw new Error(`Failed to fetch integrations": ${error}`); - // }); + const response = this.http.get(getIntegrationsUrl(search)).catch((error) => { + throw new Error(`Failed to fetch integrations": ${error}`); + }); const data = decodeOrThrow( findIntegrationsResponseRT, @@ -67,7 +68,7 @@ export class DataStreamsClient implements IDataStreamsClient { const mockIntegrationsResponse = { total: 11, - // searchAfter: [], + // searchAfter: undefined, items: [ { name: 'kubernetes', diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts index 978439dfde0eb..3389f9e349580 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts @@ -19,7 +19,7 @@ import { export const createPureIntegrationsStateMachine = ( initialContext: DefaultIntegrationsContext = DEFAULT_CONTEXT ) => - /** @xstate-layout N4IgpgJg5mDOIC5QEkB2AXMUBOBDdAlgPaqwB0ArqgdYbgDYEBekAxANoAMAuoqAA5FYBQiT4gAHogCMAJlmcyAFgCsAZiVrZa6QHY1ATk66ANCACeMgGxlOVjfq27ZKlbOkBfD2bSYc+YlIyeiJcCBooVgAZAHkAQQARZAA5AHEAfQBlAFUAYVyAUQKE4q5eJBBBYVFUcSkEAA4lBrIVY107dV0VBs5pM0sEaTayK2lODSUlCd1VYy8fDCw8GvIQsIjo+KS09IAxOOQo0p5xKpFAupklaSUyWSVuqwMrTgU1PoHEd0UVaQaetIrPZdN1VAsQL5lgESGtQuFUFAALJEbBgLaJFIZHL5IolBJlM5CC5iCr1FwGMhqFQvQzqexWFRWL4ILRqWy6Ma6JpTYydCFQ-yrYLwiIotEYnYZA5HE7lATEmpXBAAWhUdyM9hUnNmGjaBhZzXZnB6E00sgMNIMnm8kKWQsCcLCbFiiXSSJiACUCukUgAVAqpT1xP3IGLJTKEirnJVkmRA+56Pq9Xqgs0shQqVqMhrSaRqHQPBoAgX2laOkXOiCsTIFOKe3IACV9yQDQZDYYjUYV1UucYQ6tkZHzRYasn0BgBKhZKvHrQaBiURf+8k4BgMpb85dhZAgYAARkQqABjCKZMC4bDHgAWNbrDeb-sDwdD4cjp2jir7oHq+bI+k4JQrAaboZz0Fo3A0NRuk3aFhT3Q8TzPC8r1vCRYHQfAwDIXAADNMGwAAKD5OAASlYQVtyCBCj1QU9EXPS8b27Sov1JH9vhpWxmhmAwLTUBdZBnSlHmaMddHzcZNE0WCHR3dYESgPZcAIeg2G9V0EhbNsX07d95VY3t2MkGRDBaMdVAUPMtFkYCWUk-9VC0bV5DzfMVC8W1UCIPd4AqSiYVIIkjNqfsVTkO5qW0bQGgLPp6RnWybDeKCYNtALhSoGgLgYZhIGCklQo4oZ-kpB4FDHKwplmZkLEQcKJjIa0PhA14AUtWSqKdRSCtjYrrN0YdgPHV4rAnOR7L-JRLQ0WKpgBDoPPSstAu6sVUTAXrvxM1VOSpSCYri4Y1FqwYdEUUFgQUYDc26OxOtWys9wgLbjPqVwhysUdpLXR4hLqoYqtsCYaSBBpgSqpQHvgg9aPoqBGNQ16ip2gSh1zJkpmtZ4miqsCgNsUH1CTaY11kaGKwUiJlNU-LPxC5UgWmqkHj+WRWraBpJpaC11FcN53Di8nPKAA */ + /** @xstate-layout N4IgpgJg5mDOIC5QEkB2AXMUBOBDdAlgPaqwB0ArqgdYbgDYEBekAxANoAMAuoqAA5FYBQiT4gAHogCMAJlmcyAFgCsAZiVrZa6QHY1ATk66ANCACeMgGxlOVjfq27ZKlbOkBfD2bSYc+YlIyeiJcCBooVgAZAHkAQQARZAA5AHEAfQBlAFUAYVyAUQKE4q5eJBBBYVFUcSkEAA4lBrIVY107dV0VBs5pM0sEaTayK2lODSUlCd1VYy8fDCw8GvIQsIjo+KS09IAxOOQo0p5xKpFAupklaSUyWSVuqwMrTgU1PoHEd0UVaQaetIrPZdN1VAsQL5lgESGtQuFUFAALJEbBgLaJFIZHL5IolBJlM5CC5iCr1FwGMhqFQvQzqexWFRWL4ILRqWy6Ma6JpTYydCFQ-yrYLwiIotEYnYZA5HE7lATEmpXBAAWhUdyM9hUnNmGjaBhZzXZnB6E00sgMNIMnm8kKWQsCcLCbFiiXSSJiACUCukUgAVAqpT1xP3IGLJTKEirnJVk77-MgNWT2AwfXqgs0s4YqZRWJQPBoNHQFgEC+0rR0i50QViZApxT25AASvuSAaDIbDEajCuqlzjCHVsjI0i0zST+gMAJULJVsl0rQaBnzzX+8k4BgMZb8FdhZAAxgALMD7gDWYtRYEFu9IcQAbrgCPRcAAjJ8icwcU7RxX90D1FVuVGbU+lHNQPmGNRmQsGRwLITd1BcZx3GkUcVG3aFhSPE9z0RcUr3LGFbwfJ9X3fdBP3YaR5UqX9SX-RAkypVD9AaYFniaPMWXkYdOHHDRnmmDpGQwh09wgMAXyIKh9wiTIwFwbAj1retGxbf1A2DUNw0jb9exJWoB1HMh9D4qwGm6Wc9BaNwNDUbpRJvcgJKkmS5IUpTD1YCRYHQfAwDIXAADNMGwAAKNpOAASlYa8iOcyTpNQWTEXkxSjx7Wi+3oyRECsXRpFaRk8yUa0OKUaDBkAnMemXAs1wUTdHPiqsESgPZH3oNhvVdBJW3bLSu10miYz-XKhkMFok1UBRUK0ZMGizHQTNULRtXkVC0K8W1UCICT4AqOLViJbLDIY1U5BsRkOjm8C+npWdkxsN47Ic20jsrKgaAuBhmEgE6DOVVCl3ufNOCTErZkqxAVXGdlrTTTlwZpdD3sI4V1jagHY3OuaFyBCdXnyww5CzYzSvUI0pgBDpUcWHcWsxi80WxsaAM5KlbO0IsIIemDWXGEzOSsBRzIaPQ2isZqMfhf6f1O5VXGHEXx00aZl2cLM81sCYaQJ9ipmlytsLPZmCIZ1Z706sjGAo1mcvqPQFwqvpOSBcquP57V4ItOR00tTdwaN8TErc1KPKPe2zvGtQmPFpkpjKqcKqUKyKtsPX1D0PjOA3WRg6CJnEQ6p85f0nHxqBUqqQeP5ZAsxlwbJloLXUVw3ncO78+2oA */ createMachine( { context: initialContext, From e368231da07b6a1080831a1a044b8c86c70441db Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Wed, 17 May 2023 16:42:36 +0200 Subject: [PATCH 094/122] feat(observability_logs): add search and cache --- package.json | 2 +- .../common/data_streams/errors.ts | 24 ++++ .../common/immutable_cache.ts | 33 ++++++ .../data_stream_selector.tsx | 104 ++++++++++-------- .../custom_data_stream_selector.tsx | 3 +- .../public/hooks/use_integrations.ts | 7 +- .../data_streams/data_streams_client.ts | 19 ++-- .../integrations/src/defaults.ts | 2 + .../integrations/src/state_machine.ts | 58 +++++----- .../state_machines/integrations/src/types.ts | 4 +- yarn.lock | 11 +- 11 files changed, 168 insertions(+), 99 deletions(-) create mode 100644 x-pack/plugins/observability_logs/common/data_streams/errors.ts create mode 100644 x-pack/plugins/observability_logs/common/immutable_cache.ts diff --git a/package.json b/package.json index bffc8e4553b0a..904344602be69 100644 --- a/package.json +++ b/package.json @@ -96,7 +96,7 @@ "@elastic/datemath": "5.0.3", "@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@8.6.0-canary.3", "@elastic/ems-client": "8.4.0", - "@elastic/eui": "77.1.1", + "@elastic/eui": "^80.0.0", "@elastic/filesaver": "1.1.2", "@elastic/node-crypto": "1.2.1", "@elastic/numeral": "^2.5.1", diff --git a/x-pack/plugins/observability_logs/common/data_streams/errors.ts b/x-pack/plugins/observability_logs/common/data_streams/errors.ts new file mode 100644 index 0000000000000..2d34908111bd6 --- /dev/null +++ b/x-pack/plugins/observability_logs/common/data_streams/errors.ts @@ -0,0 +1,24 @@ +/* + * 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. + */ + +/* eslint-disable max-classes-per-file */ + +export class FindIntegrationsError extends Error { + constructor(message: string) { + super(message); + Object.setPrototypeOf(this, new.target.prototype); + this.name = 'FindIntegrationsError'; + } +} + +export class FindDataStreamsError extends Error { + constructor(message: string) { + super(message); + Object.setPrototypeOf(this, new.target.prototype); + this.name = 'FindDataStreamsError'; + } +} diff --git a/x-pack/plugins/observability_logs/common/immutable_cache.ts b/x-pack/plugins/observability_logs/common/immutable_cache.ts new file mode 100644 index 0000000000000..c42de75db4179 --- /dev/null +++ b/x-pack/plugins/observability_logs/common/immutable_cache.ts @@ -0,0 +1,33 @@ +/* + * 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. + */ +export class ImmutableCache { + private cache: Record; + + constructor(cache?: Record) { + this.cache = cache ?? {}; + } + + public get(key: KeyType) { + const serializedKey = this.serialize(key); + return this.cache[serializedKey]; + } + + public set(key: KeyType, value: ValueType): ImmutableCache { + const serializedKey = this.serialize(key); + this.cache[serializedKey] = value; + return new ImmutableCache(this.cache); + } + + public has(key: KeyType): boolean { + const serializedKey = this.serialize(key); + return Boolean(this.cache[serializedKey]); + } + + private serialize(key: KeyType): string { + return JSON.stringify(key, Object.keys(key).sort()); + } +} diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx index 34425a70b5374..7b034f1933b7f 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx @@ -29,6 +29,7 @@ import { import { PackageIcon } from '@kbn/fleet-plugin/public'; import useIntersection from 'react-use/lib/useIntersection'; +import { EuiContextMenuPanelId } from '@elastic/eui/src/components/context_menu/context_menu'; import { contextMenuStyles, DATA_VIEW_POPOVER_CONTENT_WIDTH, @@ -36,7 +37,7 @@ import { INTEGRATION_PANEL_ID, POPOVER_ID, selectViewLabel, - sortDirectionsLabel, + sortOrdersLabel, sortOptions, uncategorizedLabel, UNCATEGORIZED_STREAMS_PANEL_ID, @@ -55,7 +56,6 @@ export interface DataStreamSelectorProps { integrations: Integration[] | null; uncategorizedStreams: any[]; isLoadingIntegrations: boolean; - isLoadingMoreIntegrations: boolean; isLoadingUncategorizedStreams: boolean; /* Triggered when a search or sorting is performed on integrations */ onIntegrationsSearch: SearchIntegrations; @@ -80,7 +80,6 @@ export function DataStreamSelector({ integrations, uncategorizedStreams, isLoadingIntegrations, - isLoadingMoreIntegrations, isLoadingUncategorizedStreams, onIntegrationsSearch, onLoadMore, @@ -106,8 +105,8 @@ export function DataStreamSelector({ const [currentPanel, setCurrentPanel] = useState(INTEGRATION_PANEL_ID); - const handlePanelChange = ({ panelId }: { panelId: CurrentPanelId }) => { - setCurrentPanel(panelId); + const handlePanelChange = ({ panelId }: { panelId: EuiContextMenuPanelId }) => { + setCurrentPanel(panelId as CurrentPanelId); }; const handleStreamSelection = useCallback( @@ -144,6 +143,19 @@ export function DataStreamSelector({ }; }, [integrations, handleStreamSelection, onUncategorizedClick]); + const [localSearch, setLocalSearch] = useState({ sortOrder: 'asc', name: '' }); + + const filteredIntegrationPanels = integrationPanels.map((panel) => { + if (panel.id !== currentPanel) { + return panel; + } + + return { + ...panel, + items: applyStreamSearch(panel.items, localSearch), + }; + }); + const panels = [ { id: INTEGRATION_PANEL_ID, @@ -162,7 +174,7 @@ export function DataStreamSelector({ onClick: () => handleStreamSelection(stream), })), }, - ...integrationPanels, + ...filteredIntegrationPanels, ]; const button = ( @@ -171,8 +183,12 @@ export function DataStreamSelector({ ); + const handleIntegrationStreamsSearch = setLocalSearch; + // TODO: Handle search strategy by current panel id - const handleSearch = onIntegrationsSearch; + const handleSearch = + currentPanel === INTEGRATION_PANEL_ID ? onIntegrationsSearch : handleIntegrationStreamsSearch; + const searchValue = currentPanel === INTEGRATION_PANEL_ID ? search : localSearch; return ( - + - - - + ); @@ -212,32 +230,38 @@ const DataStreamButton = (props: DataStreamButtonProps) => { }; interface SearchControlsProps { + isLoading: boolean; search: IntegrationsSearchParams; - onSearch: (params: searchParams) => void; + onSearch: (params: IntegrationsSearchParams) => void; } -const SearchControls = ({ search, onSearch }: SearchControlsProps) => { +const SearchControls = ({ search, onSearch, isLoading }: SearchControlsProps) => { const handleQueryChange = (event) => { const name = event.target.value; onSearch({ ...search, name }); }; - const handleSortChange = (sortOrder: IntegrationsSearchParams['sortOrder']) => { - onSearch({ ...search, sortOrder }); + const handleSortChange = (id: string) => { + onSearch({ ...search, sortOrder: id as IntegrationsSearchParams['sortOrder'] }); }; return ( - + @@ -247,28 +271,6 @@ const SearchControls = ({ search, onSearch }: SearchControlsProps) => { ); }; -const ContextMenuSkeleton = ({ children, isLoading }) => { - return ( - - - - - - - - - - - } - loadedContent={children} - /> - ); -}; - interface IntegrationsTree { items: EuiContextMenuPanelItemDescriptor[]; panels: EuiContextMenuPanelDescriptor[]; @@ -280,6 +282,18 @@ interface IntegrationsTreeParams { spyRef: RefCallback; } +const applyStreamSearch = (streams: DataStream[], search: IntegrationsSearchParams) => { + const { name, sortOrder } = search; + + const filteredStreams = streams.filter((stream) => stream.name.includes(name ?? '')); + + const sortedStreams = filteredStreams.sort((curr, next) => curr.name.localeCompare(next.name)); + + const searchResult = sortOrder === 'asc' ? sortedStreams : sortedStreams.reverse(); + + return searchResult; +}; + const buildIntegrationsTree = ({ list, onStreamSelected, spyRef }: IntegrationsTreeParams) => { return list.reduce( (res: IntegrationsTree, entry, pos) => { diff --git a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx index d75d8dae25ead..9bf2881b747fe 100644 --- a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx @@ -23,7 +23,7 @@ interface CustomDataStreamSelectorProps { export const CustomDataStreamSelector = withProviders(({ stateContainer }) => { // Container component, here goes all the state management and custom logic usage to keep the DataStreamSelector presentational. const dataView = useDataView(); - const { integrations, isLoading, isLoadingMore, loadMore, search, searchIntegrations } = + const { integrations, isLoading, loadMore, search, searchIntegrations } = useIntegrationsContext(); const handleStreamSelection = (dataStream: DataStream) => { @@ -34,7 +34,6 @@ export const CustomDataStreamSelector = withProviders(({ stateContainer }) => { { const isLoading = useSelector( integrationsStateService, - (state) => state.matches('debouncingSearch') || state.matches('loading') - ); - const isLoadingMore = useSelector(integrationsStateService, (state) => - state.matches('loadingMore') + (state) => + state.matches('loading') || state.matches('loadingMore') || state.matches('debouncingSearch') ); const hasFailedLoading = useSelector(integrationsStateService, (state) => @@ -74,7 +72,6 @@ const useIntegrations = ({ dataStreamsClient }: IntegrationsContextDeps) => { // Loading states isUninitialized, isLoading, - isLoadingMore, // Data integrations, diff --git a/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_client.ts b/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_client.ts index 3d62ab9af266f..833ba96701831 100644 --- a/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_client.ts +++ b/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_client.ts @@ -6,6 +6,7 @@ */ import { HttpStart } from '@kbn/core/public'; +import { FindDataStreamsError, FindIntegrationsError } from '../../../common/data_streams/errors'; import { decodeOrThrow } from '../../../common/runtime_types'; import { FindDataStreamsRequestQuery, @@ -30,16 +31,18 @@ export class DataStreamsClient implements IDataStreamsClient { ): Promise { const search = decodeOrThrow( findIntegrationsRequestQueryRT, - (message: string) => new Error(`Failed to decode integrations search param: ${message}"`) + (message: string) => + new FindIntegrationsError(`Failed to decode integrations search param: ${message}"`) )(params); const response = this.http.get(getIntegrationsUrl(search)).catch((error) => { - throw new Error(`Failed to fetch integrations": ${error}`); + throw new FindIntegrationsError(`Failed to fetch integrations": ${error}`); }); const data = decodeOrThrow( findIntegrationsResponseRT, - (message: string) => new Error(`Failed to decode integrations response: ${message}"`) + (message: string) => + new FindIntegrationsError(`Failed to decode integrations response: ${message}"`) )(mockIntegrationsResponse); // TODO: switch with response return data; @@ -50,16 +53,18 @@ export class DataStreamsClient implements IDataStreamsClient { ): Promise { const search = decodeOrThrow( findDataStreamsRequestQueryRT, - (message: string) => new Error(`Failed to decode data streams search param: ${message}"`) + (message: string) => + new FindDataStreamsError(`Failed to decode data streams search param: ${message}"`) )(params); const response = await this.http.get(getDataStreamsUrl(search)).catch((error) => { - throw new Error(`Failed to fetch data streams": ${error}`); + throw new FindDataStreamsError(`Failed to fetch data streams": ${error}`); }); const data = decodeOrThrow( findDataStreamsResponseRT, - (message: string) => new Error(`Failed to decode data streams response: ${message}"`) + (message: string) => + new FindDataStreamsError(`Failed to decode data streams response: ${message}"`) )(response); return data; @@ -68,7 +73,7 @@ export class DataStreamsClient implements IDataStreamsClient { const mockIntegrationsResponse = { total: 11, - // searchAfter: undefined, + searchAfter: ['system'], items: [ { name: 'kubernetes', diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/defaults.ts b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/defaults.ts index e36a74927b0e7..6c268c7bb07b3 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/defaults.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/defaults.ts @@ -5,11 +5,13 @@ * 2.0. */ +import { ImmutableCache } from '../../../../common/immutable_cache'; import { DefaultIntegrationsContext } from './types'; export const DEFAULT_DATA_STREAMS_TYPE = 'logs'; export const DEFAULT_CONTEXT: DefaultIntegrationsContext = { + cache: new ImmutableCache(), integrations: null, error: null, search: { diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts index 3389f9e349580..11594265ff1f0 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { catchError, from, map, of, throwError } from 'rxjs'; +import { catchError, from, map, of } from 'rxjs'; import { actions, assign, createMachine } from 'xstate'; import { IDataStreamsClient } from '../../../services/data_streams'; import { DEFAULT_CONTEXT } from './defaults'; @@ -19,7 +19,7 @@ import { export const createPureIntegrationsStateMachine = ( initialContext: DefaultIntegrationsContext = DEFAULT_CONTEXT ) => - /** @xstate-layout N4IgpgJg5mDOIC5QEkB2AXMUBOBDdAlgPaqwB0ArqgdYbgDYEBekAxANoAMAuoqAA5FYBQiT4gAHogCMAJlmcyAFgCsAZiVrZa6QHY1ATk66ANCACeMgGxlOVjfq27ZKlbOkBfD2bSYc+YlIyeiJcCBooVgAZAHkAQQARZAA5AHEAfQBlAFUAYVyAUQKE4q5eJBBBYVFUcSkEAA4lBrIVY107dV0VBs5pM0sEaTayK2lODSUlCd1VYy8fDCw8GvIQsIjo+KS09IAxOOQo0p5xKpFAupklaSUyWSVuqwMrTgU1PoHEd0UVaQaetIrPZdN1VAsQL5lgESGtQuFUFAALJEbBgLaJFIZHL5IolBJlM5CC5iCr1FwGMhqFQvQzqexWFRWL4ILRqWy6Ma6JpTYydCFQ-yrYLwiIotEYnYZA5HE7lATEmpXBAAWhUdyM9hUnNmGjaBhZzXZnB6E00sgMNIMnm8kKWQsCcLCbFiiXSSJiACUCukUgAVAqpT1xP3IGLJTKEirnJVk77-MgNWT2AwfXqgs0s4YqZRWJQPBoNHQFgEC+0rR0i50QViZApxT25AASvuSAaDIbDEajCuqlzjCHVsjI0i0zST+gMAJULJVsl0rQaBnzzX+8k4BgMZb8FdhZAAxgALMD7gDWYtRYEFu9IcQAbrgCPRcAAjJ8icwcU7RxX90D1FVuVGbU+lHNQPmGNRmQsGRwLITd1BcZx3GkUcVG3aFhSPE9z0RcUr3LGFbwfJ9X3fdBP3YaR5UqX9SX-RAkypVD9AaYFniaPMWXkYdOHHDRnmmDpGQwh09wgMAXyIKh9wiTIwFwbAj1retGxbf1A2DUNw0jb9exJWoB1HMh9D4qwGm6Wc9BaNwNDUbpRJvcgJKkmS5IUpTD1YCRYHQfAwDIXAADNMGwAAKNpOAASlYa8iOcyTpNQWTEXkxSjx7Wi+3oyRECsXRpFaRk8yUa0OKUaDBkAnMemXAs1wUTdHPiqsESgPZH3oNhvVdBJW3bLSu10miYz-XKhkMFok1UBRUK0ZMGizHQTNULRtXkVC0K8W1UCICT4AqOLViJbLDIY1U5BsRkOjm8C+npWdkxsN47Ic20jsrKgaAuBhmEgE6DOVVCl3ufNOCTErZkqxAVXGdlrTTTlwZpdD3sI4V1jagHY3OuaFyBCdXnyww5CzYzSvUI0pgBDpUcWHcWsxi80WxsaAM5KlbO0IsIIemDWXGEzOSsBRzIaPQ2isZqMfhf6f1O5VXGHEXx00aZl2cLM81sCYaQJ9ipmlytsLPZmCIZ1Z706sjGAo1mcvqPQFwqvpOSBcquP57V4ItOR00tTdwaN8TErc1KPKPe2zvGtQmPFpkpjKqcKqUKyKtsPX1D0PjOA3WRg6CJnEQ6p85f0nHxqBUqqQeP5ZAsxlwbJloLXUVw3ncO78+2oA */ + /** @xstate-layout N4IgpgJg5mDOIC5QEkB2AXMUBOBDdAlgPaqwB0ArqgdYbgDYEBekAxANoAMAuoqAA5FYBQiT4gAHogCMAJk4BmMgoCcnWQA4FGjQDYFAdlkKANCACeiALTTlGzmumcD6gCwrdK2QF9vZtJg4+MSkZPREuBA0UKwAMgDyAIIAIsgAcgDiAPoAygCqAMIFAKLFyWVcvEgggsKiqOJSCFq2rm0ArO0aTrrtutIaZpYI0gpK0r0qLpycurKunNIGvv4YWHj15OGR0XFJqZlZAGKJyLEVPOK1IiGNMqoGZANtstJLTm+DFjIqSs6u8iMujmLk6KxAAXWwRIWwiUVQUAAskRsGA9il0tl8kVSuVkpUrkIbmJqk15rIyLIPO43px2gpXEZTN8EAo6WR9ANOAtXL1ehpwZCgpswnDosjUeiDtkTmcLlUBET6ncEFYDLYPCp2rMnPT9K52kNELz2hzRpNfvIVK5pO1BWthSFyAQIPQ0QkUllEfEAErFLLpAAqxQyPsSgeQ8TSOQJ1WuytJiE0tk5C1kBgU7Q8+iNCBtrkpDi8ahN-PtgQ2TrILrdrByxUSPoKAAkA2lg6Hw5Ho7HFXVbomEMmOQppGmM1ngczhozdGQXCpi9y+V1y1CRQBjAAWYA3AGtxSiwELKzDEgA3XAEei4ABG15E5g4lzjSoHoCaVi18-ktqmugWMYjFzN4506Bx2lGeZdFmNo10dGEyG3XcDwRCVjwdU9SAvK8b3vRh0CfdhpAVGo3xJD9EF6SlXA0LN1EzbNNFzeQCxgtoYKpFcDDtPwIUw6FQggMBbyIKgN2iHIwFwbBtzrBsm1bIMQzDCMoxjF8+2JBpBysJQxlkXQDA8bpFi0OZp2saRvw8AxugGKZtXceCsPIYTRPEyTpNkrdWAkWB0HwMAyFwAAzTBsAACm1TgAEpWBPQS3JEsTUAkhEpJk7dezI-sKMkKj2gLbV5mpOjs0s1VfjIRRDEXV52iMLoVBcpLRR2BEjlwtg-Q9ZI2w7VTuw00j43fAqRm0WwDH6QwJg0WR5A0VwQLHZQZi1KDeVg1xfD41AiGE+BqkSzZCTynTKNVMclCMRYsxmwDDFkXMrE0JReQMOyGtkRqtGWPjTqrKgaBuBhmEgc7tJVN4jCeOzfgBUYjBm165FsTMDF5eRZiLFRpFakVtnhKAoYTK63nkJ46TkGaFH0IwvmGDwau1KZVEZTMGsJqticPVEyfGz9qptIqjEMp7gJZKl9IURbsbUIzFh5xCazAQX8uF007r-R7aql4Y+hTc0s0tBwARV0JkP3fmMIrJKcOvO8H0IjXLomiZOGUKZ1C+xjgWYlkFjIWjGT1BbzYGS3ko8tKvKyrc3ZVentfaUqAPKqc0f0MhtS5nijLULofEBgSibFTruogJPBwmOXKVmgCOlojwQJmMg6Icew5mx7oS98IA */ createMachine( { context: initialContext, @@ -40,8 +40,8 @@ export const createPureIntegrationsStateMachine = ( }, on: { LOADING_SUCCEEDED: { - target: 'loaded', - actions: ['storeSearch', 'storeIntegrations'], + target: 'idle', + actions: ['storeSearch', 'storeIntegrations', 'storeInCache'], }, LOADING_FAILED: { target: 'loadingFailed', @@ -51,11 +51,11 @@ export const createPureIntegrationsStateMachine = ( }, loadingMore: { invoke: { - src: 'loadMoreIntegrations', + src: 'loadIntegrations', }, on: { LOADING_SUCCEEDED: { - target: 'loaded', + target: 'idle', actions: 'appendIntegrations', }, LOADING_FAILED: { @@ -64,7 +64,7 @@ export const createPureIntegrationsStateMachine = ( }, }, }, - loaded: { + idle: { entry: 'notifyLoadingSucceeded', on: { LOAD_MORE_INTEGRATIONS: 'checkingMoreIntegrationsAvailability', @@ -81,7 +81,7 @@ export const createPureIntegrationsStateMachine = ( target: 'loadingMore', }, { - target: 'loaded', + target: 'idle', }, ], }, @@ -132,6 +132,13 @@ export const createPureIntegrationsStateMachine = ( } : {} ), + storeInCache: assign((context, event) => { + if (event.type !== 'LOADING_SUCCEEDED') return {}; + + return { + cache: context.cache.set(context.search, event.data), + }; + }), appendIntegrations: assign((context, event) => 'data' in event ? { @@ -166,30 +173,14 @@ export const createIntegrationStateMachine = ({ createPureIntegrationsStateMachine(initialContext).withConfig({ actions: {}, services: { - loadIntegrations: (context, event) => - from( - 'search' in event - ? dataStreamsClient.findIntegrations({ ...context.search, ...event.search }) - : dataStreamsClient.findIntegrations(context.search) - ).pipe( - map( - (data): IntegrationsEvent => ({ - type: 'LOADING_SUCCEEDED', - data, - }) - ), - catchError((error) => - of({ - type: 'LOADING_FAILED', - error, - }) - ) - ), - loadMoreIntegrations: (context) => - from( - 'search' in context - ? dataStreamsClient.findIntegrations(context.search) - : throwError(() => new Error('Failed to load more integration')) + loadIntegrations: (context, event) => { + const searchParams = + 'search' in event ? { ...context.search, ...event.search } : context.search; + + return from( + context.cache.has(searchParams) + ? Promise.resolve(context.cache.get(searchParams)) + : dataStreamsClient.findIntegrations(searchParams) ).pipe( map( (data): IntegrationsEvent => ({ @@ -203,6 +194,7 @@ export const createIntegrationStateMachine = ({ error, }) ) - ), + ); + }, }, }); diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts index dd9e7527e1aaa..ce24a53a589a5 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts @@ -4,10 +4,12 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { ImmutableCache } from '../../../../common/immutable_cache'; import { FindIntegrationsResponse, FindIntegrationsRequestQuery } from '../../../../common/latest'; import { Integration } from '../../../../common/data_streams'; export interface DefaultIntegrationsContext { + cache: ImmutableCache; integrations: Integration[] | null; error: Error | null; search: FindIntegrationsRequestQuery; @@ -16,7 +18,7 @@ export interface DefaultIntegrationsContext { export interface IntegrationsSearchParams { name?: string; - sortOrder?: 'asc' | 'desc'; + sortOrder: 'asc' | 'desc'; } export type IntegrationTypestate = diff --git a/yarn.lock b/yarn.lock index 0bc8f96a66559..861b7bb8ef894 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1543,10 +1543,10 @@ resolved "https://registry.yarnpkg.com/@elastic/eslint-plugin-eui/-/eslint-plugin-eui-0.0.2.tgz#56b9ef03984a05cc213772ae3713ea8ef47b0314" integrity sha512-IoxURM5zraoQ7C8f+mJb9HYSENiZGgRVcG4tLQxE61yHNNRDXtGDWTZh8N1KIHcsqN1CEPETjuzBXkJYF/fDiQ== -"@elastic/eui@77.1.1": - version "77.1.1" - resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-77.1.1.tgz#82f4294bf3239d5d825c1d939c49d125bfdbeb72" - integrity sha512-guJmHoGDbvKh/738taKDZGSdNk+OXMse513oPaPf4NoXpQUeYvl3gLT50mX5J4nwILS1LFKNGrbU2Es77HM1cQ== +"@elastic/eui@^80.0.0": + version "80.0.0" + resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-80.0.0.tgz#33e1df8a3f193fb6a7a4f7e52a3028983856ba3a" + integrity sha512-0xWizIQ/kH9N2n9zOficqXPdRbVxCpHez2ZErnvISAEOneJ6401usI7fILGKrrvR+uyiYnNybfaP3H8HcgDa+g== dependencies: "@types/chroma-js" "^2.0.0" "@types/lodash" "^4.14.160" @@ -1570,6 +1570,7 @@ react-focus-on "^3.7.0" react-input-autosize "^3.0.0" react-is "^17.0.2" + react-remove-scroll-bar "^2.3.4" react-virtualized-auto-sizer "^1.0.6" react-window "^1.8.6" refractor "^3.5.0" @@ -24186,7 +24187,7 @@ react-refresh@^0.11.0: resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.11.0.tgz#77198b944733f0f1f1a90e791de4541f9f074046" integrity sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A== -react-remove-scroll-bar@^2.3.3: +react-remove-scroll-bar@^2.3.3, react-remove-scroll-bar@^2.3.4: version "2.3.4" resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.4.tgz#53e272d7a5cb8242990c7f144c44d8bd8ab5afd9" integrity sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A== From d0f9a9247f2741baa19386351ef08836c6a3e9d8 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 17 May 2023 15:20:05 +0000 Subject: [PATCH 095/122] [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' --- .../components/data_stream_selector/data_stream_selector.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx index 7b034f1933b7f..32a30f021bf7b 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx @@ -19,11 +19,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, - EuiPanel, EuiPopover, - EuiSkeletonLoading, - EuiSkeletonText, - EuiSkeletonTitle, useIsWithinBreakpoints, } from '@elastic/eui'; import { PackageIcon } from '@kbn/fleet-plugin/public'; From 6423cb57a4ac8d14b2212a56566365cc006c9b74 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Wed, 17 May 2023 19:18:10 +0200 Subject: [PATCH 096/122] refactor(observability_logs): handle missing uncategorized streams --- .../data_streams/v1/find_data_streams.ts | 14 +++++---- .../data_streams/v1/find_integrations.ts | 16 +++++----- .../data_stream_selector.tsx | 30 +++++++++---------- .../custom_data_stream_selector.tsx | 2 +- .../public/hooks/use_integrations.ts | 13 +++++++- .../state_machines/integrations/src/types.ts | 2 +- 6 files changed, 45 insertions(+), 32 deletions(-) diff --git a/x-pack/plugins/observability_logs/common/data_streams/v1/find_data_streams.ts b/x-pack/plugins/observability_logs/common/data_streams/v1/find_data_streams.ts index 1070f756cc048..2888f61338772 100644 --- a/x-pack/plugins/observability_logs/common/data_streams/v1/find_data_streams.ts +++ b/x-pack/plugins/observability_logs/common/data_streams/v1/find_data_streams.ts @@ -13,12 +13,14 @@ export const findDataStreamsResponseRT = rt.type({ items: rt.array(dataStreamRT), }); -export const findDataStreamsRequestQueryRT = rt.partial({ - datasetQuery: rt.string, - type: rt.literal('log'), - sortOrder: sortOrderRT, - uncategorisedOnly: rt.boolean, -}); +export const findDataStreamsRequestQueryRT = rt.exact( + rt.partial({ + datasetQuery: rt.string, + type: rt.literal('log'), + sortOrder: sortOrderRT, + uncategorisedOnly: rt.boolean, + }) +); export type FindDataStreamsRequestQuery = rt.TypeOf; export type FindDataStreamsResponse = rt.TypeOf; diff --git a/x-pack/plugins/observability_logs/common/data_streams/v1/find_integrations.ts b/x-pack/plugins/observability_logs/common/data_streams/v1/find_integrations.ts index 0e83c1a6bd5ec..ef36d7f2c7999 100644 --- a/x-pack/plugins/observability_logs/common/data_streams/v1/find_integrations.ts +++ b/x-pack/plugins/observability_logs/common/data_streams/v1/find_integrations.ts @@ -23,13 +23,15 @@ export const findIntegrationsResponseRT = rt.exact( ]) ); -export const findIntegrationsRequestQueryRT = rt.partial({ - nameQuery: rt.string, - perPage: rt.number, - dataStreamType: rt.literal('logs'), - searchAfter: searchAfterRT, - sortOrder: sortOrderRT, -}); +export const findIntegrationsRequestQueryRT = rt.exact( + rt.partial({ + nameQuery: rt.string, + perPage: rt.number, + dataStreamType: rt.literal('logs'), + searchAfter: searchAfterRT, + sortOrder: sortOrderRT, + }) +); export type SearchAfter = rt.TypeOf; export type FindIntegrationsRequestQuery = rt.TypeOf; diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx index 7b034f1933b7f..74b07e5aa51e5 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx @@ -19,11 +19,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, - EuiPanel, EuiPopover, - EuiSkeletonLoading, - EuiSkeletonText, - EuiSkeletonTitle, useIsWithinBreakpoints, } from '@elastic/eui'; import { PackageIcon } from '@kbn/fleet-plugin/public'; @@ -52,7 +48,7 @@ import { IntegrationsSearchParams } from '../../state_machines/integrations'; export interface DataStreamSelectorProps { title: string; - search?: IntegrationsSearchParams; + search: IntegrationsSearchParams; integrations: Integration[] | null; uncategorizedStreams: any[]; isLoadingIntegrations: boolean; @@ -72,7 +68,6 @@ type CurrentPanelId = | typeof UNCATEGORIZED_STREAMS_PANEL_ID | `integration-${string}`; -type SearchStrategy = 'integrations' | 'integrationsStreams' | 'uncategorizedStreams'; type StreamSelectionHandler = (stream: DataStream) => void; export function DataStreamSelector({ @@ -143,7 +138,10 @@ export function DataStreamSelector({ }; }, [integrations, handleStreamSelection, onUncategorizedClick]); - const [localSearch, setLocalSearch] = useState({ sortOrder: 'asc', name: '' }); + const [localSearch, setLocalSearch] = useState({ + sortOrder: 'asc', + name: '', + }); const filteredIntegrationPanels = integrationPanels.map((panel) => { if (panel.id !== currentPanel) { @@ -167,12 +165,11 @@ export function DataStreamSelector({ id: UNCATEGORIZED_STREAMS_PANEL_ID, title: uncategorizedLabel, width: DATA_VIEW_POPOVER_CONTENT_WIDTH, - items: isLoadingUncategorizedStreams - ? [] - : uncategorizedStreams.map((stream) => ({ - name: stream.name, - onClick: () => handleStreamSelection(stream), - })), + content: isLoadingUncategorizedStreams ?

LOADING!

:

No results!

, + items: uncategorizedStreams?.map((stream) => ({ + name: stream.name, + onClick: () => handleStreamSelection(stream), + })), }, ...filteredIntegrationPanels, ]; @@ -232,11 +229,11 @@ const DataStreamButton = (props: DataStreamButtonProps) => { interface SearchControlsProps { isLoading: boolean; search: IntegrationsSearchParams; - onSearch: (params: IntegrationsSearchParams) => void; + onSearch: SearchIntegrations; } const SearchControls = ({ search, onSearch, isLoading }: SearchControlsProps) => { - const handleQueryChange = (event) => { + const handleQueryChange: React.ChangeEventHandler = (event) => { const name = event.target.value; onSearch({ ...search, name }); }; @@ -312,7 +309,8 @@ const buildIntegrationsTree = ({ list, onStreamSelected, spyRef }: IntegrationsT width: DATA_VIEW_POPOVER_CONTENT_WIDTH, items: entry.dataStreams.map((stream) => ({ name: stream.name, - onClick: () => onStreamSelected(stream), + onClick: () => + onStreamSelected({ title: stream.title, name: `[${entry.name}] ${stream.name}` }), })), }); diff --git a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx index 9bf2881b747fe..3517c08be4b22 100644 --- a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx @@ -41,7 +41,7 @@ export const CustomDataStreamSelector = withProviders(({ stateContainer }) => { onUncategorizedClick={() => console.log('fetch uncategorized streams')} search={search} title={dataView.getName()} - uncategorizedStreams={mockUncategorized} + uncategorizedStreams={[]} /> ); }); diff --git a/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts b/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts index d7372725bb690..812617768a786 100644 --- a/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts +++ b/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts @@ -13,6 +13,7 @@ import { IntegrationsSearchParams, } from '../state_machines/integrations'; import { IDataStreamsClient } from '../services/data_streams'; +import { FindIntegrationsRequestQuery } from '../../common'; interface IntegrationsContextDeps { dataStreamsClient: IDataStreamsClient; @@ -29,7 +30,9 @@ const useIntegrations = ({ dataStreamsClient }: IntegrationsContextDeps) => { ); const integrations = useSelector(integrationsStateService, (state) => state.context.integrations); - const search = useSelector(integrationsStateService, (state) => state.context.search); + const search = useSelector(integrationsStateService, (state) => + filterSearchParams(state.context.search) + ); const error = useSelector(integrationsStateService, (state) => state.context.error); @@ -84,3 +87,11 @@ const useIntegrations = ({ dataStreamsClient }: IntegrationsContextDeps) => { }; export const [IntegrationsProvider, useIntegrationsContext] = createContainer(useIntegrations); + +/** + * Utils + */ +const filterSearchParams = (search: FindIntegrationsRequestQuery): IntegrationsSearchParams => ({ + name: search.nameQuery, + sortOrder: search.sortOrder, +}); diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts index ce24a53a589a5..6ed1c1a66348d 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts @@ -18,7 +18,7 @@ export interface DefaultIntegrationsContext { export interface IntegrationsSearchParams { name?: string; - sortOrder: 'asc' | 'desc'; + sortOrder?: 'asc' | 'desc'; } export type IntegrationTypestate = From df9993ab46daa2a3d98a5378c4568b8d537c8abe Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Thu, 18 May 2023 18:20:32 +0200 Subject: [PATCH 097/122] feat(observability_logs): add data stream fetching and rendering logic --- .../data_streams/v1/find_data_streams.ts | 2 +- .../common/immutable_cache.ts | 13 +- .../{constants.ts => constants.tsx} | 27 +++ .../data_stream_selector.stories.tsx | 2 +- .../data_stream_selector.tsx | 168 ++++++++++--- .../custom_data_stream_selector.tsx | 40 ++- .../public/hooks/use_data_streams.ts | 107 ++++++++ .../public/hooks/use_integrations.ts | 22 +- .../public/hooks/use_intersection_ref.ts | 20 ++ .../data_streams/data_streams_client.ts | 228 +++--------------- .../state_machines/data_streams/index.ts | 8 + .../data_streams/src/defaults.ts | 18 ++ .../state_machines/data_streams/src/index.ts | 9 + .../data_streams/src/state_machine.ts | 153 ++++++++++++ .../state_machines/data_streams/src/types.ts | 65 +++++ .../integrations/src/defaults.ts | 3 - .../integrations/src/state_machine.ts | 44 ++-- .../state_machines/integrations/src/types.ts | 6 +- 18 files changed, 652 insertions(+), 283 deletions(-) rename x-pack/plugins/observability_logs/public/components/data_stream_selector/{constants.ts => constants.tsx} (62%) create mode 100644 x-pack/plugins/observability_logs/public/hooks/use_data_streams.ts create mode 100644 x-pack/plugins/observability_logs/public/hooks/use_intersection_ref.ts create mode 100644 x-pack/plugins/observability_logs/public/state_machines/data_streams/index.ts create mode 100644 x-pack/plugins/observability_logs/public/state_machines/data_streams/src/defaults.ts create mode 100644 x-pack/plugins/observability_logs/public/state_machines/data_streams/src/index.ts create mode 100644 x-pack/plugins/observability_logs/public/state_machines/data_streams/src/state_machine.ts create mode 100644 x-pack/plugins/observability_logs/public/state_machines/data_streams/src/types.ts diff --git a/x-pack/plugins/observability_logs/common/data_streams/v1/find_data_streams.ts b/x-pack/plugins/observability_logs/common/data_streams/v1/find_data_streams.ts index 2888f61338772..6c266495d01c3 100644 --- a/x-pack/plugins/observability_logs/common/data_streams/v1/find_data_streams.ts +++ b/x-pack/plugins/observability_logs/common/data_streams/v1/find_data_streams.ts @@ -16,7 +16,7 @@ export const findDataStreamsResponseRT = rt.type({ export const findDataStreamsRequestQueryRT = rt.exact( rt.partial({ datasetQuery: rt.string, - type: rt.literal('log'), + type: rt.literal('logs'), sortOrder: sortOrderRT, uncategorisedOnly: rt.boolean, }) diff --git a/x-pack/plugins/observability_logs/common/immutable_cache.ts b/x-pack/plugins/observability_logs/common/immutable_cache.ts index c42de75db4179..5702e7c99f0ed 100644 --- a/x-pack/plugins/observability_logs/common/immutable_cache.ts +++ b/x-pack/plugins/observability_logs/common/immutable_cache.ts @@ -4,7 +4,14 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -export class ImmutableCache { +export interface IImmutableCache { + get(key: KeyType): ValueType | undefined; + set(key: KeyType, value: ValueType): ImmutableCache; + has(key: KeyType): boolean; + clear(): ImmutableCache; +} + +export class ImmutableCache implements IImmutableCache { private cache: Record; constructor(cache?: Record) { @@ -27,6 +34,10 @@ export class ImmutableCache { return Boolean(this.cache[serializedKey]); } + public clear(): ImmutableCache { + return new ImmutableCache(); + } + private serialize(key: KeyType): string { return JSON.stringify(key, Object.keys(key).sort()); } diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/constants.ts b/x-pack/plugins/observability_logs/public/components/data_stream_selector/constants.tsx similarity index 62% rename from x-pack/plugins/observability_logs/public/components/data_stream_selector/constants.ts rename to x-pack/plugins/observability_logs/public/components/data_stream_selector/constants.tsx index 1d49c8d2d1b27..84498b2d534e1 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/constants.ts +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/constants.tsx @@ -5,7 +5,10 @@ * 2.0. */ +import { EuiText, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import React from 'react'; export const POPOVER_ID = 'data-stream-selector-popover'; export const INTEGRATION_PANEL_ID = 'integrations_panel'; @@ -35,6 +38,30 @@ export const sortOrdersLabel = i18n.translate( { defaultMessage: 'Sort directions' } ); +export const noDataStreamsLabel = i18n.translate( + 'xpack.observabilityLogs.dataStreamSelector.noDataStreams', + { defaultMessage: 'No data streams found' } +); + +export const noDataStreamsDescriptionLabel = i18n.translate( + 'xpack.observabilityLogs.dataStreamSelector.noDataStreamsDescription', + { + defaultMessage: + "Looks like you don't have data stream or your search does not match any of them.", + } +); + +export const errorLabel = i18n.translate('xpack.observabilityLogs.dataStreamSelector.error', { + defaultMessage: 'error', +}); + +export const noDataStreamsRetryLabel = i18n.translate( + 'xpack.observabilityLogs.dataStreamSelector.noDataStreamsRetry', + { + defaultMessage: 'Retry', + } +); + export const sortOptions = [ { id: 'asc', diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.stories.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.stories.tsx index f89611c45a1c4..e9f63d9223fb6 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.stories.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.stories.tsx @@ -66,5 +66,5 @@ Basic.args = { // eslint-disable-next-line no-console onStreamSelected: async (stream) => console.log('Create ad hoc view for stream: ', stream), // eslint-disable-next-line no-console - onUncategorizedClick: () => console.log('Load uncategorized streams...'), + onStreamsEntryClick: () => console.log('Load uncategorized streams...'), }; diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx index 74b07e5aa51e5..b8d863b397bd7 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx @@ -15,50 +15,66 @@ import { EuiContextMenuPanel, EuiContextMenuPanelDescriptor, EuiContextMenuPanelItemDescriptor, + EuiEmptyPrompt, EuiFieldSearch, EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, + EuiPanel, EuiPopover, + EuiSkeletonText, + EuiText, + EuiToolTip, useIsWithinBreakpoints, } from '@elastic/eui'; import { PackageIcon } from '@kbn/fleet-plugin/public'; -import useIntersection from 'react-use/lib/useIntersection'; import { EuiContextMenuPanelId } from '@elastic/eui/src/components/context_menu/context_menu'; +import { FormattedMessage } from '@kbn/i18n-react'; import { - contextMenuStyles, DATA_VIEW_POPOVER_CONTENT_WIDTH, - integrationsLabel, INTEGRATION_PANEL_ID, POPOVER_ID, + UNCATEGORIZED_STREAMS_PANEL_ID, + contextMenuStyles, + integrationsLabel, selectViewLabel, - sortOrdersLabel, sortOptions, + sortOrdersLabel, uncategorizedLabel, - UNCATEGORIZED_STREAMS_PANEL_ID, + noDataStreamsLabel, + noDataStreamsDescriptionLabel, + noDataStreamsRetryLabel, + errorLabel, } from './constants'; import { getPopoverButtonStyles } from './data_stream_selector.utils'; import { useBoolean } from '../../hooks/use_boolean'; import type { DataStream, Integration } from '../../../common/data_streams'; -import { LoadMoreIntegrations, SearchIntegrations } from '../../hooks/use_integrations'; -import { IntegrationsSearchParams } from '../../state_machines/integrations'; +import { + LoadMoreIntegrations, + SearchIntegrations, + SearchIntegrationsParams, +} from '../../hooks/use_integrations'; +import { useIntersectionRef } from '../../hooks/use_intersection_ref'; export interface DataStreamSelectorProps { title: string; - search: IntegrationsSearchParams; + search: SearchIntegrationsParams; integrations: Integration[] | null; - uncategorizedStreams: any[]; + uncategorizedStreams: DataStream[] | null; + dataStreamsError: Error | null; isLoadingIntegrations: boolean; - isLoadingUncategorizedStreams: boolean; + isLoadingStreams: boolean; /* Triggered when a search or sorting is performed on integrations */ - onIntegrationsSearch: SearchIntegrations; + onSearchIntegrations: SearchIntegrations; /* Triggered when we reach the bottom of the integration list and want to load more */ - onLoadMore: LoadMoreIntegrations; + onLoadMoreIntegrations: LoadMoreIntegrations; /* Triggered when the uncategorized streams entry is selected */ - onUncategorizedClick: () => void; + onStreamsEntryClick: () => void; + /* Triggered when retrying to load the data streams */ + onStreamsReload: () => void; /* Triggered when a data stream entry is selected */ onStreamSelected: (dataStream: any) => Promise; } @@ -74,29 +90,26 @@ export function DataStreamSelector({ title, integrations, uncategorizedStreams, + dataStreamsError, isLoadingIntegrations, - isLoadingUncategorizedStreams, - onIntegrationsSearch, - onLoadMore, + isLoadingStreams, + onSearchIntegrations, + onLoadMoreIntegrations, onStreamSelected, - onUncategorizedClick, + onStreamsEntryClick, + onStreamsReload, search, }: DataStreamSelectorProps) { const isMobile = useIsWithinBreakpoints(['xs', 's']); const [isPopoverOpen, { off: closePopover, toggle: togglePopover }] = useBoolean(false); - const [loadMoreSpyRef, setRef] = useState(null); - - const loadMoreSpyIntersection = useIntersection( - { current: loadMoreSpyRef }, - { root: null, threshold: 0.5 } - ); + const [loadMoreIntersection, setRef] = useIntersectionRef(); useEffect(() => { - if (loadMoreSpyIntersection?.isIntersecting) { - onLoadMore(); + if (loadMoreIntersection?.isIntersecting) { + onLoadMoreIntegrations(); } - }, [loadMoreSpyIntersection, onLoadMore]); + }, [loadMoreIntersection, onLoadMoreIntegrations]); const [currentPanel, setCurrentPanel] = useState(INTEGRATION_PANEL_ID); @@ -115,7 +128,7 @@ export function DataStreamSelector({ const { items: integrationItems, panels: integrationPanels } = useMemo(() => { const uncategorizedStreamsItem = { name: uncategorizedLabel, - onClick: onUncategorizedClick, + onClick: onStreamsEntryClick, panel: UNCATEGORIZED_STREAMS_PANEL_ID, }; @@ -136,9 +149,9 @@ export function DataStreamSelector({ items: [uncategorizedStreamsItem, ...items], panels, }; - }, [integrations, handleStreamSelection, onUncategorizedClick]); + }, [integrations, handleStreamSelection, onStreamsEntryClick, setRef]); - const [localSearch, setLocalSearch] = useState({ + const [localSearch, setLocalSearch] = useState({ sortOrder: 'asc', name: '', }); @@ -165,11 +178,15 @@ export function DataStreamSelector({ id: UNCATEGORIZED_STREAMS_PANEL_ID, title: uncategorizedLabel, width: DATA_VIEW_POPOVER_CONTENT_WIDTH, - content: isLoadingUncategorizedStreams ?

LOADING!

:

No results!

, - items: uncategorizedStreams?.map((stream) => ({ - name: stream.name, - onClick: () => handleStreamSelection(stream), - })), + content: ( + + ), }, ...filteredIntegrationPanels, ]; @@ -184,7 +201,7 @@ export function DataStreamSelector({ // TODO: Handle search strategy by current panel id const handleSearch = - currentPanel === INTEGRATION_PANEL_ID ? onIntegrationsSearch : handleIntegrationStreamsSearch; + currentPanel === INTEGRATION_PANEL_ID ? onSearchIntegrations : handleIntegrationStreamsSearch; const searchValue = currentPanel === INTEGRATION_PANEL_ID ? search : localSearch; return ( @@ -228,7 +245,7 @@ const DataStreamButton = (props: DataStreamButtonProps) => { interface SearchControlsProps { isLoading: boolean; - search: IntegrationsSearchParams; + search: SearchIntegrationsParams; onSearch: SearchIntegrations; } @@ -239,7 +256,7 @@ const SearchControls = ({ search, onSearch, isLoading }: SearchControlsProps) => }; const handleSortChange = (id: string) => { - onSearch({ ...search, sortOrder: id as IntegrationsSearchParams['sortOrder'] }); + onSearch({ ...search, sortOrder: id as SearchIntegrationsParams['sortOrder'] }); }; return ( @@ -268,6 +285,81 @@ const SearchControls = ({ search, onSearch, isLoading }: SearchControlsProps) => ); }; +interface DataStreamListProps { + dataStreams: DataStream[] | null; + error: Error | null; + isLoading: boolean; + onStreamClick: StreamSelectionHandler; + onRetry: () => void; +} + +const DataStreamList = ({ + dataStreams, + error, + isLoading, + onStreamClick, + onRetry, +}: DataStreamListProps) => { + const isEmpty = dataStreams == null || dataStreams.length <= 0; + const hasError = error !== null; + + if (isLoading) { + return ( + + + + ); + } + + if (isEmpty) { + return ( + {noDataStreamsLabel}} + titleSize="s" + body={

{noDataStreamsDescriptionLabel}

} + /> + ); + } + + if (hasError) { + return ( + {noDataStreamsLabel}} + titleSize="s" + body={ + + {errorLabel} + + ), + }} + /> + } + actions={[{noDataStreamsRetryLabel}]} + /> + ); + } + + return ( + <> + {dataStreams.map((stream) => ( + onStreamClick(stream)}> + {stream.name} + + ))} + + ); +}; + interface IntegrationsTree { items: EuiContextMenuPanelItemDescriptor[]; panels: EuiContextMenuPanelDescriptor[]; @@ -279,7 +371,7 @@ interface IntegrationsTreeParams { spyRef: RefCallback; } -const applyStreamSearch = (streams: DataStream[], search: IntegrationsSearchParams) => { +const applyStreamSearch = (streams: DataStream[], search: SearchIntegrationsParams) => { const { name, sortOrder } = search; const filteredStreams = streams.filter((stream) => stream.name.includes(name ?? '')); diff --git a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx index 3517c08be4b22..9aae0dfa8c57c 100644 --- a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx @@ -15,6 +15,7 @@ import { ObservabilityLogsPluginProvider, ObservabilityLogsPluginProviderProps, } from '../hooks/use_kibana'; +import { DataStreamsProvider, useDataStreamsContext } from '../hooks/use_data_streams'; interface CustomDataStreamSelectorProps { stateContainer: DiscoverStateContainer; @@ -23,8 +24,23 @@ interface CustomDataStreamSelectorProps { export const CustomDataStreamSelector = withProviders(({ stateContainer }) => { // Container component, here goes all the state management and custom logic usage to keep the DataStreamSelector presentational. const dataView = useDataView(); - const { integrations, isLoading, loadMore, search, searchIntegrations } = - useIntegrationsContext(); + + const { + integrations, + isLoading: isLoadingIntegrations, + loadMore, + search, + searchIntegrations, + } = useIntegrationsContext(); + + const { + dataStreams, + error: dataStreamsError, + isLoading: isLoadingStreams, + loadDataStreams, + reloadDataStreams, + searchDataStreams, + } = useDataStreamsContext(); const handleStreamSelection = (dataStream: DataStream) => { return stateContainer.actions.onCreateDefaultAdHocDataView(dataStream); @@ -32,16 +48,18 @@ export const CustomDataStreamSelector = withProviders(({ stateContainer }) => { return ( console.log('fetch uncategorized streams')} + onStreamsEntryClick={loadDataStreams} + onStreamsReload={reloadDataStreams} search={search} title={dataView.getName()} - uncategorizedStreams={[]} + uncategorizedStreams={dataStreams} /> ); }); @@ -49,8 +67,6 @@ export const CustomDataStreamSelector = withProviders(({ stateContainer }) => { // eslint-disable-next-line import/no-default-export export default CustomDataStreamSelector; -const mockUncategorized = [{ name: 'metrics-*' }, { name: 'logs-*' }]; - export type CustomDataStreamSelectorBuilderProps = ObservabilityLogsPluginProviderProps & CustomDataStreamSelectorProps; @@ -65,7 +81,9 @@ function withProviders(Component: React.FunctionComponent - + + + diff --git a/x-pack/plugins/observability_logs/public/hooks/use_data_streams.ts b/x-pack/plugins/observability_logs/public/hooks/use_data_streams.ts new file mode 100644 index 0000000000000..d3f20fd9da67c --- /dev/null +++ b/x-pack/plugins/observability_logs/public/hooks/use_data_streams.ts @@ -0,0 +1,107 @@ +/* + * 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 { useInterpret, useSelector } from '@xstate/react'; +import createContainer from 'constate'; +import { useCallback } from 'react'; +import { createDataStreamsStateMachine } from '../state_machines/data_streams'; +import { IDataStreamsClient } from '../services/data_streams'; +import { FindDataStreamsRequestQuery } from '../../common'; + +interface DataStreamsContextDeps { + dataStreamsClient: IDataStreamsClient; +} + +export interface SearchDataStreamsParams { + name?: string; + sortOrder?: 'asc' | 'desc'; +} + +export type SearchDataStreams = (params: SearchDataStreamsParams) => void; + +const useDataStreams = ({ dataStreamsClient }: DataStreamsContextDeps) => { + const dataStreamsStateService = useInterpret(() => + createDataStreamsStateMachine({ + dataStreamsClient, + }) + ); + + const dataStreams = useSelector(dataStreamsStateService, (state) => state.context.dataStreams); + + const search = useSelector(dataStreamsStateService, (state) => + filterSearchParams(state.context.search) + ); + + const error = useSelector(dataStreamsStateService, (state) => state.context.error); + + const isUninitialized = useSelector(dataStreamsStateService, (state) => + state.matches('uninitialized') + ); + + const isLoading = useSelector( + dataStreamsStateService, + (state) => state.matches('loading') || state.matches('debouncingSearch') + ); + + const hasFailedLoading = useSelector(dataStreamsStateService, (state) => + state.matches('loadingFailed') + ); + + const loadDataStreams = useCallback( + () => dataStreamsStateService.send({ type: 'LOAD_DATA_STREAMS' }), + [dataStreamsStateService] + ); + + const reloadDataStreams = useCallback( + () => dataStreamsStateService.send({ type: 'RELOAD_DATA_STREAMS' }), + [dataStreamsStateService] + ); + + const searchDataStreams: SearchDataStreams = useCallback( + (searchParams) => + dataStreamsStateService.send({ + type: 'SEARCH_DATA_STREAMS', + search: { + datasetQuery: searchParams.name, + sortOrder: searchParams.sortOrder, + }, + }), + [dataStreamsStateService] + ); + + return { + // Underlying state machine + dataStreamsStateService, + + // Failure states + error, + hasFailedLoading, + + // Loading states + isUninitialized, + isLoading, + + // Data + dataStreams, + search, + + // Actions + loadDataStreams, + reloadDataStreams, + searchDataStreams, + }; +}; + +export const [DataStreamsProvider, useDataStreamsContext] = createContainer(useDataStreams); + +/** + * Utils + */ +const filterSearchParams = (search: FindDataStreamsRequestQuery): SearchDataStreamsParams => ({ + name: search.datasetQuery, + sortOrder: search.sortOrder, +}); diff --git a/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts b/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts index 812617768a786..88ccda74f334d 100644 --- a/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts +++ b/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts @@ -8,10 +8,7 @@ import { useInterpret, useSelector } from '@xstate/react'; import createContainer from 'constate'; import { useCallback } from 'react'; -import { - createIntegrationStateMachine, - IntegrationsSearchParams, -} from '../state_machines/integrations'; +import { createIntegrationStateMachine } from '../state_machines/integrations'; import { IDataStreamsClient } from '../services/data_streams'; import { FindIntegrationsRequestQuery } from '../../common'; @@ -19,7 +16,12 @@ interface IntegrationsContextDeps { dataStreamsClient: IDataStreamsClient; } -export type SearchIntegrations = (params: IntegrationsSearchParams) => void; +export interface SearchIntegrationsParams { + name?: string; + sortOrder?: 'asc' | 'desc'; +} + +export type SearchIntegrations = (params: SearchIntegrationsParams) => void; export type LoadMoreIntegrations = () => void; const useIntegrations = ({ dataStreamsClient }: IntegrationsContextDeps) => { @@ -30,6 +32,7 @@ const useIntegrations = ({ dataStreamsClient }: IntegrationsContextDeps) => { ); const integrations = useSelector(integrationsStateService, (state) => state.context.integrations); + const search = useSelector(integrationsStateService, (state) => filterSearchParams(state.context.search) ); @@ -54,7 +57,10 @@ const useIntegrations = ({ dataStreamsClient }: IntegrationsContextDeps) => { (searchParams) => integrationsStateService.send({ type: 'SEARCH_INTEGRATIONS', - search: searchParams, + search: { + nameQuery: searchParams.name, + sortOrder: searchParams.sortOrder, + }, }), [integrationsStateService] ); @@ -81,8 +87,8 @@ const useIntegrations = ({ dataStreamsClient }: IntegrationsContextDeps) => { search, // Actions - searchIntegrations, loadMore, + searchIntegrations, }; }; @@ -91,7 +97,7 @@ export const [IntegrationsProvider, useIntegrationsContext] = createContainer(us /** * Utils */ -const filterSearchParams = (search: FindIntegrationsRequestQuery): IntegrationsSearchParams => ({ +const filterSearchParams = (search: FindIntegrationsRequestQuery): SearchIntegrationsParams => ({ name: search.nameQuery, sortOrder: search.sortOrder, }); diff --git a/x-pack/plugins/observability_logs/public/hooks/use_intersection_ref.ts b/x-pack/plugins/observability_logs/public/hooks/use_intersection_ref.ts new file mode 100644 index 0000000000000..b7e9e56075c3b --- /dev/null +++ b/x-pack/plugins/observability_logs/public/hooks/use_intersection_ref.ts @@ -0,0 +1,20 @@ +/* + * 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 { useState } from 'react'; +import useIntersection from 'react-use/lib/useIntersection'; + +export function useIntersectionRef() { + const [intersectionRef, setRef] = useState(null); + + const intersection = useIntersection( + { current: intersectionRef }, + { root: null, threshold: 0.5 } + ); + + return [intersection, setRef] as [IntersectionObserverEntry | null, typeof setRef]; +} diff --git a/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_client.ts b/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_client.ts index 833ba96701831..1ae090d51256f 100644 --- a/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_client.ts +++ b/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_client.ts @@ -23,27 +23,42 @@ import { import { IDataStreamsClient } from './types'; +const defaultIntegrationsParams = { + dataStreamType: 'logs', +}; + +const defaultDataStreamsParams = { + type: 'logs', +}; + export class DataStreamsClient implements IDataStreamsClient { constructor(private readonly http: HttpStart) {} public async findIntegrations( params: FindIntegrationsRequestQuery = {} ): Promise { - const search = decodeOrThrow( + const search = { ...params, ...defaultIntegrationsParams }; + + const decodedSearch = decodeOrThrow( findIntegrationsRequestQueryRT, (message: string) => new FindIntegrationsError(`Failed to decode integrations search param: ${message}"`) - )(params); + )(search); - const response = this.http.get(getIntegrationsUrl(search)).catch((error) => { - throw new FindIntegrationsError(`Failed to fetch integrations": ${error}`); - }); + // const response = await this.http.get(getIntegrationsUrl(decodedSearch)).catch((error) => { + // throw new FindIntegrationsError(`Failed to fetch integrations": ${error}`); + // }); + const response = await fetch('http://localhost:3000' + getIntegrationsUrl(decodedSearch)) + .then((res) => res.json()) + .catch((error) => { + throw new FindIntegrationsError(`Failed to fetch integrations": ${error}`); + }); const data = decodeOrThrow( findIntegrationsResponseRT, (message: string) => new FindIntegrationsError(`Failed to decode integrations response: ${message}"`) - )(mockIntegrationsResponse); // TODO: switch with response + )(response); return data; } @@ -51,15 +66,22 @@ export class DataStreamsClient implements IDataStreamsClient { public async findDataStreams( params: FindDataStreamsRequestQuery = {} ): Promise { - const search = decodeOrThrow( + const search = { ...params, ...defaultDataStreamsParams }; + + const decodedSearch = decodeOrThrow( findDataStreamsRequestQueryRT, (message: string) => new FindDataStreamsError(`Failed to decode data streams search param: ${message}"`) - )(params); + )(search); - const response = await this.http.get(getDataStreamsUrl(search)).catch((error) => { - throw new FindDataStreamsError(`Failed to fetch data streams": ${error}`); - }); + // const response = await this.http.get(getDataStreamsUrl(decodedSearch)).catch((error) => { + // throw new FindDataStreamsError(`Failed to fetch data streams": ${error}`); + // }); + const response = await fetch('http://localhost:3000' + getDataStreamsUrl(decodedSearch)) + .then((res) => res.json()) + .catch((error) => { + throw new FindDataStreamsError(`Failed to decode data streams response: ${error}"`); + }); const data = decodeOrThrow( findDataStreamsResponseRT, @@ -70,187 +92,3 @@ export class DataStreamsClient implements IDataStreamsClient { return data; } } - -const mockIntegrationsResponse = { - total: 11, - searchAfter: ['system'], - items: [ - { - name: 'kubernetes', - version: '3.2.0', - status: 'installed', - dataStreams: [ - { - name: 'Kubernetes metrics stream', - title: 'k8s-metrics-*', - }, - { - name: 'Kubernetes logs stream', - title: 'k8s-logs-*', - }, - ], - }, - { - name: 'mysql', - version: '4.6.1', - status: 'installed', - dataStreams: [ - { - name: 'MySQL metrics stream', - title: 'mysql-metrics-*', - }, - { - name: 'MySQL slow logs stream', - title: 'mysql-slow-logs-*', - }, - { - name: 'MySQL error logs stream', - title: 'mysql-error-logs-*', - }, - ], - }, - { - name: 'apache', - version: '1.5.0', - status: 'installed', - dataStreams: [ - { - name: 'Apache metrics stream', - title: 'apache-metrics-*', - }, - { - name: 'Apache logs stream', - title: 'apache-logs-*', - }, - { - name: 'Apache error logs stream', - title: 'apache-error-logs-*', - }, - ], - }, - { - name: 'nginx', - version: '1.2.0', - status: 'installed', - dataStreams: [ - { - name: 'Nginx metrics stream', - title: 'nginx-metrics-*', - }, - { - name: 'Nginx access logs stream', - title: 'nginx-access-logs-*', - }, - ], - }, - { - name: 'postgresql', - version: '1.13.0', - status: 'installed', - dataStreams: [ - { - name: 'PostgreSQL metrics stream', - title: 'postgresql-metrics-*', - }, - { - name: 'PostgreSQL slow query logs stream', - title: 'postgresql-slow-query-logs-*', - }, - { - name: 'PostgreSQL error logs stream', - title: 'postgresql-error-logs-*', - }, - ], - }, - { - name: 'rabbitmq', - version: '3.0.1', - status: 'installed', - dataStreams: [ - { - name: 'RabbitMQ metrics stream', - title: 'rabbitmq-metrics-*', - }, - { - name: 'RabbitMQ queues stream', - title: 'rabbitmq-queues-*', - }, - { - name: 'RabbitMQ error logs stream', - title: 'rabbitmq-error-logs-*', - }, - ], - }, - { - name: 'redis', - version: '4.3.2', - status: 'installed', - dataStreams: [ - { - name: 'Redis metrics stream', - title: 'redis-metrics-*', - }, - { - name: 'Redis slow logs stream', - title: 'redis-slow-logs-*', - }, - ], - }, - { - name: 'elasticsearch', - version: '7.11.1', - status: 'installed', - dataStreams: [ - { - name: 'Elasticsearch metrics stream', - title: 'elasticsearch-metrics-*', - }, - { - name: 'Elasticsearch indices stream', - title: 'elasticsearch-indices-*', - }, - ], - }, - { - name: 'mongodb', - version: '3.6.1', - status: 'installed', - dataStreams: [ - { - name: 'MongoDB metrics stream', - title: 'mongodb-metrics-*', - }, - { - name: 'MongoDB slow query logs stream', - title: 'mongodb-slow-query-logs-*', - }, - ], - }, - { - name: 'prometheus', - version: '2.28.1', - status: 'installed', - dataStreams: [ - { - name: 'Prometheus metrics stream', - title: 'prometheus-metrics-*', - }, - ], - }, - { - name: 'haproxy', - version: '1.7.5', - status: 'installed', - dataStreams: [ - { - name: 'HAProxy metrics stream', - title: 'haproxy-metrics-*', - }, - { - name: 'HAProxy logs stream', - title: 'haproxy-logs-*', - }, - ], - }, - ], -}; diff --git a/x-pack/plugins/observability_logs/public/state_machines/data_streams/index.ts b/x-pack/plugins/observability_logs/public/state_machines/data_streams/index.ts new file mode 100644 index 0000000000000..3b2a320ae181f --- /dev/null +++ b/x-pack/plugins/observability_logs/public/state_machines/data_streams/index.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export * from './src'; diff --git a/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/defaults.ts b/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/defaults.ts new file mode 100644 index 0000000000000..16b8a3dea460a --- /dev/null +++ b/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/defaults.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ImmutableCache } from '../../../../common/immutable_cache'; +import { DefaultDataStreamsContext } from './types'; + +export const DEFAULT_CONTEXT: DefaultDataStreamsContext = { + cache: new ImmutableCache(), + dataStreams: null, + error: null, + search: { + sortOrder: 'asc', + }, +}; diff --git a/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/index.ts b/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/index.ts new file mode 100644 index 0000000000000..86a51fb2c0e16 --- /dev/null +++ b/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/index.ts @@ -0,0 +1,9 @@ +/* + * 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. + */ + +export * from './state_machine'; +export * from './types'; diff --git a/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/state_machine.ts b/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/state_machine.ts new file mode 100644 index 0000000000000..d48f0084a8ee0 --- /dev/null +++ b/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/state_machine.ts @@ -0,0 +1,153 @@ +/* + * 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 { catchError, from, map, of } from 'rxjs'; +import { assign, createMachine } from 'xstate'; +import { IDataStreamsClient } from '../../../services/data_streams'; +import { DEFAULT_CONTEXT } from './defaults'; +import { + DataStreamsContext, + DataStreamsEvent, + DefaultDataStreamsContext, + IntegrationTypestate, +} from './types'; + +export const createPureDataStreamsStateMachine = ( + initialContext: DefaultDataStreamsContext = DEFAULT_CONTEXT +) => + /** @xstate-layout N4IgpgJg5mDOIC5QBECGAXVBldAnMqAtrAHQCuAdgJbXpWoA2VAXpAMQAyA8gILID6yHgBUe-LMIBKAUR4BZLAG0ADAF1EoAA4B7WFTraKGkAA9EAdgDMAVhLWAjNYBsAJkvnlADgCcAFm-2ADQgAJ6InvYk-t5+5gHKvvbezgC+KcFomDj4RKQM2qgQNFCcvMgAkgByAOLiAKoAwg3S0sitKupIIDp6BkZdZghWtg7Obh4+-kGhiE72lnYeLp4u886uTmkZGNh4BMQk+YXFpXxVtQBiPOUc7WrGPfpUhsaD5uZOJOYRLk5O1r4nPEnMEwggXMplF9-J5rMpLJ4EvYXG4tiBMrscgcjhB2FhZJIGgAJQQiMQSGTyJT3LqPPqvRAuXwuEjeVaeIHWczLSzzSygxDWCFfOGQ6zWSxOSzuexojHZfakXEAI20lAAxsUsARcOqABZsfE8QkkoSicRSWQKDoPXRPF4DRC+HwkdnmQFOXxebzKFwChC+Zmu6KS97SlGeOU7BW5EgqtUUTUUKDa1C6g0mWCYdBgEioABmOdwAApRQBKNjyvax+MarU6-U22l2+mOoY2OyOVzub1Tf1CllMmIIiU+qW+KNZavYgpFZMXVBUBjsGTcPik80Uq3UzpaFvPfqgQZMlls+wc5Lczy86X+yyrKIxbxSvyQ7yeTwT9Lo6PTvKz4oFyXPECWJDdyUtKkmz3XoDwZcEgzPC8uR5Pl-V8DxXVFax3yBD82S-b8KG0XF4C6KssTImD7UPUxEAAWhBGYEEYkhIXYjiON8SxJ0xRVyGoWh6CYVgIFtWCHSPQVMMmCVYRwtx4X9CJWSfDD4kSZJNm-Cj+JxYpxJo+CYlsZQPHDf4XAlZ1-ScZRvEfd8fWfJIuV4mMZ0KSBDNbKSEHmXwoicTxzHFCEXGfa9zFs6xPCiew-klQEkjcL9tinSi4zAVU62TVN0x8uC2wcFlfWGd0fVHdDvHMEVITcBLu0sNKfwyvSAPnRdlzE5sJNowZmX9ZZTyfIVLHfSUeLSFIgA */ + createMachine( + { + context: initialContext, + preserveActionOrder: true, + predictableActionArguments: true, + id: 'DataStreams', + initial: 'uninitialized', + states: { + uninitialized: { + on: { + LOAD_DATA_STREAMS: 'loading', + }, + }, + loading: { + invoke: { + src: 'loadDataStreams', + }, + on: { + LOADING_SUCCEEDED: { + target: 'loaded', + actions: ['storeInCache', 'storeDataStreams', 'storeSearch'], + }, + LOADING_FAILED: { + target: 'loadingFailed', + }, + }, + }, + loaded: { + on: { + SEARCH_DATA_STREAMS: { + target: 'debouncingSearch', + }, + }, + }, + debouncingSearch: { + entry: 'storeSearch', + on: { + SEARCH_DATA_STREAMS: { + target: 'debouncingSearch', + }, + }, + after: { + 500: { + target: 'loading', + }, + }, + }, + loadingFailed: { + entry: ['clearCache', 'storeError'], + exit: 'clearError', + on: { + RELOAD_DATA_STREAMS: 'loading', + SEARCH_DATA_STREAMS: 'debouncingSearch', + }, + }, + }, + }, + { + actions: { + storeSearch: assign((_context, event) => ({ + // Store search from search event + ...('search' in event && { search: event.search }), + })), + storeDataStreams: assign((_context, event) => + 'data' in event + ? { + dataStreams: event.data.items, + } + : {} + ), + storeInCache: assign((context, event) => { + if (event.type !== 'LOADING_SUCCEEDED') return {}; + + return { + cache: context.cache.set(context.search, event.data), + }; + }), + clearCache: assign((context, event) => { + if (event.type !== 'LOADING_FAILED') return {}; + + return { + cache: context.cache.clear(), + }; + }), + storeError: assign((_context, event) => + 'error' in event + ? { + error: event.error, + } + : {} + ), + clearError: assign((_context) => ({ error: null })), + }, + } + ); + +export interface DataStreamsStateMachineDependencies { + initialContext?: DataStreamsContext; + dataStreamsClient: IDataStreamsClient; +} + +export const createDataStreamsStateMachine = ({ + initialContext, + dataStreamsClient, +}: DataStreamsStateMachineDependencies) => + createPureDataStreamsStateMachine(initialContext).withConfig({ + services: { + loadDataStreams: (context, event) => { + const searchParams = 'search' in event ? event.search : {}; + + return from( + context.cache.has(searchParams) + ? Promise.resolve(context.cache.get(searchParams)) + : dataStreamsClient.findDataStreams(searchParams) + ).pipe( + map( + (data): DataStreamsEvent => ({ + type: 'LOADING_SUCCEEDED', + data, + }) + ), + catchError((error) => + of({ + type: 'LOADING_FAILED', + error, + }) + ) + ); + }, + }, + }); diff --git a/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/types.ts b/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/types.ts new file mode 100644 index 0000000000000..05fc2a0f04aa9 --- /dev/null +++ b/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/types.ts @@ -0,0 +1,65 @@ +/* + * 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 type { IImmutableCache } from '../../../../common/immutable_cache'; +import { FindDataStreamsRequestQuery, FindDataStreamsResponse } from '../../../../common/latest'; +import { DataStream } from '../../../../common/data_streams'; + +export interface DefaultDataStreamsContext { + cache: IImmutableCache; + dataStreams: DataStream[] | null; + error: Error | null; + search: FindDataStreamsRequestQuery; +} + +export interface DataStreamsSearchParams { + datasetQuery?: string; + sortOrder?: 'asc' | 'desc'; +} + +export type IntegrationTypestate = + | { + value: 'uninitialized'; + context: DefaultDataStreamsContext; + } + | { + value: 'loading'; + context: DefaultDataStreamsContext; + } + | { + value: 'loaded'; + context: DefaultDataStreamsContext; + } + | { + value: 'loadingFailed'; + context: DefaultDataStreamsContext; + } + | { + value: 'debouncingSearch'; + context: DefaultDataStreamsContext; + }; + +export type DataStreamsContext = IntegrationTypestate['context']; + +export type DataStreamsEvent = + | { + type: 'LOAD_DATA_STREAMS'; + } + | { + type: 'LOADING_SUCCEEDED'; + data: FindDataStreamsResponse; + } + | { + type: 'LOADING_FAILED'; + error: Error; + } + | { + type: 'RELOAD_DATA_STREAMS'; + } + | { + type: 'SEARCH_DATA_STREAMS'; + search: DataStreamsSearchParams; + }; diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/defaults.ts b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/defaults.ts index 6c268c7bb07b3..2c6d025444e0c 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/defaults.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/defaults.ts @@ -8,14 +8,11 @@ import { ImmutableCache } from '../../../../common/immutable_cache'; import { DefaultIntegrationsContext } from './types'; -export const DEFAULT_DATA_STREAMS_TYPE = 'logs'; - export const DEFAULT_CONTEXT: DefaultIntegrationsContext = { cache: new ImmutableCache(), integrations: null, error: null, search: { sortOrder: 'asc', - dataStreamType: DEFAULT_DATA_STREAMS_TYPE, }, }; diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts index 11594265ff1f0..e79b6e198dc63 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts @@ -6,7 +6,7 @@ */ import { catchError, from, map, of } from 'rxjs'; -import { actions, assign, createMachine } from 'xstate'; +import { assign, createMachine } from 'xstate'; import { IDataStreamsClient } from '../../../services/data_streams'; import { DEFAULT_CONTEXT } from './defaults'; import { @@ -19,7 +19,7 @@ import { export const createPureIntegrationsStateMachine = ( initialContext: DefaultIntegrationsContext = DEFAULT_CONTEXT ) => - /** @xstate-layout N4IgpgJg5mDOIC5QEkB2AXMUBOBDdAlgPaqwB0ArqgdYbgDYEBekAxANoAMAuoqAA5FYBQiT4gAHogCMAJk4BmMgoCcnWQA4FGjQDYFAdlkKANCACeiALTTlGzmumcD6gCwrdK2QF9vZtJg4+MSkZPREuBA0UKwAMgDyAIIAIsgAcgDiAPoAygCqAMIFAKLFyWVcvEgggsKiqOJSCFq2rm0ArO0aTrrtutIaZpYI0gpK0r0qLpycurKunNIGvv4YWHj15OGR0XFJqZlZAGKJyLEVPOK1IiGNMqoGZANtstJLTm+DFjIqSs6u8iMujmLk6KxAAXWwRIWwiUVQUAAskRsGA9il0tl8kVSuVkpUrkIbmJqk15rIyLIPO43px2gpXEZTN8EAo6WR9ANOAtXL1ehpwZCgpswnDosjUeiDtkTmcLlUBET6ncEFYDLYPCp2rMnPT9K52kNELz2hzRpNfvIVK5pO1BWthSFyAQIPQ0QkUllEfEAErFLLpAAqxQyPsSgeQ8TSOQJ1WuytJiE0tk5C1kBgU7Q8+iNCBtrkpDi8ahN-PtgQ2TrILrdrByxUSPoKAAkA2lg6Hw5Ho7HFXVbomEMmOQppGmM1ngczhozdGQXCpi9y+V1y1CRQBjAAWYA3AGtxSiwELKzDEgA3XAEei4ABG15E5g4lzjSoHoCaVi18-ktqmugWMYjFzN4506Bx2lGeZdFmNo10dGEyG3XcDwRCVjwdU9SAvK8b3vRh0CfdhpAVGo3xJD9EF6SlXA0LN1EzbNNFzeQCxgtoYKpFcDDtPwIUw6FQggMBbyIKgN2iHIwFwbBtzrBsm1bIMQzDCMoxjF8+2JBpBysJQxlkXQDA8bpFi0OZp2saRvw8AxugGKZtXceCsPIYTRPEyTpNkrdWAkWB0HwMAyFwAAzTBsAACm1TgAEpWBPQS3JEsTUAkhEpJk7dezI-sKMkKj2gLbV5mpOjs0s1VfjIRRDEXV52iMLoVBcpLRR2BEjlwtg-Q9ZI2w7VTuw00j43fAqRm0WwDH6QwJg0WR5A0VwQLHZQZi1KDeVg1xfD41AiGE+BqkSzZCTynTKNVMclCMRYsxmwDDFkXMrE0JReQMOyGtkRqtGWPjTqrKgaBuBhmEgc7tJVN4jCeOzfgBUYjBm165FsTMDF5eRZiLFRpFakVtnhKAoYTK63nkJ46TkGaFH0IwvmGDwau1KZVEZTMGsJqticPVEyfGz9qptIqjEMp7gJZKl9IURbsbUIzFh5xCazAQX8uF007r-R7aql4Y+hTc0s0tBwARV0JkP3fmMIrJKcOvO8H0IjXLomiZOGUKZ1C+xjgWYlkFjIWjGT1BbzYGS3ko8tKvKyrc3ZVentfaUqAPKqc0f0MhtS5nijLULofEBgSibFTruogJPBwmOXKVmgCOlojwQJmMg6Icew5mx7oS98IA */ + /** @xstate-layout N4IgpgJg5mDOIC5QEkB2AXMUBOBDdAlgPaqwB0ArqgdYbgDYEBekAxANoAMAuoqAA5FYBQiT4gAHogBMARgDsnMgFZpAZjWc1ADgCcANm0AWbQBoQAT0TbpKzvenSjy-WqP79RgL5fzaTDj4xKRk9ES4EDRQrAAyAPIAggAiyAByAOIA+gDKAKoAwvkAokVJpVy8SCCCwqKo4lIIcooq6po6BsZmloj68rpk0pzGw-JjsspGsj5+GFh4deRhEVGxiSkZmQBiCcgx5TziNSLBDTJysmTyepyyRpz9ym7mVk32g0a6X7pu+rra+jkMxA-nmQRIS3CkVQUAAskRsGA1sk0lk8oUSmUkhUjkITmIqo1ZJpLronnc5GoFNIXr1tGpBsp7PI3LpZB5-sDQYFFqEoVF4YjkRssjs9gdKgI8XUzghtDYyEZnGppLonK4NPJaQh5J8yJwDEY1E96U5+ly5jzgpCImx4slMrC4gAlIqZNIAFSK6WdCQ9yDiqWyOKqxxlhMQxNuZDJVKMlOp2qVl3kTNugKe0nkHnkFoCC2tfNtEFY2SKCWd+QAEu7Ul6fX6A0GQ1LaqcIwh2T8yHdjJoyfp08ptcoAWQdNpRopZACXHmwbyAMYACzAi4A1gKEWBuQWIQkAG64Aj0XAAIxPIgsHEOoel7dAjWM+hU2mUP2U2cHo-02sUDINTx5GkU01HkO55ytCEyBXNdNxhQUd0tPdSEPY9TwvRh0GvdhZElap7wJR9rHcV932NL9OB-EdPCuNM1TkQEqX0SCUPICAwDPIgqEXKJsjAXBsBXUty0rGtPW9X1-UDYNb1bfF6g7WQfkuekf11aRPBVEcHjIeVJ20cC9EBGdWPBEIOK4ni+IEoTl1YCRYHQfAwDIXAADNMGwAAKNMAEpWF3cz2M47jUF4mF+MElcWwItsiMkGQFCUVQNC0YyuhHQy9PlB49BVTS3zM3llmhKAtnQthXXtJJa3rKSm1k-CwwfRKmiVOixjZT5dCoplfx6BBeoZN9dBMZMqPy4rC1KqIKpPO11kdF03QkhtpObOS4oU2UnCMTr+juL4+s4AbXhcJR7HsJVDLNDRpug2aYXm+g2DLCtqzqyTGxk2KWoSxo9oO7rjuUfrtRVS4rs4LMDH0GcsxY4FUCIDj4CqILFlxeLFOIhAAFozsQfHlBjb53xzPQWU-ZQHpCKgaBOBhmEgbGdo7IYDSuU7TQ8e5wO1bRLjTW5ZCo-41TUJHZnzYKizKtnwzxpwQJUQcs1kM12S1Qa31J8X7njcCjGzNQ6ZtMrEMV1qiQeJQWSNTwxfZdwiZ1MWe36XU7bcVNpl8EFkLl0rWbvHHZQmYwyD+J4ph0Th43pP8nH1SYxgT3R+mMaRzZg1cNy3RFMetNCT3PS9sOtgHEGUBRFW+XrdTGVxh0Gi67HsN8hmcbNc8ssKIqgKK7Kr3G2uUpk9LUTPhhuH4qJohkJ1OsDXEnMbc6e8rKogUfZXjEcwP1K74c0-Ra6+HwfCAA */ createMachine( { context: initialContext, @@ -34,18 +34,16 @@ export const createPureIntegrationsStateMachine = ( }, }, loading: { - entry: 'notifyLoadingStarted', invoke: { src: 'loadIntegrations', }, on: { LOADING_SUCCEEDED: { - target: 'idle', - actions: ['storeSearch', 'storeIntegrations', 'storeInCache'], + target: 'loaded', + actions: ['storeInCache', 'storeIntegrations', 'storeSearch'], }, LOADING_FAILED: { target: 'loadingFailed', - actions: 'storeError', }, }, }, @@ -55,22 +53,19 @@ export const createPureIntegrationsStateMachine = ( }, on: { LOADING_SUCCEEDED: { - target: 'idle', - actions: 'appendIntegrations', + target: 'loaded', + actions: ['storeInCache', 'appendIntegrations', 'storeSearch'], }, LOADING_FAILED: { target: 'loadingFailed', - actions: 'storeError', }, }, }, - idle: { - entry: 'notifyLoadingSucceeded', + loaded: { on: { LOAD_MORE_INTEGRATIONS: 'checkingMoreIntegrationsAvailability', SEARCH_INTEGRATIONS: { target: 'debouncingSearch', - actions: 'storeSearch', }, }, }, @@ -81,15 +76,15 @@ export const createPureIntegrationsStateMachine = ( target: 'loadingMore', }, { - target: 'idle', + target: 'loaded', }, ], }, debouncingSearch: { + entry: 'storeSearch', on: { SEARCH_INTEGRATIONS: { target: 'debouncingSearch', - actions: 'storeSearch', }, }, after: { @@ -99,20 +94,18 @@ export const createPureIntegrationsStateMachine = ( }, }, loadingFailed: { - entry: 'notifyLoadingFailed', + entry: ['clearCache', 'storeError'], + exit: 'clearError', on: { - RELOAD_INTEGRATIONS: { - target: 'loading', - }, + LOAD_MORE_INTEGRATIONS: 'checkingMoreIntegrationsAvailability', + RELOAD_INTEGRATIONS: 'loading', + SEARCH_INTEGRATIONS: 'debouncingSearch', }, }, }, }, { actions: { - notifyLoadingStarted: actions.pure(() => undefined), - notifyLoadingSucceeded: actions.pure(() => undefined), - notifyLoadingFailed: actions.pure(() => undefined), storeSearch: assign((context, event) => ({ // Store search from search event ...('search' in event && { search: event.search }), @@ -139,6 +132,13 @@ export const createPureIntegrationsStateMachine = ( cache: context.cache.set(context.search, event.data), }; }), + clearCache: assign((context, event) => { + if (event.type !== 'LOADING_FAILED') return {}; + + return { + cache: context.cache.clear(), + }; + }), appendIntegrations: assign((context, event) => 'data' in event ? { @@ -154,6 +154,7 @@ export const createPureIntegrationsStateMachine = ( } : {} ), + clearError: assign((_context) => ({ error: null })), }, guards: { hasMoreIntegrations: (context) => Boolean(context.search.searchAfter), @@ -171,7 +172,6 @@ export const createIntegrationStateMachine = ({ dataStreamsClient, }: IntegrationsStateMachineDependencies) => createPureIntegrationsStateMachine(initialContext).withConfig({ - actions: {}, services: { loadIntegrations: (context, event) => { const searchParams = diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts index 6ed1c1a66348d..db2d295baa558 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts @@ -4,12 +4,12 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { ImmutableCache } from '../../../../common/immutable_cache'; +import type { IImmutableCache } from '../../../../common/immutable_cache'; import { FindIntegrationsResponse, FindIntegrationsRequestQuery } from '../../../../common/latest'; import { Integration } from '../../../../common/data_streams'; export interface DefaultIntegrationsContext { - cache: ImmutableCache; + cache: IImmutableCache; integrations: Integration[] | null; error: Error | null; search: FindIntegrationsRequestQuery; @@ -17,7 +17,7 @@ export interface DefaultIntegrationsContext { } export interface IntegrationsSearchParams { - name?: string; + nameQuery?: string; sortOrder?: 'asc' | 'desc'; } From 844d96332c7bb0f9d4f90acd73f943d80352daec Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 18 May 2023 16:27:28 +0000 Subject: [PATCH 098/122] [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' --- .../public/components/data_stream_selector/constants.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/constants.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/constants.tsx index 84498b2d534e1..e8fbed93b6839 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/constants.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/constants.tsx @@ -5,10 +5,7 @@ * 2.0. */ -import { EuiText, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; -import React from 'react'; export const POPOVER_ID = 'data-stream-selector-popover'; export const INTEGRATION_PANEL_ID = 'integrations_panel'; From e4d38a53e05ef46915dffd139632f208111a1f10 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Thu, 18 May 2023 19:14:31 +0200 Subject: [PATCH 099/122] feat(observability_logs): implement dynamic and lazy load content --- .../observability_logs/common/dynamic.tsx | 24 ++++ .../common/immutable_cache.ts | 2 +- .../data_stream_selector.tsx | 102 ++--------------- .../data_streams_list.tsx | 105 ++++++++++++++++++ .../custom_data_stream_selector.tsx | 4 +- .../public/customizations/index.tsx | 15 +-- .../public/hooks/use_integrations.ts | 2 +- 7 files changed, 151 insertions(+), 103 deletions(-) create mode 100644 x-pack/plugins/observability_logs/common/dynamic.tsx create mode 100644 x-pack/plugins/observability_logs/public/components/data_stream_selector/data_streams_list.tsx diff --git a/x-pack/plugins/observability_logs/common/dynamic.tsx b/x-pack/plugins/observability_logs/common/dynamic.tsx new file mode 100644 index 0000000000000..47d87c863f8f8 --- /dev/null +++ b/x-pack/plugins/observability_logs/common/dynamic.tsx @@ -0,0 +1,24 @@ +/* + * 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 React, { ComponentType, lazy, Suspense } from 'react'; + +type LoadableComponent = () => Promise<{ default: ComponentType }>; + +interface DynamicOptions { + fallback?: React.ReactNode; +} + +export function dynamic(loader: LoadableComponent, options: DynamicOptions = {}) { + const Component = lazy(loader); + + return (props: any) => ( + + + + ); +} diff --git a/x-pack/plugins/observability_logs/common/immutable_cache.ts b/x-pack/plugins/observability_logs/common/immutable_cache.ts index 5702e7c99f0ed..d405d9f9fa6dd 100644 --- a/x-pack/plugins/observability_logs/common/immutable_cache.ts +++ b/x-pack/plugins/observability_logs/common/immutable_cache.ts @@ -18,7 +18,7 @@ export class ImmutableCache implements IImmutableCache
@@ -291,38 +296,27 @@ interface IntegrationsTreeParams { spyRef: RefCallback; } -const applyStreamSearch = (streams: DataStream[], search: SearchIntegrationsParams) => { - const { name, sortOrder } = search; - - const filteredStreams = streams.filter((stream) => stream.name.includes(name ?? '')); - - const sortedStreams = filteredStreams.sort((curr, next) => curr.name.localeCompare(next.name)); - - const searchResult = sortOrder === 'asc' ? sortedStreams : sortedStreams.reverse(); - - return searchResult; -}; - const buildIntegrationsTree = ({ list, onStreamSelected, spyRef }: IntegrationsTreeParams) => { return list.reduce( - (res: IntegrationsTree, entry, pos) => { - const entryId: CurrentPanelId = `integration-${entry.name}`; + (res: IntegrationsTree, integration, pos) => { + const entryId: CurrentPanelId = getIntegrationId(integration); + const { name, version, dataStreams } = integration; res.items.push({ - name: entry.name, - icon: , + name, + icon: , panel: entryId, buttonRef: pos === list.length - 1 ? spyRef : undefined, }); res.panels.push({ id: entryId, - title: entry.name, + title: name, width: DATA_VIEW_POPOVER_CONTENT_WIDTH, - items: entry.dataStreams.map((stream) => ({ + items: dataStreams.map((stream) => ({ name: stream.name, onClick: () => - onStreamSelected({ title: stream.title, name: `[${entry.name}] ${stream.name}` }), + onStreamSelected({ title: stream.title, name: `[${name}] ${stream.name}` }), })), }); @@ -331,3 +325,9 @@ const buildIntegrationsTree = ({ list, onStreamSelected, spyRef }: IntegrationsT { items: [], panels: [] } ); }; + +const getSearchStrategy = (panelId: CurrentPanelId) => { + if (panelId === UNCATEGORIZED_STREAMS_PANEL_ID) return SearchStrategy.DATA_STREAMS; + if (panelId === INTEGRATION_PANEL_ID) return SearchStrategy.INTEGRATIONS; + return SearchStrategy.INTEGRATIONS_DATA_STREAMS; +}; diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_skeleton.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_skeleton.tsx new file mode 100644 index 0000000000000..2b9a6bfb2b64d --- /dev/null +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_skeleton.tsx @@ -0,0 +1,16 @@ +/* + * 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 React from 'react'; +import { EuiPanel, EuiSkeletonText } from '@elastic/eui'; +import { uncategorizedLabel } from './constants'; + +export const DataStreamSkeleton = () => ( + + + +); diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_streams_list.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_streams_list.tsx index 799daf208b573..f324a07bac6a6 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_streams_list.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_streams_list.tsx @@ -6,31 +6,23 @@ */ import React from 'react'; -import { - EuiButton, - EuiContextMenuItem, - EuiEmptyPrompt, - EuiPanel, - EuiSkeletonText, - EuiText, - EuiToolTip, -} from '@elastic/eui'; +import { EuiButton, EuiContextMenuItem, EuiEmptyPrompt, EuiText, EuiToolTip } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { errorLabel, noDataStreamsDescriptionLabel, noDataStreamsLabel, - noDataStreamsRetryLabel, - uncategorizedLabel, + noDataRetryLabel, } from './constants'; -import type { DataStreamSelectionHandler } from '../../customizations/custom_data_stream_selector'; import { DataStream } from '../../../common/data_streams'; +import { DataStreamSkeleton } from './data_stream_skeleton'; +import { SearchHandler } from './data_stream_selector'; interface DataStreamListProps { dataStreams: DataStream[] | null; - error: Error | null; + error?: Error | null; isLoading: boolean; - onStreamClick: DataStreamSelectionHandler; + onStreamClick: SearchHandler; onRetry: () => void; } @@ -45,11 +37,7 @@ export const DataStreamsList = ({ const hasError = error !== null; if (isLoading) { - return ( - - - - ); + return ; } if (isEmpty) { @@ -85,7 +73,7 @@ export const DataStreamsList = ({ }} /> } - actions={[{noDataStreamsRetryLabel}]} + actions={[{noDataRetryLabel}]} /> ); } diff --git a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx index dfae6f6568358..e78cf403379f2 100644 --- a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx @@ -50,18 +50,19 @@ export const CustomDataStreamSelector = withProviders(({ stateContainer }) => { return ( ); }); diff --git a/x-pack/plugins/observability_logs/public/hooks/use_data_streams.ts b/x-pack/plugins/observability_logs/public/hooks/use_data_streams.ts index d3f20fd9da67c..36761cc043e77 100644 --- a/x-pack/plugins/observability_logs/public/hooks/use_data_streams.ts +++ b/x-pack/plugins/observability_logs/public/hooks/use_data_streams.ts @@ -10,7 +10,7 @@ import createContainer from 'constate'; import { useCallback } from 'react'; import { createDataStreamsStateMachine } from '../state_machines/data_streams'; import { IDataStreamsClient } from '../services/data_streams'; -import { FindDataStreamsRequestQuery } from '../../common'; +import { FindDataStreamsRequestQuery, SortOrder } from '../../common'; interface DataStreamsContextDeps { dataStreamsClient: IDataStreamsClient; @@ -18,7 +18,7 @@ interface DataStreamsContextDeps { export interface SearchDataStreamsParams { name?: string; - sortOrder?: 'asc' | 'desc'; + sortOrder?: SortOrder; } export type SearchDataStreams = (params: SearchDataStreamsParams) => void; diff --git a/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts b/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts index 3eb083d983fd9..47880c01ecbe3 100644 --- a/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts +++ b/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts @@ -8,9 +8,10 @@ import { useInterpret, useSelector } from '@xstate/react'; import createContainer from 'constate'; import { useCallback } from 'react'; +import { SearchStrategy } from '../../common/data_streams'; import { createIntegrationStateMachine } from '../state_machines/integrations'; import { IDataStreamsClient } from '../services/data_streams'; -import { FindIntegrationsRequestQuery } from '../../common'; +import { FindIntegrationsRequestQuery, SortOrder } from '../../common'; interface IntegrationsContextDeps { dataStreamsClient: IDataStreamsClient; @@ -18,7 +19,8 @@ interface IntegrationsContextDeps { export interface SearchIntegrationsParams { name?: string; - sortOrder: 'asc' | 'desc'; + sortOrder?: SortOrder; + strategy: SearchStrategy; } export type SearchIntegrations = (params: SearchIntegrationsParams) => void; @@ -58,8 +60,8 @@ const useIntegrations = ({ dataStreamsClient }: IntegrationsContextDeps) => { integrationsStateService.send({ type: 'SEARCH_INTEGRATIONS', search: { + ...searchParams, nameQuery: searchParams.name, - sortOrder: searchParams.sortOrder, }, }), [integrationsStateService] @@ -97,7 +99,9 @@ export const [IntegrationsProvider, useIntegrationsContext] = createContainer(us /** * Utils */ -const filterSearchParams = (search: FindIntegrationsRequestQuery): SearchIntegrationsParams => ({ +const filterSearchParams = ( + search: FindIntegrationsRequestQuery +): Pick => ({ name: search.nameQuery, sortOrder: search.sortOrder, }); diff --git a/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/state_machine.ts b/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/state_machine.ts index d48f0084a8ee0..0d4fdaed744dc 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/state_machine.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/state_machine.ts @@ -7,6 +7,7 @@ import { catchError, from, map, of } from 'rxjs'; import { assign, createMachine } from 'xstate'; +import { FindDataStreamsResponse } from '../../../../common'; import { IDataStreamsClient } from '../../../services/data_streams'; import { DEFAULT_CONTEXT } from './defaults'; import { @@ -19,7 +20,7 @@ import { export const createPureDataStreamsStateMachine = ( initialContext: DefaultDataStreamsContext = DEFAULT_CONTEXT ) => - /** @xstate-layout N4IgpgJg5mDOIC5QBECGAXVBldAnMqAtrAHQCuAdgJbXpWoA2VAXpAMQAyA8gILID6yHgBUe-LMIBKAUR4BZLAG0ADAF1EoAA4B7WFTraKGkAA9EAdgDMAVhLWAjNYBsAJkvnlADgCcAFm-2ADQgAJ6InvYk-t5+5gHKvvbezgC+KcFomDj4RKQM2qgQNFCcvMgAkgByAOLiAKoAwg3S0sitKupIIDp6BkZdZghWtg7Obh4+-kGhiE72lnYeLp4u886uTmkZGNh4BMQk+YXFpXxVtQBiPOUc7WrGPfpUhsaD5uZOJOYRLk5O1r4nPEnMEwggXMplF9-J5rMpLJ4EvYXG4tiBMrscgcjhB2FhZJIGgAJQQiMQSGTyJT3LqPPqvRAuXwuEjeVaeIHWczLSzzSygxDWCFfOGQ6zWSxOSzuexojHZfakXEAI20lAAxsUsARcOqABZsfE8QkkoSicRSWQKDoPXRPF4DRC+HwkdnmQFOXxebzKFwChC+Zmu6KS97SlGeOU7BW5EgqtUUTUUKDa1C6g0mWCYdBgEioABmOdwAApRQBKNjyvax+MarU6-U22l2+mOoY2OyOVzub1Tf1CllMmIIiU+qW+KNZavYgpFZMXVBUBjsGTcPik80Uq3UzpaFvPfqgQZMlls+wc5Lczy86X+yyrKIxbxSvyQ7yeTwT9Lo6PTvKz4oFyXPECWJDdyUtKkmz3XoDwZcEgzPC8uR5Pl-V8DxXVFax3yBD82S-b8KG0XF4C6KssTImD7UPUxEAAWhBGYEEYkhIXYjiON8SxJ0xRVyGoWh6CYVgIFtWCHSPQVMMmCVYRwtx4X9CJWSfDD4kSZJNm-Cj+JxYpxJo+CYlsZQPHDf4XAlZ1-ScZRvEfd8fWfJIuV4mMZ0KSBDNbKSEHmXwoicTxzHFCEXGfa9zFs6xPCiew-klQEkjcL9tinSi4zAVU62TVN0x8uC2wcFlfWGd0fVHdDvHMEVITcBLu0sNKfwyvSAPnRdlzE5sJNowZmX9ZZTyfIVLHfSUeLSFIgA */ + /** @xstate-layout N4IgpgJg5mDOIC5QBECGAXVBldAnMqAtrAHQCuAdgJbXpWoA2VAXpAMQAyA8gILID6yHgBUe-LMIBKAUR4BZLAG0ADAF1EoAA4B7WFTraKGkAA9EAdgAsAVhLWAjNYBs11-fPXzADnsAaEACeiD52AL6h-miYOPhEpAzaqBA0UJy8yACSAHIA4uIAqgDChdLSyGUq6kggOnoGRtVmCFa2Ds6uDh7efoGITvYAzHbmygOWA8runj7hkRjYeATEJAlJKWl82XkAYjwZHBVqxrX6VIbGTd5OJN0ATF7KndM9QQi3yso31rMgUQuxy1WEHYWFkkkKAAlBCIxBIZPIlEdqid6hdELdrLcSABOW72LwefyvTGfDw-P4xJakYEAI20lAAxiksARcAyABZsUE8cFQoSicRSWQKSrHXSnc6NRCWbHYkh4gkPJ7dInSyxYjHk+aUuIkWn0ihMihQFmoNmckywTDoMAkVAAMxtuAAFNYPgBKNgUxa6-WM5msjmi5Hi1FS5o2OyOFxuLo+VUITEa74RX7an2AxLJY3bVBUBjsGTcPjQgVw4WIqpaUNnBqgJoYrG4-GE3oIAZ4sKp70A+JZlK5-MgsGQ0uwoUI4PVuq1tFvTE4hWt16WEbylOpijaYHwao9qlimeS+uIAC0TgT5610QzpEoNFOjBYkEPErrpkQ1geJC82Js-XMJw4xeYJbmvf4qRWftjVfMMTwQbEvFsZQRjGCYphVNtbkQuwPlGR4MJmbt017KCkhfEMj3fJpBksEhLCcAlrATFwvHo+wnE4rjuKccCdWWP1DQDM0OVg2dwyTEhlFucwBisW4gOeBMZXML4+NvMjsygQcCwgMTjw-BB1QTAYkJIJxNXCUIgA */ createMachine( { context: initialContext, @@ -128,11 +129,12 @@ export const createDataStreamsStateMachine = ({ createPureDataStreamsStateMachine(initialContext).withConfig({ services: { loadDataStreams: (context, event) => { - const searchParams = 'search' in event ? event.search : {}; + const searchParams = + 'search' in event ? { ...context.search, ...event.search } : context.search; return from( context.cache.has(searchParams) - ? Promise.resolve(context.cache.get(searchParams)) + ? Promise.resolve(context.cache.get(searchParams) as FindDataStreamsResponse) : dataStreamsClient.findDataStreams(searchParams) ).pipe( map( diff --git a/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/types.ts b/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/types.ts index 05fc2a0f04aa9..17c1ec00a8734 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/types.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/types.ts @@ -5,7 +5,11 @@ * 2.0. */ import type { IImmutableCache } from '../../../../common/immutable_cache'; -import { FindDataStreamsRequestQuery, FindDataStreamsResponse } from '../../../../common/latest'; +import { + FindDataStreamsRequestQuery, + FindDataStreamsResponse, + SortOrder, +} from '../../../../common/latest'; import { DataStream } from '../../../../common/data_streams'; export interface DefaultDataStreamsContext { @@ -17,7 +21,7 @@ export interface DefaultDataStreamsContext { export interface DataStreamsSearchParams { datasetQuery?: string; - sortOrder?: 'asc' | 'desc'; + sortOrder?: SortOrder; } export type IntegrationTypestate = diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/defaults.ts b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/defaults.ts index 2c6d025444e0c..176b7b672cc2e 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/defaults.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/defaults.ts @@ -10,6 +10,7 @@ import { DefaultIntegrationsContext } from './types'; export const DEFAULT_CONTEXT: DefaultIntegrationsContext = { cache: new ImmutableCache(), + integrationsSource: null, integrations: null, error: null, search: { diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts index e79b6e198dc63..1a99e18b29ae3 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts @@ -5,21 +5,25 @@ * 2.0. */ -import { catchError, from, map, of } from 'rxjs'; +import { catchError, from, map, of, throwError } from 'rxjs'; import { assign, createMachine } from 'xstate'; +import { EntityList } from '../../../../common/entity_list'; +import { DataStream, Integration, SearchStrategy } from '../../../../common/data_streams'; +import { FindIntegrationsResponse, getIntegrationId } from '../../../../common'; import { IDataStreamsClient } from '../../../services/data_streams'; import { DEFAULT_CONTEXT } from './defaults'; import { DefaultIntegrationsContext, IntegrationsContext, IntegrationsEvent, + IntegrationsSearchParams, IntegrationTypestate, } from './types'; export const createPureIntegrationsStateMachine = ( initialContext: DefaultIntegrationsContext = DEFAULT_CONTEXT ) => - /** @xstate-layout N4IgpgJg5mDOIC5QEkB2AXMUBOBDdAlgPaqwB0ArqgdYbgDYEBekAxANoAMAuoqAA5FYBQiT4gAHogBMARgDsnMgFZpAZjWc1ADgCcANm0AWbQBoQAT0TbpKzvenSjy-WqP79RgL5fzaTDj4xKRk9ES4EDRQrAAyAPIAggAiyAByAOIA+gDKAKoAwvkAokVJpVy8SCCCwqKo4lIIcooq6po6BsZmloj68rpk0pzGw-JjsspGsj5+GFh4deRhEVGxiSkZmQBiCcgx5TziNSLBDTJysmTyepyyRpz9ym7mVk32g0a6X7pu+rra+jkMxA-nmQRIS3CkVQUAAskRsGA1sk0lk8oUSmUkhUjkITmIqo1ZJpLronnc5GoFNIXr1tGpBsp7PI3LpZB5-sDQYFFqEoVF4YjkRssjs9gdKgI8XUzghtDYyEZnGppLonK4NPJaQh5J8yJwDEY1E96U5+ly5jzgpCImx4slMrC4gAlIqZNIAFSK6WdCQ9yDiqWyOKqxxlhMQxNuZDJVKMlOp2qVl3kTNugKe0nkHnkFoCC2tfNtEFY2SKCWd+QAEu7Ul6fX6A0GQ1LaqcIwh2T8yHdjJoyfp08ptcoAWQdNpRopZACXHmwbyAMYACzAi4A1gKEWBuQWIQkAG64Aj0XAAIxPIgsHEOoel7dAjWM+hU2mUP2U2cHo-02sUDINTx5GkU01HkO55ytCEyBXNdNxhQUd0tPdSEPY9TwvRh0GvdhZElap7wJR9rHcV932NL9OB-EdPCuNM1TkQEqX0SCUPICAwDPIgqEXKJsjAXBsBXUty0rGtPW9X1-UDYNb1bfF6g7WQfkuekf11aRPBVEcHjIeVJ20cC9EBGdWPBEIOK4ni+IEoTl1YCRYHQfAwDIXAADNMGwAAKNMAEpWF3cz2M47jUF4mF+MElcWwItsiMkGQFCUVQNC0YyuhHQy9PlB49BVTS3zM3llmhKAtnQthXXtJJa3rKSm1k-CwwfRKmiVOixjZT5dCoplfx6BBeoZN9dBMZMqPy4rC1KqIKpPO11kdF03QkhtpObOS4oU2UnCMTr+juL4+s4AbXhcJR7HsJVDLNDRpug2aYXm+g2DLCtqzqyTGxk2KWoSxo9oO7rjuUfrtRVS4rs4LMDH0GcsxY4FUCIDj4CqILFlxeLFOIhAAFozsQfHlBjb53xzPQWU-ZQHpCKgaBOBhmEgbGdo7IYDSuU7TQ8e5wO1bRLjTW5ZCo-41TUJHZnzYKizKtnwzxpwQJUQcs1kM12S1Qa31J8X7njcCjGzNQ6ZtMrEMV1qiQeJQWSNTwxfZdwiZ1MWe36XU7bcVNpl8EFkLl0rWbvHHZQmYwyD+J4ph0Th43pP8nH1SYxgT3R+mMaRzZg1cNy3RFMetNCT3PS9sOtgHEGUBRFW+XrdTGVxh0Gi67HsN8hmcbNc8ssKIqgKK7Kr3G2uUpk9LUTPhhuH4qJohkJ1OsDXEnMbc6e8rKogUfZXjEcwP1K74c0-Ra6+HwfCAA */ + /** @xstate-layout N4IgpgJg5mDOIC5QEkB2AXMUBOBDdAlgPaqwB0ArqgdYbgDYEBekAxANoAMAuoqAA5FYBQiT4gAHogCMAZgBsAVjLTOATlly18gCwAOHbL0AaEAE9EshSoBMigL73TaTDnzFSZekVwQaUVgAZAHkAQQARZAA5AHEAfQBlAFUAYRSAUXTwrK5eJBBBYVFUcSkEOQB2NRVdTk5pHV0DI1MLBAq9eTJ5NUVNO0dnDCw8YvJvX38gsMjYuIAxUORAnJ5xQpEPUpkdGy75PQrDPW19Y9bEGwayWQqHJxAXEfcScZ8-VCgAWSJsMGmItF4sk0plsuFcushJsxPkyjY9HoyFdDooLggDF1pIpsfIBg8nm4xl53v4fn8AbN4otlqs8gJocVtggEUiUXd0VVlEpcfihq5Rh43r42CEInEvsEAErpOLRAAq6RiUtC8uQwSiCUh+Q2TLhiCOSJ6fWkdnRqk4shud0Gj2GRKFJJFEFYCXSoSlKQAEnKoorlar1ZrtQyilt9e19N1ev00eYDdiyKjbYTBa8yABjAAWYAzAGsyb8wKmXqRQgA3XAEei4ABG1ZEZg4ax1jPDoDKek4XVR5u71Xq6lu935z2J2dzBc+5OL9rTZcr1brDfQTfY0npBTbsI7iE6SOkvfjCF6FWtI7tAtL5AgYFrRCoGf8CTAuGw2dd7s9PoVSpVao1LUW1DGESgjVRFCNGxZEtaR5Dgq5pHRWQNG6PlLzHR1b3vR9n1fd8s1YCRYHQfAwDIXAADNMGwAAKRQ6gASlYEtiWwh9UCfT4XzfbMQy3MMd0kA0o2NWN0T0ZRkwJOdrzIdjcO4-CP2I0jMAo6iwDohjOGY1isLvDiuKgHiCPXTddXbYTygUZRVA0LQmnOY8dAqK08QvfT01gZSs2fdA-lwABbWBPw9b1ElSDIsjpKFBLA3cWT0K1ZEUDQOWPHobCTNRmhtGSr2JHzeL87iAtfEKwu-BYlhWCFgIE0DmVZZFDwytpoM4c8U1k4kJg+KB5irehRRmCVpVlX8AwA4MGssoSykNaMTTNTKlG6grMPTfr-CG6s2BlMVwl9f1-yDICLO3BLrKWsTTTjNp4J0JN8tHB1ttJT49pGl03XCn8-T-QNAP4+brsW0SY3u9FGi6zh8oeVAiFveB8i80g4qaiMAFp5HRXGesKx0qBoTYGGYSBMb1RLpGkVD5FkBFoePGw1GqHQVs83rHR2z4qassoEOetQKiUepGjOFoXJ0ap7sJrbPF574i35hbEF2TlemRUXFA8+X3sV95KdbeLmUg9m6ZsCpOGSqwrB0TlWbINR0PR8gJ3zQs-jdithuXRhV1V8GZE4HRpDIThoMMdrEF6JFXK5on0wUzi8JKoPmVUCozzxHpbk6PFaZhmXbEThXyGKgj-MCkKM4jEv5CqA4Y4xcOy4N4UBu+42QOp6zFCtshs9kaOHsuTg7PxRwgA */ createMachine( { context: initialContext, @@ -40,7 +44,7 @@ export const createPureIntegrationsStateMachine = ( on: { LOADING_SUCCEEDED: { target: 'loaded', - actions: ['storeInCache', 'storeIntegrations', 'storeSearch'], + actions: ['storeInCache', 'storeIntegrationsResponse', 'storeSearch'], }, LOADING_FAILED: { target: 'loadingFailed', @@ -88,8 +92,28 @@ export const createPureIntegrationsStateMachine = ( }, }, after: { - 500: { - target: 'loading', + 500: [ + { + cond: 'isStreamSearch', + target: 'searchingStreams', + }, + { + target: 'loading', + }, + ], + }, + }, + searchingStreams: { + invoke: { + src: 'searchStreams', + }, + on: { + SEARCH_SUCCEEDED: { + target: 'loaded', + actions: ['storeIntegrations'], + }, + SEARCH_FAILED: { + target: 'loadingFailed', }, }, }, @@ -117,14 +141,18 @@ export const createPureIntegrationsStateMachine = ( }, }), })), - storeIntegrations: assign((context, event) => + storeIntegrationsResponse: assign((context, event) => 'data' in event ? { + integrationsSource: event.data.items, integrations: event.data.items, total: event.data.total, } : {} ), + storeIntegrations: assign((_context, event) => + 'integrations' in event ? { integrations: event.integrations } : {} + ), storeInCache: assign((context, event) => { if (event.type !== 'LOADING_SUCCEEDED') return {}; @@ -142,6 +170,7 @@ export const createPureIntegrationsStateMachine = ( appendIntegrations: assign((context, event) => 'data' in event ? { + integrationsSource: context.integrations?.concat(event.data.items) ?? [], integrations: context.integrations?.concat(event.data.items) ?? [], total: event.data.total, } @@ -158,6 +187,8 @@ export const createPureIntegrationsStateMachine = ( }, guards: { hasMoreIntegrations: (context) => Boolean(context.search.searchAfter), + isStreamSearch: (context) => + context.search.strategy === SearchStrategy.INTEGRATIONS_DATA_STREAMS, }, } ); @@ -173,13 +204,12 @@ export const createIntegrationStateMachine = ({ }: IntegrationsStateMachineDependencies) => createPureIntegrationsStateMachine(initialContext).withConfig({ services: { - loadIntegrations: (context, event) => { - const searchParams = - 'search' in event ? { ...context.search, ...event.search } : context.search; + loadIntegrations: (context) => { + const searchParams = context.search; return from( context.cache.has(searchParams) - ? Promise.resolve(context.cache.get(searchParams)) + ? Promise.resolve(context.cache.get(searchParams) as FindIntegrationsResponse) : dataStreamsClient.findIntegrations(searchParams) ).pipe( map( @@ -196,5 +226,56 @@ export const createIntegrationStateMachine = ({ ) ); }, + searchStreams: (context) => { + const searchParams = context.search; + + return from( + context.integrationsSource !== null + ? Promise.resolve(searchIntegrationStreams(context.integrationsSource, searchParams)) + : throwError( + () => + new Error( + 'Failed to filter integration streams: No integrations found in context.' + ) + ) + ).pipe( + map( + (integrations): IntegrationsEvent => ({ + type: 'SEARCH_SUCCEEDED', + integrations, + }) + ), + catchError((error) => + of({ + type: 'SEARCH_FAILED', + error, + }) + ) + ); + }, }, }); + +const searchIntegrationStreams = ( + integrations: Integration[], + search: IntegrationsSearchParams +) => { + const { nameQuery, sortOrder, integrationId } = search; + + return integrations.map((integration) => { + const id = getIntegrationId(integration) + + if (id !== integrationId) { + return integration; + } + + return { + ...integration, + // Filter and sort the dataStreams by the search criteria + dataStreams: new EntityList(integration.dataStreams) + .filterBy((stream) => stream.name.includes(nameQuery ?? '')) + .sortBy('name', sortOrder) + .build(), + }; + } +}; diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts index db2d295baa558..a2c81d5aa367b 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts @@ -5,20 +5,27 @@ * 2.0. */ import type { IImmutableCache } from '../../../../common/immutable_cache'; -import { FindIntegrationsResponse, FindIntegrationsRequestQuery } from '../../../../common/latest'; -import { Integration } from '../../../../common/data_streams'; +import { + FindIntegrationsResponse, + FindIntegrationsRequestQuery, + SortOrder, +} from '../../../../common/latest'; +import { Integration, SearchStrategy } from '../../../../common/data_streams'; export interface DefaultIntegrationsContext { cache: IImmutableCache; + integrationsSource: Integration[] | null; integrations: Integration[] | null; error: Error | null; - search: FindIntegrationsRequestQuery; + search: FindIntegrationsRequestQuery & { strategy?: SearchStrategy }; total?: number; } export interface IntegrationsSearchParams { nameQuery?: string; - sortOrder?: 'asc' | 'desc'; + sortOrder?: SortOrder; + strategy?: SearchStrategy; + integrationId?: string; } export type IntegrationTypestate = @@ -46,6 +53,10 @@ export type IntegrationTypestate = value: 'debouncingSearch'; context: DefaultIntegrationsContext; } + | { + value: 'searchingStreams'; + context: DefaultIntegrationsContext; + } | { value: 'checkingMoreIntegrationsAvailability'; context: DefaultIntegrationsContext; @@ -62,6 +73,14 @@ export type IntegrationsEvent = type: 'LOADING_FAILED'; error: Error; } + | { + type: 'SEARCH_SUCCEEDED'; + integrations: Integration[]; + } + | { + type: 'SEARCH_FAILED'; + error: Error; + } | { type: 'RELOAD_INTEGRATIONS'; } From 5a2eb549161812b61b6bb7496c247d74729c076f Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Mon, 22 May 2023 17:39:14 +0200 Subject: [PATCH 101/122] feat(observability_logs): renames --- .../data_stream_selector.stories.tsx | 4 ++-- .../data_stream_selector.tsx | 16 +++++--------- .../custom_data_stream_selector.tsx | 22 ++++++++++++++----- 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.stories.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.stories.tsx index 4eb46e788618d..88776639178b4 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.stories.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.stories.tsx @@ -7,9 +7,9 @@ /* eslint-disable no-console */ +import React from 'react'; import { I18nProvider } from '@kbn/i18n-react'; import { Story } from '@storybook/react'; -import React from 'react'; import { DataStreamSelector, DataStreamSelectorProps } from './data_stream_selector'; export default { @@ -405,7 +405,7 @@ Basic.args = { isLoadingIntegrations: false, isLoadingStreams: false, onIntegrationsLoadMore: () => console.log('Loading more integrations...'), - onIntegrationsSearch: (params) => console.log('Search integrations by: ', params), + onSearch: (params) => console.log('Search integrations by: ', params), onStreamSelected: (stream) => console.log('Create ad hoc view for stream: ', stream), onStreamsEntryClick: () => console.log('Load uncategorized streams...'), onStreamsReload: () => console.log('Reloading streams...'), diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx index b8fd375b31227..812e30259ddf3 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx @@ -79,15 +79,13 @@ export interface DataStreamSelectorProps { isLoadingIntegrations: boolean; isLoadingStreams: boolean; /* Triggered when a search or sorting is performed on integrations */ - onIntegrationsSearch: SearchHandler; + onSearch: SearchHandler; /* Triggered when we reach the bottom of the integration list and want to load more */ onIntegrationsLoadMore: LoadMoreIntegrations; /* Triggered when the uncategorized streams entry is selected */ onStreamsEntryClick: () => void; /* Triggered when retrying to load the data streams */ onStreamsReload: () => void; - /* Triggered when a search or sorting is performed on data streams */ - onStreamsSearch: SearchHandler; /* Triggered when a data stream entry is selected */ onStreamSelected: DataStreamSelectionHandler; } @@ -96,7 +94,7 @@ export function DataStreamSelector({ title, integrations, isLoadingIntegrations, - onIntegrationsSearch, + onSearch, onIntegrationsLoadMore, onStreamSelected, search, @@ -104,7 +102,6 @@ export function DataStreamSelector({ isLoadingStreams, onStreamsEntryClick, onStreamsReload, - onStreamsSearch, dataStreams, }: DataStreamSelectorProps) { const isMobile = useIsWithinBreakpoints(['xs', 's']); @@ -156,7 +153,7 @@ export function DataStreamSelector({ items: [dataStreamsItem, ...items], panels, }; - }, [integrations, handleStreamSelection, setRef]); + }, [integrations, handleStreamSelection, onStreamsEntryClick, setRef]); const panels = [ { @@ -188,12 +185,9 @@ export function DataStreamSelector({ ); - // const handleIntegrationStreamsSearch = setLocalSearch; - - // TODO: Handle search strategy by current panel id const handleSearch = (params: SearchParams) => { const strategy = getSearchStrategy(currentPanel); - return onIntegrationsSearch({ + return onSearch({ ...params, strategy, ...(strategy === SearchStrategy.INTEGRATIONS_DATA_STREAMS && { integrationId: currentPanel }), @@ -201,7 +195,7 @@ export function DataStreamSelector({ }; const searchValue = search; // const handleSearch = - // currentPanel === INTEGRATION_PANEL_ID ? onIntegrationsSearch : handleIntegrationStreamsSearch; + // currentPanel === INTEGRATION_PANEL_ID ? onSearch : handleIntegrationStreamsSearch; // const searchValue = currentPanel === INTEGRATION_PANEL_ID ? search : localSearch; return ( diff --git a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx index e78cf403379f2..e6ad6d177b9b0 100644 --- a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx @@ -7,8 +7,8 @@ import React from 'react'; import { DiscoverStateContainer } from '@kbn/discover-plugin/public'; -import { DataStream } from '../../common/data_streams'; -import { DataStreamSelector } from '../components/data_stream_selector'; +import { DataStream, SearchStrategy } from '../../common/data_streams'; +import { DataStreamSelector, SearchHandler } from '../components/data_stream_selector'; import { InternalStateProvider, useDataView } from '../utils/internal_state_container_context'; import { IntegrationsProvider, useIntegrationsContext } from '../hooks/use_integrations'; import { @@ -31,7 +31,7 @@ export const CustomDataStreamSelector = withProviders(({ stateContainer }) => { integrations, isLoading: isLoadingIntegrations, loadMore, - search, + search: integrationsSearch, searchIntegrations, } = useIntegrationsContext(); @@ -41,6 +41,7 @@ export const CustomDataStreamSelector = withProviders(({ stateContainer }) => { isLoading: isLoadingStreams, loadDataStreams, reloadDataStreams, + search: dataStreamsSearch, searchDataStreams, } = useDataStreamsContext(); @@ -48,6 +49,16 @@ export const CustomDataStreamSelector = withProviders(({ stateContainer }) => { return stateContainer.actions.onCreateDefaultAdHocDataView(dataStream); }; + const handleSearch: SearchHandler = (params) => { + if (params.strategy === SearchStrategy.DATA_STREAMS) { + return searchDataStreams({ name: params.name, sortOrder: params.sortOrder }); + } else { + searchIntegrations(params); + } + }; + + // TODO: handle search states for different strategies + return ( { isLoadingIntegrations={isLoadingIntegrations} isLoadingStreams={isLoadingStreams} onIntegrationsLoadMore={loadMore} - onIntegrationsSearch={searchIntegrations} + onSearch={handleSearch} onStreamSelected={handleStreamSelection} onStreamsEntryClick={loadDataStreams} onStreamsReload={reloadDataStreams} - onStreamsSearch={searchDataStreams} - search={search} + search={integrationsSearch} title={dataView.getName()} /> ); From bd9cf6202bb7172709e009ce5ddf4432cee8243d Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Tue, 23 May 2023 10:47:15 +0200 Subject: [PATCH 102/122] feat(observability_logs): update DataStreamSelector story --- .../.storybook/__mocks__/package_icon.tsx | 11 + .../observability_logs/.storybook/main.js | 9 + .../observability_logs/.storybook/preview.js | 1 - .../common/data_streams/types.ts | 4 +- .../data_stream_selector.stories.tsx | 816 +++++++++--------- .../data_stream_selector.tsx | 48 +- .../custom_data_stream_selector.tsx | 8 +- .../integrations/src/state_machine.ts | 4 +- 8 files changed, 489 insertions(+), 412 deletions(-) create mode 100644 x-pack/plugins/observability_logs/.storybook/__mocks__/package_icon.tsx diff --git a/x-pack/plugins/observability_logs/.storybook/__mocks__/package_icon.tsx b/x-pack/plugins/observability_logs/.storybook/__mocks__/package_icon.tsx new file mode 100644 index 0000000000000..2bb5c6c7614aa --- /dev/null +++ b/x-pack/plugins/observability_logs/.storybook/__mocks__/package_icon.tsx @@ -0,0 +1,11 @@ +/* + * 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 React from 'react'; +import { EuiIcon } from '@elastic/eui'; + +// Export mock package icon that doesn't trigger http requests +export const PackageIcon = () => ; diff --git a/x-pack/plugins/observability_logs/.storybook/main.js b/x-pack/plugins/observability_logs/.storybook/main.js index acfa6b43b2082..79b438e7eb7ce 100644 --- a/x-pack/plugins/observability_logs/.storybook/main.js +++ b/x-pack/plugins/observability_logs/.storybook/main.js @@ -10,4 +10,13 @@ const defaultConfig = require('@kbn/storybook').defaultConfig; module.exports = { ...defaultConfig, stories: ['../**/*.stories.mdx', ...defaultConfig.stories], + webpackFinal: async (config) => { + const originalConfig = await defaultConfig.webpackFinal(config); + + // Mock fleet plugin for PackageIcon component + originalConfig.resolve.alias['@kbn/fleet-plugin/public'] = require.resolve( + './__mocks__/package_icon' + ); + return originalConfig; + }, }; diff --git a/x-pack/plugins/observability_logs/.storybook/preview.js b/x-pack/plugins/observability_logs/.storybook/preview.js index 59df773136b79..33a07bd3fac1f 100644 --- a/x-pack/plugins/observability_logs/.storybook/preview.js +++ b/x-pack/plugins/observability_logs/.storybook/preview.js @@ -4,7 +4,6 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - export const parameters = { docs: { source: { diff --git a/x-pack/plugins/observability_logs/common/data_streams/types.ts b/x-pack/plugins/observability_logs/common/data_streams/types.ts index be3c8862311b9..26d56aca3d632 100644 --- a/x-pack/plugins/observability_logs/common/data_streams/types.ts +++ b/x-pack/plugins/observability_logs/common/data_streams/types.ts @@ -11,10 +11,10 @@ import * as rt from 'io-ts'; export const dataStreamRT = rt.exact( rt.intersection([ rt.type({ - name: rt.string, + name: indexPatternRt, }), rt.partial({ - title: indexPatternRt, + title: rt.string, }), ]) ); diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.stories.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.stories.tsx index 88776639178b4..edd1197d59247 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.stories.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.stories.tsx @@ -7,411 +7,455 @@ /* eslint-disable no-console */ -import React from 'react'; +import React, { useState } from 'react'; import { I18nProvider } from '@kbn/i18n-react'; import { Story } from '@storybook/react'; +import { DataStream, Integration } from '../../../common/data_streams'; import { DataStreamSelector, DataStreamSelectorProps } from './data_stream_selector'; export default { component: DataStreamSelector, title: 'observability_logs/DataStreamSelector', decorators: [(wrappedStory) => {wrappedStory()}], + argTypes: { + dataStreamsError: { + options: [null, { message: 'Failed to fetch data streams' }], + control: { type: 'radio' }, + }, + }, }; -const DataStreamSelectorTemplate: Story = (args) => ( - -); +const DataStreamSelectorTemplate: Story = (args) => { + const [title, setTitle] = useState(mockIntegrations[0].dataStreams[0].title as string); + const [search, setSearch] = useState(args.search); + const [integrations, setIntegrations] = useState(() => mockIntegrations.slice(0, 10)); + + const onIntegrationsLoadMore = () => { + if (integrations.length < mockIntegrations.length) { + setIntegrations((prev) => prev.concat(mockIntegrations.slice(prev.length, prev.length + 10))); + } + }; + + const onStreamSelected = (stream: DataStream) => { + setTitle(stream.title || stream.name); + }; + + const filteredIntegrations = integrations.filter((integration) => + integration.name.includes(search.name as string) + ); + + const sortedIntegrations = + search.sortOrder === 'asc' ? filteredIntegrations : filteredIntegrations.reverse(); + + const filteredDataStreams = mockDataStreams.filter((dataStream) => + dataStream.name.includes(search.name as string) + ); + + const sortedDataStreams = + search.sortOrder === 'asc' ? filteredDataStreams : filteredDataStreams.reverse(); + + return ( + + ); +}; export const Basic = DataStreamSelectorTemplate.bind({}); Basic.args = { - title: 'Current stream name', - dataStreams: [ - { name: 'logs-*' }, - { name: 'system-logs-*' }, - { name: 'nginx-logs-*' }, - { name: 'apache-logs-*' }, - { name: 'security-logs-*' }, - { name: 'error-logs-*' }, - { name: 'access-logs-*' }, - { name: 'firewall-logs-*' }, - { name: 'application-logs-*' }, - { name: 'debug-logs-*' }, - { name: 'transaction-logs-*' }, - { name: 'audit-logs-*' }, - { name: 'server-logs-*' }, - { name: 'database-logs-*' }, - { name: 'event-logs-*' }, - { name: 'auth-logs-*' }, - { name: 'billing-logs-*' }, - { name: 'network-logs-*' }, - { name: 'performance-logs-*' }, - { name: 'email-logs-*' }, - { name: 'job-logs-*' }, - { name: 'task-logs-*' }, - { name: 'user-logs-*' }, - { name: 'request-logs-*' }, - { name: 'payment-logs-*' }, - { name: 'inventory-logs-*' }, - { name: 'debugging-logs-*' }, - { name: 'scheduler-logs-*' }, - { name: 'diagnostic-logs-*' }, - { name: 'cluster-logs-*' }, - { name: 'service-logs-*' }, - { name: 'framework-logs-*' }, - { name: 'api-logs-*' }, - { name: 'load-balancer-logs-*' }, - { name: 'reporting-logs-*' }, - { name: 'backend-logs-*' }, - { name: 'frontend-logs-*' }, - { name: 'chat-logs-*' }, - { name: 'error-tracking-logs-*' }, - { name: 'payment-gateway-logs-*' }, - { name: 'auth-service-logs-*' }, - { name: 'billing-service-logs-*' }, - { name: 'database-service-logs-*' }, - { name: 'api-gateway-logs-*' }, - { name: 'event-service-logs-*' }, - { name: 'notification-service-logs-*' }, - { name: 'search-service-logs-*' }, - { name: 'logging-service-logs-*' }, - { name: 'performance-service-logs-*' }, - { name: 'load-testing-logs-*' }, - { name: 'mobile-app-logs-*' }, - { name: 'web-app-logs-*' }, - { name: 'stream-processing-logs-*' }, - { name: 'batch-processing-logs-*' }, - { name: 'cloud-service-logs-*' }, - { name: 'container-logs-*' }, - { name: 'serverless-logs-*' }, - { name: 'server-administration-logs-*' }, - { name: 'application-deployment-logs-*' }, - { name: 'webserver-logs-*' }, - { name: 'payment-processor-logs-*' }, - { name: 'inventory-service-logs-*' }, - { name: 'data-pipeline-logs-*' }, - { name: 'frontend-service-logs-*' }, - { name: 'backend-service-logs-*' }, - { name: 'resource-monitoring-logs-*' }, - { name: 'logging-aggregation-logs-*' }, - { name: 'container-orchestration-logs-*' }, - { name: 'security-audit-logs-*' }, - { name: 'api-management-logs-*' }, - { name: 'service-mesh-logs-*' }, - { name: 'data-processing-logs-*' }, - { name: 'data-science-logs-*' }, - { name: 'machine-learning-logs-*' }, - { name: 'experimentation-logs-*' }, - { name: 'data-visualization-logs-*' }, - { name: 'data-cleaning-logs-*' }, - { name: 'data-transformation-logs-*' }, - { name: 'data-analysis-logs-*' }, - { name: 'data-storage-logs-*' }, - { name: 'data-retrieval-logs-*' }, - { name: 'data-warehousing-logs-*' }, - { name: 'data-modeling-logs-*' }, - { name: 'data-integration-logs-*' }, - { name: 'data-quality-logs-*' }, - { name: 'data-security-logs-*' }, - { name: 'data-encryption-logs-*' }, - { name: 'data-governance-logs-*' }, - { name: 'data-compliance-logs-*' }, - { name: 'data-privacy-logs-*' }, - { name: 'data-auditing-logs-*' }, - { name: 'data-discovery-logs-*' }, - { name: 'data-protection-logs-*' }, - { name: 'data-archiving-logs-*' }, - { name: 'data-backup-logs-*' }, - { name: 'data-recovery-logs-*' }, - { name: 'data-replication-logs-*' }, - { name: 'data-synchronization-logs-*' }, - { name: 'data-migration-logs-*' }, - { name: 'data-load-balancing-logs-*' }, - { name: 'data-scaling-logs-*' }, - ], dataStreamsError: null, - integrations: [ - { - name: 'system', - version: '1.25.2', - status: 'installed', - dataStreams: [ - { - name: 'System metrics stream', - title: 'system-metrics-*', - }, - { - name: 'System logs stream', - title: 'system-logs-*', - }, - ], - }, - { - name: 'kubernetes', - version: '1.35.0', - status: 'installed', - dataStreams: [ - { - name: 'Kubernetes metrics stream', - title: 'k8s-metrics-*', - }, - { - name: 'Kubernetes logs stream', - title: 'k8s-logs-*', - }, - ], - }, - { - name: 'mysql', - version: '1.11.0', - status: 'installed', - dataStreams: [ - { - name: 'MySQL metrics stream', - title: 'mysql-metrics-*', - }, - { - name: 'MySQL slow logs stream', - title: 'mysql-slow-logs-*', - }, - { - name: 'MySQL error logs stream', - title: 'mysql-error-logs-*', - }, - ], - }, - { - name: 'apache', - version: '1.12.0', - status: 'installed', - dataStreams: [ - { - name: 'Apache metrics stream', - title: 'apache-metrics-*', - }, - { - name: 'Apache logs stream', - title: 'apache-logs-*', - }, - { - name: 'Apache error logs stream', - title: 'apache-error-logs-*', - }, - ], - }, - { - name: 'nginx', - version: '1.11.1', - status: 'installed', - dataStreams: [ - { - name: 'Nginx metrics stream', - title: 'nginx-metrics-*', - }, - { - name: 'Nginx access logs stream', - title: 'nginx-access-logs-*', - }, - ], - }, - { - name: 'postgresql', - version: '1.13.0', - status: 'installed', - dataStreams: [ - { - name: 'PostgreSQL metrics stream', - title: 'postgresql-metrics-*', - }, - { - name: 'PostgreSQL slow query logs stream', - title: 'postgresql-slow-query-logs-*', - }, - { - name: 'PostgreSQL error logs stream', - title: 'postgresql-error-logs-*', - }, - ], - }, - { - name: 'rabbitmq', - version: '1.8.8', - status: 'installed', - dataStreams: [ - { - name: 'RabbitMQ metrics stream', - title: 'rabbitmq-metrics-*', - }, - { - name: 'RabbitMQ queues stream', - title: 'rabbitmq-queues-*', - }, - { - name: 'RabbitMQ error logs stream', - title: 'rabbitmq-error-logs-*', - }, - ], - }, - { - name: 'redis', - version: '1.9.2', - status: 'installed', - dataStreams: [ - { - name: 'Redis metrics stream', - title: 'redis-metrics-*', - }, - { - name: 'Redis slow logs stream', - title: 'redis-slow-logs-*', - }, - ], - }, - { - name: 'elasticsearch', - version: '1.5.0', - status: 'installed', - dataStreams: [ - { - name: 'Elasticsearch metrics stream', - title: 'elasticsearch-metrics-*', - }, - { - name: 'Elasticsearch indices stream', - title: 'elasticsearch-indices-*', - }, - ], - }, - { - name: 'mongodb', - version: '1.9.3', - status: 'installed', - dataStreams: [ - { - name: 'MongoDB metrics stream', - title: 'mongodb-metrics-*', - }, - { - name: 'MongoDB slow query logs stream', - title: 'mongodb-slow-query-logs-*', - }, - ], - }, - { - name: 'prometheus', - version: '1.3.2', - status: 'installed', - dataStreams: [ - { - name: 'Prometheus metrics stream', - title: 'prometheus-metrics-*', - }, - ], - }, - { - name: 'haproxy', - version: '1.5.1', - status: 'installed', - dataStreams: [ - { - name: 'HAProxy metrics stream', - title: 'haproxy-metrics-*', - }, - { - name: 'HAProxy logs stream', - title: 'haproxy-logs-*', - }, - ], - }, - { - name: 'atlassian_jira', - version: '1.10.0', - status: 'installed', - dataStreams: [ - { name: 'Atlassian metrics stream', title: 'metrics-*' }, - { name: 'Atlassian secondary', title: 'metrics-*' }, - ], - }, - { - name: 'atlassian_confluence', - version: '1.10.0', - status: 'installed', - dataStreams: [ - { name: 'Atlassian metrics stream', title: 'metrics-*' }, - { name: 'Atlassian secondary', title: 'metrics-*' }, - ], - }, - { - name: 'atlassian_bitbucket', - version: '1.9.0', - status: 'installed', - dataStreams: [ - { name: 'Atlassian metrics stream', title: 'metrics-*' }, - { name: 'Atlassian secondary', title: 'metrics-*' }, - ], - }, - { - name: 'docker', - version: '2.4.3', - status: 'installed', - dataStreams: [ - { name: 'Docker container logs', title: 'docker-*' }, - { name: 'Docker daemon logs', title: 'docker-daemon-*' }, - ], - }, - { - name: 'aws', - version: '1.36.3', - status: 'installed', - dataStreams: [ - { name: 'AWS S3 object access logs', title: 'aws-s3-access-' }, - { name: 'AWS S3 bucket access logs', title: 'aws-s3-bucket-access-' }, - ], - }, - { - name: 'cassandra', - version: '1.6.0', - status: 'installed', - dataStreams: [ - { name: 'Cassandra server logs', title: 'cassandra-' }, - { name: 'Cassandra slow queries', title: 'cassandra-slow-' }, - { name: 'Cassandra errors', title: 'cassandra-errors-' }, - ], - }, - { - name: 'nginx_ingress_controller', - version: '1.7.1', - status: 'installed', - dataStreams: [{ name: 'Nginx ingress logs', title: 'nginx-ingress-' }], - }, - { - name: 'gcp', - version: '2.20.1', - status: 'installed', - dataStreams: [{ name: 'GCP Stackdriver logs', title: 'gcp-stackdriver-*' }], - }, - { - name: 'kafka', - version: '1.5.6', - status: 'installed', - dataStreams: [{ name: 'Kafka server logs', title: 'kafka-*' }], - }, - { - name: 'kibana', - version: '2.3.4', - status: 'installed', - dataStreams: [{ name: 'Kibana server logs', title: 'kibana-*' }], - }, - ], isLoadingIntegrations: false, isLoadingStreams: false, - onIntegrationsLoadMore: () => console.log('Loading more integrations...'), - onSearch: (params) => console.log('Search integrations by: ', params), - onStreamSelected: (stream) => console.log('Create ad hoc view for stream: ', stream), onStreamsEntryClick: () => console.log('Load uncategorized streams...'), - onStreamsReload: () => console.log('Reloading streams...'), - onStreamsSearch: (params) => console.log('Search streams by: ', params), + onStreamsReload: () => alert('Reloading streams...'), search: { sortOrder: 'asc', name: '', }, }; + +const mockIntegrations: Integration[] = [ + { + name: 'system', + version: '1.25.2', + status: 'installed', + dataStreams: [ + { + title: 'System metrics stream', + name: 'system-metrics-*', + }, + { + title: 'System logs stream', + name: 'system-logs-*', + }, + ], + }, + { + name: 'kubernetes', + version: '1.35.0', + status: 'installed', + dataStreams: [ + { + title: 'Kubernetes metrics stream', + name: 'k8s-metrics-*', + }, + { + title: 'Kubernetes logs stream', + name: 'k8s-logs-*', + }, + ], + }, + { + name: 'mysql', + version: '1.11.0', + status: 'installed', + dataStreams: [ + { + title: 'MySQL metrics stream', + name: 'mysql-metrics-*', + }, + { + title: 'MySQL slow logs stream', + name: 'mysql-slow-logs-*', + }, + { + title: 'MySQL error logs stream', + name: 'mysql-error-logs-*', + }, + ], + }, + { + name: 'apache', + version: '1.12.0', + status: 'installed', + dataStreams: [ + { + title: 'Apache metrics stream', + name: 'apache-metrics-*', + }, + { + title: 'Apache logs stream', + name: 'apache-logs-*', + }, + { + title: 'Apache error logs stream', + name: 'apache-error-logs-*', + }, + ], + }, + { + name: 'nginx', + version: '1.11.1', + status: 'installed', + dataStreams: [ + { + title: 'Nginx metrics stream', + name: 'nginx-metrics-*', + }, + { + title: 'Nginx access logs stream', + name: 'nginx-access-logs-*', + }, + ], + }, + { + name: 'postgresql', + version: '1.13.0', + status: 'installed', + dataStreams: [ + { + title: 'PostgreSQL metrics stream', + name: 'postgresql-metrics-*', + }, + { + title: 'PostgreSQL slow query logs stream', + name: 'postgresql-slow-query-logs-*', + }, + { + title: 'PostgreSQL error logs stream', + name: 'postgresql-error-logs-*', + }, + ], + }, + { + name: 'rabbitmq', + version: '1.8.8', + status: 'installed', + dataStreams: [ + { + title: 'RabbitMQ metrics stream', + name: 'rabbitmq-metrics-*', + }, + { + title: 'RabbitMQ queues stream', + name: 'rabbitmq-queues-*', + }, + { + title: 'RabbitMQ error logs stream', + name: 'rabbitmq-error-logs-*', + }, + ], + }, + { + name: 'redis', + version: '1.9.2', + status: 'installed', + dataStreams: [ + { + title: 'Redis metrics stream', + name: 'redis-metrics-*', + }, + { + title: 'Redis slow logs stream', + name: 'redis-slow-logs-*', + }, + ], + }, + { + name: 'elasticsearch', + version: '1.5.0', + status: 'installed', + dataStreams: [ + { + title: 'Elasticsearch metrics stream', + name: 'elasticsearch-metrics-*', + }, + { + title: 'Elasticsearch indices stream', + name: 'elasticsearch-indices-*', + }, + ], + }, + { + name: 'mongodb', + version: '1.9.3', + status: 'installed', + dataStreams: [ + { + title: 'MongoDB metrics stream', + name: 'mongodb-metrics-*', + }, + { + title: 'MongoDB slow query logs stream', + name: 'mongodb-slow-query-logs-*', + }, + ], + }, + { + name: 'prometheus', + version: '1.3.2', + status: 'installed', + dataStreams: [ + { + title: 'Prometheus metrics stream', + name: 'prometheus-metrics-*', + }, + ], + }, + { + name: 'haproxy', + version: '1.5.1', + status: 'installed', + dataStreams: [ + { + title: 'HAProxy metrics stream', + name: 'haproxy-metrics-*', + }, + { + title: 'HAProxy logs stream', + name: 'haproxy-logs-*', + }, + ], + }, + { + name: 'atlassian_jira', + version: '1.10.0', + status: 'installed', + dataStreams: [ + { title: 'Atlassian metrics stream', name: 'metrics-*' }, + { title: 'Atlassian secondary', name: 'metrics-*' }, + ], + }, + { + name: 'atlassian_confluence', + version: '1.10.0', + status: 'installed', + dataStreams: [ + { title: 'Atlassian metrics stream', name: 'metrics-*' }, + { title: 'Atlassian secondary', name: 'metrics-*' }, + ], + }, + { + name: 'atlassian_bitbucket', + version: '1.9.0', + status: 'installed', + dataStreams: [ + { title: 'Atlassian metrics stream', name: 'metrics-*' }, + { title: 'Atlassian secondary', name: 'metrics-*' }, + ], + }, + { + name: 'docker', + version: '2.4.3', + status: 'installed', + dataStreams: [ + { title: 'Docker container logs', name: 'docker-*' }, + { title: 'Docker daemon logs', name: 'docker-daemon-*' }, + ], + }, + { + name: 'aws', + version: '1.36.3', + status: 'installed', + dataStreams: [ + { title: 'AWS S3 object access logs', name: 'aws-s3-access-' }, + { title: 'AWS S3 bucket access logs', name: 'aws-s3-bucket-access-' }, + ], + }, + { + name: 'cassandra', + version: '1.6.0', + status: 'installed', + dataStreams: [ + { title: 'Cassandra server logs', name: 'cassandra-' }, + { title: 'Cassandra slow queries', name: 'cassandra-slow-' }, + { title: 'Cassandra errors', name: 'cassandra-errors-' }, + ], + }, + { + name: 'nginx_ingress_controller', + version: '1.7.1', + status: 'installed', + dataStreams: [{ title: 'Nginx ingress logs', name: 'nginx-ingress-' }], + }, + { + name: 'gcp', + version: '2.20.1', + status: 'installed', + dataStreams: [{ title: 'GCP Stackdriver logs', name: 'gcp-stackdriver-*' }], + }, + { + name: 'kafka', + version: '1.5.6', + status: 'installed', + dataStreams: [{ title: 'Kafka server logs', name: 'kafka-*' }], + }, + { + name: 'kibana', + version: '2.3.4', + status: 'installed', + dataStreams: [{ title: 'Kibana server logs', name: 'kibana-*' }], + }, +]; + +const mockDataStreams: DataStream[] = [ + { name: 'logs-*' }, + { name: 'system-logs-*' }, + { name: 'nginx-logs-*' }, + { name: 'apache-logs-*' }, + { name: 'security-logs-*' }, + { name: 'error-logs-*' }, + { name: 'access-logs-*' }, + { name: 'firewall-logs-*' }, + { name: 'application-logs-*' }, + { name: 'debug-logs-*' }, + { name: 'transaction-logs-*' }, + { name: 'audit-logs-*' }, + { name: 'server-logs-*' }, + { name: 'database-logs-*' }, + { name: 'event-logs-*' }, + { name: 'auth-logs-*' }, + { name: 'billing-logs-*' }, + { name: 'network-logs-*' }, + { name: 'performance-logs-*' }, + { name: 'email-logs-*' }, + { name: 'job-logs-*' }, + { name: 'task-logs-*' }, + { name: 'user-logs-*' }, + { name: 'request-logs-*' }, + { name: 'payment-logs-*' }, + { name: 'inventory-logs-*' }, + { name: 'debugging-logs-*' }, + { name: 'scheduler-logs-*' }, + { name: 'diagnostic-logs-*' }, + { name: 'cluster-logs-*' }, + { name: 'service-logs-*' }, + { name: 'framework-logs-*' }, + { name: 'api-logs-*' }, + { name: 'load-balancer-logs-*' }, + { name: 'reporting-logs-*' }, + { name: 'backend-logs-*' }, + { name: 'frontend-logs-*' }, + { name: 'chat-logs-*' }, + { name: 'error-tracking-logs-*' }, + { name: 'payment-gateway-logs-*' }, + { name: 'auth-service-logs-*' }, + { name: 'billing-service-logs-*' }, + { name: 'database-service-logs-*' }, + { name: 'api-gateway-logs-*' }, + { name: 'event-service-logs-*' }, + { name: 'notification-service-logs-*' }, + { name: 'search-service-logs-*' }, + { name: 'logging-service-logs-*' }, + { name: 'performance-service-logs-*' }, + { name: 'load-testing-logs-*' }, + { name: 'mobile-app-logs-*' }, + { name: 'web-app-logs-*' }, + { name: 'stream-processing-logs-*' }, + { name: 'batch-processing-logs-*' }, + { name: 'cloud-service-logs-*' }, + { name: 'container-logs-*' }, + { name: 'serverless-logs-*' }, + { name: 'server-administration-logs-*' }, + { name: 'application-deployment-logs-*' }, + { name: 'webserver-logs-*' }, + { name: 'payment-processor-logs-*' }, + { name: 'inventory-service-logs-*' }, + { name: 'data-pipeline-logs-*' }, + { name: 'frontend-service-logs-*' }, + { name: 'backend-service-logs-*' }, + { name: 'resource-monitoring-logs-*' }, + { name: 'logging-aggregation-logs-*' }, + { name: 'container-orchestration-logs-*' }, + { name: 'security-audit-logs-*' }, + { name: 'api-management-logs-*' }, + { name: 'service-mesh-logs-*' }, + { name: 'data-processing-logs-*' }, + { name: 'data-science-logs-*' }, + { name: 'machine-learning-logs-*' }, + { name: 'experimentation-logs-*' }, + { name: 'data-visualization-logs-*' }, + { name: 'data-cleaning-logs-*' }, + { name: 'data-transformation-logs-*' }, + { name: 'data-analysis-logs-*' }, + { name: 'data-storage-logs-*' }, + { name: 'data-retrieval-logs-*' }, + { name: 'data-warehousing-logs-*' }, + { name: 'data-modeling-logs-*' }, + { name: 'data-integration-logs-*' }, + { name: 'data-quality-logs-*' }, + { name: 'data-security-logs-*' }, + { name: 'data-encryption-logs-*' }, + { name: 'data-governance-logs-*' }, + { name: 'data-compliance-logs-*' }, + { name: 'data-privacy-logs-*' }, + { name: 'data-auditing-logs-*' }, + { name: 'data-discovery-logs-*' }, + { name: 'data-protection-logs-*' }, + { name: 'data-archiving-logs-*' }, + { name: 'data-backup-logs-*' }, + { name: 'data-recovery-logs-*' }, + { name: 'data-replication-logs-*' }, + { name: 'data-synchronization-logs-*' }, + { name: 'data-migration-logs-*' }, + { name: 'data-load-balancing-logs-*' }, + { name: 'data-scaling-logs-*' }, +]; diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx index 812e30259ddf3..2e83bfa902b24 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx @@ -68,11 +68,13 @@ export interface SearchParams { integrationId?: CurrentPanelId; } +type PartialSearchParams = Pick; + export type SearchHandler = (params: SearchParams) => void; export interface DataStreamSelectorProps { title: string; - search: Pick; + search: PartialSearchParams; integrations: Integration[] | null; dataStreams: DataStream[] | null; dataStreamsError?: Error | null; @@ -107,7 +109,7 @@ export function DataStreamSelector({ const isMobile = useIsWithinBreakpoints(['xs', 's']); const [isPopoverOpen, { off: closePopover, toggle: togglePopover }] = useBoolean(false); - const [loadMoreIntersection, setRef] = useIntersectionRef(); + const [loadMoreIntersection, setSpyRef] = useIntersectionRef(); useEffect(() => { if (loadMoreIntersection?.isIntersecting) { @@ -144,16 +146,17 @@ export function DataStreamSelector({ } const { items, panels } = buildIntegrationsTree({ - list: integrations, + integrations, onStreamSelected: handleStreamSelection, - spyRef: setRef, }); + setIntegrationListSpy(items, setSpyRef); + return { items: [dataStreamsItem, ...items], panels, }; - }, [integrations, handleStreamSelection, onStreamsEntryClick, setRef]); + }, [integrations, handleStreamSelection, onStreamsEntryClick, setSpyRef]); const panels = [ { @@ -185,7 +188,7 @@ export function DataStreamSelector({ ); - const handleSearch = (params: SearchParams) => { + const handleSearch = (params: PartialSearchParams) => { const strategy = getSearchStrategy(currentPanel); return onSearch({ ...params, @@ -194,9 +197,6 @@ export function DataStreamSelector({ }); }; const searchValue = search; - // const handleSearch = - // currentPanel === INTEGRATION_PANEL_ID ? onSearch : handleIntegrationStreamsSearch; - // const searchValue = currentPanel === INTEGRATION_PANEL_ID ? search : localSearch; return ( { interface SearchControlsProps { isLoading: boolean; - search: SearchParams; - onSearch: SearchHandler; + search: PartialSearchParams; + onSearch: (params: PartialSearchParams) => void; } const SearchControls = ({ search, onSearch, isLoading }: SearchControlsProps) => { @@ -285,14 +285,13 @@ interface IntegrationsTree { } interface IntegrationsTreeParams { - list: Integration[]; + integrations: Integration[]; onStreamSelected: DataStreamSelectionHandler; - spyRef: RefCallback; } -const buildIntegrationsTree = ({ list, onStreamSelected, spyRef }: IntegrationsTreeParams) => { - return list.reduce( - (res: IntegrationsTree, integration, pos) => { +const buildIntegrationsTree = ({ integrations, onStreamSelected }: IntegrationsTreeParams) => { + return integrations.reduce( + (res: IntegrationsTree, integration) => { const entryId: CurrentPanelId = getIntegrationId(integration); const { name, version, dataStreams } = integration; @@ -300,7 +299,6 @@ const buildIntegrationsTree = ({ list, onStreamSelected, spyRef }: IntegrationsT name, icon: , panel: entryId, - buttonRef: pos === list.length - 1 ? spyRef : undefined, }); res.panels.push({ @@ -308,9 +306,9 @@ const buildIntegrationsTree = ({ list, onStreamSelected, spyRef }: IntegrationsT title: name, width: DATA_VIEW_POPOVER_CONTENT_WIDTH, items: dataStreams.map((stream) => ({ - name: stream.name, + name: stream.title, onClick: () => - onStreamSelected({ title: stream.title, name: `[${name}] ${stream.name}` }), + onStreamSelected({ title: `[${name}] ${stream.title}`, name: stream.name }), })), }); @@ -320,6 +318,16 @@ const buildIntegrationsTree = ({ list, onStreamSelected, spyRef }: IntegrationsT ); }; +const setIntegrationListSpy = ( + items: EuiContextMenuPanelItemDescriptor[], + spyRef: RefCallback +) => { + const lastItem = items.at(-1); + if (lastItem) { + lastItem.buttonRef = spyRef; + } +}; + const getSearchStrategy = (panelId: CurrentPanelId) => { if (panelId === UNCATEGORIZED_STREAMS_PANEL_ID) return SearchStrategy.DATA_STREAMS; if (panelId === INTEGRATION_PANEL_ID) return SearchStrategy.INTEGRATIONS; diff --git a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx index e6ad6d177b9b0..5acccd28963e2 100644 --- a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx @@ -46,7 +46,12 @@ export const CustomDataStreamSelector = withProviders(({ stateContainer }) => { } = useDataStreamsContext(); const handleStreamSelection: DataStreamSelectionHandler = (dataStream) => { - return stateContainer.actions.onCreateDefaultAdHocDataView(dataStream); + return stateContainer.actions.onCreateDefaultAdHocDataView({ + // Invert the property because the API returns the index pattern as `name` + // and a readable name as `title` + name: dataStream.title, + title: dataStream.name, + }); }; const handleSearch: SearchHandler = (params) => { @@ -61,6 +66,7 @@ export const CustomDataStreamSelector = withProviders(({ stateContainer }) => { return ( { - const id = getIntegrationId(integration) + const id = getIntegrationId(integration); if (id !== integrationId) { return integration; @@ -277,5 +277,5 @@ const searchIntegrationStreams = ( .sortBy('name', sortOrder) .build(), }; - } + }); }; From f526319cf84fdd350c5b078665d080fc0d8a650e Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Tue, 23 May 2023 17:30:16 +0200 Subject: [PATCH 103/122] refactor(observability_logs): reorganize code --- .../data_stream_selector.stories.tsx | 301 +++++++++--------- .../data_stream_selector.test.ts | 5 + .../data_stream_selector.tsx | 237 ++------------ .../data_stream_selector.utils.ts | 12 - .../components/data_stream_selector/index.ts | 1 + .../data_streams_list.tsx | 12 +- .../sub_components/data_streams_popover.tsx | 44 +++ .../data_streams_skeleton.tsx} | 2 +- .../sub_components/search_controls.tsx | 114 +++++++ .../components/data_stream_selector/types.ts | 54 ++++ .../components/data_stream_selector/utils.tsx | 80 +++++ .../custom_data_stream_selector.tsx | 35 +- .../public/hooks/use_data_streams.ts | 5 +- .../public/hooks/use_integrations.ts | 10 +- .../public/hooks/use_intersection_ref.ts | 18 +- .../public/hooks/use_kibana.tsx | 45 --- .../observability_logs/public/plugin.tsx | 4 +- .../data_streams/src/defaults.ts | 1 + .../data_streams/src/state_machine.ts | 21 +- .../state_machines/data_streams/src/types.ts | 3 +- .../integrations/src/defaults.ts | 1 + .../integrations/src/state_machine.ts | 57 ++-- .../state_machines/integrations/src/types.ts | 10 +- 23 files changed, 557 insertions(+), 515 deletions(-) delete mode 100644 x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.utils.ts rename x-pack/plugins/observability_logs/public/components/data_stream_selector/{ => sub_components}/data_streams_list.tsx (89%) create mode 100644 x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/data_streams_popover.tsx rename x-pack/plugins/observability_logs/public/components/data_stream_selector/{data_stream_skeleton.tsx => sub_components/data_streams_skeleton.tsx} (90%) create mode 100644 x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/search_controls.tsx create mode 100644 x-pack/plugins/observability_logs/public/components/data_stream_selector/types.ts create mode 100644 x-pack/plugins/observability_logs/public/components/data_stream_selector/utils.tsx delete mode 100644 x-pack/plugins/observability_logs/public/hooks/use_kibana.tsx diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.stories.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.stories.tsx index edd1197d59247..ca98f87f403e6 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.stories.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.stories.tsx @@ -10,8 +10,10 @@ import React, { useState } from 'react'; import { I18nProvider } from '@kbn/i18n-react'; import { Story } from '@storybook/react'; +import { IndexPatternType } from '@kbn/io-ts-utils'; import { DataStream, Integration } from '../../../common/data_streams'; -import { DataStreamSelector, DataStreamSelectorProps } from './data_stream_selector'; +import { DataStreamSelector } from './data_stream_selector'; +import { DataStreamSelectorProps, SearchControlsParams } from './types'; export default { component: DataStreamSelector, @@ -27,7 +29,7 @@ export default { const DataStreamSelectorTemplate: Story = (args) => { const [title, setTitle] = useState(mockIntegrations[0].dataStreams[0].title as string); - const [search, setSearch] = useState(args.search); + const [search, setSearch] = useState({ sortOrder: 'asc', name: '' }); const [integrations, setIntegrations] = useState(() => mockIntegrations.slice(0, 10)); const onIntegrationsLoadMore = () => { @@ -60,7 +62,6 @@ const DataStreamSelectorTemplate: Story = (args) => { key={title} dataStreams={sortedDataStreams} integrations={sortedIntegrations} - search={search} title={title} onIntegrationsLoadMore={onIntegrationsLoadMore} onStreamSelected={onStreamSelected} @@ -76,10 +77,6 @@ Basic.args = { isLoadingStreams: false, onStreamsEntryClick: () => console.log('Load uncategorized streams...'), onStreamsReload: () => alert('Reloading streams...'), - search: { - sortOrder: 'asc', - name: '', - }, }; const mockIntegrations: Integration[] = [ @@ -90,11 +87,11 @@ const mockIntegrations: Integration[] = [ dataStreams: [ { title: 'System metrics stream', - name: 'system-metrics-*', + name: 'system-metrics-*' as IndexPatternType, }, { title: 'System logs stream', - name: 'system-logs-*', + name: 'system-logs-*' as IndexPatternType, }, ], }, @@ -105,11 +102,11 @@ const mockIntegrations: Integration[] = [ dataStreams: [ { title: 'Kubernetes metrics stream', - name: 'k8s-metrics-*', + name: 'k8s-metrics-*' as IndexPatternType, }, { title: 'Kubernetes logs stream', - name: 'k8s-logs-*', + name: 'k8s-logs-*' as IndexPatternType, }, ], }, @@ -120,15 +117,15 @@ const mockIntegrations: Integration[] = [ dataStreams: [ { title: 'MySQL metrics stream', - name: 'mysql-metrics-*', + name: 'mysql-metrics-*' as IndexPatternType, }, { title: 'MySQL slow logs stream', - name: 'mysql-slow-logs-*', + name: 'mysql-slow-logs-*' as IndexPatternType, }, { title: 'MySQL error logs stream', - name: 'mysql-error-logs-*', + name: 'mysql-error-logs-*' as IndexPatternType, }, ], }, @@ -139,15 +136,15 @@ const mockIntegrations: Integration[] = [ dataStreams: [ { title: 'Apache metrics stream', - name: 'apache-metrics-*', + name: 'apache-metrics-*' as IndexPatternType, }, { title: 'Apache logs stream', - name: 'apache-logs-*', + name: 'apache-logs-*' as IndexPatternType, }, { title: 'Apache error logs stream', - name: 'apache-error-logs-*', + name: 'apache-error-logs-*' as IndexPatternType, }, ], }, @@ -158,11 +155,11 @@ const mockIntegrations: Integration[] = [ dataStreams: [ { title: 'Nginx metrics stream', - name: 'nginx-metrics-*', + name: 'nginx-metrics-*' as IndexPatternType, }, { title: 'Nginx access logs stream', - name: 'nginx-access-logs-*', + name: 'nginx-access-logs-*' as IndexPatternType, }, ], }, @@ -173,15 +170,15 @@ const mockIntegrations: Integration[] = [ dataStreams: [ { title: 'PostgreSQL metrics stream', - name: 'postgresql-metrics-*', + name: 'postgresql-metrics-*' as IndexPatternType, }, { title: 'PostgreSQL slow query logs stream', - name: 'postgresql-slow-query-logs-*', + name: 'postgresql-slow-query-logs-*' as IndexPatternType, }, { title: 'PostgreSQL error logs stream', - name: 'postgresql-error-logs-*', + name: 'postgresql-error-logs-*' as IndexPatternType, }, ], }, @@ -192,15 +189,15 @@ const mockIntegrations: Integration[] = [ dataStreams: [ { title: 'RabbitMQ metrics stream', - name: 'rabbitmq-metrics-*', + name: 'rabbitmq-metrics-*' as IndexPatternType, }, { title: 'RabbitMQ queues stream', - name: 'rabbitmq-queues-*', + name: 'rabbitmq-queues-*' as IndexPatternType, }, { title: 'RabbitMQ error logs stream', - name: 'rabbitmq-error-logs-*', + name: 'rabbitmq-error-logs-*' as IndexPatternType, }, ], }, @@ -211,11 +208,11 @@ const mockIntegrations: Integration[] = [ dataStreams: [ { title: 'Redis metrics stream', - name: 'redis-metrics-*', + name: 'redis-metrics-*' as IndexPatternType, }, { title: 'Redis slow logs stream', - name: 'redis-slow-logs-*', + name: 'redis-slow-logs-*' as IndexPatternType, }, ], }, @@ -226,11 +223,11 @@ const mockIntegrations: Integration[] = [ dataStreams: [ { title: 'Elasticsearch metrics stream', - name: 'elasticsearch-metrics-*', + name: 'elasticsearch-metrics-*' as IndexPatternType, }, { title: 'Elasticsearch indices stream', - name: 'elasticsearch-indices-*', + name: 'elasticsearch-indices-*' as IndexPatternType, }, ], }, @@ -241,11 +238,11 @@ const mockIntegrations: Integration[] = [ dataStreams: [ { title: 'MongoDB metrics stream', - name: 'mongodb-metrics-*', + name: 'mongodb-metrics-*' as IndexPatternType, }, { title: 'MongoDB slow query logs stream', - name: 'mongodb-slow-query-logs-*', + name: 'mongodb-slow-query-logs-*' as IndexPatternType, }, ], }, @@ -256,7 +253,7 @@ const mockIntegrations: Integration[] = [ dataStreams: [ { title: 'Prometheus metrics stream', - name: 'prometheus-metrics-*', + name: 'prometheus-metrics-*' as IndexPatternType, }, ], }, @@ -267,11 +264,11 @@ const mockIntegrations: Integration[] = [ dataStreams: [ { title: 'HAProxy metrics stream', - name: 'haproxy-metrics-*', + name: 'haproxy-metrics-*' as IndexPatternType, }, { title: 'HAProxy logs stream', - name: 'haproxy-logs-*', + name: 'haproxy-logs-*' as IndexPatternType, }, ], }, @@ -280,8 +277,8 @@ const mockIntegrations: Integration[] = [ version: '1.10.0', status: 'installed', dataStreams: [ - { title: 'Atlassian metrics stream', name: 'metrics-*' }, - { title: 'Atlassian secondary', name: 'metrics-*' }, + { title: 'Atlassian metrics stream', name: 'metrics-*' as IndexPatternType }, + { title: 'Atlassian secondary', name: 'metrics-*' as IndexPatternType }, ], }, { @@ -289,8 +286,8 @@ const mockIntegrations: Integration[] = [ version: '1.10.0', status: 'installed', dataStreams: [ - { title: 'Atlassian metrics stream', name: 'metrics-*' }, - { title: 'Atlassian secondary', name: 'metrics-*' }, + { title: 'Atlassian metrics stream', name: 'metrics-*' as IndexPatternType }, + { title: 'Atlassian secondary', name: 'metrics-*' as IndexPatternType }, ], }, { @@ -298,8 +295,8 @@ const mockIntegrations: Integration[] = [ version: '1.9.0', status: 'installed', dataStreams: [ - { title: 'Atlassian metrics stream', name: 'metrics-*' }, - { title: 'Atlassian secondary', name: 'metrics-*' }, + { title: 'Atlassian metrics stream', name: 'metrics-*' as IndexPatternType }, + { title: 'Atlassian secondary', name: 'metrics-*' as IndexPatternType }, ], }, { @@ -307,8 +304,8 @@ const mockIntegrations: Integration[] = [ version: '2.4.3', status: 'installed', dataStreams: [ - { title: 'Docker container logs', name: 'docker-*' }, - { title: 'Docker daemon logs', name: 'docker-daemon-*' }, + { title: 'Docker container logs', name: 'docker-*' as IndexPatternType }, + { title: 'Docker daemon logs', name: 'docker-daemon-*' as IndexPatternType }, ], }, { @@ -316,8 +313,8 @@ const mockIntegrations: Integration[] = [ version: '1.36.3', status: 'installed', dataStreams: [ - { title: 'AWS S3 object access logs', name: 'aws-s3-access-' }, - { title: 'AWS S3 bucket access logs', name: 'aws-s3-bucket-access-' }, + { title: 'AWS S3 object access logs', name: 'aws-s3-access-' as IndexPatternType }, + { title: 'AWS S3 bucket access logs', name: 'aws-s3-bucket-access-' as IndexPatternType }, ], }, { @@ -325,137 +322,137 @@ const mockIntegrations: Integration[] = [ version: '1.6.0', status: 'installed', dataStreams: [ - { title: 'Cassandra server logs', name: 'cassandra-' }, - { title: 'Cassandra slow queries', name: 'cassandra-slow-' }, - { title: 'Cassandra errors', name: 'cassandra-errors-' }, + { title: 'Cassandra server logs', name: 'cassandra-' as IndexPatternType }, + { title: 'Cassandra slow queries', name: 'cassandra-slow-' as IndexPatternType }, + { title: 'Cassandra errors', name: 'cassandra-errors-' as IndexPatternType }, ], }, { name: 'nginx_ingress_controller', version: '1.7.1', status: 'installed', - dataStreams: [{ title: 'Nginx ingress logs', name: 'nginx-ingress-' }], + dataStreams: [{ title: 'Nginx ingress logs', name: 'nginx-ingress-' as IndexPatternType }], }, { name: 'gcp', version: '2.20.1', status: 'installed', - dataStreams: [{ title: 'GCP Stackdriver logs', name: 'gcp-stackdriver-*' }], + dataStreams: [{ title: 'GCP Stackdriver logs', name: 'gcp-stackdriver-*' as IndexPatternType }], }, { name: 'kafka', version: '1.5.6', status: 'installed', - dataStreams: [{ title: 'Kafka server logs', name: 'kafka-*' }], + dataStreams: [{ title: 'Kafka server logs', name: 'kafka-*' as IndexPatternType }], }, { name: 'kibana', version: '2.3.4', status: 'installed', - dataStreams: [{ title: 'Kibana server logs', name: 'kibana-*' }], + dataStreams: [{ title: 'Kibana server logs', name: 'kibana-*' as IndexPatternType }], }, ]; const mockDataStreams: DataStream[] = [ - { name: 'logs-*' }, - { name: 'system-logs-*' }, - { name: 'nginx-logs-*' }, - { name: 'apache-logs-*' }, - { name: 'security-logs-*' }, - { name: 'error-logs-*' }, - { name: 'access-logs-*' }, - { name: 'firewall-logs-*' }, - { name: 'application-logs-*' }, - { name: 'debug-logs-*' }, - { name: 'transaction-logs-*' }, - { name: 'audit-logs-*' }, - { name: 'server-logs-*' }, - { name: 'database-logs-*' }, - { name: 'event-logs-*' }, - { name: 'auth-logs-*' }, - { name: 'billing-logs-*' }, - { name: 'network-logs-*' }, - { name: 'performance-logs-*' }, - { name: 'email-logs-*' }, - { name: 'job-logs-*' }, - { name: 'task-logs-*' }, - { name: 'user-logs-*' }, - { name: 'request-logs-*' }, - { name: 'payment-logs-*' }, - { name: 'inventory-logs-*' }, - { name: 'debugging-logs-*' }, - { name: 'scheduler-logs-*' }, - { name: 'diagnostic-logs-*' }, - { name: 'cluster-logs-*' }, - { name: 'service-logs-*' }, - { name: 'framework-logs-*' }, - { name: 'api-logs-*' }, - { name: 'load-balancer-logs-*' }, - { name: 'reporting-logs-*' }, - { name: 'backend-logs-*' }, - { name: 'frontend-logs-*' }, - { name: 'chat-logs-*' }, - { name: 'error-tracking-logs-*' }, - { name: 'payment-gateway-logs-*' }, - { name: 'auth-service-logs-*' }, - { name: 'billing-service-logs-*' }, - { name: 'database-service-logs-*' }, - { name: 'api-gateway-logs-*' }, - { name: 'event-service-logs-*' }, - { name: 'notification-service-logs-*' }, - { name: 'search-service-logs-*' }, - { name: 'logging-service-logs-*' }, - { name: 'performance-service-logs-*' }, - { name: 'load-testing-logs-*' }, - { name: 'mobile-app-logs-*' }, - { name: 'web-app-logs-*' }, - { name: 'stream-processing-logs-*' }, - { name: 'batch-processing-logs-*' }, - { name: 'cloud-service-logs-*' }, - { name: 'container-logs-*' }, - { name: 'serverless-logs-*' }, - { name: 'server-administration-logs-*' }, - { name: 'application-deployment-logs-*' }, - { name: 'webserver-logs-*' }, - { name: 'payment-processor-logs-*' }, - { name: 'inventory-service-logs-*' }, - { name: 'data-pipeline-logs-*' }, - { name: 'frontend-service-logs-*' }, - { name: 'backend-service-logs-*' }, - { name: 'resource-monitoring-logs-*' }, - { name: 'logging-aggregation-logs-*' }, - { name: 'container-orchestration-logs-*' }, - { name: 'security-audit-logs-*' }, - { name: 'api-management-logs-*' }, - { name: 'service-mesh-logs-*' }, - { name: 'data-processing-logs-*' }, - { name: 'data-science-logs-*' }, - { name: 'machine-learning-logs-*' }, - { name: 'experimentation-logs-*' }, - { name: 'data-visualization-logs-*' }, - { name: 'data-cleaning-logs-*' }, - { name: 'data-transformation-logs-*' }, - { name: 'data-analysis-logs-*' }, - { name: 'data-storage-logs-*' }, - { name: 'data-retrieval-logs-*' }, - { name: 'data-warehousing-logs-*' }, - { name: 'data-modeling-logs-*' }, - { name: 'data-integration-logs-*' }, - { name: 'data-quality-logs-*' }, - { name: 'data-security-logs-*' }, - { name: 'data-encryption-logs-*' }, - { name: 'data-governance-logs-*' }, - { name: 'data-compliance-logs-*' }, - { name: 'data-privacy-logs-*' }, - { name: 'data-auditing-logs-*' }, - { name: 'data-discovery-logs-*' }, - { name: 'data-protection-logs-*' }, - { name: 'data-archiving-logs-*' }, - { name: 'data-backup-logs-*' }, - { name: 'data-recovery-logs-*' }, - { name: 'data-replication-logs-*' }, - { name: 'data-synchronization-logs-*' }, - { name: 'data-migration-logs-*' }, - { name: 'data-load-balancing-logs-*' }, - { name: 'data-scaling-logs-*' }, + { name: 'logs-*' as IndexPatternType }, + { name: 'system-logs-*' as IndexPatternType }, + { name: 'nginx-logs-*' as IndexPatternType }, + { name: 'apache-logs-*' as IndexPatternType }, + { name: 'security-logs-*' as IndexPatternType }, + { name: 'error-logs-*' as IndexPatternType }, + { name: 'access-logs-*' as IndexPatternType }, + { name: 'firewall-logs-*' as IndexPatternType }, + { name: 'application-logs-*' as IndexPatternType }, + { name: 'debug-logs-*' as IndexPatternType }, + { name: 'transaction-logs-*' as IndexPatternType }, + { name: 'audit-logs-*' as IndexPatternType }, + { name: 'server-logs-*' as IndexPatternType }, + { name: 'database-logs-*' as IndexPatternType }, + { name: 'event-logs-*' as IndexPatternType }, + { name: 'auth-logs-*' as IndexPatternType }, + { name: 'billing-logs-*' as IndexPatternType }, + { name: 'network-logs-*' as IndexPatternType }, + { name: 'performance-logs-*' as IndexPatternType }, + { name: 'email-logs-*' as IndexPatternType }, + { name: 'job-logs-*' as IndexPatternType }, + { name: 'task-logs-*' as IndexPatternType }, + { name: 'user-logs-*' as IndexPatternType }, + { name: 'request-logs-*' as IndexPatternType }, + { name: 'payment-logs-*' as IndexPatternType }, + { name: 'inventory-logs-*' as IndexPatternType }, + { name: 'debugging-logs-*' as IndexPatternType }, + { name: 'scheduler-logs-*' as IndexPatternType }, + { name: 'diagnostic-logs-*' as IndexPatternType }, + { name: 'cluster-logs-*' as IndexPatternType }, + { name: 'service-logs-*' as IndexPatternType }, + { name: 'framework-logs-*' as IndexPatternType }, + { name: 'api-logs-*' as IndexPatternType }, + { name: 'load-balancer-logs-*' as IndexPatternType }, + { name: 'reporting-logs-*' as IndexPatternType }, + { name: 'backend-logs-*' as IndexPatternType }, + { name: 'frontend-logs-*' as IndexPatternType }, + { name: 'chat-logs-*' as IndexPatternType }, + { name: 'error-tracking-logs-*' as IndexPatternType }, + { name: 'payment-gateway-logs-*' as IndexPatternType }, + { name: 'auth-service-logs-*' as IndexPatternType }, + { name: 'billing-service-logs-*' as IndexPatternType }, + { name: 'database-service-logs-*' as IndexPatternType }, + { name: 'api-gateway-logs-*' as IndexPatternType }, + { name: 'event-service-logs-*' as IndexPatternType }, + { name: 'notification-service-logs-*' as IndexPatternType }, + { name: 'search-service-logs-*' as IndexPatternType }, + { name: 'logging-service-logs-*' as IndexPatternType }, + { name: 'performance-service-logs-*' as IndexPatternType }, + { name: 'load-testing-logs-*' as IndexPatternType }, + { name: 'mobile-app-logs-*' as IndexPatternType }, + { name: 'web-app-logs-*' as IndexPatternType }, + { name: 'stream-processing-logs-*' as IndexPatternType }, + { name: 'batch-processing-logs-*' as IndexPatternType }, + { name: 'cloud-service-logs-*' as IndexPatternType }, + { name: 'container-logs-*' as IndexPatternType }, + { name: 'serverless-logs-*' as IndexPatternType }, + { name: 'server-administration-logs-*' as IndexPatternType }, + { name: 'application-deployment-logs-*' as IndexPatternType }, + { name: 'webserver-logs-*' as IndexPatternType }, + { name: 'payment-processor-logs-*' as IndexPatternType }, + { name: 'inventory-service-logs-*' as IndexPatternType }, + { name: 'data-pipeline-logs-*' as IndexPatternType }, + { name: 'frontend-service-logs-*' as IndexPatternType }, + { name: 'backend-service-logs-*' as IndexPatternType }, + { name: 'resource-monitoring-logs-*' as IndexPatternType }, + { name: 'logging-aggregation-logs-*' as IndexPatternType }, + { name: 'container-orchestration-logs-*' as IndexPatternType }, + { name: 'security-audit-logs-*' as IndexPatternType }, + { name: 'api-management-logs-*' as IndexPatternType }, + { name: 'service-mesh-logs-*' as IndexPatternType }, + { name: 'data-processing-logs-*' as IndexPatternType }, + { name: 'data-science-logs-*' as IndexPatternType }, + { name: 'machine-learning-logs-*' as IndexPatternType }, + { name: 'experimentation-logs-*' as IndexPatternType }, + { name: 'data-visualization-logs-*' as IndexPatternType }, + { name: 'data-cleaning-logs-*' as IndexPatternType }, + { name: 'data-transformation-logs-*' as IndexPatternType }, + { name: 'data-analysis-logs-*' as IndexPatternType }, + { name: 'data-storage-logs-*' as IndexPatternType }, + { name: 'data-retrieval-logs-*' as IndexPatternType }, + { name: 'data-warehousing-logs-*' as IndexPatternType }, + { name: 'data-modeling-logs-*' as IndexPatternType }, + { name: 'data-integration-logs-*' as IndexPatternType }, + { name: 'data-quality-logs-*' as IndexPatternType }, + { name: 'data-security-logs-*' as IndexPatternType }, + { name: 'data-encryption-logs-*' as IndexPatternType }, + { name: 'data-governance-logs-*' as IndexPatternType }, + { name: 'data-compliance-logs-*' as IndexPatternType }, + { name: 'data-privacy-logs-*' as IndexPatternType }, + { name: 'data-auditing-logs-*' as IndexPatternType }, + { name: 'data-discovery-logs-*' as IndexPatternType }, + { name: 'data-protection-logs-*' as IndexPatternType }, + { name: 'data-archiving-logs-*' as IndexPatternType }, + { name: 'data-backup-logs-*' as IndexPatternType }, + { name: 'data-recovery-logs-*' as IndexPatternType }, + { name: 'data-replication-logs-*' as IndexPatternType }, + { name: 'data-synchronization-logs-*' as IndexPatternType }, + { name: 'data-migration-logs-*' as IndexPatternType }, + { name: 'data-load-balancing-logs-*' as IndexPatternType }, + { name: 'data-scaling-logs-*' as IndexPatternType }, ]; diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.test.ts b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.test.ts index 1fec1c76430eb..2ebf895c8fa42 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.test.ts +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.test.ts @@ -4,3 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ + +/** + * THIS COMPONENT WILL CHANGE QUITE A LOT UNTIL WE WON'T GET TO A FINAL DESIGN. + * AS SOON AS A FIRST SOLID MVP IS DEFINED FOR PRODUCTION, A COMPLETE TEST WILL BE IMPLEMENTED. + */ diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx index 2e83bfa902b24..6323772a3d281 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx @@ -5,93 +5,35 @@ * 2.0. */ -import React, { RefCallback, useCallback, useEffect, useMemo, useState } from 'react'; -import { - EuiButton, - EuiButtonGroup, - EuiButtonProps, - EuiContextMenu, - EuiContextMenuItem, - EuiContextMenuPanel, - EuiContextMenuPanelDescriptor, - EuiContextMenuPanelItemDescriptor, - EuiFieldSearch, - EuiFlexGroup, - EuiFlexItem, - EuiHorizontalRule, - EuiPopover, - useIsWithinBreakpoints, -} from '@elastic/eui'; -import { PackageIcon } from '@kbn/fleet-plugin/public'; - +import { EuiContextMenu, EuiContextMenuPanel, EuiHorizontalRule } from '@elastic/eui'; import { EuiContextMenuPanelId } from '@elastic/eui/src/components/context_menu/context_menu'; -import { getIntegrationId, IntegrationId, SortOrder } from '../../../common'; +import React, { useCallback, useMemo, useState } from 'react'; import { dynamic } from '../../../common/dynamic'; +import type { DataStreamSelectionHandler } from '../../customizations/custom_data_stream_selector'; +import { useBoolean } from '../../hooks/use_boolean'; +import { useIntersectionRef } from '../../hooks/use_intersection_ref'; import { - DATA_VIEW_POPOVER_CONTENT_WIDTH, - INTEGRATION_PANEL_ID, - POPOVER_ID, - UNCATEGORIZED_STREAMS_PANEL_ID, contextMenuStyles, + DATA_VIEW_POPOVER_CONTENT_WIDTH, integrationsLabel, + INTEGRATION_PANEL_ID, selectViewLabel, - sortOptions, - sortOrdersLabel, uncategorizedLabel, + UNCATEGORIZED_STREAMS_PANEL_ID, } from './constants'; -import { getPopoverButtonStyles } from './data_stream_selector.utils'; - -import { useBoolean } from '../../hooks/use_boolean'; - -import { DataStream, Integration, SearchStrategy } from '../../../common/data_streams'; -import { LoadMoreIntegrations } from '../../hooks/use_integrations'; -import { useIntersectionRef } from '../../hooks/use_intersection_ref'; -import type { DataStreamSelectionHandler } from '../../customizations/custom_data_stream_selector'; -import { DataStreamSkeleton } from './data_stream_skeleton'; +import { DataStreamsPopover } from './sub_components/data_streams_popover'; +import { DataStreamSkeleton } from './sub_components/data_streams_skeleton'; +import { SearchControls, useSearchStrategy } from './sub_components/search_controls'; +import { CurrentPanelId, DataStreamSelectorProps } from './types'; +import { buildIntegrationsTree, setIntegrationListSpy } from './utils'; /** * Lazy load hidden components */ -const DataStreamsList = dynamic(() => import('./data_streams_list'), { +const DataStreamsList = dynamic(() => import('./sub_components/data_streams_list'), { fallback: , }); -type CurrentPanelId = - | typeof INTEGRATION_PANEL_ID - | typeof UNCATEGORIZED_STREAMS_PANEL_ID - | IntegrationId; - -export interface SearchParams { - name?: string; - sortOrder?: SortOrder; - strategy: SearchStrategy; - integrationId?: CurrentPanelId; -} - -type PartialSearchParams = Pick; - -export type SearchHandler = (params: SearchParams) => void; - -export interface DataStreamSelectorProps { - title: string; - search: PartialSearchParams; - integrations: Integration[] | null; - dataStreams: DataStream[] | null; - dataStreamsError?: Error | null; - isLoadingIntegrations: boolean; - isLoadingStreams: boolean; - /* Triggered when a search or sorting is performed on integrations */ - onSearch: SearchHandler; - /* Triggered when we reach the bottom of the integration list and want to load more */ - onIntegrationsLoadMore: LoadMoreIntegrations; - /* Triggered when the uncategorized streams entry is selected */ - onStreamsEntryClick: () => void; - /* Triggered when retrying to load the data streams */ - onStreamsReload: () => void; - /* Triggered when a data stream entry is selected */ - onStreamSelected: DataStreamSelectionHandler; -} - export function DataStreamSelector({ title, integrations, @@ -99,26 +41,20 @@ export function DataStreamSelector({ onSearch, onIntegrationsLoadMore, onStreamSelected, - search, dataStreamsError, isLoadingStreams, onStreamsEntryClick, onStreamsReload, dataStreams, }: DataStreamSelectorProps) { - const isMobile = useIsWithinBreakpoints(['xs', 's']); const [isPopoverOpen, { off: closePopover, toggle: togglePopover }] = useBoolean(false); - const [loadMoreIntersection, setSpyRef] = useIntersectionRef(); - - useEffect(() => { - if (loadMoreIntersection?.isIntersecting) { - onIntegrationsLoadMore(); - } - }, [loadMoreIntersection, onIntegrationsLoadMore]); + const [setSpyRef] = useIntersectionRef({ onIntersecting: onIntegrationsLoadMore }); const [currentPanel, setCurrentPanel] = useState(INTEGRATION_PANEL_ID); + const [search, handleSearch] = useSearchStrategy({ id: currentPanel, onSearch }); + const handlePanelChange = ({ panelId }: { panelId: EuiContextMenuPanelId }) => { setCurrentPanel(panelId as CurrentPanelId); }; @@ -182,37 +118,19 @@ export function DataStreamSelector({ ...integrationPanels, ]; - const button = ( - - {title} - - ); - - const handleSearch = (params: PartialSearchParams) => { - const strategy = getSearchStrategy(currentPanel); - return onSearch({ - ...params, - strategy, - ...(strategy === SearchStrategy.INTEGRATIONS_DATA_STREAMS && { integrationId: currentPanel }), - }); - }; - const searchValue = search; - return ( - - - ); -} - -interface DataStreamButtonProps extends EuiButtonProps { - onClick: () => void; -} - -const DataStreamButton = (props: DataStreamButtonProps) => { - const buttonStyles = getPopoverButtonStyles({ fullWidth: props.fullWidth }); - - return ; -}; - -interface SearchControlsProps { - isLoading: boolean; - search: PartialSearchParams; - onSearch: (params: PartialSearchParams) => void; -} - -const SearchControls = ({ search, onSearch, isLoading }: SearchControlsProps) => { - const handleQueryChange: React.ChangeEventHandler = (event) => { - const name = event.target.value; - onSearch({ ...search, name }); - }; - - const handleSortChange = (id: string) => { - onSearch({ ...search, sortOrder: id as SearchParams['sortOrder'] }); - }; - - return ( - - - - - - - - - - + ); -}; - -interface IntegrationsTree { - items: EuiContextMenuPanelItemDescriptor[]; - panels: EuiContextMenuPanelDescriptor[]; } - -interface IntegrationsTreeParams { - integrations: Integration[]; - onStreamSelected: DataStreamSelectionHandler; -} - -const buildIntegrationsTree = ({ integrations, onStreamSelected }: IntegrationsTreeParams) => { - return integrations.reduce( - (res: IntegrationsTree, integration) => { - const entryId: CurrentPanelId = getIntegrationId(integration); - const { name, version, dataStreams } = integration; - - res.items.push({ - name, - icon: , - panel: entryId, - }); - - res.panels.push({ - id: entryId, - title: name, - width: DATA_VIEW_POPOVER_CONTENT_WIDTH, - items: dataStreams.map((stream) => ({ - name: stream.title, - onClick: () => - onStreamSelected({ title: `[${name}] ${stream.title}`, name: stream.name }), - })), - }); - - return res; - }, - { items: [], panels: [] } - ); -}; - -const setIntegrationListSpy = ( - items: EuiContextMenuPanelItemDescriptor[], - spyRef: RefCallback -) => { - const lastItem = items.at(-1); - if (lastItem) { - lastItem.buttonRef = spyRef; - } -}; - -const getSearchStrategy = (panelId: CurrentPanelId) => { - if (panelId === UNCATEGORIZED_STREAMS_PANEL_ID) return SearchStrategy.DATA_STREAMS; - if (panelId === INTEGRATION_PANEL_ID) return SearchStrategy.INTEGRATIONS; - return SearchStrategy.INTEGRATIONS_DATA_STREAMS; -}; diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.utils.ts b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.utils.ts deleted file mode 100644 index 3ea2a65b37cda..0000000000000 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.utils.ts +++ /dev/null @@ -1,12 +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 { DATA_VIEW_POPOVER_CONTENT_WIDTH } from './constants'; - -export const getPopoverButtonStyles = ({ fullWidth }: { fullWidth?: boolean }) => ({ - maxWidth: fullWidth ? undefined : DATA_VIEW_POPOVER_CONTENT_WIDTH, -}); diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/index.ts b/x-pack/plugins/observability_logs/public/components/data_stream_selector/index.ts index ec33977320e7e..bfea3ad1f40db 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/index.ts +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/index.ts @@ -6,3 +6,4 @@ */ export * from './data_stream_selector'; +export * from './types'; diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_streams_list.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/data_streams_list.tsx similarity index 89% rename from x-pack/plugins/observability_logs/public/components/data_stream_selector/data_streams_list.tsx rename to x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/data_streams_list.tsx index f324a07bac6a6..aa40ffbe799d0 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_streams_list.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/data_streams_list.tsx @@ -13,16 +13,16 @@ import { noDataStreamsDescriptionLabel, noDataStreamsLabel, noDataRetryLabel, -} from './constants'; -import { DataStream } from '../../../common/data_streams'; -import { DataStreamSkeleton } from './data_stream_skeleton'; -import { SearchHandler } from './data_stream_selector'; +} from '../constants'; +import { DataStream } from '../../../../common/data_streams'; +import { DataStreamSkeleton } from './data_streams_skeleton'; +import { DataStreamSelectionHandler } from '../types'; interface DataStreamListProps { dataStreams: DataStream[] | null; - error?: Error | null; + error: Error | null; isLoading: boolean; - onStreamClick: SearchHandler; + onStreamClick: DataStreamSelectionHandler; onRetry: () => void; } diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/data_streams_popover.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/data_streams_popover.tsx new file mode 100644 index 0000000000000..20c21f8f47b0f --- /dev/null +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/data_streams_popover.tsx @@ -0,0 +1,44 @@ +/* + * 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 React from 'react'; +import { EuiButton, EuiPopover, EuiPopoverProps, useIsWithinBreakpoints } from '@elastic/eui'; +import { POPOVER_ID } from '../constants'; +import { getPopoverButtonStyles } from '../utils'; + +interface DataStreamsPopoverProps extends Omit { + onClick: () => void; + title: string; + children: React.ReactNode; +} + +export const DataStreamsPopover = ({ title, onClick, ...props }: DataStreamsPopoverProps) => { + const isMobile = useIsWithinBreakpoints(['xs', 's']); + + const buttonStyles = getPopoverButtonStyles({ fullWidth: isMobile }); + + return ( + + {title} + + } + panelPaddingSize="none" + buffer={8} + {...(isMobile && { display: 'block' })} + {...props} + /> + ); +}; diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_skeleton.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/data_streams_skeleton.tsx similarity index 90% rename from x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_skeleton.tsx rename to x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/data_streams_skeleton.tsx index 2b9a6bfb2b64d..22e406afb22b0 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_skeleton.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/data_streams_skeleton.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { EuiPanel, EuiSkeletonText } from '@elastic/eui'; -import { uncategorizedLabel } from './constants'; +import { uncategorizedLabel } from '../constants'; export const DataStreamSkeleton = () => ( diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/search_controls.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/search_controls.tsx new file mode 100644 index 0000000000000..abf4046d7c882 --- /dev/null +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/search_controls.tsx @@ -0,0 +1,114 @@ +/* + * 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 React, { useReducer } from 'react'; +import { EuiButtonGroup, EuiFieldSearch, EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; +import { SearchStrategy } from '../../../../common/data_streams'; +import { SortOrder } from '../../../../common'; +import { + DATA_VIEW_POPOVER_CONTENT_WIDTH, + INTEGRATION_PANEL_ID, + sortOptions, + sortOrdersLabel, +} from '../constants'; +import { CurrentPanelId, SearchControlsParams, SearchHandler } from '../types'; +import { getSearchStrategy } from '../utils'; + +type SearchControlsHandler = (params: SearchControlsParams) => void; + +const defaultSearch: SearchControlsParams = { + name: '', + sortOrder: 'asc', +}; + +const initialCache = { + [INTEGRATION_PANEL_ID]: defaultSearch, +}; + +const searchCacheReducer = ( + cache: Record, + update: { id: CurrentPanelId; params: SearchControlsParams } +) => { + const { id, params } = update; + + return { + ...cache, + [id]: params, + }; +}; + +interface UseSearchStrategyOptions { + id: CurrentPanelId; + onSearch: SearchHandler; +} + +export const useSearchStrategy = ({ id, onSearch }: UseSearchStrategyOptions) => { + const [searchCache, insertSearch] = useReducer(searchCacheReducer, initialCache); + + const search = searchCache[id] ?? defaultSearch; + console.log({ id, searchCache, search }); + + const handleSearch = (params: SearchControlsParams) => { + const strategy = getSearchStrategy(id); + + insertSearch({ id, params }); + + return onSearch({ + ...params, + strategy, + ...(strategy === SearchStrategy.INTEGRATIONS_DATA_STREAMS && { integrationId: id }), + }); + }; + + return [search, handleSearch] as [SearchControlsParams, SearchControlsHandler]; +}; + +/** + * SearchControls component definition, it works with the useSearchStrategy custom hook + */ +interface SearchControlsProps { + isLoading: boolean; + onSearch: SearchControlsHandler; + search: SearchControlsParams; +} + +export const SearchControls = ({ search, onSearch, isLoading }: SearchControlsProps) => { + const handleQueryChange: React.ChangeEventHandler = (event) => { + const newSearch = { ...search, name: event.target.value }; + onSearch(newSearch); + }; + + const handleSortChange = (id: string) => { + const newSearch = { ...search, sortOrder: id as SearchControlsParams['sortOrder'] }; + onSearch(newSearch); + }; + + return ( + + + + + + + + + + + ); +}; diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/types.ts b/x-pack/plugins/observability_logs/public/components/data_stream_selector/types.ts new file mode 100644 index 0000000000000..68262c9da7271 --- /dev/null +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/types.ts @@ -0,0 +1,54 @@ +/* + * 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 { IntegrationId, SortOrder } from '../../../common'; +import { DataStream, Integration, SearchStrategy } from '../../../common/data_streams'; +import { LoadMoreIntegrations } from '../../hooks/use_integrations'; +import { INTEGRATION_PANEL_ID, UNCATEGORIZED_STREAMS_PANEL_ID } from './constants'; + +export interface DataStreamSelectorProps { + /* The human-readable name of the currently selected view */ + title: string; + /* The integrations list, each integration includes its data streams */ + integrations: Integration[] | null; + /* The generic data stream list */ + dataStreams: DataStream[] | null; + /* Any error occurred to show when the user preview the generic data streams */ + dataStreamsError?: Error | null; + /* Flag for loading/searching integrations */ + isLoadingIntegrations: boolean; + /* Flag for loading/searching generic streams */ + isLoadingStreams: boolean; + /* Triggered when a search or sorting is performed on integrations */ + onSearch: SearchHandler; + /* Triggered when we reach the bottom of the integration list and want to load more */ + onIntegrationsLoadMore: LoadMoreIntegrations; + /* Triggered when the uncategorized streams entry is selected */ + onStreamsEntryClick: () => void; + /* Triggered when retrying to load the data streams */ + onStreamsReload: () => void; + /* Triggered when a data stream entry is selected */ + onStreamSelected: DataStreamSelectionHandler; +} + +export type CurrentPanelId = + | typeof INTEGRATION_PANEL_ID + | typeof UNCATEGORIZED_STREAMS_PANEL_ID + | IntegrationId; + +export interface SearchParams { + integrationId?: CurrentPanelId; + name?: string; + sortOrder?: SortOrder; + strategy: SearchStrategy; +} + +export type SearchControlsParams = Pick; + +export type SearchHandler = (params: SearchParams) => void; + +export type DataStreamSelectionHandler = (stream: DataStream) => void; diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/utils.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/utils.tsx new file mode 100644 index 0000000000000..ec260b5bd0194 --- /dev/null +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/utils.tsx @@ -0,0 +1,80 @@ +/* + * 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 { EuiContextMenuPanelDescriptor, EuiContextMenuPanelItemDescriptor } from '@elastic/eui'; +import { PackageIcon } from '@kbn/fleet-plugin/public'; +import React, { RefCallback } from 'react'; +import { getIntegrationId } from '../../../common'; +import { Integration, SearchStrategy } from '../../../common/data_streams'; +import { + DATA_VIEW_POPOVER_CONTENT_WIDTH, + INTEGRATION_PANEL_ID, + UNCATEGORIZED_STREAMS_PANEL_ID, +} from './constants'; +import { CurrentPanelId, DataStreamSelectionHandler } from './types'; + +export const getPopoverButtonStyles = ({ fullWidth }: { fullWidth?: boolean }) => ({ + maxWidth: fullWidth ? undefined : DATA_VIEW_POPOVER_CONTENT_WIDTH, +}); + +interface IntegrationsTreeParams { + integrations: Integration[]; + onStreamSelected: DataStreamSelectionHandler; +} + +interface IntegrationsTree { + items: EuiContextMenuPanelItemDescriptor[]; + panels: EuiContextMenuPanelDescriptor[]; +} + +export const buildIntegrationsTree = ({ + integrations, + onStreamSelected, +}: IntegrationsTreeParams) => { + return integrations.reduce( + (res: IntegrationsTree, integration) => { + const entryId: CurrentPanelId = getIntegrationId(integration); + const { name, version, dataStreams } = integration; + + res.items.push({ + name, + icon: , + panel: entryId, + }); + + res.panels.push({ + id: entryId, + title: name, + width: DATA_VIEW_POPOVER_CONTENT_WIDTH, + items: dataStreams.map((stream) => ({ + name: stream.title, + onClick: () => + onStreamSelected({ title: `[${name}] ${stream.title}`, name: stream.name }), + })), + }); + + return res; + }, + { items: [], panels: [] } + ); +}; + +export const setIntegrationListSpy = ( + items: EuiContextMenuPanelItemDescriptor[], + spyRef: RefCallback +) => { + const lastItem = items.at(-1); + if (lastItem) { + lastItem.buttonRef = spyRef; + } +}; + +export const getSearchStrategy = (panelId: CurrentPanelId) => { + if (panelId === UNCATEGORIZED_STREAMS_PANEL_ID) return SearchStrategy.DATA_STREAMS; + if (panelId === INTEGRATION_PANEL_ID) return SearchStrategy.INTEGRATIONS; + return SearchStrategy.INTEGRATIONS_DATA_STREAMS; +}; diff --git a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx index 5acccd28963e2..0fb053b9c5247 100644 --- a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx @@ -11,11 +11,8 @@ import { DataStream, SearchStrategy } from '../../common/data_streams'; import { DataStreamSelector, SearchHandler } from '../components/data_stream_selector'; import { InternalStateProvider, useDataView } from '../utils/internal_state_container_context'; import { IntegrationsProvider, useIntegrationsContext } from '../hooks/use_integrations'; -import { - ObservabilityLogsPluginProvider, - ObservabilityLogsPluginProviderProps, -} from '../hooks/use_kibana'; import { DataStreamsProvider, useDataStreamsContext } from '../hooks/use_data_streams'; +import { IDataStreamsClient } from '../services/data_streams'; interface CustomDataStreamSelectorProps { stateContainer: DiscoverStateContainer; @@ -31,7 +28,6 @@ export const CustomDataStreamSelector = withProviders(({ stateContainer }) => { integrations, isLoading: isLoadingIntegrations, loadMore, - search: integrationsSearch, searchIntegrations, } = useIntegrationsContext(); @@ -41,7 +37,6 @@ export const CustomDataStreamSelector = withProviders(({ stateContainer }) => { isLoading: isLoadingStreams, loadDataStreams, reloadDataStreams, - search: dataStreamsSearch, searchDataStreams, } = useDataStreamsContext(); @@ -62,8 +57,6 @@ export const CustomDataStreamSelector = withProviders(({ stateContainer }) => { } }; - // TODO: handle search states for different strategies - return ( { onStreamSelected={handleStreamSelection} onStreamsEntryClick={loadDataStreams} onStreamsReload={reloadDataStreams} - search={integrationsSearch} title={dataView.getName()} /> ); @@ -86,26 +78,23 @@ export const CustomDataStreamSelector = withProviders(({ stateContainer }) => { // eslint-disable-next-line import/no-default-export export default CustomDataStreamSelector; -export type CustomDataStreamSelectorBuilderProps = ObservabilityLogsPluginProviderProps & - CustomDataStreamSelectorProps; +export type CustomDataStreamSelectorBuilderProps = CustomDataStreamSelectorProps & { + dataStreamsClient: IDataStreamsClient; +}; function withProviders(Component: React.FunctionComponent) { return function ComponentWithProviders({ - core, - plugins, - pluginStart, stateContainer, + dataStreamsClient, }: CustomDataStreamSelectorBuilderProps) { return ( - - - - - - - - - + + + + + + + ); }; } diff --git a/x-pack/plugins/observability_logs/public/hooks/use_data_streams.ts b/x-pack/plugins/observability_logs/public/hooks/use_data_streams.ts index 36761cc043e77..c844c7725141f 100644 --- a/x-pack/plugins/observability_logs/public/hooks/use_data_streams.ts +++ b/x-pack/plugins/observability_logs/public/hooks/use_data_streams.ts @@ -64,13 +64,14 @@ const useDataStreams = ({ dataStreamsClient }: DataStreamsContextDeps) => { const searchDataStreams: SearchDataStreams = useCallback( (searchParams) => dataStreamsStateService.send({ - type: 'SEARCH_DATA_STREAMS', + type: 'SEARCH', + delay: search.name !== searchParams.name ? 500 : 0, search: { datasetQuery: searchParams.name, sortOrder: searchParams.sortOrder, }, }), - [dataStreamsStateService] + [dataStreamsStateService, search] ); return { diff --git a/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts b/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts index 47880c01ecbe3..a20ddb3c6a217 100644 --- a/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts +++ b/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts @@ -21,6 +21,7 @@ export interface SearchIntegrationsParams { name?: string; sortOrder?: SortOrder; strategy: SearchStrategy; + integrationId?: string; } export type SearchIntegrations = (params: SearchIntegrationsParams) => void; @@ -58,13 +59,16 @@ const useIntegrations = ({ dataStreamsClient }: IntegrationsContextDeps) => { const searchIntegrations: SearchIntegrations = useCallback( (searchParams) => integrationsStateService.send({ - type: 'SEARCH_INTEGRATIONS', + type: 'SEARCH', + delay: search.name !== searchParams.name ? 500 : 0, search: { - ...searchParams, nameQuery: searchParams.name, + strategy: searchParams.strategy, + sortOrder: searchParams.sortOrder, + integrationId: searchParams.integrationId, }, }), - [integrationsStateService] + [integrationsStateService, search] ); const loadMore = useCallback( diff --git a/x-pack/plugins/observability_logs/public/hooks/use_intersection_ref.ts b/x-pack/plugins/observability_logs/public/hooks/use_intersection_ref.ts index b7e9e56075c3b..83cae0847fa77 100644 --- a/x-pack/plugins/observability_logs/public/hooks/use_intersection_ref.ts +++ b/x-pack/plugins/observability_logs/public/hooks/use_intersection_ref.ts @@ -5,10 +5,16 @@ * 2.0. */ -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import useIntersection from 'react-use/lib/useIntersection'; -export function useIntersectionRef() { +interface IntersectionOptions { + onIntersecting?: () => void; +} + +export function useIntersectionRef({ + onIntersecting, +}: IntersectionOptions = {}) { const [intersectionRef, setRef] = useState(null); const intersection = useIntersection( @@ -16,5 +22,11 @@ export function useIntersectionRef { + if (intersection?.isIntersecting && onIntersecting) { + onIntersecting(); + } + }, [intersection, onIntersecting]); + + return [setRef, intersection] as [typeof setRef, IntersectionObserverEntry | null]; } diff --git a/x-pack/plugins/observability_logs/public/hooks/use_kibana.tsx b/x-pack/plugins/observability_logs/public/hooks/use_kibana.tsx deleted file mode 100644 index 73d27c25b9d02..0000000000000 --- a/x-pack/plugins/observability_logs/public/hooks/use_kibana.tsx +++ /dev/null @@ -1,45 +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 React, { PropsWithChildren } from 'react'; -import { CoreStart } from '@kbn/core/public'; -import { - createKibanaReactContext, - KibanaReactContextValue, - useKibana, -} from '@kbn/kibana-react-plugin/public'; -import { useMemo } from 'react'; -import { ObservabilityLogsPluginStart, ObservabilityLogsStartDeps } from '../types'; - -export type ObservabilityLogsContext = CoreStart & - ObservabilityLogsStartDeps & - ObservabilityLogsPluginStart; - -export const useObservabilityLogsPlugin = - useKibana as () => KibanaReactContextValue; - -export interface ObservabilityLogsPluginProviderProps { - core: CoreStart; - plugins: ObservabilityLogsStartDeps; - pluginStart: ObservabilityLogsPluginStart; -} - -export const ObservabilityLogsPluginProvider = ({ - core, - plugins, - pluginStart, - children, -}: PropsWithChildren) => { - const context = useMemo( - () => ({ ...core, ...plugins, ...pluginStart }), - [core, pluginStart, plugins] - ); - - const { Provider } = createKibanaReactContext(context); - - return ; -}; diff --git a/x-pack/plugins/observability_logs/public/plugin.tsx b/x-pack/plugins/observability_logs/public/plugin.tsx index 9b1da6f2c7966..4cbc8436628bb 100644 --- a/x-pack/plugins/observability_logs/public/plugin.tsx +++ b/x-pack/plugins/observability_logs/public/plugin.tsx @@ -22,8 +22,6 @@ export class ObservabilityLogsPlugin implements ObservabilityLogsClientPluginCla public start(core: CoreStart, plugins: ObservabilityLogsStartDeps) { const { discover } = plugins; - const getStartServices = () => ({ core, plugins, pluginStart }); - const dataStreamsService = this.dataStreamsService.start({ http: core.http, }); @@ -39,7 +37,7 @@ export class ObservabilityLogsPlugin implements ObservabilityLogsClientPluginCla customizations.set({ id: 'search_bar', CustomDataViewPicker: createLazyCustomDataStreamSelector({ - ...getStartServices(), + dataStreamsClient: dataStreamsService.client, stateContainer, }), }); diff --git a/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/defaults.ts b/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/defaults.ts index 16b8a3dea460a..cbc80532e64b7 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/defaults.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/defaults.ts @@ -13,6 +13,7 @@ export const DEFAULT_CONTEXT: DefaultDataStreamsContext = { dataStreams: null, error: null, search: { + datasetQuery: '', sortOrder: 'asc', }, }; diff --git a/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/state_machine.ts b/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/state_machine.ts index 0d4fdaed744dc..e5a8ff2eeaa03 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/state_machine.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/state_machine.ts @@ -43,27 +43,23 @@ export const createPureDataStreamsStateMachine = ( target: 'loaded', actions: ['storeInCache', 'storeDataStreams', 'storeSearch'], }, - LOADING_FAILED: { - target: 'loadingFailed', - }, + LOADING_FAILED: 'loadingFailed', }, }, loaded: { on: { - SEARCH_DATA_STREAMS: { - target: 'debouncingSearch', - }, + SEARCH: 'debouncingSearch', }, }, debouncingSearch: { entry: 'storeSearch', on: { - SEARCH_DATA_STREAMS: { + SEARCH: { target: 'debouncingSearch', }, }, after: { - 500: { + SEARCH_DELAY: { target: 'loading', }, }, @@ -73,7 +69,7 @@ export const createPureDataStreamsStateMachine = ( exit: 'clearError', on: { RELOAD_DATA_STREAMS: 'loading', - SEARCH_DATA_STREAMS: 'debouncingSearch', + SEARCH: 'debouncingSearch', }, }, }, @@ -114,6 +110,13 @@ export const createPureDataStreamsStateMachine = ( ), clearError: assign((_context) => ({ error: null })), }, + delays: { + SEARCH_DELAY: (_context, event) => { + if (event.type !== 'SEARCH' || !event.delay) return 0; + + return event.delay; + }, + }, } ); diff --git a/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/types.ts b/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/types.ts index 17c1ec00a8734..13a2bfb00266b 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/types.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/types.ts @@ -64,6 +64,7 @@ export type DataStreamsEvent = type: 'RELOAD_DATA_STREAMS'; } | { - type: 'SEARCH_DATA_STREAMS'; + type: 'SEARCH'; search: DataStreamsSearchParams; + delay?: number; }; diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/defaults.ts b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/defaults.ts index 176b7b672cc2e..347b0ca648338 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/defaults.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/defaults.ts @@ -14,6 +14,7 @@ export const DEFAULT_CONTEXT: DefaultIntegrationsContext = { integrations: null, error: null, search: { + nameQuery: '', sortOrder: 'asc', }, }; diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts index 21dfd7b59365a..13cafb1d30a26 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts @@ -23,7 +23,7 @@ import { export const createPureIntegrationsStateMachine = ( initialContext: DefaultIntegrationsContext = DEFAULT_CONTEXT ) => - /** @xstate-layout N4IgpgJg5mDOIC5QEkB2AXMUBOBDdAlgPaqwB0ArqgdYbgDYEBekAxANoAMAuoqAA5FYBQiT4gAHogCMAZgBsAVjLTOATlly18gCwAOHbL0AaEAE9EshSoBMigL73TaTDnzFSZekVwQaUVgAZAHkAQQARZAA5AHEAfQBlAFUAYRSAUXTwrK5eJBBBYVFUcSkEOQB2NRVdTk5pHV0DI1MLBAq9eTJ5NUVNO0dnDCw8YvJvX38gsMjYuIAxUORAnJ5xQpEPUpkdGy75PQrDPW19Y9bEGwayWQqHJxAXEfcScZ8-VCgAWSJsMGmItF4sk0plsuFcushJsxPkyjY9HoyFdDooLggDF1pIpsfIBg8nm4xl53v4fn8AbN4otlqs8gJocVtggEUiUXd0VVlEpcfihq5Rh43r42CEInEvsEAErpOLRAAq6RiUtC8uQwSiCUh+Q2TLhiCOSJ6fWkdnRqk4shud0Gj2GRKFJJFEFYCXSoSlKQAEnKoorlar1ZrtQyilt9e19N1ev00eYDdiyKjbYTBa8yABjAAWYAzAGsyb8wKmXqRQgA3XAEei4ABG1ZEZg4ax1jPDoDKek4XVR5u71Xq6lu935z2J2dzBc+5OL9rTZcr1brDfQTfY0npBTbsI7iE6SOkvfjCF6FWtI7tAtL5AgYFrRCoGf8CTAuGw2dd7s9PoVSpVao1LUW1DGESgjVRFCNGxZEtaR5Dgq5pHRWQNG6PlLzHR1b3vR9n1fd8s1YCRYHQfAwDIXAADNMGwAAKRQ6gASlYEtiWwh9UCfT4XzfbMQy3MMd0kA0o2NWN0T0ZRkwJOdrzIdjcO4-CP2I0jMAo6iwDohjOGY1isLvDiuKgHiCPXTddXbYTygUZRVA0LQmnOY8dAqK08QvfT01gZSs2fdA-lwABbWBPw9b1ElSDIsjpKFBLA3cWT0K1ZEUDQOWPHobCTNRmhtGSr2JHzeL87iAtfEKwu-BYlhWCFgIE0DmVZZFDwytpoM4c8U1k4kJg+KB5irehRRmCVpVlX8AwA4MGssoSykNaMTTNTKlG6grMPTfr-CG6s2BlMVwl9f1-yDICLO3BLrKWsTTTjNp4J0JN8tHB1ttJT49pGl03XCn8-T-QNAP4+brsW0SY3u9FGi6zh8oeVAiFveB8i80g4qaiMAFp5HRXGesKx0qBoTYGGYSBMb1RLpGkVD5FkBFoePGw1GqHQVs83rHR2z4qassoEOetQKiUepGjOFoXJ0ap7sJrbPF574i35hbEF2TlemRUXFA8+X3sV95KdbeLmUg9m6ZsCpOGSqwrB0TlWbINR0PR8gJ3zQs-jdithuXRhV1V8GZE4HRpDIThoMMdrEF6JFXK5on0wUzi8JKoPmVUCozzxHpbk6PFaZhmXbEThXyGKgj-MCkKM4jEv5CqA4Y4xcOy4N4UBu+42QOp6zFCtshs9kaOHsuTg7PxRwgA */ + /** @xstate-layout N4IgpgJg5mDOIC5QEkB2AXMUBOBDdAlgPaqwB0ArqgdYbgDYEBekAxANoAMAuoqAA5FYBQiT4gAHogCMADgDMZWdIBMAVk5rlAFgDsagJwAaEAE9EG3Uum7dKzrO1rtm2QDYAvh5NpMOfMSkZPREuBA0UKwAMgDyAIIAIsgAcgDiAPoAygCqAMK5AKIFCcVcvEgggsKiqOJSCGrqZNI29trSBtJaJuYI8vLSZGrObu6yanbao14+GFh4NeQhYRHR8Ulp6QBicchRpTziVSKBdRZNLXac7Z3dZueyZAbaKro2avIuU7ozIL7zARIS1C4VQUAAskRsGA1okUhkcvkiiUEmUjkITmIKvVpJxdAYyG9ZLZOP1ZCoOsZ7gh1IMVNoDAZZI5OM8DG4VL9-v5FsEQRFIdDYRsMjs9gdygIMTUzggutpFAyVPY3M9WbjpD1EC5FLiDPJdOS3PKJly5jzAsCwmxYol0uCYgAlArpFIAFQKqUdcTdyBiyUyaIqxxl2MQxIJ8nJ+gMuj0CjcWr6jLIblZ8mc8jxnBNZr8C0tfOtEFYmQKcUduQAEkGpdVTmGEBGyFHXoY44b5InqfZOISNJxrhMNI1VHmAbyIGAAEZEKgAYwimTAuGw84AFqXy5Wa4dg9KG6B6uo3NoyMrVU4OXjDbokwrHh1XpxlcSHNptOOLUCyFPZwulxXNdNwkWB0HwMAyFwAAzTBsAACjLCtq3SEoojiABNABKVhuQLH8-znVBFzBZdVw3WtKgPLEj0QClbBba4DBUNVtGUJN8UfJx8XUZ45AMNQv3woJCIA0igI3VhQPAzAoNgsAEKQndUIKdDsNw81hPIUTiMA8j13YaRJSo+saMkOj7AJXQ3AmU8HFjOMk2UIZB0HWxmVjD8fm8P5NMBIJYAk9cl3QaFcAAW1gLdkKrLI8kKYoJXRUzakbRo1GaVprg6LpZCTCYCQ-Z9pC7RwDAcIT-PIQL9JCsLIui5SxX2VE9zrTFUtohoLiym5cqTNx9CUATmTePRVDYyreWWUEoC2XACHoG11ntJ0XXdT1vV9f1AzakyOtleVFWYlU1U6HMkwzFRU3TVRyQK9l5CmwsZoiebFrYJTq0okND3MmkbCsLMlVY9jqTUaQ3FTZjBsNB9rIMLwfNQIgp3gCo8Kq5KDsbABabtenxshXJJ0m3Oen8qBoE4GGYSBsdDLqXnyzgoeUWx1DGlRjU-HzMem-kwQZv7j0NYbHAzWM01Ve8FBbQH+kZHKsyevm-IFlYwUFMBhbMnEsysV5PijOwPkZS63F1Zi43oilXjjCmghm+n9xS2VrL7Zj2XxdLSXaS7ZAJFRjaVhlNA+R3tJnIiSKgMjgN1zr-vUYPCWUEdpDY4PNWpBlFFVbn3FULoVHGXnZnzKqyBq4C6pXSLE9lUuoYmZQowZTpZCHDiPiGewFAVO2u28iuJxewW5oWpaIEbxsOmc1tvYhhy1HvQbodeSGWnaNQOW8rwgA */ createMachine( { context: initialContext, @@ -33,9 +33,7 @@ export const createPureIntegrationsStateMachine = ( initial: 'uninitialized', states: { uninitialized: { - always: { - target: 'loading', - }, + always: 'loading', }, loading: { invoke: { @@ -46,9 +44,7 @@ export const createPureIntegrationsStateMachine = ( target: 'loaded', actions: ['storeInCache', 'storeIntegrationsResponse', 'storeSearch'], }, - LOADING_FAILED: { - target: 'loadingFailed', - }, + LOADING_FAILED: 'loadingFailed', }, }, loadingMore: { @@ -60,39 +56,25 @@ export const createPureIntegrationsStateMachine = ( target: 'loaded', actions: ['storeInCache', 'appendIntegrations', 'storeSearch'], }, - LOADING_FAILED: { - target: 'loadingFailed', - }, + LOADING_FAILED: 'loadingFailed', }, }, loaded: { on: { - LOAD_MORE_INTEGRATIONS: 'checkingMoreIntegrationsAvailability', - SEARCH_INTEGRATIONS: { - target: 'debouncingSearch', - }, - }, - }, - checkingMoreIntegrationsAvailability: { - always: [ - { + LOAD_MORE_INTEGRATIONS: { cond: 'hasMoreIntegrations', target: 'loadingMore', }, - { - target: 'loaded', - }, - ], + SEARCH: 'debouncingSearch', + }, }, debouncingSearch: { entry: 'storeSearch', on: { - SEARCH_INTEGRATIONS: { - target: 'debouncingSearch', - }, + SEARCH: 'debouncingSearch', }, after: { - 500: [ + SEARCH_DELAY: [ { cond: 'isStreamSearch', target: 'searchingStreams', @@ -110,20 +92,20 @@ export const createPureIntegrationsStateMachine = ( on: { SEARCH_SUCCEEDED: { target: 'loaded', - actions: ['storeIntegrations'], - }, - SEARCH_FAILED: { - target: 'loadingFailed', + actions: 'storeIntegrations', }, + SEARCH_FAILED: 'loadingFailed', }, }, loadingFailed: { entry: ['clearCache', 'storeError'], exit: 'clearError', on: { - LOAD_MORE_INTEGRATIONS: 'checkingMoreIntegrationsAvailability', - RELOAD_INTEGRATIONS: 'loading', - SEARCH_INTEGRATIONS: 'debouncingSearch', + LOAD_MORE_INTEGRATIONS: { + cond: 'hasMoreIntegrations', + target: 'loadingMore', + }, + SEARCH: 'debouncingSearch', }, }, }, @@ -190,6 +172,13 @@ export const createPureIntegrationsStateMachine = ( isStreamSearch: (context) => context.search.strategy === SearchStrategy.INTEGRATIONS_DATA_STREAMS, }, + delays: { + SEARCH_DELAY: (_context, event) => { + if (event.type !== 'SEARCH' || !event.delay) return 0; + + return event.delay; + }, + }, } ); diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts index a2c81d5aa367b..836b7c08f0826 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts @@ -56,10 +56,6 @@ export type IntegrationTypestate = | { value: 'searchingStreams'; context: DefaultIntegrationsContext; - } - | { - value: 'checkingMoreIntegrationsAvailability'; - context: DefaultIntegrationsContext; }; export type IntegrationsContext = IntegrationTypestate['context']; @@ -81,13 +77,11 @@ export type IntegrationsEvent = type: 'SEARCH_FAILED'; error: Error; } - | { - type: 'RELOAD_INTEGRATIONS'; - } | { type: 'LOAD_MORE_INTEGRATIONS'; } | { - type: 'SEARCH_INTEGRATIONS'; + type: 'SEARCH'; search: IntegrationsSearchParams; + delay?: number; }; From 754906d82d26bf000702e3fc7553f854d49aa8dc Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Wed, 24 May 2023 11:11:44 +0200 Subject: [PATCH 104/122] refactor(observability_logs): update search strategy --- .../common/data_streams/v1/common.ts | 21 +- .../common/runtime_types.ts | 13 + .../data_stream_selector/constants.tsx | 6 +- .../data_stream_selector.tsx | 14 +- .../sub_components/data_streams_list.tsx | 24 +- .../sub_components/search_controls.tsx | 33 +- .../custom_data_stream_selector.tsx | 20 +- .../public/hooks/use_data_streams.ts | 2 +- .../public/hooks/use_integrations.ts | 4 +- .../data_streams/data_streams_client.ts | 32 +- .../integrations/src/state_machine.ts | 2 +- .../observability_logs/server/routes/index.ts | 482 +++++++++++++++++- 12 files changed, 570 insertions(+), 83 deletions(-) diff --git a/x-pack/plugins/observability_logs/common/data_streams/v1/common.ts b/x-pack/plugins/observability_logs/common/data_streams/v1/common.ts index ae65786dab1e6..552814c5224dd 100644 --- a/x-pack/plugins/observability_logs/common/data_streams/v1/common.ts +++ b/x-pack/plugins/observability_logs/common/data_streams/v1/common.ts @@ -26,24 +26,9 @@ export type IntegrationId = `integration-${string}-${string}`; /** * Getters */ - export const getIntegrationId = (integration: Integration): IntegrationId => `integration-${integration.name}-${integration.version}`; -export const getDataStreamsUrl = (search = {}) => { - const cleanSearch = omitBy(search, isEmpty); - const querySearch = new URLSearchParams(cleanSearch).toString(); - - return [DATA_STREAMS_URL, querySearch].filter(Boolean).join('?'); -}; - -export const getIntegrationsUrl = (search = {}) => { - const cleanSearch = stringifyByProp(omitBy(search, isEmpty), ['searchAfter']); - const querySearch = new URLSearchParams(cleanSearch).toString(); - - return [INTEGRATIONS_URL, querySearch].filter(Boolean).join('?'); -}; - /** * Utils */ @@ -53,3 +38,9 @@ function stringifyByProp( ) { return mapValues(obj, (val, key) => (props.includes(key) ? JSON.stringify(val) : val)); } + +export const formatSearch = ( + search: FindIntegrationsRequestQuery | FindDataStreamsRequestQuery +) => { + return stringifyByProp(omitBy(search, isEmpty), ['searchAfter']); +}; diff --git a/x-pack/plugins/observability_logs/common/runtime_types.ts b/x-pack/plugins/observability_logs/common/runtime_types.ts index d6d0336eafdd7..71d0031a3ed4b 100644 --- a/x-pack/plugins/observability_logs/common/runtime_types.ts +++ b/x-pack/plugins/observability_logs/common/runtime_types.ts @@ -51,3 +51,16 @@ export const decodeOrThrow = ) => (inputValue: InputValue) => pipe(runtimeType.decode(inputValue), fold(throwErrors(createError), identity)); + +export const createValidationFunction = + ( + runtimeType: Type + ): RouteValidationFunction => + (inputValue, { badRequest, ok }) => + pipe( + runtimeType.decode(inputValue), + fold>( + (errors: Errors) => badRequest(formatErrors(errors)), + (result: DecodedValue) => ok(result) + ) + ); diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/constants.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/constants.tsx index 74c85abc25b96..14d03c03499f9 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/constants.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/constants.tsx @@ -15,9 +15,9 @@ export const DATA_VIEW_POPOVER_CONTENT_WIDTH = 300; export const contextMenuStyles = { maxHeight: 320 }; -export const selectViewLabel = i18n.translate( - 'xpack.observabilityLogs.dataStreamSelector.selectView', - { defaultMessage: 'Select view' } +export const selectDatasetLabel = i18n.translate( + 'xpack.observabilityLogs.dataStreamSelector.selectDataset', + { defaultMessage: 'Select dataset' } ); export const integrationsLabel = i18n.translate( diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx index 6323772a3d281..f0bc88beb6a35 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx @@ -17,7 +17,7 @@ import { DATA_VIEW_POPOVER_CONTENT_WIDTH, integrationsLabel, INTEGRATION_PANEL_ID, - selectViewLabel, + selectDatasetLabel, uncategorizedLabel, UNCATEGORIZED_STREAMS_PANEL_ID, } from './constants'; @@ -35,17 +35,17 @@ const DataStreamsList = dynamic(() => import('./sub_components/data_streams_list }); export function DataStreamSelector({ - title, + dataStreams, + dataStreamsError, integrations, isLoadingIntegrations, - onSearch, + isLoadingStreams, onIntegrationsLoadMore, + onSearch, onStreamSelected, - dataStreamsError, - isLoadingStreams, onStreamsEntryClick, onStreamsReload, - dataStreams, + title, }: DataStreamSelectorProps) { const [isPopoverOpen, { off: closePopover, toggle: togglePopover }] = useBoolean(false); @@ -125,7 +125,7 @@ export function DataStreamSelector({ closePopover={closePopover} onClick={togglePopover} > - + ; } - if (isEmpty) { - return ( - {noDataStreamsLabel}} - titleSize="s" - body={

{noDataStreamsDescriptionLabel}

} - /> - ); - } - if (hasError) { return ( {noDataStreamsLabel}} + titleSize="s" + body={

{noDataStreamsDescriptionLabel}

} + /> + ); + } + return ( <> {dataStreams.map((stream) => ( diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/search_controls.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/search_controls.tsx index abf4046d7c882..40d6baa6bdcfb 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/search_controls.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/search_controls.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useReducer } from 'react'; +import React, { useCallback, useEffect, useReducer } from 'react'; import { EuiButtonGroup, EuiFieldSearch, EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; import { SearchStrategy } from '../../../../common/data_streams'; import { SortOrder } from '../../../../common'; @@ -50,19 +50,29 @@ export const useSearchStrategy = ({ id, onSearch }: UseSearchStrategyOptions) => const [searchCache, insertSearch] = useReducer(searchCacheReducer, initialCache); const search = searchCache[id] ?? defaultSearch; - console.log({ id, searchCache, search }); - const handleSearch = (params: SearchControlsParams) => { - const strategy = getSearchStrategy(id); + const handleSearch = useCallback( + (params: SearchControlsParams) => { + const strategy = getSearchStrategy(id); - insertSearch({ id, params }); + insertSearch({ id, params }); - return onSearch({ - ...params, - strategy, - ...(strategy === SearchStrategy.INTEGRATIONS_DATA_STREAMS && { integrationId: id }), - }); - }; + return onSearch({ + ...params, + strategy, + ...(strategy === SearchStrategy.INTEGRATIONS_DATA_STREAMS && { integrationId: id }), + }); + }, + [id, onSearch] + ); + + // Restore the search result when navigating into an existing search + // This should be cached so it'll be transparent to the user + useEffect(() => { + if (searchCache[id]) { + handleSearch(searchCache[id]); + } + }, [id]); return [search, handleSearch] as [SearchControlsParams, SearchControlsHandler]; }; @@ -94,6 +104,7 @@ export const SearchControls = ({ search, onSearch, isLoading }: SearchControlsPr diff --git a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx index 0fb053b9c5247..3c52b2f8b54e9 100644 --- a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React from 'react'; +import React, { useCallback } from 'react'; import { DiscoverStateContainer } from '@kbn/discover-plugin/public'; import { DataStream, SearchStrategy } from '../../common/data_streams'; import { DataStreamSelector, SearchHandler } from '../components/data_stream_selector'; @@ -49,17 +49,19 @@ export const CustomDataStreamSelector = withProviders(({ stateContainer }) => { }); }; - const handleSearch: SearchHandler = (params) => { - if (params.strategy === SearchStrategy.DATA_STREAMS) { - return searchDataStreams({ name: params.name, sortOrder: params.sortOrder }); - } else { - searchIntegrations(params); - } - }; + const handleSearch: SearchHandler = useCallback( + (params) => { + if (params.strategy === SearchStrategy.DATA_STREAMS) { + return searchDataStreams({ name: params.name, sortOrder: params.sortOrder }); + } else { + searchIntegrations(params); + } + }, + [searchDataStreams, searchIntegrations] + ); return ( { sortOrder: searchParams.sortOrder, }, }), - [dataStreamsStateService, search] + [dataStreamsStateService, search.name] ); return { diff --git a/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts b/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts index a20ddb3c6a217..5563d31896166 100644 --- a/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts +++ b/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts @@ -60,7 +60,7 @@ const useIntegrations = ({ dataStreamsClient }: IntegrationsContextDeps) => { (searchParams) => integrationsStateService.send({ type: 'SEARCH', - delay: search.name !== searchParams.name ? 500 : 0, + delay: search.name !== searchParams.name ? 300 : 0, search: { nameQuery: searchParams.name, strategy: searchParams.strategy, @@ -68,7 +68,7 @@ const useIntegrations = ({ dataStreamsClient }: IntegrationsContextDeps) => { integrationId: searchParams.integrationId, }, }), - [integrationsStateService, search] + [integrationsStateService, search.name] ); const loadMore = useCallback( diff --git a/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_client.ts b/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_client.ts index 1ae090d51256f..21dc61137a9a9 100644 --- a/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_client.ts +++ b/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_client.ts @@ -9,6 +9,8 @@ import { HttpStart } from '@kbn/core/public'; import { FindDataStreamsError, FindIntegrationsError } from '../../../common/data_streams/errors'; import { decodeOrThrow } from '../../../common/runtime_types'; import { + formatSearch, + DATA_STREAMS_URL, FindDataStreamsRequestQuery, findDataStreamsRequestQueryRT, FindDataStreamsResponse, @@ -17,8 +19,7 @@ import { findIntegrationsRequestQueryRT, FindIntegrationsResponse, findIntegrationsResponseRT, - getDataStreamsUrl, - getIntegrationsUrl, + INTEGRATIONS_URL, } from '../../../common'; import { IDataStreamsClient } from './types'; @@ -29,6 +30,7 @@ const defaultIntegrationsParams = { const defaultDataStreamsParams = { type: 'logs', + uncategorisedOnly: true, }; export class DataStreamsClient implements IDataStreamsClient { @@ -45,14 +47,11 @@ export class DataStreamsClient implements IDataStreamsClient { new FindIntegrationsError(`Failed to decode integrations search param: ${message}"`) )(search); - // const response = await this.http.get(getIntegrationsUrl(decodedSearch)).catch((error) => { - // throw new FindIntegrationsError(`Failed to fetch integrations": ${error}`); - // }); - const response = await fetch('http://localhost:3000' + getIntegrationsUrl(decodedSearch)) - .then((res) => res.json()) - .catch((error) => { - throw new FindIntegrationsError(`Failed to fetch integrations": ${error}`); - }); + const query = formatSearch(decodedSearch); + + const response = await this.http.get(INTEGRATIONS_URL, { query }).catch((error) => { + throw new FindIntegrationsError(`Failed to fetch integrations": ${error}`); + }); const data = decodeOrThrow( findIntegrationsResponseRT, @@ -74,14 +73,11 @@ export class DataStreamsClient implements IDataStreamsClient { new FindDataStreamsError(`Failed to decode data streams search param: ${message}"`) )(search); - // const response = await this.http.get(getDataStreamsUrl(decodedSearch)).catch((error) => { - // throw new FindDataStreamsError(`Failed to fetch data streams": ${error}`); - // }); - const response = await fetch('http://localhost:3000' + getDataStreamsUrl(decodedSearch)) - .then((res) => res.json()) - .catch((error) => { - throw new FindDataStreamsError(`Failed to decode data streams response: ${error}"`); - }); + const query = formatSearch(decodedSearch); + + const response = await this.http.get(DATA_STREAMS_URL, { query }).catch((error) => { + throw new FindDataStreamsError(`Failed to fetch data streams": ${error}`); + }); const data = decodeOrThrow( findDataStreamsResponseRT, diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts index 13cafb1d30a26..e45e083020b16 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts @@ -262,7 +262,7 @@ const searchIntegrationStreams = ( ...integration, // Filter and sort the dataStreams by the search criteria dataStreams: new EntityList(integration.dataStreams) - .filterBy((stream) => stream.name.includes(nameQuery ?? '')) + .filterBy((stream) => Boolean(stream.title?.includes(nameQuery ?? ''))) .sortBy('name', sortOrder) .build(), }; diff --git a/x-pack/plugins/observability_logs/server/routes/index.ts b/x-pack/plugins/observability_logs/server/routes/index.ts index 449d5ec992e4d..a9dce118075b0 100644 --- a/x-pack/plugins/observability_logs/server/routes/index.ts +++ b/x-pack/plugins/observability_logs/server/routes/index.ts @@ -5,20 +5,494 @@ * 2.0. */ +/* eslint-disable prefer-const */ + import { IRouter } from '@kbn/core/server'; +import { schema } from '@kbn/config-schema'; +import { FindIntegrationsRequestQuery } from '../../common'; export function defineRoutes(router: IRouter) { router.get( { - path: '/api/observability_logs/example', - validate: false, + path: '/api/fleet/epm/packages/installed', + validate: { + query: schema.object({ + dataStreamType: schema.maybe( + schema.oneOf([ + schema.literal('logs'), + schema.literal('metrics'), + schema.literal('traces'), + schema.literal('synthetics'), + schema.literal('profiling'), + ]) + ), + nameQuery: schema.maybe(schema.string()), + searchAfter: schema.maybe( + schema.arrayOf(schema.oneOf([schema.string(), schema.number()])) + ), + perPage: schema.number({ defaultValue: 10 }), + sortOrder: schema.oneOf([schema.literal('asc'), schema.literal('desc')], { + defaultValue: 'asc', + }), + }), + }, + }, + async (_context, request, response) => { + await new Promise((res) => setTimeout(res, 500)); + let { + nameQuery = '', + perPage = 10, + searchAfter = '', + sortOrder, + } = request.query as FindIntegrationsRequestQuery; + + let filteredPackages = items.filter((pkg) => pkg.name.includes(nameQuery)); + if (sortOrder === 'desc') { + filteredPackages.sort((a, b) => b.name.localeCompare(a.name)); + } else { + filteredPackages.sort((a, b) => a.name.localeCompare(b.name)); + } + + const searchAfterIndex = searchAfter[0] + ? filteredPackages.findIndex((pkg) => pkg.name === searchAfter[0]) + : -1; + if (searchAfterIndex >= 0) { + filteredPackages = filteredPackages.slice( + searchAfterIndex + 1, + searchAfterIndex + perPage + 1 + ); + } else { + filteredPackages = filteredPackages.slice(0, perPage); + } + + return response.ok({ + body: { + total: items.length, + searchAfter: filteredPackages.length + ? [filteredPackages[filteredPackages.length - 1].name] + : undefined, + items: filteredPackages, + }, + }); + } + ); + + router.get( + { + path: '/api/fleet/epm/data_streams', + validate: { + query: schema.object({ + type: schema.maybe( + schema.oneOf([ + schema.literal('logs'), + schema.literal('metrics'), + schema.literal('traces'), + schema.literal('synthetics'), + schema.literal('profiling'), + ]) + ), + datasetQuery: schema.maybe(schema.string()), + sortOrder: schema.oneOf([schema.literal('asc'), schema.literal('desc')], { + defaultValue: 'asc', + }), + uncategorisedOnly: schema.boolean({ defaultValue: false }), + }), + }, }, - async (context, request, response) => { + async (_context, request, response) => { + await new Promise((res) => setTimeout(res, 1000)); + const { datasetQuery = '', sortOrder } = request.query; + + const filteredPackages = streams.filter((pkg) => pkg.name.includes(datasetQuery)); + if (sortOrder === 'desc') { + filteredPackages.sort((a, b) => b.name.localeCompare(a.name)); + } else { + filteredPackages.sort((a, b) => a.name.localeCompare(b.name)); + } + return response.ok({ body: { - time: new Date().toISOString(), + items: filteredPackages, }, }); } ); } + +const items = [ + { + name: 'system', + version: '1.25.2', + status: 'installed', + dataStreams: [ + { + title: 'System metrics stream', + name: 'system-metrics-*', + }, + { + title: 'System logs stream', + name: 'system-logs-*', + }, + ], + }, + { + name: 'kubernetes', + version: '1.35.0', + status: 'installed', + dataStreams: [ + { + title: 'Kubernetes metrics stream', + name: 'k8s-metrics-*', + }, + { + title: 'Kubernetes logs stream', + name: 'k8s-logs-*', + }, + ], + }, + { + name: 'mysql', + version: '1.11.0', + status: 'installed', + dataStreams: [ + { + title: 'MySQL metrics stream', + name: 'mysql-metrics-*', + }, + { + title: 'MySQL slow logs stream', + name: 'mysql-slow-logs-*', + }, + { + title: 'MySQL error logs stream', + name: 'mysql-error-logs-*', + }, + ], + }, + { + name: 'apache', + version: '1.12.0', + status: 'installed', + dataStreams: [ + { + title: 'Apache metrics stream', + name: 'apache-metrics-*', + }, + { + title: 'Apache logs stream', + name: 'apache-logs-*', + }, + { + title: 'Apache error logs stream', + name: 'apache-error-logs-*', + }, + ], + }, + { + name: 'nginx', + version: '1.11.1', + status: 'installed', + dataStreams: [ + { + title: 'Nginx metrics stream', + name: 'nginx-metrics-*', + }, + { + title: 'Nginx access logs stream', + name: 'nginx-access-logs-*', + }, + ], + }, + { + name: 'postgresql', + version: '1.13.0', + status: 'installed', + dataStreams: [ + { + title: 'PostgreSQL metrics stream', + name: 'postgresql-metrics-*', + }, + { + title: 'PostgreSQL slow query logs stream', + name: 'postgresql-slow-query-logs-*', + }, + { + title: 'PostgreSQL error logs stream', + name: 'postgresql-error-logs-*', + }, + ], + }, + { + name: 'rabbitmq', + version: '1.8.8', + status: 'installed', + dataStreams: [ + { + title: 'RabbitMQ metrics stream', + name: 'rabbitmq-metrics-*', + }, + { + title: 'RabbitMQ queues stream', + name: 'rabbitmq-queues-*', + }, + { + title: 'RabbitMQ error logs stream', + name: 'rabbitmq-error-logs-*', + }, + ], + }, + { + name: 'redis', + version: '1.9.2', + status: 'installed', + dataStreams: [ + { + title: 'Redis metrics stream', + name: 'redis-metrics-*', + }, + { + title: 'Redis slow logs stream', + name: 'redis-slow-logs-*', + }, + ], + }, + { + name: 'elasticsearch', + version: '1.5.0', + status: 'installed', + dataStreams: [ + { + title: 'Elasticsearch metrics stream', + name: 'elasticsearch-metrics-*', + }, + { + title: 'Elasticsearch indices stream', + name: 'elasticsearch-indices-*', + }, + ], + }, + { + name: 'mongodb', + version: '1.9.3', + status: 'installed', + dataStreams: [ + { + title: 'MongoDB metrics stream', + name: 'mongodb-metrics-*', + }, + { + title: 'MongoDB slow query logs stream', + name: 'mongodb-slow-query-logs-*', + }, + ], + }, + { + name: 'prometheus', + version: '1.3.2', + status: 'installed', + dataStreams: [ + { + title: 'Prometheus metrics stream', + name: 'prometheus-metrics-*', + }, + ], + }, + { + name: 'haproxy', + version: '1.5.1', + status: 'installed', + dataStreams: [ + { + title: 'HAProxy metrics stream', + name: 'haproxy-metrics-*', + }, + { + title: 'HAProxy logs stream', + name: 'haproxy-logs-*', + }, + ], + }, + { + name: 'atlassian_jira', + version: '1.10.0', + status: 'installed', + dataStreams: [ + { title: 'Atlassian metrics stream', name: 'metrics-*' }, + { title: 'Atlassian secondary', name: 'metrics-*' }, + ], + }, + { + name: 'atlassian_confluence', + version: '1.10.0', + status: 'installed', + dataStreams: [ + { title: 'Atlassian metrics stream', name: 'metrics-*' }, + { title: 'Atlassian secondary', name: 'metrics-*' }, + ], + }, + { + name: 'atlassian_bitbucket', + version: '1.9.0', + status: 'installed', + dataStreams: [ + { title: 'Atlassian metrics stream', name: 'metrics-*' }, + { title: 'Atlassian secondary', name: 'metrics-*' }, + ], + }, + { + name: 'docker', + version: '2.4.3', + status: 'installed', + dataStreams: [ + { title: 'Docker container logs', name: 'docker-*' }, + { title: 'Docker daemon logs', name: 'docker-daemon-*' }, + ], + }, + { + name: 'aws', + version: '1.36.3', + status: 'installed', + dataStreams: [ + { title: 'AWS S3 object access logs', name: 'aws-s3-access-' }, + { title: 'AWS S3 bucket access logs', name: 'aws-s3-bucket-access-' }, + ], + }, + { + name: 'cassandra', + version: '1.6.0', + status: 'installed', + dataStreams: [ + { title: 'Cassandra server logs', name: 'cassandra-' }, + { title: 'Cassandra slow queries', name: 'cassandra-slow-' }, + { title: 'Cassandra errors', name: 'cassandra-errors-' }, + ], + }, + { + name: 'nginx_ingress_controller', + version: '1.7.1', + status: 'installed', + dataStreams: [{ title: 'Nginx ingress logs', name: 'nginx-ingress-' }], + }, + { + name: 'gcp', + version: '2.20.1', + status: 'installed', + dataStreams: [{ title: 'GCP Stackdriver logs', name: 'gcp-stackdriver-*' }], + }, + { + name: 'kafka', + version: '1.5.6', + status: 'installed', + dataStreams: [{ title: 'Kafka server logs', name: 'kafka-*' }], + }, + { + name: 'kibana', + version: '2.3.4', + status: 'installed', + dataStreams: [{ title: 'Kibana server logs', name: 'kibana-*' }], + }, +]; + +const streams = [ + { name: 'logs-*' }, + { name: 'system-logs-*' }, + { name: 'nginx-logs-*' }, + { name: 'apache-logs-*' }, + { name: 'security-logs-*' }, + { name: 'error-logs-*' }, + { name: 'access-logs-*' }, + { name: 'firewall-logs-*' }, + { name: 'application-logs-*' }, + { name: 'debug-logs-*' }, + { name: 'transaction-logs-*' }, + { name: 'audit-logs-*' }, + { name: 'server-logs-*' }, + { name: 'database-logs-*' }, + { name: 'event-logs-*' }, + { name: 'auth-logs-*' }, + { name: 'billing-logs-*' }, + { name: 'network-logs-*' }, + { name: 'performance-logs-*' }, + { name: 'email-logs-*' }, + { name: 'job-logs-*' }, + { name: 'task-logs-*' }, + { name: 'user-logs-*' }, + { name: 'request-logs-*' }, + { name: 'payment-logs-*' }, + { name: 'inventory-logs-*' }, + { name: 'debugging-logs-*' }, + { name: 'scheduler-logs-*' }, + { name: 'diagnostic-logs-*' }, + { name: 'cluster-logs-*' }, + { name: 'service-logs-*' }, + { name: 'framework-logs-*' }, + { name: 'api-logs-*' }, + { name: 'load-balancer-logs-*' }, + { name: 'reporting-logs-*' }, + { name: 'backend-logs-*' }, + { name: 'frontend-logs-*' }, + { name: 'chat-logs-*' }, + { name: 'error-tracking-logs-*' }, + { name: 'payment-gateway-logs-*' }, + { name: 'auth-service-logs-*' }, + { name: 'billing-service-logs-*' }, + { name: 'database-service-logs-*' }, + { name: 'api-gateway-logs-*' }, + { name: 'event-service-logs-*' }, + { name: 'notification-service-logs-*' }, + { name: 'search-service-logs-*' }, + { name: 'logging-service-logs-*' }, + { name: 'performance-service-logs-*' }, + { name: 'load-testing-logs-*' }, + { name: 'mobile-app-logs-*' }, + { name: 'web-app-logs-*' }, + { name: 'stream-processing-logs-*' }, + { name: 'batch-processing-logs-*' }, + { name: 'cloud-service-logs-*' }, + { name: 'container-logs-*' }, + { name: 'serverless-logs-*' }, + { name: 'server-administration-logs-*' }, + { name: 'application-deployment-logs-*' }, + { name: 'webserver-logs-*' }, + { name: 'payment-processor-logs-*' }, + { name: 'inventory-service-logs-*' }, + { name: 'data-pipeline-logs-*' }, + { name: 'frontend-service-logs-*' }, + { name: 'backend-service-logs-*' }, + { name: 'resource-monitoring-logs-*' }, + { name: 'logging-aggregation-logs-*' }, + { name: 'container-orchestration-logs-*' }, + { name: 'security-audit-logs-*' }, + { name: 'api-management-logs-*' }, + { name: 'service-mesh-logs-*' }, + { name: 'data-processing-logs-*' }, + { name: 'data-science-logs-*' }, + { name: 'machine-learning-logs-*' }, + { name: 'experimentation-logs-*' }, + { name: 'data-visualization-logs-*' }, + { name: 'data-cleaning-logs-*' }, + { name: 'data-transformation-logs-*' }, + { name: 'data-analysis-logs-*' }, + { name: 'data-storage-logs-*' }, + { name: 'data-retrieval-logs-*' }, + { name: 'data-warehousing-logs-*' }, + { name: 'data-modeling-logs-*' }, + { name: 'data-integration-logs-*' }, + { name: 'data-quality-logs-*' }, + { name: 'data-security-logs-*' }, + { name: 'data-encryption-logs-*' }, + { name: 'data-governance-logs-*' }, + { name: 'data-compliance-logs-*' }, + { name: 'data-privacy-logs-*' }, + { name: 'data-auditing-logs-*' }, + { name: 'data-discovery-logs-*' }, + { name: 'data-protection-logs-*' }, + { name: 'data-archiving-logs-*' }, + { name: 'data-backup-logs-*' }, + { name: 'data-recovery-logs-*' }, + { name: 'data-replication-logs-*' }, + { name: 'data-synchronization-logs-*' }, + { name: 'data-migration-logs-*' }, + { name: 'data-load-balancing-logs-*' }, + { name: 'data-scaling-logs-*' }, +]; From 8fbce8cf4bdacc02683ef92c06cebefadec7b69f Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Wed, 24 May 2023 11:52:38 +0200 Subject: [PATCH 105/122] refactor(observability_logs): store initial search in cache --- .../public/state_machines/integrations/src/defaults.ts | 2 ++ .../public/state_machines/integrations/src/state_machine.ts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/defaults.ts b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/defaults.ts index 347b0ca648338..30d4bbac03a1a 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/defaults.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/defaults.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { SearchStrategy } from '../../../../common/data_streams'; import { ImmutableCache } from '../../../../common/immutable_cache'; import { DefaultIntegrationsContext } from './types'; @@ -16,5 +17,6 @@ export const DEFAULT_CONTEXT: DefaultIntegrationsContext = { search: { nameQuery: '', sortOrder: 'asc', + strategy: SearchStrategy.INTEGRATIONS, }, }; diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts index e45e083020b16..4c585fb8a4cac 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts @@ -23,7 +23,7 @@ import { export const createPureIntegrationsStateMachine = ( initialContext: DefaultIntegrationsContext = DEFAULT_CONTEXT ) => - /** @xstate-layout N4IgpgJg5mDOIC5QEkB2AXMUBOBDdAlgPaqwB0ArqgdYbgDYEBekAxANoAMAuoqAA5FYBQiT4gAHogCMADgDMZWdIBMAVk5rlAFgDsagJwAaEAE9EG3Uum7dKzrO1rtm2QDYAvh5NpMOfMSkZPREuBA0UKwAMgDyAIIAIsgAcgDiAPoAygCqAMK5AKIFCcVcvEgggsKiqOJSCGrqZNI29trSBtJaJuYI8vLSZGrObu6yanbao14+GFh4NeQhYRHR8Ulp6QBicchRpTziVSKBdRZNLXac7Z3dZueyZAbaKro2avIuU7ozIL7zARIS1C4VQUAAskRsGA1okUhkcvkiiUEmUjkITmIKvVpJxdAYyG9ZLZOP1ZCoOsZ7gh1IMVNoDAZZI5OM8DG4VL9-v5FsEQRFIdDYRsMjs9gdygIMTUzggutpFAyVPY3M9WbjpD1EC5FLiDPJdOS3PKJly5jzAsCwmxYol0uCYgAlArpFIAFQKqUdcTdyBiyUyaIqxxl2MQxIJ8nJ+gMuj0CjcWr6jLIblZ8mc8jxnBNZr8C0tfOtEFYmQKcUduQAEkGpdVTmGEBGyFHXoY44b5InqfZOISNJxrhMNI1VHmAbyIGAAEZEKgAYwimTAuGw84AFqXy5Wa4dg9KG6B6uo3NoyMrVU4OXjDbokwrHh1XpxlcSHNptOOLUCyFPZwulxXNdNwkWB0HwMAyFwAAzTBsAACjLCtq3SEoojiABNABKVhuQLH8-znVBFzBZdVw3WtKgPLEj0QClbBba4DBUNVtGUJN8UfJx8XUZ45AMNQv3woJCIA0igI3VhQPAzAoNgsAEKQndUIKdDsNw81hPIUTiMA8j13YaRJSo+saMkOj7AJXQ3AmU8HFjOMk2UIZB0HWxmVjD8fm8P5NMBIJYAk9cl3QaFcAAW1gLdkKrLI8kKYoJXRUzakbRo1GaVprg6LpZCTCYCQ-Z9pC7RwDAcIT-PIQL9JCsLIui5SxX2VE9zrTFUtohoLiym5cqTNx9CUATmTePRVDYyreWWUEoC2XACHoG11ntJ0XXdT1vV9f1AzakyOtleVFWYlU1U6HMkwzFRU3TVRyQK9l5CmwsZoiebFrYJTq0okND3MmkbCsLMlVY9jqTUaQ3FTZjBsNB9rIMLwfNQIgp3gCo8Kq5KDsbABabtenxshXJJ0m3Oen8qBoE4GGYSBsdDLqXnyzgoeUWx1DGlRjU-HzMem-kwQZv7j0NYbHAzWM01Ve8FBbQH+kZHKsyevm-IFlYwUFMBhbMnEsysV5PijOwPkZS63F1Zi43oilXjjCmghm+n9xS2VrL7Zj2XxdLSXaS7ZAJFRjaVhlNA+R3tJnIiSKgMjgN1zr-vUYPCWUEdpDY4PNWpBlFFVbn3FULoVHGXnZnzKqyBq4C6pXSLE9lUuoYmZQowZTpZCHDiPiGewFAVO2u28iuJxewW5oWpaIEbxsOmc1tvYhhy1HvQbodeSGWnaNQOW8rwgA */ + /** @xstate-layout N4IgpgJg5mDOIC5QEkB2AXMUBOBDdAlgPaqwB0ArqgdYbgDYEBekAxANoAMAuoqAA5FYBQiT4gAHogCMADgDMZWdIBMAVnnyAnFtWctKgDQgAnojX6l0gOyyNAFjWyAbDukBfd8bSYc+YqRk9ES4EDRQrAAyAPIAggAiyAByAOIA+gDKAKoAwjkAovnxRVy8SCCCwqKo4lIIaupk0jYq9q52Wtac1sZmCJqKamr21lrOmlqcKuOe3hhYeNXkwaHhUXGJqWkAYrHIkSU84pUiAbXmjc3Wre1qnd295vKcZFr2KrIjTpzPP7MgPgW-hIyxCYVQUAAskRsGB1glkulsnlCsV4qVjkJTmJynVpN1rGRrHIFHJnJx3mpHgh1NIyOpOJwhnJ5NJnCppGp-oC-EsgmDwtDYfDNuldvtDmUBFjqucEJz7Ip7AZnm9Js4bM5qfYKU19BYKTr7IrZNZufNeQFQaE2DEEmlIdEAEr5NLJAAq+RSTti7uQ0SSGQx5ROstxiFNWjI8lkH0+nGcrhU8i1pkQOrp5Le7NjLmk8nNvkWVv5NogrAy+ViTpyAAlg9KqmdwwhI9Hc-HEyrU30VIyicqtApnjGEwnC0C+RAwAAjIhUADG4QyYFw2AXAAsK1Wa-WjiGZc3QHV1M57PTpl0VNYr-I2tqdU0DC5Psm1DYtFyvACLcWQWRpznRdl1XdctwkWB0HwMAyFwAAzTBsAACkras6zSYpIliABNABKVgeT-QJAPnVAlwhFc103BsKkPHFj0QDkb2jClnBcIYPgpalZH7aRHF0T4XHkaw7gnS1-xI4CKNAzdWAgqDMFghCwGQ1Ddww-IsLwgjf2BYjZ1I8ioEosD2GkKVaKbejJEYvso2sZw1Ec1o5GGaRuLpQch1aH5NEcM1v0IvTyFgGSN2XdBYVwABbWBtzQ2tMlyAoiklTErJqFsGjUJoWjaIc7i6Ho03qZQyGND4KvkO43gCuYi2CshQqo8KKMi1dYvi9TxQOdF90bbFMoY+pLjy257mKvpnGY2RPwMa9WU5AtAt0vkVnBKBtlwAh6FtDYHWdV0PS9H0-QDIN+sswa5QVJUVX0ZUE01alqsULMLGEz5rGVL96snEt1vCLadrYNS6xo0MjxsmkbEJZ42nYhoePsal31kMgk0+IdlVZWbPG-VAiGneByiCpZ0uulsAFoe0QKmcp0RmmaZ0YxKI8gqBoU4GGYSAKbDYb3lRzh0eUWw5HsaQ1WmNnGsBiF+ahk9bCUbGHO6VkfiMEr7DsaMbFNVQNQ5VxZbWgUISFMBFesvFnkJBb306YkFHvEqUxyqXOONWQ7FsZa-vEwJ1r5g8MrldXXhUToNCKtRvqpd3dHpO8LFUByGj+FaGqnAypOMsKbaG6H1GTIllDvVxOirxO+h+jHn0c7oDFUYSzZLZqwIiqLYqLuUPmcMh47F9RhK0eRWmpEScszt4Rm0XQ2Pb-95c27bdogPuWylsqYwHnQXem7VXAbj47gaFR1F0ex8fcIA */ createMachine( { context: initialContext, From 2944a26efd6d9577e90eae0a00ced581314663a3 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Wed, 24 May 2023 16:39:54 +0200 Subject: [PATCH 106/122] refactor(observability_logs): integrations status --- .../data_stream_selector/constants.tsx | 15 +- .../data_stream_selector.tsx | 27 ++- .../integrations_list_status.tsx | 72 +++++++ .../components/data_stream_selector/types.ts | 4 + .../custom_data_stream_selector.tsx | 4 + .../public/hooks/use_integrations.ts | 6 + .../integrations/src/state_machine.ts | 6 +- .../state_machines/integrations/src/types.ts | 3 + .../observability_logs/server/routes/index.ts | 190 +++++++++--------- 9 files changed, 223 insertions(+), 104 deletions(-) create mode 100644 x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/integrations_list_status.tsx diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/constants.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/constants.tsx index 14d03c03499f9..91ef4cf35bd87 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/constants.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/constants.tsx @@ -13,7 +13,7 @@ export const UNCATEGORIZED_STREAMS_PANEL_ID = 'uncategorized_streams_panel'; export const DATA_VIEW_POPOVER_CONTENT_WIDTH = 300; -export const contextMenuStyles = { maxHeight: 320 }; +export const contextMenuStyles = { maxHeight: 440 }; export const selectDatasetLabel = i18n.translate( 'xpack.observabilityLogs.dataStreamSelector.selectDataset', @@ -48,6 +48,19 @@ export const noDataStreamsDescriptionLabel = i18n.translate( } ); +export const noIntegrationsLabel = i18n.translate( + 'xpack.observabilityLogs.dataStreamSelector.noIntegrations', + { defaultMessage: 'No integrations found' } +); + +export const noIntegrationsDescriptionLabel = i18n.translate( + 'xpack.observabilityLogs.dataStreamSelector.noIntegrationsDescription', + { + defaultMessage: + "Looks like you don't have integrations or your search does not match any of them.", + } +); + export const errorLabel = i18n.translate('xpack.observabilityLogs.dataStreamSelector.error', { defaultMessage: 'error', }); diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx index f0bc88beb6a35..b8df0712b71f0 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx @@ -33,14 +33,17 @@ import { buildIntegrationsTree, setIntegrationListSpy } from './utils'; const DataStreamsList = dynamic(() => import('./sub_components/data_streams_list'), { fallback: , }); +const IntegrationsListStatus = dynamic(() => import('./sub_components/integrations_list_status')); export function DataStreamSelector({ dataStreams, dataStreamsError, integrations, + integrationsError, isLoadingIntegrations, isLoadingStreams, onIntegrationsLoadMore, + onIntegrationsReload, onSearch, onStreamSelected, onStreamsEntryClick, @@ -74,9 +77,20 @@ export function DataStreamSelector({ panel: UNCATEGORIZED_STREAMS_PANEL_ID, }; + const createIntegrationStatusItem = () => ({ + disabled: true, + name: ( + + ), + }); + if (!integrations) { return { - items: [dataStreamsItem], + items: [dataStreamsItem, createIntegrationStatusItem()], panels: [], }; } @@ -88,11 +102,20 @@ export function DataStreamSelector({ setIntegrationListSpy(items, setSpyRef); + if (items.length === 0) items.push(createIntegrationStatusItem()); + return { items: [dataStreamsItem, ...items], panels, }; - }, [integrations, handleStreamSelection, onStreamsEntryClick, setSpyRef]); + }, [ + integrations, + integrationsError, + handleStreamSelection, + onIntegrationsReload, + onStreamsEntryClick, + setSpyRef, + ]); const panels = [ { diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/integrations_list_status.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/integrations_list_status.tsx new file mode 100644 index 0000000000000..00019e23aec93 --- /dev/null +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/integrations_list_status.tsx @@ -0,0 +1,72 @@ +/* + * 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 { EuiButton, EuiEmptyPrompt, EuiText, EuiToolTip } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import React from 'react'; +import { Integration } from '../../../../common/data_streams'; +import { + errorLabel, + noDataRetryLabel, + noDataStreamsLabel, + noIntegrationsDescriptionLabel, + noIntegrationsLabel, +} from '../constants'; + +interface DataStreamListProps { + integrations: Integration[] | null; + error: Error | null; + onRetry: () => void; +} + +export const IntegrationsListStatus = ({ integrations, error, onRetry }: DataStreamListProps) => { + const isEmpty = integrations == null || integrations.length <= 0; + const hasError = error !== null; + + if (hasError) { + return ( + {noIntegrationsLabel}} + titleSize="s" + body={ + + {errorLabel} + + ), + }} + /> + } + actions={[{noDataRetryLabel}]} + /> + ); + } + + if (isEmpty) { + return ( + {noIntegrationsLabel}} + titleSize="s" + body={

{noIntegrationsDescriptionLabel}

} + /> + ); + } + + return null; +}; + +// eslint-disable-next-line import/no-default-export +export default IntegrationsListStatus; diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/types.ts b/x-pack/plugins/observability_logs/public/components/data_stream_selector/types.ts index 68262c9da7271..8302557f22524 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/types.ts +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/types.ts @@ -15,6 +15,8 @@ export interface DataStreamSelectorProps { title: string; /* The integrations list, each integration includes its data streams */ integrations: Integration[] | null; + /* Any error occurred to show when the user preview the integrations */ + integrationsError?: Error | null; /* The generic data stream list */ dataStreams: DataStream[] | null; /* Any error occurred to show when the user preview the generic data streams */ @@ -27,6 +29,8 @@ export interface DataStreamSelectorProps { onSearch: SearchHandler; /* Triggered when we reach the bottom of the integration list and want to load more */ onIntegrationsLoadMore: LoadMoreIntegrations; + /* Triggered when we reach the bottom of the integration list and want to load more */ + onIntegrationsReload: LoadMoreIntegrations; /* Triggered when the uncategorized streams entry is selected */ onStreamsEntryClick: () => void; /* Triggered when retrying to load the data streams */ diff --git a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx index 3c52b2f8b54e9..a99e02c6c95db 100644 --- a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx @@ -25,9 +25,11 @@ export const CustomDataStreamSelector = withProviders(({ stateContainer }) => { const dataView = useDataView(); const { + error: integrationsError, integrations, isLoading: isLoadingIntegrations, loadMore, + reloadIntegrations, searchIntegrations, } = useIntegrationsContext(); @@ -65,9 +67,11 @@ export const CustomDataStreamSelector = withProviders(({ stateContainer }) => { dataStreams={dataStreams} dataStreamsError={dataStreamsError} integrations={integrations} + integrationsError={integrationsError} isLoadingIntegrations={isLoadingIntegrations} isLoadingStreams={isLoadingStreams} onIntegrationsLoadMore={loadMore} + onIntegrationsReload={reloadIntegrations} onSearch={handleSearch} onStreamSelected={handleStreamSelection} onStreamsEntryClick={loadDataStreams} diff --git a/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts b/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts index 5563d31896166..981baa04c3179 100644 --- a/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts +++ b/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts @@ -71,6 +71,11 @@ const useIntegrations = ({ dataStreamsClient }: IntegrationsContextDeps) => { [integrationsStateService, search.name] ); + const reloadIntegrations = useCallback( + () => integrationsStateService.send({ type: 'RELOAD_INTEGRATIONS' }), + [integrationsStateService] + ); + const loadMore = useCallback( () => integrationsStateService.send({ type: 'LOAD_MORE_INTEGRATIONS' }), [integrationsStateService] @@ -94,6 +99,7 @@ const useIntegrations = ({ dataStreamsClient }: IntegrationsContextDeps) => { // Actions loadMore, + reloadIntegrations, searchIntegrations, }; }; diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts index 4c585fb8a4cac..e9dab316b3eec 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts @@ -23,7 +23,7 @@ import { export const createPureIntegrationsStateMachine = ( initialContext: DefaultIntegrationsContext = DEFAULT_CONTEXT ) => - /** @xstate-layout N4IgpgJg5mDOIC5QEkB2AXMUBOBDdAlgPaqwB0ArqgdYbgDYEBekAxANoAMAuoqAA5FYBQiT4gAHogCMADgDMZWdIBMAVnnyAnFtWctKgDQgAnojX6l0gOyyNAFjWyAbDukBfd8bSYc+YqRk9ES4EDRQrAAyAPIAggAiyAByAOIA+gDKAKoAwjkAovnxRVy8SCCCwqKo4lIIaupk0jYq9q52Wtac1sZmCJqKamr21lrOmlqcKuOe3hhYeNXkwaHhUXGJqWkAYrHIkSU84pUiAbXmjc3Wre1qnd295vKcZFr2KrIjTpzPP7MgPgW-hIyxCYVQUAAskRsGB1glkulsnlCsV4qVjkJTmJynVpN1rGRrHIFHJnJx3mpHgh1NIyOpOJwhnJ5NJnCppGp-oC-EsgmDwtDYfDNuldvtDmUBFjqucEJz7Ip7AZnm9Js4bM5qfYKU19BYKTr7IrZNZufNeQFQaE2DEEmlIdEAEr5NLJAAq+RSTti7uQ0SSGQx5ROstxiFNWjI8lkH0+nGcrhU8i1pkQOrp5Le7NjLmk8nNvkWVv5NogrAy+ViTpyAAlg9KqmdwwhI9Hc-HEyrU30VIyicqtApnjGEwnC0C+RAwAAjIhUADG4QyYFw2AXAAsK1Wa-WjiGZc3QHV1M57PTpl0VNYr-I2tqdU0DC5Psm1DYtFyvACLcWQWRpznRdl1XdctwkWB0HwMAyFwAAzTBsAACkras6zSYpIliABNABKVgeT-QJAPnVAlwhFc103BsKkPHFj0QDkb2jClnBcIYPgpalZH7aRHF0T4XHkaw7gnS1-xI4CKNAzdWAgqDMFghCwGQ1Ddww-IsLwgjf2BYjZ1I8ioEosD2GkKVaKbejJEYvso2sZw1Ec1o5GGaRuLpQch1aH5NEcM1v0IvTyFgGSN2XdBYVwABbWBtzQ2tMlyAoiklTErJqFsGjUJoWjaIc7i6Ho03qZQyGND4KvkO43gCuYi2CshQqo8KKMi1dYvi9TxQOdF90bbFMoY+pLjy257mKvpnGY2RPwMa9WU5AtAt0vkVnBKBtlwAh6FtDYHWdV0PS9H0-QDIN+sswa5QVJUVX0ZUE01alqsULMLGEz5rGVL96snEt1vCLadrYNS6xo0MjxsmkbEJZ42nYhoePsal31kMgk0+IdlVZWbPG-VAiGneByiCpZ0uulsAFoe0QKmcp0RmmaZ0YxKI8gqBoU4GGYSAKbDYb3lRzh0eUWw5HsaQ1WmNnGsBiF+ahk9bCUbGHO6VkfiMEr7DsaMbFNVQNQ5VxZbWgUISFMBFesvFnkJBb306YkFHvEqUxyqXOONWQ7FsZa-vEwJ1r5g8MrldXXhUToNCKtRvqpd3dHpO8LFUByGj+FaGqnAypOMsKbaG6H1GTIllDvVxOirxO+h+jHn0c7oDFUYSzZLZqwIiqLYqLuUPmcMh47F9RhK0eRWmpEScszt4Rm0XQ2Pb-95c27bdogPuWylsqYwHnQXem7VXAbj47gaFR1F0ex8fcIA */ + /** @xstate-layout N4IgpgJg5mDOIC5QEkB2AXMUBOBDdAlgPaqwB0ArqgdYbgDYEBekAxANoAMAuoqAA5FYBQiT4gAHogCMADgDMZWZ3nyFAVgBsATnnaATNs0AaEAE9E6zrLIB2dfaPX5D7QBYAvh9NpMOfMSkZPREuBA0UKwAMgDyAIIAIsgAcgDiAPoAygCqAMK5AKIFCcVcvEgggsKiqOJSCOrS2mRunOpaerL6+tKqphYI3bZkKrbybg6yvZyctppePhhYeDXkIWER0fFJaekAYnHIUaU84lUigXWW+uojsuranJqcTa1usv3XzbK2Y5rSmi0Ux+CxAvmWARIa1C4VQUAAskRsGAtokUhkcvkiiUEmUzkILmIKvVpLNhpp5NJGvIKXM3J9Bo0yD1dLYnNppN1pKDwf5VsEYRFEcjUTsMgcjidygICTUrggqW5FDTbJy3LZZP8buoGa1FG0XrN-qr1IYeUs+YFoWE2LFEul4TEAEoFdIpAAqBVSTri7uQMWSmTxFXOcuJiB+zUpc1kbiaFKe+l1MzI7UNc2kJrN3jBFpWVoFNogrEyBTiTtyAAlgzLqpdwwhI2Ro5pY-H5ImGfoU7ZDFM3HHNIZVfJzX581CyBAwAAjIhUADGEUyYFw2AXAAsS2WK9XTiHZfXQPUbhSyNptJr9Jo3LoHH1zIglc1pAP9LZONo5ppVdyc7yJyCac50XZdV3XLcJFgdB8DAMhcAAM0wbAAApS3LKt0hKKI4gATQASlYADISA2d51QJc4RXNdNxrSpDyJY9EB6X4yE5FwvwpC9GgZNkbBHbRTSHdp320McIX5YDyMoqBqIg1goJgzB4KQsBUPQ3csIKHCCKIvMSPIKTQKo8DaOkaV6LrRjJGY7tmkmUldH0PQGSmFp7h-ON1HVd41HEy1J1gUyN2XdBkVwABbWBtwwyssjyQpiilfErNqBtTVuawHieF53E4d4GXUZRm00HRZE4-RgTE-99P5IKaJCqiwtXKKYs0iVjlxfda0JNKmIaG47my55Xnyj5HwQH9NBaVtGl6BQ3EqzwavHAzC1hKA9lwAh6FtbYHWdV0PS9H0-QDINuss3r5UVZUfzVDUtXaBkXH0VNnm8-RFqVd9ZH8wDrQ2radrYDSqzo0MjxswZM2GdiHjmPRBOkQrMzIIcLy-byHBxrwc1QIhp3gCpiNWFLrobABaEwJsp25Ma-TpbwHTz-rWqgaAuBhmEgcmw36xbCtmOwtF0TVOEMak2f5dYNr5qGTw1JRbFvH5TT0Ad1V1WNmWeKYWNjdQ9FHFaJILWWhSRMB5eskkOzhrRuwxp5AXkF6bzYt5emyrpOXUaXzZhXmD1S+U5k4ZWVUaB5nLGF7HjY34VaT9pxgBAPJyMiiwIam2+uhm5nNTRbZC6AdSVvXV3HR1RyseLp7pNxZVrq4LQvCqK8-lSrpvaXsDBUH8xjjib7DcEYjCmNkZjkfKM6CC24WB3aIC7hsmjc7sgUHZzSqTCa3FbGuNQljs9GvZavCAA */ createMachine( { context: initialContext, @@ -105,6 +105,7 @@ export const createPureIntegrationsStateMachine = ( cond: 'hasMoreIntegrations', target: 'loadingMore', }, + RELOAD_INTEGRATIONS: 'loading', SEARCH: 'debouncingSearch', }, }, @@ -147,6 +148,8 @@ export const createPureIntegrationsStateMachine = ( return { cache: context.cache.clear(), + integrationsSource: null, + integrations: null, }; }), appendIntegrations: assign((context, event) => @@ -171,6 +174,7 @@ export const createPureIntegrationsStateMachine = ( hasMoreIntegrations: (context) => Boolean(context.search.searchAfter), isStreamSearch: (context) => context.search.strategy === SearchStrategy.INTEGRATIONS_DATA_STREAMS, + isRequestCached: (context) => context.cache.has(context.search), }, delays: { SEARCH_DELAY: (_context, event) => { diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts index 836b7c08f0826..4022f9d4cc9f0 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts @@ -80,6 +80,9 @@ export type IntegrationsEvent = | { type: 'LOAD_MORE_INTEGRATIONS'; } + | { + type: 'RELOAD_INTEGRATIONS'; + } | { type: 'SEARCH'; search: IntegrationsSearchParams; diff --git a/x-pack/plugins/observability_logs/server/routes/index.ts b/x-pack/plugins/observability_logs/server/routes/index.ts index a9dce118075b0..d3f93ae892ac2 100644 --- a/x-pack/plugins/observability_logs/server/routes/index.ts +++ b/x-pack/plugins/observability_logs/server/routes/index.ts @@ -395,104 +395,94 @@ const items = [ const streams = [ { name: 'logs-*' }, - { name: 'system-logs-*' }, - { name: 'nginx-logs-*' }, - { name: 'apache-logs-*' }, - { name: 'security-logs-*' }, - { name: 'error-logs-*' }, - { name: 'access-logs-*' }, - { name: 'firewall-logs-*' }, - { name: 'application-logs-*' }, - { name: 'debug-logs-*' }, - { name: 'transaction-logs-*' }, - { name: 'audit-logs-*' }, - { name: 'server-logs-*' }, - { name: 'database-logs-*' }, - { name: 'event-logs-*' }, - { name: 'auth-logs-*' }, - { name: 'billing-logs-*' }, - { name: 'network-logs-*' }, - { name: 'performance-logs-*' }, - { name: 'email-logs-*' }, - { name: 'job-logs-*' }, - { name: 'task-logs-*' }, - { name: 'user-logs-*' }, - { name: 'request-logs-*' }, - { name: 'payment-logs-*' }, - { name: 'inventory-logs-*' }, - { name: 'debugging-logs-*' }, - { name: 'scheduler-logs-*' }, - { name: 'diagnostic-logs-*' }, - { name: 'cluster-logs-*' }, - { name: 'service-logs-*' }, - { name: 'framework-logs-*' }, - { name: 'api-logs-*' }, - { name: 'load-balancer-logs-*' }, - { name: 'reporting-logs-*' }, - { name: 'backend-logs-*' }, - { name: 'frontend-logs-*' }, - { name: 'chat-logs-*' }, - { name: 'error-tracking-logs-*' }, - { name: 'payment-gateway-logs-*' }, - { name: 'auth-service-logs-*' }, - { name: 'billing-service-logs-*' }, - { name: 'database-service-logs-*' }, - { name: 'api-gateway-logs-*' }, - { name: 'event-service-logs-*' }, - { name: 'notification-service-logs-*' }, - { name: 'search-service-logs-*' }, - { name: 'logging-service-logs-*' }, - { name: 'performance-service-logs-*' }, - { name: 'load-testing-logs-*' }, - { name: 'mobile-app-logs-*' }, - { name: 'web-app-logs-*' }, - { name: 'stream-processing-logs-*' }, - { name: 'batch-processing-logs-*' }, - { name: 'cloud-service-logs-*' }, - { name: 'container-logs-*' }, - { name: 'serverless-logs-*' }, - { name: 'server-administration-logs-*' }, - { name: 'application-deployment-logs-*' }, - { name: 'webserver-logs-*' }, - { name: 'payment-processor-logs-*' }, - { name: 'inventory-service-logs-*' }, - { name: 'data-pipeline-logs-*' }, - { name: 'frontend-service-logs-*' }, - { name: 'backend-service-logs-*' }, - { name: 'resource-monitoring-logs-*' }, - { name: 'logging-aggregation-logs-*' }, - { name: 'container-orchestration-logs-*' }, - { name: 'security-audit-logs-*' }, - { name: 'api-management-logs-*' }, - { name: 'service-mesh-logs-*' }, - { name: 'data-processing-logs-*' }, - { name: 'data-science-logs-*' }, - { name: 'machine-learning-logs-*' }, - { name: 'experimentation-logs-*' }, - { name: 'data-visualization-logs-*' }, - { name: 'data-cleaning-logs-*' }, - { name: 'data-transformation-logs-*' }, - { name: 'data-analysis-logs-*' }, - { name: 'data-storage-logs-*' }, - { name: 'data-retrieval-logs-*' }, - { name: 'data-warehousing-logs-*' }, - { name: 'data-modeling-logs-*' }, - { name: 'data-integration-logs-*' }, - { name: 'data-quality-logs-*' }, - { name: 'data-security-logs-*' }, - { name: 'data-encryption-logs-*' }, - { name: 'data-governance-logs-*' }, - { name: 'data-compliance-logs-*' }, - { name: 'data-privacy-logs-*' }, - { name: 'data-auditing-logs-*' }, - { name: 'data-discovery-logs-*' }, - { name: 'data-protection-logs-*' }, - { name: 'data-archiving-logs-*' }, - { name: 'data-backup-logs-*' }, - { name: 'data-recovery-logs-*' }, - { name: 'data-replication-logs-*' }, - { name: 'data-synchronization-logs-*' }, - { name: 'data-migration-logs-*' }, - { name: 'data-load-balancing-logs-*' }, - { name: 'data-scaling-logs-*' }, + { name: 'logs-*-*' }, + { name: 'logs-nginx-*' }, + { name: 'logs-apache-*' }, + { name: 'logs-security-*' }, + { name: 'logs-error-*' }, + { name: 'logs-access-*' }, + { name: 'logs-firewall-*' }, + { name: 'logs-application-*' }, + { name: 'logs-debug-*' }, + { name: 'logs-transaction-*' }, + { name: 'logs-server-*' }, + { name: 'logs-database-*' }, + { name: 'logs-event-*' }, + { name: 'logs-auth-*' }, + { name: 'logs-billing-*' }, + { name: 'logs-network-*' }, + { name: 'logs-performance-*' }, + { name: 'logs-email-*' }, + { name: 'logs-job-*' }, + { name: 'logs-task-*' }, + { name: 'logs-user-*' }, + { name: 'logs-request-*' }, + { name: 'logs-payment-*' }, + { name: 'logs-inventory-*' }, + { name: 'logs-debugging-*' }, + { name: 'logs-scheduler-*' }, + { name: 'logs-diagnostic-*' }, + { name: 'logs-cluster-*' }, + { name: 'logs-service-*' }, + { name: 'logs-framework-*' }, + { name: 'logs-api-*' }, + { name: 'logs-reporting-*' }, + { name: 'logs-backend-*' }, + { name: 'logs-frontend-*' }, + { name: 'logs-chat-*' }, + { name: 'logs-api.gateway-*' }, + { name: 'logs-event.service-*' }, + { name: 'logs-notification.service-*' }, + { name: 'logs-search.service-*' }, + { name: 'logs-logging-service-*' }, + { name: 'logs-performance.service-*' }, + { name: 'logs-load-testing-*' }, + { name: 'logs-mobile-app-*' }, + { name: 'logs-web-app-*' }, + { name: 'logs-stream-processing-*' }, + { name: 'logs-batch-processing-*' }, + { name: 'logs-cloud-service-*' }, + { name: 'logs-container-*' }, + { name: 'logs-serverless-*' }, + { name: 'logs-server.administration-*' }, + { name: 'logs-application.deployment-*' }, + { name: 'logs-webserver-*' }, + { name: 'logs-pipeline-*' }, + { name: 'logs-frontend-service-*' }, + { name: 'logs-backend-service-*' }, + { name: 'logs-resource-monitoring-*' }, + { name: 'logs-logging-aggregation-*' }, + { name: 'logs-container-orchestration-*' }, + { name: 'logs-audit-*' }, + { name: 'logs-management-*' }, + { name: 'logs-mesh-*' }, + { name: 'logs-processing-*' }, + { name: 'logs-science-*' }, + { name: 'logs-machine.learning-*' }, + { name: 'logs-experimentation-*' }, + { name: 'logs-visualization-*' }, + { name: 'logs-cleaning-*' }, + { name: 'logs-transformation-*' }, + { name: 'logs-analysis-*' }, + { name: 'logs-storage-*' }, + { name: 'logs-retrieval-*' }, + { name: 'logs-warehousing-*' }, + { name: 'logs-modeling-*' }, + { name: 'logs-integration-*' }, + { name: 'logs-quality-*' }, + { name: 'logs-encryption-*' }, + { name: 'logs-governance-*' }, + { name: 'logs-compliance-*' }, + { name: 'logs-privacy-*' }, + { name: 'logs-auditing-*' }, + { name: 'logs-discovery-*' }, + { name: 'logs-protection-*' }, + { name: 'logs-archiving-*' }, + { name: 'logs-backup-*' }, + { name: 'logs-recovery-*' }, + { name: 'logs-replication-*' }, + { name: 'logs-synchronization-*' }, + { name: 'logs-migration-*' }, + { name: 'logs-load-balancing-*' }, + { name: 'logs-scaling-*' }, ]; From 67e39e6e8649a0d39446aca424bb9f6d41ab0912 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 24 May 2023 14:46:43 +0000 Subject: [PATCH 107/122] [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' --- .../sub_components/integrations_list_status.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/integrations_list_status.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/integrations_list_status.tsx index 00019e23aec93..c045fbd4e219a 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/integrations_list_status.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/integrations_list_status.tsx @@ -12,7 +12,6 @@ import { Integration } from '../../../../common/data_streams'; import { errorLabel, noDataRetryLabel, - noDataStreamsLabel, noIntegrationsDescriptionLabel, noIntegrationsLabel, } from '../constants'; From f8de8668e29fafc070bef153a0cad6cf4abf0594 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Thu, 25 May 2023 14:27:38 +0200 Subject: [PATCH 108/122] refactor(observability_logs): improve typestates --- .../data_streams/src/state_machine.ts | 6 +- .../state_machines/data_streams/src/types.ts | 74 ++++++++++++----- .../integrations/src/state_machine.ts | 3 +- .../state_machines/integrations/src/types.ts | 79 ++++++++++++++----- 4 files changed, 119 insertions(+), 43 deletions(-) diff --git a/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/state_machine.ts b/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/state_machine.ts index e5a8ff2eeaa03..dac6308c71a64 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/state_machine.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/state_machine.ts @@ -14,14 +14,14 @@ import { DataStreamsContext, DataStreamsEvent, DefaultDataStreamsContext, - IntegrationTypestate, + DataStreamsTypestate, } from './types'; export const createPureDataStreamsStateMachine = ( initialContext: DefaultDataStreamsContext = DEFAULT_CONTEXT ) => /** @xstate-layout N4IgpgJg5mDOIC5QBECGAXVBldAnMqAtrAHQCuAdgJbXpWoA2VAXpAMQAyA8gILID6yHgBUe-LMIBKAUR4BZLAG0ADAF1EoAA4B7WFTraKGkAA9EAdgAsAVhLWAjNYBs11-fPXzADnsAaEACeiD52AL6h-miYOPhEpAzaqBA0UJy8yACSAHIA4uIAqgDChdLSyGUq6kggOnoGRtVmCFa2Ds6uDh7efoGITvYAzHbmygOWA8runj7hkRjYeATEJAlJKWl82XkAYjwZHBVqxrX6VIbGTd5OJN0ATF7KndM9QQi3yso31rMgUQuxy1WEHYWFkkkKAAlBCIxBIZPIlEdqid6hdELdrLcSABOW72LwefyvTGfDw-P4xJakYEAI20lAAxiksARcAyABZsUE8cFQoSicRSWQKSrHXSnc6NRCWbHYkh4gkPJ7dInSyxYjHk+aUuIkWn0ihMihQFmoNmckywTDoMAkVAAMxtuAAFNYPgBKNgUxa6-WM5msjmi5Hi1FS5o2OyOFxuLo+VUITEa74RX7an2AxLJY3bVBUBjsGTcPjQgVw4WIqpaUNnBqgJoYrG4-GE3oIAZ4sKp70A+JZlK5-MgsGQ0uwoUI4PVuq1tFvTE4hWt16WEbylOpijaYHwao9qlimeS+uIAC0TgT5610QzpEoNFOjBYkEPErrpkQ1geJC82Js-XMJw4xeYJbmvf4qRWftjVfMMTwQbEvFsZQRjGCYphVNtbkQuwPlGR4MJmbt017KCkhfEMj3fJpBksEhLCcAlrATFwvHo+wnE4rjuKccCdWWP1DQDM0OVg2dwyTEhlFucwBisW4gOeBMZXML4+NvMjsygQcCwgMTjw-BB1QTAYkJIJxNXCUIgA */ - createMachine( + createMachine( { context: initialContext, preserveActionOrder: true, @@ -121,7 +121,7 @@ export const createPureDataStreamsStateMachine = ( ); export interface DataStreamsStateMachineDependencies { - initialContext?: DataStreamsContext; + initialContext?: DefaultDataStreamsContext; dataStreamsClient: IDataStreamsClient; } diff --git a/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/types.ts b/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/types.ts index 13a2bfb00266b..a511c8d4adacf 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/types.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/types.ts @@ -5,48 +5,86 @@ * 2.0. */ import type { IImmutableCache } from '../../../../common/immutable_cache'; -import { - FindDataStreamsRequestQuery, - FindDataStreamsResponse, - SortOrder, -} from '../../../../common/latest'; +import { FindDataStreamsResponse, SortOrder } from '../../../../common/latest'; import { DataStream } from '../../../../common/data_streams'; -export interface DefaultDataStreamsContext { - cache: IImmutableCache; - dataStreams: DataStream[] | null; - error: Error | null; - search: FindDataStreamsRequestQuery; -} - export interface DataStreamsSearchParams { datasetQuery?: string; sortOrder?: SortOrder; } -export type IntegrationTypestate = +interface WithCache { + cache: IImmutableCache; +} + +interface WithSearch { + search: DataStreamsSearchParams; +} + +interface WithDataStreams { + dataStreams: DataStream[] | null; +} + +interface WithNullishDataStreams { + dataStreams: null; +} + +interface WithError { + error: Error; +} + +interface WithNullishError { + error: null; +} + +interface WithTotal { + total: number; +} + +export type DefaultDataStreamsContext = WithCache & + WithNullishDataStreams & + WithSearch & + WithNullishError; + +type LoadingDataStreamsContext = DefaultDataStreamsContext; + +type LoadedDataStreamsContext = WithCache & + WithDataStreams & + WithTotal & + WithSearch & + WithNullishError; + +type LoadingFailedDataStreamsContext = WithCache & + WithDataStreams & + Partial & + WithSearch & + WithError; + +type SearchingDataStreamsContext = LoadedDataStreamsContext | LoadingFailedDataStreamsContext; + +export type DataStreamsTypestate = | { value: 'uninitialized'; context: DefaultDataStreamsContext; } | { value: 'loading'; - context: DefaultDataStreamsContext; + context: LoadingDataStreamsContext; } | { value: 'loaded'; - context: DefaultDataStreamsContext; + context: LoadedDataStreamsContext; } | { value: 'loadingFailed'; - context: DefaultDataStreamsContext; + context: LoadingFailedDataStreamsContext; } | { value: 'debouncingSearch'; - context: DefaultDataStreamsContext; + context: SearchingDataStreamsContext; }; -export type DataStreamsContext = IntegrationTypestate['context']; +export type DataStreamsContext = DataStreamTypestate['context']; export type DataStreamsEvent = | { diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts index e9dab316b3eec..2942d0adbb679 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts @@ -174,7 +174,6 @@ export const createPureIntegrationsStateMachine = ( hasMoreIntegrations: (context) => Boolean(context.search.searchAfter), isStreamSearch: (context) => context.search.strategy === SearchStrategy.INTEGRATIONS_DATA_STREAMS, - isRequestCached: (context) => context.cache.has(context.search), }, delays: { SEARCH_DELAY: (_context, event) => { @@ -187,7 +186,7 @@ export const createPureIntegrationsStateMachine = ( ); export interface IntegrationsStateMachineDependencies { - initialContext?: IntegrationsContext; + initialContext?: DefaultIntegrationsContext; dataStreamsClient: IDataStreamsClient; } diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts index 4022f9d4cc9f0..e5eb3203f45b3 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts @@ -5,29 +5,68 @@ * 2.0. */ import type { IImmutableCache } from '../../../../common/immutable_cache'; -import { - FindIntegrationsResponse, - FindIntegrationsRequestQuery, - SortOrder, -} from '../../../../common/latest'; +import { FindIntegrationsResponse, SortOrder, SearchAfter } from '../../../../common/latest'; import { Integration, SearchStrategy } from '../../../../common/data_streams'; -export interface DefaultIntegrationsContext { - cache: IImmutableCache; - integrationsSource: Integration[] | null; - integrations: Integration[] | null; - error: Error | null; - search: FindIntegrationsRequestQuery & { strategy?: SearchStrategy }; - total?: number; -} - export interface IntegrationsSearchParams { nameQuery?: string; + searchAfter?: SearchAfter; sortOrder?: SortOrder; strategy?: SearchStrategy; integrationId?: string; } +interface WithCache { + cache: IImmutableCache; +} + +interface WithSearch { + search: IntegrationsSearchParams; +} + +interface WithIntegrations { + integrationsSource: Integration[] | null; + integrations: Integration[] | null; +} + +interface WithNullishIntegrations { + integrationsSource: null; + integrations: null; +} + +interface WithError { + error: Error; +} + +interface WithNullishError { + error: null; +} + +interface WithTotal { + total: number; +} + +export type DefaultIntegrationsContext = WithCache & + WithNullishIntegrations & + WithSearch & + WithNullishError; + +type LoadingIntegrationsContext = DefaultIntegrationsContext; + +type LoadedIntegrationsContext = WithCache & + WithIntegrations & + WithTotal & + WithSearch & + WithNullishError; + +type LoadingFailedIntegrationsContext = WithCache & + WithIntegrations & + Partial & + WithSearch & + WithError; + +type SearchingIntegrationsContext = LoadedIntegrationsContext | LoadingFailedIntegrationsContext; + export type IntegrationTypestate = | { value: 'uninitialized'; @@ -35,27 +74,27 @@ export type IntegrationTypestate = } | { value: 'loading'; - context: DefaultIntegrationsContext; + context: LoadingIntegrationsContext; } | { value: 'loaded'; - context: DefaultIntegrationsContext; + context: LoadedIntegrationsContext; } | { value: 'loadingFailed'; - context: DefaultIntegrationsContext; + context: LoadingFailedIntegrationsContext; } | { value: 'loadingMore'; - context: DefaultIntegrationsContext; + context: LoadingIntegrationsContext; } | { value: 'debouncingSearch'; - context: DefaultIntegrationsContext; + context: SearchingIntegrationsContext; } | { value: 'searchingStreams'; - context: DefaultIntegrationsContext; + context: SearchingIntegrationsContext; }; export type IntegrationsContext = IntegrationTypestate['context']; From ed1fa7bf0bc44f4a97c6b43a29610ebc12b11078 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Thu, 25 May 2023 19:07:03 +0200 Subject: [PATCH 109/122] refactor(observability_logs): begin data stream selector state refactor --- .../observability_logs/common/index.ts | 3 +- .../data_stream_selector.tsx | 23 ++++--- .../state_machine/defaults.ts | 19 ++++++ .../state_machine/index.ts | 9 +++ .../state_machine/state_machine.ts | 60 +++++++++++++++++++ .../state_machine/types.ts | 36 +++++++++++ .../state_machine/use_data_stream_selector.ts | 38 ++++++++++++ .../sub_components/search_controls.tsx | 6 +- .../components/data_stream_selector/types.ts | 4 +- .../components/data_stream_selector/utils.tsx | 6 +- .../custom_data_stream_selector.tsx | 14 +++-- .../public/hooks/use_boolean.ts | 36 ----------- .../public/hooks/use_data_streams.ts | 8 +-- .../use_data_view.ts} | 0 .../public/hooks/use_integrations.ts | 10 ++-- .../observability_logs/public/plugin.tsx | 51 ++++++++-------- .../data_streams/data_streams_client.ts | 7 +-- .../data_streams/src/state_machine.ts | 8 ++- .../integrations/src/state_machine.ts | 8 ++- .../observability_logs/public/types.ts | 4 +- 20 files changed, 246 insertions(+), 104 deletions(-) create mode 100644 x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/defaults.ts create mode 100644 x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/index.ts create mode 100644 x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/state_machine.ts create mode 100644 x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/types.ts create mode 100644 x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/use_data_stream_selector.ts delete mode 100644 x-pack/plugins/observability_logs/public/hooks/use_boolean.ts rename x-pack/plugins/observability_logs/public/{utils/internal_state_container_context.tsx => hooks/use_data_view.ts} (100%) diff --git a/x-pack/plugins/observability_logs/common/index.ts b/x-pack/plugins/observability_logs/common/index.ts index 4549d55176308..79e0ce88d3e22 100644 --- a/x-pack/plugins/observability_logs/common/index.ts +++ b/x-pack/plugins/observability_logs/common/index.ts @@ -7,8 +7,7 @@ /* eslint-disable @kbn/eslint/no_export_all */ -export const PLUGIN_ID = 'observabilityLogs'; -export const PLUGIN_NAME = 'observabilityLogs'; +export const OBSERVABILITY_LOGS_PROFILE_ID = 'observability-logs'; /** * Exporting versioned APIs types diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx index b8df0712b71f0..4364e59e0ae14 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx @@ -10,7 +10,6 @@ import { EuiContextMenuPanelId } from '@elastic/eui/src/components/context_menu/ import React, { useCallback, useMemo, useState } from 'react'; import { dynamic } from '../../../common/dynamic'; import type { DataStreamSelectionHandler } from '../../customizations/custom_data_stream_selector'; -import { useBoolean } from '../../hooks/use_boolean'; import { useIntersectionRef } from '../../hooks/use_intersection_ref'; import { contextMenuStyles, @@ -21,10 +20,11 @@ import { uncategorizedLabel, UNCATEGORIZED_STREAMS_PANEL_ID, } from './constants'; +import { useDataStreamSelector } from './state_machine/use_data_stream_selector'; import { DataStreamsPopover } from './sub_components/data_streams_popover'; import { DataStreamSkeleton } from './sub_components/data_streams_skeleton'; import { SearchControls, useSearchStrategy } from './sub_components/search_controls'; -import { CurrentPanelId, DataStreamSelectorProps } from './types'; +import { PanelId, DataStreamSelectorProps } from './types'; import { buildIntegrationsTree, setIntegrationListSpy } from './utils'; /** @@ -35,6 +35,11 @@ const DataStreamsList = dynamic(() => import('./sub_components/data_streams_list }); const IntegrationsListStatus = dynamic(() => import('./sub_components/integrations_list_status')); +/** + * TODO + * - Refactor internal state management to work as a SM + */ + export function DataStreamSelector({ dataStreams, dataStreamsError, @@ -50,24 +55,24 @@ export function DataStreamSelector({ onStreamsReload, title, }: DataStreamSelectorProps) { - const [isPopoverOpen, { off: closePopover, toggle: togglePopover }] = useBoolean(false); + const { isOpen, togglePopover } = useDataStreamSelector(); const [setSpyRef] = useIntersectionRef({ onIntersecting: onIntegrationsLoadMore }); - const [currentPanel, setCurrentPanel] = useState(INTEGRATION_PANEL_ID); + const [currentPanel, setCurrentPanel] = useState(INTEGRATION_PANEL_ID); const [search, handleSearch] = useSearchStrategy({ id: currentPanel, onSearch }); const handlePanelChange = ({ panelId }: { panelId: EuiContextMenuPanelId }) => { - setCurrentPanel(panelId as CurrentPanelId); + setCurrentPanel(panelId as PanelId); }; const handleStreamSelection = useCallback( (dataStream) => { onStreamSelected(dataStream); - closePopover(); + togglePopover(); }, - [closePopover, onStreamSelected] + [togglePopover, onStreamSelected] ); const { items: integrationItems, panels: integrationPanels } = useMemo(() => { @@ -144,8 +149,8 @@ export function DataStreamSelector({ return ( diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/defaults.ts b/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/defaults.ts new file mode 100644 index 0000000000000..e6d02309147d1 --- /dev/null +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/defaults.ts @@ -0,0 +1,19 @@ +/* + * 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 { ImmutableCache } from '../../../../common/immutable_cache'; +import { INTEGRATION_PANEL_ID } from '../constants'; +import { DefaultDataStreamsSelectorContext } from './types'; + +export const DEFAULT_CONTEXT: DefaultDataStreamsSelectorContext = { + searchCache: new ImmutableCache(), + panelId: INTEGRATION_PANEL_ID, + search: { + name: '', + sortOrder: 'asc', + }, +}; diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/index.ts b/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/index.ts new file mode 100644 index 0000000000000..86a51fb2c0e16 --- /dev/null +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/index.ts @@ -0,0 +1,9 @@ +/* + * 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. + */ + +export * from './state_machine'; +export * from './types'; diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/state_machine.ts b/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/state_machine.ts new file mode 100644 index 0000000000000..61d06a705e4aa --- /dev/null +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/state_machine.ts @@ -0,0 +1,60 @@ +/* + * 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 { createMachine } from 'xstate'; +import { DEFAULT_CONTEXT } from './defaults'; +import { + DataStreamsSelectorContext, + DataStreamsSelectorEvent, + DataStreamsSelectorTypestate, + DefaultDataStreamsSelectorContext, +} from './types'; + +export const createPureDataStreamsSelectorStateMachine = ( + initialContext: DefaultDataStreamsSelectorContext = DEFAULT_CONTEXT +) => + /** @xstate-layout N4IgpgJg5mDOIC5QBECGAXVBldAnMqAtrFmADZgDG6A9rgHSVk2yQDEAKgPIDiPAMgFEA2gAYAuolAAHFgEt0cmgDspIAB6IAjKIDs9AGwHdATgMAOXQFYANCACe281foBmUVZPmTV86--+ACwAvsF2aJg4+EQk5FS0DDTSYMqcvAIiEmqysApKqkga2nqGxmaWtg6IAEz0WoEmuq4+fgGuga6hYSDKNBBwahHYeATEpBTUdNnyiipqmggAtAZ2jkvOblai1QaigVpGhwah4RjD0WNxkwxMLJDTubMFoAuB1avaWrUHR7+HJyAhlFRrEJgl6EkUg88nNCgtqlYXFZdKJXF9KmsDK56FYAUCRjFxvE6BDksp6HIIBRoU95oh3Njkaj0R8EForFousEgA */ + createMachine( + { + context: initialContext, + preserveActionOrder: true, + predictableActionArguments: true, + id: 'DataStreamsSelector', + initial: 'closed', + states: { + closed: { + id: 'closed', + on: { + TOGGLE: 'open', + }, + }, + open: { + initial: 'idle', + on: { + TOGGLE: 'closed', + }, + states: { + idle: {}, + }, + }, + }, + }, + { + actions: {}, + } + ); + +export interface DataStreamsStateMachineDependencies { + initialContext?: DefaultDataStreamsSelectorContext; +} + +export const createDataStreamsSelectorStateMachine = ({ + initialContext, +}: DataStreamsStateMachineDependencies) => + createPureDataStreamsSelectorStateMachine(initialContext).withConfig({ + services: {}, + }); diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/types.ts b/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/types.ts new file mode 100644 index 0000000000000..ea8d1ef31ab99 --- /dev/null +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/types.ts @@ -0,0 +1,36 @@ +/* + * 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 type { IImmutableCache } from '../../../../common/immutable_cache'; +import { SortOrder } from '../../../../common/latest'; +import { PanelId } from '../types'; + +export interface DataStreamsSelectorSearchParams { + name?: string; + sortOrder?: SortOrder; +} + +export interface DefaultDataStreamsSelectorContext { + panelId: PanelId; + searchCache: IImmutableCache; + search: DataStreamsSelectorSearchParams; +} + +export type DataStreamsSelectorTypestate = + | { + value: 'closed'; + context: DefaultDataStreamsSelectorContext; + } + | { + value: 'open'; + context: DefaultDataStreamsSelectorContext; + }; + +export type DataStreamsSelectorContext = DataStreamsSelectorTypestate['context']; + +export interface DataStreamsSelectorEvent { + type: 'TOGGLE'; +} diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/use_data_stream_selector.ts b/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/use_data_stream_selector.ts new file mode 100644 index 0000000000000..6986272594b68 --- /dev/null +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/use_data_stream_selector.ts @@ -0,0 +1,38 @@ +/* + * 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 { useInterpret, useSelector } from '@xstate/react'; +import { useCallback } from 'react'; +import { createDataStreamsSelectorStateMachine } from './state_machine'; + +export const useDataStreamSelector = () => { + const dataStreamsSelectorStateService = useInterpret(() => + createDataStreamsSelectorStateMachine({}) + ); + + const isOpen = useSelector(dataStreamsSelectorStateService, (state) => state.matches('open')); + + const togglePopover = useCallback( + () => dataStreamsSelectorStateService.send({ type: 'TOGGLE' }), + [dataStreamsSelectorStateService] + ); + + return { + // Data + isOpen, + + // Failure states + + // Loading states + + // Actions + togglePopover, + }; +}; + +// export const [IntegrationsProvider, useIntegrationsContext] = +// createContainer(useDataStreamSelector); diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/search_controls.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/search_controls.tsx index 40d6baa6bdcfb..160338f83a62d 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/search_controls.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/search_controls.tsx @@ -15,7 +15,7 @@ import { sortOptions, sortOrdersLabel, } from '../constants'; -import { CurrentPanelId, SearchControlsParams, SearchHandler } from '../types'; +import { PanelId, SearchControlsParams, SearchHandler } from '../types'; import { getSearchStrategy } from '../utils'; type SearchControlsHandler = (params: SearchControlsParams) => void; @@ -31,7 +31,7 @@ const initialCache = { const searchCacheReducer = ( cache: Record, - update: { id: CurrentPanelId; params: SearchControlsParams } + update: { id: PanelId; params: SearchControlsParams } ) => { const { id, params } = update; @@ -42,7 +42,7 @@ const searchCacheReducer = ( }; interface UseSearchStrategyOptions { - id: CurrentPanelId; + id: PanelId; onSearch: SearchHandler; } diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/types.ts b/x-pack/plugins/observability_logs/public/components/data_stream_selector/types.ts index 8302557f22524..fbb74e5409bdc 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/types.ts +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/types.ts @@ -39,13 +39,13 @@ export interface DataStreamSelectorProps { onStreamSelected: DataStreamSelectionHandler; } -export type CurrentPanelId = +export type PanelId = | typeof INTEGRATION_PANEL_ID | typeof UNCATEGORIZED_STREAMS_PANEL_ID | IntegrationId; export interface SearchParams { - integrationId?: CurrentPanelId; + integrationId?: PanelId; name?: string; sortOrder?: SortOrder; strategy: SearchStrategy; diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/utils.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/utils.tsx index ec260b5bd0194..02e0d5eaad622 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/utils.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/utils.tsx @@ -15,7 +15,7 @@ import { INTEGRATION_PANEL_ID, UNCATEGORIZED_STREAMS_PANEL_ID, } from './constants'; -import { CurrentPanelId, DataStreamSelectionHandler } from './types'; +import { PanelId, DataStreamSelectionHandler } from './types'; export const getPopoverButtonStyles = ({ fullWidth }: { fullWidth?: boolean }) => ({ maxWidth: fullWidth ? undefined : DATA_VIEW_POPOVER_CONTENT_WIDTH, @@ -37,7 +37,7 @@ export const buildIntegrationsTree = ({ }: IntegrationsTreeParams) => { return integrations.reduce( (res: IntegrationsTree, integration) => { - const entryId: CurrentPanelId = getIntegrationId(integration); + const entryId: PanelId = getIntegrationId(integration); const { name, version, dataStreams } = integration; res.items.push({ @@ -73,7 +73,7 @@ export const setIntegrationListSpy = ( } }; -export const getSearchStrategy = (panelId: CurrentPanelId) => { +export const getSearchStrategy = (panelId: PanelId) => { if (panelId === UNCATEGORIZED_STREAMS_PANEL_ID) return SearchStrategy.DATA_STREAMS; if (panelId === INTEGRATION_PANEL_ID) return SearchStrategy.INTEGRATIONS; return SearchStrategy.INTEGRATIONS_DATA_STREAMS; diff --git a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx index a99e02c6c95db..aeb5709243085 100644 --- a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx @@ -7,19 +7,21 @@ import React, { useCallback } from 'react'; import { DiscoverStateContainer } from '@kbn/discover-plugin/public'; -import { DataStream, SearchStrategy } from '../../common/data_streams'; -import { DataStreamSelector, SearchHandler } from '../components/data_stream_selector'; -import { InternalStateProvider, useDataView } from '../utils/internal_state_container_context'; -import { IntegrationsProvider, useIntegrationsContext } from '../hooks/use_integrations'; +import { SearchStrategy } from '../../common/data_streams'; +import { + DataStreamSelectionHandler, + DataStreamSelector, + SearchHandler, +} from '../components/data_stream_selector'; import { DataStreamsProvider, useDataStreamsContext } from '../hooks/use_data_streams'; +import { InternalStateProvider, useDataView } from '../hooks/use_data_view'; +import { IntegrationsProvider, useIntegrationsContext } from '../hooks/use_integrations'; import { IDataStreamsClient } from '../services/data_streams'; interface CustomDataStreamSelectorProps { stateContainer: DiscoverStateContainer; } -export type DataStreamSelectionHandler = (stream: DataStream) => void; - export const CustomDataStreamSelector = withProviders(({ stateContainer }) => { // Container component, here goes all the state management and custom logic usage to keep the DataStreamSelector presentational. const dataView = useDataView(); diff --git a/x-pack/plugins/observability_logs/public/hooks/use_boolean.ts b/x-pack/plugins/observability_logs/public/hooks/use_boolean.ts deleted file mode 100644 index 321dddf627eda..0000000000000 --- a/x-pack/plugins/observability_logs/public/hooks/use_boolean.ts +++ /dev/null @@ -1,36 +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 { useMemo } from 'react'; -import useToggle from 'react-use/lib/useToggle'; - -export type VoidHandler = () => void; - -export type DispatchWithOptionalAction = (_arg?: Type | unknown) => void; - -export interface UseBooleanHandlers { - on: VoidHandler; - off: VoidHandler; - toggle: DispatchWithOptionalAction; -} - -export type UseBooleanResult = [boolean, UseBooleanHandlers]; - -export const useBoolean = (initialValue: boolean = false): UseBooleanResult => { - const [value, toggle] = useToggle(initialValue); - - const handlers = useMemo( - () => ({ - toggle, - on: () => toggle(true), - off: () => toggle(false), - }), - [toggle] - ); - - return [value, handlers]; -}; diff --git a/x-pack/plugins/observability_logs/public/hooks/use_data_streams.ts b/x-pack/plugins/observability_logs/public/hooks/use_data_streams.ts index 8354741d9659c..561f51dc2a921 100644 --- a/x-pack/plugins/observability_logs/public/hooks/use_data_streams.ts +++ b/x-pack/plugins/observability_logs/public/hooks/use_data_streams.ts @@ -5,12 +5,12 @@ * 2.0. */ -import { useInterpret, useSelector } from '@xstate/react'; -import createContainer from 'constate'; import { useCallback } from 'react'; -import { createDataStreamsStateMachine } from '../state_machines/data_streams'; -import { IDataStreamsClient } from '../services/data_streams'; +import createContainer from 'constate'; +import { useInterpret, useSelector } from '@xstate/react'; import { FindDataStreamsRequestQuery, SortOrder } from '../../common'; +import { IDataStreamsClient } from '../services/data_streams'; +import { createDataStreamsStateMachine } from '../state_machines/data_streams'; interface DataStreamsContextDeps { dataStreamsClient: IDataStreamsClient; diff --git a/x-pack/plugins/observability_logs/public/utils/internal_state_container_context.tsx b/x-pack/plugins/observability_logs/public/hooks/use_data_view.ts similarity index 100% rename from x-pack/plugins/observability_logs/public/utils/internal_state_container_context.tsx rename to x-pack/plugins/observability_logs/public/hooks/use_data_view.ts diff --git a/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts b/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts index 981baa04c3179..e08b143da1d75 100644 --- a/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts +++ b/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts @@ -5,13 +5,13 @@ * 2.0. */ -import { useInterpret, useSelector } from '@xstate/react'; -import createContainer from 'constate'; import { useCallback } from 'react'; -import { SearchStrategy } from '../../common/data_streams'; -import { createIntegrationStateMachine } from '../state_machines/integrations'; -import { IDataStreamsClient } from '../services/data_streams'; +import createContainer from 'constate'; +import { useInterpret, useSelector } from '@xstate/react'; import { FindIntegrationsRequestQuery, SortOrder } from '../../common'; +import type { SearchStrategy } from '../../common/data_streams'; +import { IDataStreamsClient } from '../services/data_streams'; +import { createIntegrationStateMachine } from '../state_machines/integrations'; interface IntegrationsContextDeps { dataStreamsClient: IDataStreamsClient; diff --git a/x-pack/plugins/observability_logs/public/plugin.tsx b/x-pack/plugins/observability_logs/public/plugin.tsx index 4cbc8436628bb..fd349bff54f0c 100644 --- a/x-pack/plugins/observability_logs/public/plugin.tsx +++ b/x-pack/plugins/observability_logs/public/plugin.tsx @@ -6,6 +6,7 @@ */ import { CoreStart } from '@kbn/core/public'; +import { OBSERVABILITY_LOGS_PROFILE_ID } from '../common'; import { createLazyCustomDataStreamSelector } from './customizations'; import { DataStreamsService } from './services/data_streams'; import { ObservabilityLogsClientPluginClass, ObservabilityLogsStartDeps } from './types'; @@ -17,7 +18,7 @@ export class ObservabilityLogsPlugin implements ObservabilityLogsClientPluginCla this.dataStreamsService = new DataStreamsService(); } - public setup() {} + public setup() { } public start(core: CoreStart, plugins: ObservabilityLogsStartDeps) { const { discover } = plugins; @@ -33,32 +34,30 @@ export class ObservabilityLogsPlugin implements ObservabilityLogsClientPluginCla /** * Replace the DataViewPicker with a custom DataStreamSelector to access only integrations streams */ - discover.customize('observability-logs', async ({ customizations, stateContainer }) => { - customizations.set({ - id: 'search_bar', - CustomDataViewPicker: createLazyCustomDataStreamSelector({ - dataStreamsClient: dataStreamsService.client, - stateContainer, - }), - }); + discover.customize( + OBSERVABILITY_LOGS_PROFILE_ID, + async ({ customizations, stateContainer }) => { + customizations.set({ + id: 'search_bar', + CustomDataViewPicker: createLazyCustomDataStreamSelector({ + dataStreamsClient: dataStreamsService.client, + stateContainer, + }), + }); - /** - * Hide New, Open and Save settings to prevent working with saved views. - */ - customizations.set({ - id: 'top_nav', - defaultMenu: { - new: { disabled: true }, - open: { disabled: true }, - save: { disabled: true }, - }, - }); - - return () => { - // eslint-disable-next-line no-console - console.log('Cleaning up Logs explorer customizations'); - }; - }); + /** + * Hide New, Open and Save settings to prevent working with saved views. + */ + customizations.set({ + id: 'top_nav', + defaultMenu: { + new: { disabled: true }, + open: { disabled: true }, + save: { disabled: true }, + }, + }); + } + ); return pluginStart; } diff --git a/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_client.ts b/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_client.ts index 21dc61137a9a9..485ad9a717807 100644 --- a/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_client.ts +++ b/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_client.ts @@ -6,10 +6,7 @@ */ import { HttpStart } from '@kbn/core/public'; -import { FindDataStreamsError, FindIntegrationsError } from '../../../common/data_streams/errors'; -import { decodeOrThrow } from '../../../common/runtime_types'; import { - formatSearch, DATA_STREAMS_URL, FindDataStreamsRequestQuery, findDataStreamsRequestQueryRT, @@ -19,9 +16,11 @@ import { findIntegrationsRequestQueryRT, FindIntegrationsResponse, findIntegrationsResponseRT, + formatSearch, INTEGRATIONS_URL, } from '../../../common'; - +import { FindDataStreamsError, FindIntegrationsError } from '../../../common/data_streams/errors'; +import { decodeOrThrow } from '../../../common/runtime_types'; import { IDataStreamsClient } from './types'; const defaultIntegrationsParams = { diff --git a/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/state_machine.ts b/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/state_machine.ts index dac6308c71a64..fcb23be366bc1 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/state_machine.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/state_machine.ts @@ -17,10 +17,16 @@ import { DataStreamsTypestate, } from './types'; +/** + * TODO + * - Split search and sort into 2 different events + * - Split states into UI accessible interactions, keep track of current UI state for this + */ + export const createPureDataStreamsStateMachine = ( initialContext: DefaultDataStreamsContext = DEFAULT_CONTEXT ) => - /** @xstate-layout N4IgpgJg5mDOIC5QBECGAXVBldAnMqAtrAHQCuAdgJbXpWoA2VAXpAMQAyA8gILID6yHgBUe-LMIBKAUR4BZLAG0ADAF1EoAA4B7WFTraKGkAA9EAdgAsAVhLWAjNYBs11-fPXzADnsAaEACeiD52AL6h-miYOPhEpAzaqBA0UJy8yACSAHIA4uIAqgDChdLSyGUq6kggOnoGRtVmCFa2Ds6uDh7efoGITvYAzHbmygOWA8runj7hkRjYeATEJAlJKWl82XkAYjwZHBVqxrX6VIbGTd5OJN0ATF7KndM9QQi3yso31rMgUQuxy1WEHYWFkkkKAAlBCIxBIZPIlEdqid6hdELdrLcSABOW72LwefyvTGfDw-P4xJakYEAI20lAAxiksARcAyABZsUE8cFQoSicRSWQKSrHXSnc6NRCWbHYkh4gkPJ7dInSyxYjHk+aUuIkWn0ihMihQFmoNmckywTDoMAkVAAMxtuAAFNYPgBKNgUxa6-WM5msjmi5Hi1FS5o2OyOFxuLo+VUITEa74RX7an2AxLJY3bVBUBjsGTcPjQgVw4WIqpaUNnBqgJoYrG4-GE3oIAZ4sKp70A+JZlK5-MgsGQ0uwoUI4PVuq1tFvTE4hWt16WEbylOpijaYHwao9qlimeS+uIAC0TgT5610QzpEoNFOjBYkEPErrpkQ1geJC82Js-XMJw4xeYJbmvf4qRWftjVfMMTwQbEvFsZQRjGCYphVNtbkQuwPlGR4MJmbt017KCkhfEMj3fJpBksEhLCcAlrATFwvHo+wnE4rjuKccCdWWP1DQDM0OVg2dwyTEhlFucwBisW4gOeBMZXML4+NvMjsygQcCwgMTjw-BB1QTAYkJIJxNXCUIgA */ + /** @xstate-layout N4IgpgJg5mDOIC5QBECGAXVBldAnMqAtrAHQCuAdgJbXpWoA2VAXpAMQAyA8gILID6yHgBUe-LMIBKAUR4BZLAG0ADAF1EoAA4B7WFTraKGkAA9EAdmUBGEuavKAnAGYArACYHy5QA5l5gDQgAJ6I3jYuXl4AbM7KUS5WrgC+SYFomDj4RKQM2qgQNFCcvMgAkgByAOLiAKoAwnXS0sjNKupIIDp6BkYdZgiWNnaOrh5evgHBiFGJJC6WTm4+1lHeYSlpGNh4BMQkufmFxXwV1QBiPKUcrWrGXfpUhsb95gAsbiQO5vFuVq-eUQWbkCIQQS2UtleDi+Dg8Diir3+GxA6W2WT2Bwg7CwskkdQAEm07roHk8+og3O5Pr81k5AW4nFY3iDEO4IW9oeY3N4nG9LN5kajMrtSFiAEbaSgAY0KWAIuClAAs2DieHjCbcOvces9Wa8bK9vi5XvNFuZnE4WQhER9KV4mfEXPDlG5BVthdkSOLJRQZRQoHLUArlSZYJh0GASKgAGYR3AAClV6sE0g4PAAmgBKNhCnae73S2XypVErUknXkgbWWz2ZzuTzLSag9y2iLWZxMpzKRZujJ5jF5Ar+s6oKgMdgybh8QQiMQSGTyJSarTlx69UD9SkfWFWWn0xnMqYIRbhSIeVZOBxWJZOXtokX7QeFEdj7G4gmllfdNe6sFUnd7lyB6vFaiIOCQLpuK8ygRPqPguC4UQpKkIAUNoWLwB0ubophX6kuupiIAAtFEVpES4nzQpyV4Mr8USUneHp7JQNAPIwLCQMS35khurJOiQ3gON4iI8mEglQVaYSUVRiRuOYTpREhKHYQ+mKFFx+G-g4+okG48QzN2yjvOYixWh43hzJEViwtBThrIijH9jkg6cWW3EEf0iSvCQryrPJ5rmACRlfFa8QWfqikmYkCR0o5OFemAEqFv6gbBhpFa8QgEQ2N216vE4BWdl2IFHlC5iQlRdKRIhSmbH28VqcOo7jhA6U-pW7xWnZFH0VCDguL4vlXshSRAA */ createMachine( { context: initialContext, diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts index 2942d0adbb679..6ffffd640ba37 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts @@ -20,10 +20,16 @@ import { IntegrationTypestate, } from './types'; +/** + * TODO + * - Split search and sort into 2 different events + * - Split states into UI accessible interactions, keep track of current UI state for this + */ + export const createPureIntegrationsStateMachine = ( initialContext: DefaultIntegrationsContext = DEFAULT_CONTEXT ) => - /** @xstate-layout N4IgpgJg5mDOIC5QEkB2AXMUBOBDdAlgPaqwB0ArqgdYbgDYEBekAxANoAMAuoqAA5FYBQiT4gAHogCMADgDMZWZ3nyFAVgBsATnnaATNs0AaEAE9E6zrLIB2dfaPX5D7QBYAvh9NpMOfMSkZPREuBA0UKwAMgDyAIIAIsgAcgDiAPoAygCqAMK5AKIFCcVcvEgggsKiqOJSCOrS2mRunOpaerL6+tKqphYI3bZkKrbybg6yvZyctppePhhYeDXkIWER0fFJaekAYnHIUaU84lUigXWW+uojsuranJqcTa1usv3XzbK2Y5rSmi0Ux+CxAvmWARIa1C4VQUAAskRsGAtokUhkcvkiiUEmUzkILmIKvVpLNhpp5NJGvIKXM3J9Bo0yD1dLYnNppN1pKDwf5VsEYRFEcjUTsMgcjidygICTUrggqW5FDTbJy3LZZP8buoGa1FG0XrN-qr1IYeUs+YFoWE2LFEul4TEAEoFdIpAAqBVSTri7uQMWSmTxFXOcuJiB+zUpc1kbiaFKe+l1MzI7UNc2kJrN3jBFpWVoFNogrEyBTiTtyAAlgzLqpdwwhI2Ro5pY-H5ImGfoU7ZDFM3HHNIZVfJzX581CyBAwAAjIhUADGEUyYFw2AXAAsS2WK9XTiHZfXQPUbhSyNptJr9Jo3LoHH1zIglc1pAP9LZONo5ppVdyc7yJyCac50XZdV3XLcJFgdB8DAMhcAAM0wbAAApS3LKt0hKKI4gATQASlYADISA2d51QJc4RXNdNxrSpDyJY9EB6X4yE5FwvwpC9GgZNkbBHbRTSHdp320McIX5YDyMoqBqIg1goJgzB4KQsBUPQ3csIKHCCKIvMSPIKTQKo8DaOkaV6LrRjJGY7tmkmUldH0PQGSmFp7h-ON1HVd41HEy1J1gUyN2XdBkVwABbWBtwwyssjyQpiilfErNqBtTVuawHieF53E4d4GXUZRm00HRZE4-RgTE-99P5IKaJCqiwtXKKYs0iVjlxfda0JNKmIaG47my55Xnyj5HwQH9NBaVtGl6BQ3EqzwavHAzC1hKA9lwAh6FtbYHWdV0PS9H0-QDINuss3r5UVZUfzVDUtXaBkXH0VNnm8-RFqVd9ZH8wDrQ2radrYDSqzo0MjxswZM2GdiHjmPRBOkQrMzIIcLy-byHBxrwc1QIhp3gCpiNWFLrobABaEwJsp25Ma-TpbwHTz-rWqgaAuBhmEgcmw36xbCtmOwtF0TVOEMak2f5dYNr5qGTw1JRbFvH5TT0Ad1V1WNmWeKYWNjdQ9FHFaJILWWhSRMB5eskkOzhrRuwxp5AXkF6bzYt5emyrpOXUaXzZhXmD1S+U5k4ZWVUaB5nLGF7HjY34VaT9pxgBAPJyMiiwIam2+uhm5nNTRbZC6AdSVvXV3HR1RyseLp7pNxZVrq4LQvCqK8-lSrpvaXsDBUH8xjjib7DcEYjCmNkZjkfKM6CC24WB3aIC7hsmjc7sgUHZzSqTCa3FbGuNQljs9GvZavCAA */ + /** @xstate-layout N4IgpgJg5mDOIC5QEkB2AXMUBOBDdAlgPaqwB0ArqgdYbgDYEBekAxANoAMAuoqAA5FYBQiT4gAHogDsAZgCsZABxyAbAE4AjOqXrVqzqoA0IAJ6JNS1WWkAWJbfnz98pZs23ZAXy8m0mHHxiUjJ6IlwIGihWABkAeQBBABFkADkAcQB9AGUAVQBhfIBRIqTSrl4kEEFhUVRxKQRNWXV1MnUAJlltXS1PeVsTcwQOzk4bDp0PQ1tVWVlVDp8-DCw8OvIwiKjYxJSMzIAxBOQY8p5xGpFghsR1aUVbJ85NTls398mhxC7bMi7VHZbFoQc5bMsQP41kESJtwpFUFAALJEbBgXbJNJZPKFEplJIVS5Ca5iKqNZqtdpdHqtDwKQZmRDvcZOF6cVqOWxde4QqGBDaheFRFFojH7LLHU7nSoCYl1W4Ie6PZ6vd7PWTfBAtaxTdyaaTSbR2JS81b84JwiJseLJTJIuIAJSKmTSABUiukHQlXcg4qlsoSqld5WSLC02p1ujpaf0GcMFONDJx5JNkx0HMDpKaAusLYKrRBWNkigkHfkABKB2W1G6hprqWyaf7J6ScaTqWQdJ6OTVPaRkRb62SOB6TeQd7PQgUQMAAIyIVAAxlFsmBcNhFwALIslsuVi5BuW10CNLrpsicJSGdROTvSDqagZN1TyTiyK-yTRzUbySfm2FkDO85Liua4btuEiwOg+BgGQuAAGaYNgAAUxalhWmRlDECQAJoAJSsHyuYAUBC6oMuiKruuW5VtUR6kieFgNk2P6tu2nbdvImpKFYZCeN0XIGpo8jzEsviQmaxEhKRIGUWBW6sJB0GYHBiFgChaF7phRTYfhhGSTC0lzmRFFQFR4HsJoMp0TWDGSExcgDgYwnUpwkw9oyCBuHxSjOBonjto2n5-lJ5CwPJm4rugaK4AAtrAO7oeWOQFMUpTSkStn1HW6Ysumqi2NIL7JgY6iaoaShkPIDxyDxHTuDxmghYZYURVFMXxYlWmSmcBIHtWJLZYxIxXlV+WFcVzjso+94Dn276OIYvm-uJREtfmCJQIcuAEPQ1p7HajrOm6Hpej6foBv1NmDQqciKCoCwgro+iGL2hV8a2GgdPojgiSaq0GQKWybdtu1sE6NpJC6qTup63q+v6tHBse9kIHdyhqE9egGMYnnDtYwLCbIl7sTe-0rDm63A1EoN7YWmkVkj9FDaj6MPRoNIvbjwyLOMzQ464kz3J2PjiagRAzvAVRrRsmU3XWAC03OIErF5jOrGvq9VzUClQNDXAwzCQHLIbDU4bRtm+bnFV+ziapYih2NIV46LM9XvFmAOU0DQqIibKPkl+sg2JoHRdEoxOC90mr1Sx7yqG4cwiaMhU63m1OIiKYD+3ZgdzCHYfvpHSjUr27gXqVHaWHYXYrRTU7p-CxuHllCqWLI-ZFfqFIFfcb5vZVodDiOKY3hOXsNyRxmyWZEU5yzp7pv2izAiorZuNIj73GQFJ6NMPGtKoacAeF1GRZR0VrvF88Kly3GNsowlky7rhvsfIQZ1tO10zfdavn8N5qqjHvL5d8m9PIpmDi+DoIlna5U8EfUWQA */ createMachine( { context: initialContext, diff --git a/x-pack/plugins/observability_logs/public/types.ts b/x-pack/plugins/observability_logs/public/types.ts index 70399a0a3284f..1f3d1390b6005 100644 --- a/x-pack/plugins/observability_logs/public/types.ts +++ b/x-pack/plugins/observability_logs/public/types.ts @@ -4,8 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import type { Plugin } from '@kbn/core/public'; -import type { DiscoverStart } from '@kbn/discover-plugin/public'; +import { Plugin } from '@kbn/core/public'; +import { DiscoverStart } from '@kbn/discover-plugin/public'; import { DataStreamsServiceStart } from './services/data_streams'; export type ObservabilityLogsPluginSetup = void; From 319973a2c17a2e4a8cea5fd662397d2afb942fd0 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Thu, 25 May 2023 19:32:19 +0200 Subject: [PATCH 110/122] refactor(observability_logs): wip on selector state machine --- .../data_stream_selector.tsx | 16 +++++----------- .../state_machine/state_machine.ts | 13 ++++++++++--- .../data_stream_selector/state_machine/types.ts | 13 ++++++++++--- .../state_machine/use_data_stream_selector.ts | 15 +++++++++++++++ 4 files changed, 40 insertions(+), 17 deletions(-) diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx index 4364e59e0ae14..a296bfa090a7e 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx @@ -55,17 +55,11 @@ export function DataStreamSelector({ onStreamsReload, title, }: DataStreamSelectorProps) { - const { isOpen, togglePopover } = useDataStreamSelector(); + const { isOpen, panelId, changePanel, togglePopover } = useDataStreamSelector(); const [setSpyRef] = useIntersectionRef({ onIntersecting: onIntegrationsLoadMore }); - const [currentPanel, setCurrentPanel] = useState(INTEGRATION_PANEL_ID); - - const [search, handleSearch] = useSearchStrategy({ id: currentPanel, onSearch }); - - const handlePanelChange = ({ panelId }: { panelId: EuiContextMenuPanelId }) => { - setCurrentPanel(panelId as PanelId); - }; + const [search, handleSearch] = useSearchStrategy({ id: panelId, onSearch }); const handleStreamSelection = useCallback( (dataStream) => { @@ -155,16 +149,16 @@ export function DataStreamSelector({ > diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/state_machine.ts b/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/state_machine.ts index 61d06a705e4aa..6fb8675ed812b 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/state_machine.ts +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/state_machine.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { createMachine } from 'xstate'; +import { assign, createMachine } from 'xstate'; import { DEFAULT_CONTEXT } from './defaults'; import { DataStreamsSelectorContext, @@ -17,7 +17,7 @@ import { export const createPureDataStreamsSelectorStateMachine = ( initialContext: DefaultDataStreamsSelectorContext = DEFAULT_CONTEXT ) => - /** @xstate-layout N4IgpgJg5mDOIC5QBECGAXVBldAnMqAtrFmADZgDG6A9rgHSVk2yQDEAKgPIDiPAMgFEA2gAYAuolAAHFgEt0cmgDspIAB6IAjKIDs9AGwHdATgMAOXQFYANCACe281foBmUVZPmTV86--+ACwAvsF2aJg4+EQk5FS0DDTSYMqcvAIiEmqysApKqkga2nqGxmaWtg6IAEz0WoEmuq4+fgGuga6hYSDKNBBwahHYeATEpBTUdNnyiipqmggAtAZ2jkvOblai1QaigVpGhwah4RjD0WNxkwxMLJDTubMFoAuB1avaWrUHR7+HJyAhlFRrEJgl6EkUg88nNCgtqlYXFZdKJXF9KmsDK56FYAUCRjFxvE6BDksp6HIIBRoU95oh3Njkaj0R8EForFousEgA */ + /** @xstate-layout N4IgpgJg5mDOIC5QBECGAXVBldAnMqAtrFmADZgDG6A9rgHSVk2yQDEAKgPIDiPAMgFEA2gAYAuolAAHFgEt0cmgDspIAB6IAjABYAnPQBMAdgDMOnQDZjOrbqsBWADQgAnokM6H9Y4dOiHBwAOUT0g4wc9QwBfaJc0TBx8IhJyKloGGmkwZU5eAREJNVlYBSVVJA1tfSMzC2tbe0tnN209b0M-UQtIwwdTSNj4jGw8AmJSCmo6eiyctgBhAAkAQQA5HkEAfQAFdcF+MUlKkrKVNU0ELUCjXSDLHVEQvuNdF3crrXoHoN+vB9M-kMWmMsTiIGUNAgcDUCVGyQmaWmuGK8kU50qlwAtJZ3ogsRF6EEHHZrjoBpZLHpjEEhiA4UlxqkphlGMxWBBUaV0RVQJcdIY8QhDKIvv5LKZjNT2gLBuCGWMUpN0jM5ryQKceRcPNYfKJjKIHiDDHpxUKtA96NT-A5DEEtEE7UEBnSFQjmSrMtllPQ5BAKFyzurLqZrnqDUbfKbDUK+oZ6DozKJQ1pTIZLA6HJYwdEgA */ createMachine( { context: initialContext, @@ -36,6 +36,9 @@ export const createPureDataStreamsSelectorStateMachine = ( initial: 'idle', on: { TOGGLE: 'closed', + CHANGE_PANEL: { + actions: 'storePanelId', + }, }, states: { idle: {}, @@ -44,7 +47,11 @@ export const createPureDataStreamsSelectorStateMachine = ( }, }, { - actions: {}, + actions: { + storePanelId: assign((_context, event) => + 'panelId' in event ? { panelId: event.panelId } : {} + ), + }, } ); diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/types.ts b/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/types.ts index ea8d1ef31ab99..f69a42cc386b0 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/types.ts +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/types.ts @@ -8,6 +8,8 @@ import type { IImmutableCache } from '../../../../common/immutable_cache'; import { SortOrder } from '../../../../common/latest'; import { PanelId } from '../types'; +export type ChangePanelHandler = ({ panelId }: { panelId: string }) => void; + export interface DataStreamsSelectorSearchParams { name?: string; sortOrder?: SortOrder; @@ -31,6 +33,11 @@ export type DataStreamsSelectorTypestate = export type DataStreamsSelectorContext = DataStreamsSelectorTypestate['context']; -export interface DataStreamsSelectorEvent { - type: 'TOGGLE'; -} +export type DataStreamsSelectorEvent = + | { + type: 'TOGGLE'; + } + | { + type: 'CHANGE_PANEL'; + panelId: PanelId; + }; diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/use_data_stream_selector.ts b/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/use_data_stream_selector.ts index 6986272594b68..398175f4ec549 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/use_data_stream_selector.ts +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/use_data_stream_selector.ts @@ -7,7 +7,9 @@ import { useInterpret, useSelector } from '@xstate/react'; import { useCallback } from 'react'; +import { PanelId } from '../types'; import { createDataStreamsSelectorStateMachine } from './state_machine'; +import { ChangePanelHandler } from './types'; export const useDataStreamSelector = () => { const dataStreamsSelectorStateService = useInterpret(() => @@ -16,20 +18,33 @@ export const useDataStreamSelector = () => { const isOpen = useSelector(dataStreamsSelectorStateService, (state) => state.matches('open')); + const panelId = useSelector(dataStreamsSelectorStateService, (state) => state.context.panelId); + const togglePopover = useCallback( () => dataStreamsSelectorStateService.send({ type: 'TOGGLE' }), [dataStreamsSelectorStateService] ); + const changePanel = useCallback( + (panelDetails) => + dataStreamsSelectorStateService.send({ + type: 'CHANGE_PANEL', + panelId: panelDetails.panelId as PanelId, + }), + [dataStreamsSelectorStateService] + ); + return { // Data isOpen, + panelId, // Failure states // Loading states // Actions + changePanel, togglePopover, }; }; From 8ba8fe2103c3504abff11b93465a4ac2ed515a45 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 25 May 2023 17:38:02 +0000 Subject: [PATCH 111/122] [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' --- .../components/data_stream_selector/data_stream_selector.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx index a296bfa090a7e..5059c653c389c 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx @@ -6,8 +6,7 @@ */ import { EuiContextMenu, EuiContextMenuPanel, EuiHorizontalRule } from '@elastic/eui'; -import { EuiContextMenuPanelId } from '@elastic/eui/src/components/context_menu/context_menu'; -import React, { useCallback, useMemo, useState } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { dynamic } from '../../../common/dynamic'; import type { DataStreamSelectionHandler } from '../../customizations/custom_data_stream_selector'; import { useIntersectionRef } from '../../hooks/use_intersection_ref'; @@ -24,7 +23,7 @@ import { useDataStreamSelector } from './state_machine/use_data_stream_selector' import { DataStreamsPopover } from './sub_components/data_streams_popover'; import { DataStreamSkeleton } from './sub_components/data_streams_skeleton'; import { SearchControls, useSearchStrategy } from './sub_components/search_controls'; -import { PanelId, DataStreamSelectorProps } from './types'; +import { DataStreamSelectorProps } from './types'; import { buildIntegrationsTree, setIntegrationListSpy } from './utils'; /** From 982e6faf7008f3dd730341c4f1c8d54aa0e13977 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 25 May 2023 18:15:37 +0000 Subject: [PATCH 112/122] [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' --- x-pack/plugins/observability_logs/public/plugin.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/observability_logs/public/plugin.tsx b/x-pack/plugins/observability_logs/public/plugin.tsx index fd349bff54f0c..a3f08326b4ad6 100644 --- a/x-pack/plugins/observability_logs/public/plugin.tsx +++ b/x-pack/plugins/observability_logs/public/plugin.tsx @@ -18,7 +18,7 @@ export class ObservabilityLogsPlugin implements ObservabilityLogsClientPluginCla this.dataStreamsService = new DataStreamsService(); } - public setup() { } + public setup() {} public start(core: CoreStart, plugins: ObservabilityLogsStartDeps) { const { discover } = plugins; From 92e8c893ce5b33c59c4102cc3d3cfe3c01115cf3 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Fri, 26 May 2023 20:14:19 +0200 Subject: [PATCH 113/122] refactor(observability_logs): create internal state machine and split integration machine events --- .../common/immutable_cache.ts | 4 + .../data_stream_selector/constants.tsx | 2 +- .../data_stream_selector.tsx | 71 +++--- .../state_machine/defaults.ts | 12 +- .../state_machine/state_machine.ts | 142 +++++++++++- .../state_machine/types.ts | 40 +++- .../state_machine/use_data_stream_selector.ts | 77 +++++-- .../sub_components/search_controls.tsx | 97 ++------ .../components/data_stream_selector/types.ts | 20 +- .../components/data_stream_selector/utils.tsx | 4 +- .../custom_data_stream_selector.tsx | 32 +-- .../public/hooks/use_integrations.ts | 69 ++++-- .../integrations/src/state_machine.ts | 214 +++++++----------- .../state_machines/integrations/src/types.ts | 48 ++-- 14 files changed, 474 insertions(+), 358 deletions(-) diff --git a/x-pack/plugins/observability_logs/common/immutable_cache.ts b/x-pack/plugins/observability_logs/common/immutable_cache.ts index ce837b25a1794..b54e518a32c33 100644 --- a/x-pack/plugins/observability_logs/common/immutable_cache.ts +++ b/x-pack/plugins/observability_logs/common/immutable_cache.ts @@ -41,6 +41,10 @@ export class ImmutableCache implements IImmutableCache( - (dataStream) => { - onStreamSelected(dataStream); - togglePopover(); - }, - [togglePopover, onStreamSelected] - ); + const { + isOpen, + panelId, + search, + changePanel, + scrollToIntegrationsBottom, + searchByName, + selectDataStream, + sortByOrder, + togglePopover, + } = useDataStreamSelector({ + onIntegrationsLoadMore, + onIntegrationsReload, + onIntegrationsSearch, + onIntegrationsSort, + onIntegrationsStreamsSearch, + onIntegrationsStreamsSort, + onUnmanagedStreamsSearch, + onUnmanagedStreamsSort, + onUnmanagedStreamsReload, + onStreamSelected, + }); + + const [setSpyRef] = useIntersectionRef({ onIntersecting: scrollToIntegrationsBottom }); const { items: integrationItems, panels: integrationPanels } = useMemo(() => { const dataStreamsItem = { name: uncategorizedLabel, onClick: onStreamsEntryClick, - panel: UNCATEGORIZED_STREAMS_PANEL_ID, + panel: UNMANAGED_STREAMS_PANEL_ID, }; const createIntegrationStatusItem = () => ({ @@ -96,7 +110,7 @@ export function DataStreamSelector({ const { items, panels } = buildIntegrationsTree({ integrations, - onStreamSelected: handleStreamSelection, + onStreamSelected: selectDataStream, }); setIntegrationListSpy(items, setSpyRef); @@ -110,7 +124,7 @@ export function DataStreamSelector({ }, [ integrations, integrationsError, - handleStreamSelection, + selectDataStream, onIntegrationsReload, onStreamsEntryClick, setSpyRef, @@ -124,16 +138,16 @@ export function DataStreamSelector({ items: integrationItems, }, { - id: UNCATEGORIZED_STREAMS_PANEL_ID, + id: UNMANAGED_STREAMS_PANEL_ID, title: uncategorizedLabel, width: DATA_VIEW_POPOVER_CONTENT_WIDTH, content: ( ), }, @@ -151,7 +165,8 @@ export function DataStreamSelector({ diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/defaults.ts b/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/defaults.ts index e6d02309147d1..984e850103c0d 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/defaults.ts +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/defaults.ts @@ -7,13 +7,15 @@ import { ImmutableCache } from '../../../../common/immutable_cache'; import { INTEGRATION_PANEL_ID } from '../constants'; -import { DefaultDataStreamsSelectorContext } from './types'; +import { DataStreamsSelectorSearchParams, DefaultDataStreamsSelectorContext } from './types'; + +export const defaultSearch: DataStreamsSelectorSearchParams = { + name: '', + sortOrder: 'asc', +}; export const DEFAULT_CONTEXT: DefaultDataStreamsSelectorContext = { searchCache: new ImmutableCache(), panelId: INTEGRATION_PANEL_ID, - search: { - name: '', - sortOrder: 'asc', - }, + search: defaultSearch, }; diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/state_machine.ts b/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/state_machine.ts index 6fb8675ed812b..e658e7ec2f7a0 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/state_machine.ts +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/state_machine.ts @@ -5,8 +5,9 @@ * 2.0. */ -import { assign, createMachine } from 'xstate'; -import { DEFAULT_CONTEXT } from './defaults'; +import { assign, createMachine, send } from 'xstate'; +import { INTEGRATION_PANEL_ID, UNMANAGED_STREAMS_PANEL_ID } from '../constants'; +import { defaultSearch, DEFAULT_CONTEXT } from './defaults'; import { DataStreamsSelectorContext, DataStreamsSelectorEvent, @@ -17,7 +18,7 @@ import { export const createPureDataStreamsSelectorStateMachine = ( initialContext: DefaultDataStreamsSelectorContext = DEFAULT_CONTEXT ) => - /** @xstate-layout N4IgpgJg5mDOIC5QBECGAXVBldAnMqAtrFmADZgDG6A9rgHSVk2yQDEAKgPIDiPAMgFEA2gAYAuolAAHFgEt0cmgDspIAB6IAjABYAnPQBMAdgDMOnQDZjOrbqsBWADQgAnokM6H9Y4dOiHBwAOUT0g4wc9QwBfaJc0TBx8IhJyKloGGmkwZU5eAREJNVlYBSVVJA1tfSMzC2tbe0tnN209b0M-UQtIwwdTSNj4jGw8AmJSCmo6eiyctgBhAAkAQQA5HkEAfQAFdcF+MUlKkrKVNU0ELUCjXSDLHVEQvuNdF3crrXoHoN+vB9M-kMWmMsTiIGUNAgcDUCVGyQmaWmuGK8kU50qlwAtJZ3ogsRF6EEHHZrjoBpZLHpjEEhiA4UlxqkphlGMxWBBUaV0RVQJcdIY8QhDKIvv5LKZjNT2gLBuCGWMUpN0jM5ryQKceRcPNYfKJjKIHiDDHpxUKtA96NT-A5DEEtEE7UEBnSFQjmSrMtllPQ5BAKFyzurLqZrnqDUbfKbDUK+oZ6DozKJQ1pTIZLA6HJYwdEgA */ + /** @xstate-layout N4IgpgJg5mDOIC5QBECGAXVBldAnMqAtrFmADZgDG6A9rgHSVk2yQDEAKgPIDiPAMgFEA2gAYAuolAAHFgEt0cmgDspIAB6IArACYANCACeiABwBGegBYAnLZMmAzNYDstyzucBfTwbSYc+EQk5FS0DDTSYMqcvAIiEmqysApKqkgaiGZa2fSiAGw6upaizpYOeaJaBsYIWnkW+YVmedZ5zo4ODt6+GNh4BMSkFNR09BFR9PiwYWAACqjK5GxikulJKSpqmgg6nblO+ZYmzmbWoqJmztWI7Vr0ZoWiDrrODqVa3SB+fYGDISPhSLKSZwGbzRZkZZmVYyeSKTbpba7Bz7M55I4nM4XK5GRC2O5vLQPSo6URnN6fb4BAbBYZhMZAkHTOhzBZLYQ6GEgdbwtKgJF7J5ojGnc6Xa47M70ExaayWLRlXYy+yU3rUoJDUKjcbAshyaZyZRQACSynQYCguAwqVgbAAwgAJACCADkeIIAPqzV2CfgrRJw1JbUyWZz0BwmHTmAqWGy6fS4hDHHTh5xaURlfEleWq-z9DX-ek6+h6g1G03my3WlS2x2u91en1+6EB5K84NJ0PhyPRnSx6zxiVaRzSh5E9w2UTI3M-GmagEMialxTls0Wq2821YO0AJS4-H4Hu4HuNLo4gh4O6dHGNXBdWA9ACEuBxuABZf1rQMI-naKf0BU8mOI5rE6LI8glPI6nuTEzHsAcgKcGd1T+OltUZZdDRNNcq03NgsEEJ0d0dJ8AE0PRdJ033iLkeSDRFtGcUR6CVDwBweDxhyHBwU2cKD0VsDMYy6HwvjVfNUK1QEl31FdsMrDcbXwrgdw4MiPRU5BBB3T9YTbejf1qdpUxMckdDgrJzIlYprHDWVY2KUl0V2SxkIk2kpMXXVZKwit12rZQUNrZ03U9b0XV9XTuW-PkMgQMwM2Y0odFJSMHCedpuJRBwxx0FoEsKZwvFEql3PnIsMJ81cFICoL8N9QQ7TUrAOB3QiPwSL99J-OKshyRoimzcpKkgswLDqR4ynyWUVRK8Tfg8hdi0w6r-N5OqCKIkjH3IyjqKiuieu2B56hY9L00Q0yEpxGonEse4JuyPsp3yaw3IW8r0Jkst5LW1INpUtSdo0nctJ0zq9I2WLjrykx6FaY4ZsuODyglDwkpsawTBjawzFDESejzD7Cy+7yfoAVWUQgFlQGAIDqutQsbCK-Qh6LuuhzJEvoZLUt2DKTAlSMLERyo8n49K3rmom5xJ6SybkynqeUWnIA2hqmo9Fq2qog6Yo7Pq7gG57SmGqpEwHMNTJlepLIVKd3tltD5ZLKqoCVmm6fVraHXUvaaNbKGDby5isYzPjZRaSw8gcIWnisIC+IqBx3EKHRHYLZ2vNdimqc9tX3OU1T1M07S9Y54OgJ5mULJ4kx8kcSD69yHGmLKZo6m8UTlBoCA4DUUriedwP2wYhAAFprAlce8gzySFyYFhIBHgzerTe6mJy0MTpjuChyr3Gmi0JjLjJEw58WiqohXo7ECcCUUZYzHI3yV4ivyC-PpdqYwTZMgb85kmG6NwRyEnDiUVwWQCZiRlpnTyy03Z+VwjaABHZdB5HoPlYo9RzBkj7BKNMYYCjpijEVKax9P5y2zitX6yCVBBVQWPB4ewiR1HlDoLGHhShDhSgBYhfZdg71ntLWccClqVVzsrVW9N3KMMMswlErD0S6E4WQiUoERZpieCQ3QJgbBd08EAA */ createMachine( { context: initialContext, @@ -33,15 +34,81 @@ export const createPureDataStreamsSelectorStateMachine = ( }, }, open: { - initial: 'idle', + initial: 'restorePanel', on: { TOGGLE: 'closed', - CHANGE_PANEL: { - actions: 'storePanelId', - }, }, states: { - idle: {}, + restorePanel: { + always: [ + { + cond: 'isIntegrationsId', + target: 'listingIntegrations', + }, + { + cond: 'isUnmanagedStreamsId', + target: 'listingUnmanagedStreams', + }, + { + target: 'listingIntegrationStreams', + }, + ], + }, + listingIntegrations: { + entry: ['storePanelId', 'retrieveSearchFromCache', 'restoreSearchResult'], + on: { + CHANGE_PANEL: [ + { + cond: 'isUnmanagedStreamsId', + target: 'listingUnmanagedStreams', + }, + { + target: 'listingIntegrationStreams', + }, + ], + SCROLL_TO_INTEGRATIONS_BOTTOM: { + actions: 'loadMoreIntegrations', + }, + SEARCH_BY_NAME: { + actions: ['storeSearch', 'searchIntegrations'], + }, + SORT_BY_ORDER: { + actions: ['storeSearch', 'sortIntegrations'], + }, + }, + }, + listingIntegrationStreams: { + entry: ['storePanelId', 'retrieveSearchFromCache', 'restoreSearchResult'], + on: { + CHANGE_PANEL: 'listingIntegrations', + SELECT_STREAM: { + actions: 'selectStream', + target: '#closed', + }, + SEARCH_BY_NAME: { + actions: ['storeSearch', 'searchIntegrationsStreams'], + }, + SORT_BY_ORDER: { + actions: ['storeSearch', 'sortIntegrationsStreams'], + }, + }, + }, + listingUnmanagedStreams: { + entry: ['storePanelId', 'retrieveSearchFromCache', 'restoreSearchResult'], + on: { + CHANGE_PANEL: 'listingIntegrations', + SELECT_STREAM: { + actions: 'selectStream', + target: '#closed', + }, + SEARCH_BY_NAME: { + actions: ['storeSearch', 'searchUnmanagedStreams'], + }, + SORT_BY_ORDER: { + actions: ['storeSearch', 'sortUnmanagedStreams'], + }, + }, + }, }, }, }, @@ -51,6 +118,31 @@ export const createPureDataStreamsSelectorStateMachine = ( storePanelId: assign((_context, event) => 'panelId' in event ? { panelId: event.panelId } : {} ), + storeSearch: assign((context, event) => { + if ('search' in event) { + return { + search: event.search, + searchCache: context.searchCache.set(context.panelId, event.search), + }; + } + return {}; + }), + retrieveSearchFromCache: assign((context, event) => + 'panelId' in event + ? { search: context.searchCache.get(event.panelId) ?? defaultSearch } + : {} + ), + restoreSearchResult: (context) => send({ type: 'SORT', search: context.search }), + }, + guards: { + isIntegrationsId: (context, event) => { + const id = 'panelId' in event ? event.panelId : context.panelId; + return id === INTEGRATION_PANEL_ID; + }, + isUnmanagedStreamsId: (context, event) => { + const id = 'panelId' in event ? event.panelId : context.panelId; + return id === UNMANAGED_STREAMS_PANEL_ID; + }, }, } ); @@ -61,7 +153,39 @@ export interface DataStreamsStateMachineDependencies { export const createDataStreamsSelectorStateMachine = ({ initialContext, + onIntegrationsLoadMore, + onIntegrationsReload, + onIntegrationsSearch, + onIntegrationsSort, + onIntegrationsStreamsSearch, + onIntegrationsStreamsSort, + onUnmanagedStreamsSearch, + onUnmanagedStreamsSort, + onStreamSelected, + onPanelChange, + onUnmanagedStreamsReload, }: DataStreamsStateMachineDependencies) => createPureDataStreamsSelectorStateMachine(initialContext).withConfig({ - services: {}, + actions: { + selectStream: (_context, event) => + 'dataStream' in event && onStreamSelected(event.dataStream), + loadMoreIntegrations: onIntegrationsLoadMore, + relaodIntegrations: onIntegrationsReload, + // Search actions + searchIntegrations: (_context, event) => + 'search' in event && onIntegrationsSearch(event.search), + sortIntegrations: (_context, event) => 'search' in event && onIntegrationsSort(event.search), + searchIntegrationsStreams: (context, event) => + 'search' in event && + onIntegrationsStreamsSearch({ ...event.search, integrationId: context.panelId }), + sortIntegrationsStreams: (context, event) => + 'search' in event && + onIntegrationsStreamsSort({ ...event.search, integrationId: context.panelId }), + searchUnmanagedStreams: (_context, event) => + 'search' in event && onUnmanagedStreamsSearch(event.search), + sortUnmanagedStreams: (_context, event) => + 'search' in event && onUnmanagedStreamsSort(event.search), + changePanel: onPanelChange, + reloadUnmanagedStreams: onUnmanagedStreamsReload, + }, }); diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/types.ts b/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/types.ts index f69a42cc386b0..d8c24e7bd2bcc 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/types.ts +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/types.ts @@ -4,17 +4,18 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { DataStream } from '../../../../common/data_streams'; import type { IImmutableCache } from '../../../../common/immutable_cache'; import { SortOrder } from '../../../../common/latest'; import { PanelId } from '../types'; -export type ChangePanelHandler = ({ panelId }: { panelId: string }) => void; - export interface DataStreamsSelectorSearchParams { - name?: string; - sortOrder?: SortOrder; + name: string; + sortOrder: SortOrder; } +export type DataStreamsSelectorSearchHandler = (params: DataStreamsSelectorSearchParams) => void; + export interface DefaultDataStreamsSelectorContext { panelId: PanelId; searchCache: IImmutableCache; @@ -29,6 +30,22 @@ export type DataStreamsSelectorTypestate = | { value: 'open'; context: DefaultDataStreamsSelectorContext; + } + | { + value: 'restorePanel'; + context: DefaultDataStreamsSelectorContext; + } + | { + value: { open: 'listingIntegrations' }; + context: DefaultDataStreamsSelectorContext; + } + | { + value: { open: 'listingIntegrationStreams' }; + context: DefaultDataStreamsSelectorContext; + } + | { + value: { open: 'listingUnmanagedStreams' }; + context: DefaultDataStreamsSelectorContext; }; export type DataStreamsSelectorContext = DataStreamsSelectorTypestate['context']; @@ -40,4 +57,19 @@ export type DataStreamsSelectorEvent = | { type: 'CHANGE_PANEL'; panelId: PanelId; + } + | { + type: 'SELECT_STREAM'; + dataStream: DataStream; + } + | { + type: 'SCROLL_TO_INTEGRATIONS_BOTTOM'; + } + | { + type: 'SEARCH_BY_NAME'; + search: DataStreamsSelectorSearchParams; + } + | { + type: 'SORT_BY_ORDER'; + search: DataStreamsSelectorSearchParams; }; diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/use_data_stream_selector.ts b/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/use_data_stream_selector.ts index 398175f4ec549..cc754ccea8d36 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/use_data_stream_selector.ts +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/use_data_stream_selector.ts @@ -5,25 +5,46 @@ * 2.0. */ +import { EuiContextMenuPanelId } from '@elastic/eui/src/components/context_menu/context_menu'; import { useInterpret, useSelector } from '@xstate/react'; import { useCallback } from 'react'; -import { PanelId } from '../types'; +import { DataStreamSelectionHandler, PanelId } from '../types'; import { createDataStreamsSelectorStateMachine } from './state_machine'; -import { ChangePanelHandler } from './types'; +import { DataStreamsSelectorSearchHandler } from './types'; -export const useDataStreamSelector = () => { +type ChangePanelHandler = ({ panelId }: { panelId: EuiContextMenuPanelId }) => void; + +export const useDataStreamSelector = ({ + onIntegrationsLoadMore, + onIntegrationsReload, + onIntegrationsSearch, + onIntegrationsSort, + onIntegrationsStreamsSearch, + onIntegrationsStreamsSort, + onUnmanagedStreamsSearch, + onUnmanagedStreamsSort, + onStreamSelected, + onUnmanagedStreamsReload, +}) => { const dataStreamsSelectorStateService = useInterpret(() => - createDataStreamsSelectorStateMachine({}) + createDataStreamsSelectorStateMachine({ + onIntegrationsLoadMore, + onIntegrationsReload, + onIntegrationsSearch, + onIntegrationsSort, + onIntegrationsStreamsSearch, + onIntegrationsStreamsSort, + onUnmanagedStreamsSearch, + onUnmanagedStreamsSort, + onStreamSelected, + onUnmanagedStreamsReload, + }) ); const isOpen = useSelector(dataStreamsSelectorStateService, (state) => state.matches('open')); const panelId = useSelector(dataStreamsSelectorStateService, (state) => state.context.panelId); - - const togglePopover = useCallback( - () => dataStreamsSelectorStateService.send({ type: 'TOGGLE' }), - [dataStreamsSelectorStateService] - ); + const search = useSelector(dataStreamsSelectorStateService, (state) => state.context.search); const changePanel = useCallback( (panelDetails) => @@ -34,20 +55,42 @@ export const useDataStreamSelector = () => { [dataStreamsSelectorStateService] ); + const scrollToIntegrationsBottom = useCallback( + () => dataStreamsSelectorStateService.send({ type: 'SCROLL_TO_INTEGRATIONS_BOTTOM' }), + [dataStreamsSelectorStateService] + ); + + const searchByName = useCallback( + (params) => dataStreamsSelectorStateService.send({ type: 'SEARCH_BY_NAME', search: params }), + [dataStreamsSelectorStateService] + ); + + const selectDataStream = useCallback( + (dataStream) => dataStreamsSelectorStateService.send({ type: 'SELECT_STREAM', dataStream }), + [dataStreamsSelectorStateService] + ); + + const sortByOrder = useCallback( + (params) => dataStreamsSelectorStateService.send({ type: 'SORT_BY_ORDER', search: params }), + [dataStreamsSelectorStateService] + ); + + const togglePopover = useCallback( + () => dataStreamsSelectorStateService.send({ type: 'TOGGLE' }), + [dataStreamsSelectorStateService] + ); + return { // Data isOpen, panelId, - - // Failure states - - // Loading states - + search, // Actions changePanel, + scrollToIntegrationsBottom, + searchByName, + selectDataStream, + sortByOrder, togglePopover, }; }; - -// export const [IntegrationsProvider, useIntegrationsContext] = -// createContainer(useDataStreamSelector); diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/search_controls.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/search_controls.tsx index 160338f83a62d..03dbeea3f457c 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/search_controls.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/search_controls.tsx @@ -5,98 +5,35 @@ * 2.0. */ -import React, { useCallback, useEffect, useReducer } from 'react'; +import React from 'react'; import { EuiButtonGroup, EuiFieldSearch, EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; -import { SearchStrategy } from '../../../../common/data_streams'; import { SortOrder } from '../../../../common'; +import { DATA_VIEW_POPOVER_CONTENT_WIDTH, sortOptions, sortOrdersLabel } from '../constants'; import { - DATA_VIEW_POPOVER_CONTENT_WIDTH, - INTEGRATION_PANEL_ID, - sortOptions, - sortOrdersLabel, -} from '../constants'; -import { PanelId, SearchControlsParams, SearchHandler } from '../types'; -import { getSearchStrategy } from '../utils'; + DataStreamsSelectorSearchHandler, + DataStreamsSelectorSearchParams, +} from '../state_machine'; -type SearchControlsHandler = (params: SearchControlsParams) => void; - -const defaultSearch: SearchControlsParams = { - name: '', - sortOrder: 'asc', -}; - -const initialCache = { - [INTEGRATION_PANEL_ID]: defaultSearch, -}; - -const searchCacheReducer = ( - cache: Record, - update: { id: PanelId; params: SearchControlsParams } -) => { - const { id, params } = update; - - return { - ...cache, - [id]: params, - }; -}; - -interface UseSearchStrategyOptions { - id: PanelId; - onSearch: SearchHandler; -} - -export const useSearchStrategy = ({ id, onSearch }: UseSearchStrategyOptions) => { - const [searchCache, insertSearch] = useReducer(searchCacheReducer, initialCache); - - const search = searchCache[id] ?? defaultSearch; - - const handleSearch = useCallback( - (params: SearchControlsParams) => { - const strategy = getSearchStrategy(id); - - insertSearch({ id, params }); - - return onSearch({ - ...params, - strategy, - ...(strategy === SearchStrategy.INTEGRATIONS_DATA_STREAMS && { integrationId: id }), - }); - }, - [id, onSearch] - ); - - // Restore the search result when navigating into an existing search - // This should be cached so it'll be transparent to the user - useEffect(() => { - if (searchCache[id]) { - handleSearch(searchCache[id]); - } - }, [id]); - - return [search, handleSearch] as [SearchControlsParams, SearchControlsHandler]; -}; - -/** - * SearchControls component definition, it works with the useSearchStrategy custom hook - */ interface SearchControlsProps { isLoading: boolean; - onSearch: SearchControlsHandler; - search: SearchControlsParams; + onSearch: DataStreamsSelectorSearchHandler; + onSort: DataStreamsSelectorSearchHandler; + search: DataStreamsSelectorSearchParams; } -export const SearchControls = ({ search, onSearch, isLoading }: SearchControlsProps) => { - const handleQueryChange: React.ChangeEventHandler = (event) => { - const newSearch = { ...search, name: event.target.value }; +export const SearchControls = ({ search, onSearch, onSort, isLoading }: SearchControlsProps) => { + const handleNameChange: React.ChangeEventHandler = (event) => { + const newSearch = { + ...search, + name: event.target.value, + }; onSearch(newSearch); }; const handleSortChange = (id: string) => { - const newSearch = { ...search, sortOrder: id as SearchControlsParams['sortOrder'] }; - onSearch(newSearch); + const newSearch = { ...search, sortOrder: id as DataStreamsSelectorSearchParams['sortOrder'] }; + onSort(newSearch); }; - return ( @@ -105,7 +42,7 @@ export const SearchControls = ({ search, onSearch, isLoading }: SearchControlsPr compressed incremental value={search.name} - onChange={handleQueryChange} + onChange={handleNameChange} isLoading={isLoading} /> diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/types.ts b/x-pack/plugins/observability_logs/public/components/data_stream_selector/types.ts index fbb74e5409bdc..f9413c0254cf8 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/types.ts +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/types.ts @@ -7,8 +7,9 @@ import { IntegrationId, SortOrder } from '../../../common'; import { DataStream, Integration, SearchStrategy } from '../../../common/data_streams'; -import { LoadMoreIntegrations } from '../../hooks/use_integrations'; -import { INTEGRATION_PANEL_ID, UNCATEGORIZED_STREAMS_PANEL_ID } from './constants'; +import { SearchDataStreams } from '../../hooks/use_data_streams'; +import { LoadMoreIntegrations, SearchIntegrations } from '../../hooks/use_integrations'; +import { INTEGRATION_PANEL_ID, UNMANAGED_STREAMS_PANEL_ID } from './constants'; export interface DataStreamSelectorProps { /* The human-readable name of the currently selected view */ @@ -25,8 +26,13 @@ export interface DataStreamSelectorProps { isLoadingIntegrations: boolean; /* Flag for loading/searching generic streams */ isLoadingStreams: boolean; - /* Triggered when a search or sorting is performed on integrations */ - onSearch: SearchHandler; + /* Triggered when a search or sorting is performed */ + onIntegrationsSearch: SearchIntegrations; + onIntegrationsSort: SearchIntegrations; + onIntegrationsStreamsSearch: SearchIntegrations; + onIntegrationsStreamsSort: SearchIntegrations; + onUnmanagedStreamsSearch: SearchDataStreams; + onUnmanagedStreamsSort: SearchDataStreams; /* Triggered when we reach the bottom of the integration list and want to load more */ onIntegrationsLoadMore: LoadMoreIntegrations; /* Triggered when we reach the bottom of the integration list and want to load more */ @@ -34,14 +40,14 @@ export interface DataStreamSelectorProps { /* Triggered when the uncategorized streams entry is selected */ onStreamsEntryClick: () => void; /* Triggered when retrying to load the data streams */ - onStreamsReload: () => void; + onUnmanagedStreamsReload: () => void; /* Triggered when a data stream entry is selected */ onStreamSelected: DataStreamSelectionHandler; } export type PanelId = | typeof INTEGRATION_PANEL_ID - | typeof UNCATEGORIZED_STREAMS_PANEL_ID + | typeof UNMANAGED_STREAMS_PANEL_ID | IntegrationId; export interface SearchParams { @@ -53,6 +59,4 @@ export interface SearchParams { export type SearchControlsParams = Pick; -export type SearchHandler = (params: SearchParams) => void; - export type DataStreamSelectionHandler = (stream: DataStream) => void; diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/utils.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/utils.tsx index 02e0d5eaad622..ea5004bb7e0e8 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/utils.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/utils.tsx @@ -13,7 +13,7 @@ import { Integration, SearchStrategy } from '../../../common/data_streams'; import { DATA_VIEW_POPOVER_CONTENT_WIDTH, INTEGRATION_PANEL_ID, - UNCATEGORIZED_STREAMS_PANEL_ID, + UNMANAGED_STREAMS_PANEL_ID, } from './constants'; import { PanelId, DataStreamSelectionHandler } from './types'; @@ -74,7 +74,7 @@ export const setIntegrationListSpy = ( }; export const getSearchStrategy = (panelId: PanelId) => { - if (panelId === UNCATEGORIZED_STREAMS_PANEL_ID) return SearchStrategy.DATA_STREAMS; + if (panelId === UNMANAGED_STREAMS_PANEL_ID) return SearchStrategy.DATA_STREAMS; if (panelId === INTEGRATION_PANEL_ID) return SearchStrategy.INTEGRATIONS; return SearchStrategy.INTEGRATIONS_DATA_STREAMS; }; diff --git a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx index aeb5709243085..c85f248c80a9b 100644 --- a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx @@ -5,14 +5,9 @@ * 2.0. */ -import React, { useCallback } from 'react'; +import React from 'react'; import { DiscoverStateContainer } from '@kbn/discover-plugin/public'; -import { SearchStrategy } from '../../common/data_streams'; -import { - DataStreamSelectionHandler, - DataStreamSelector, - SearchHandler, -} from '../components/data_stream_selector'; +import { DataStreamSelectionHandler, DataStreamSelector } from '../components/data_stream_selector'; import { DataStreamsProvider, useDataStreamsContext } from '../hooks/use_data_streams'; import { InternalStateProvider, useDataView } from '../hooks/use_data_view'; import { IntegrationsProvider, useIntegrationsContext } from '../hooks/use_integrations'; @@ -33,6 +28,9 @@ export const CustomDataStreamSelector = withProviders(({ stateContainer }) => { loadMore, reloadIntegrations, searchIntegrations, + sortIntegrations, + searchIntegrationsStreams, + sortIntegrationsStreams, } = useIntegrationsContext(); const { @@ -53,17 +51,6 @@ export const CustomDataStreamSelector = withProviders(({ stateContainer }) => { }); }; - const handleSearch: SearchHandler = useCallback( - (params) => { - if (params.strategy === SearchStrategy.DATA_STREAMS) { - return searchDataStreams({ name: params.name, sortOrder: params.sortOrder }); - } else { - searchIntegrations(params); - } - }, - [searchDataStreams, searchIntegrations] - ); - return ( { isLoadingStreams={isLoadingStreams} onIntegrationsLoadMore={loadMore} onIntegrationsReload={reloadIntegrations} - onSearch={handleSearch} + onIntegrationsSearch={searchIntegrations} + onIntegrationsSort={sortIntegrations} + onIntegrationsStreamsSearch={searchIntegrationsStreams} + onIntegrationsStreamsSort={sortIntegrationsStreams} + onUnmanagedStreamsSearch={console.log} + onUnmanagedStreamsSort={console.log} + onUnmanagedStreamsReload={reloadDataStreams} onStreamSelected={handleStreamSelection} onStreamsEntryClick={loadDataStreams} - onStreamsReload={reloadDataStreams} title={dataView.getName()} /> ); diff --git a/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts b/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts index e08b143da1d75..fede1fa6384dd 100644 --- a/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts +++ b/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts @@ -9,7 +9,6 @@ import { useCallback } from 'react'; import createContainer from 'constate'; import { useInterpret, useSelector } from '@xstate/react'; import { FindIntegrationsRequestQuery, SortOrder } from '../../common'; -import type { SearchStrategy } from '../../common/data_streams'; import { IDataStreamsClient } from '../services/data_streams'; import { createIntegrationStateMachine } from '../state_machines/integrations'; @@ -18,9 +17,8 @@ interface IntegrationsContextDeps { } export interface SearchIntegrationsParams { - name?: string; - sortOrder?: SortOrder; - strategy: SearchStrategy; + name: string; + sortOrder: SortOrder; integrationId?: string; } @@ -36,10 +34,6 @@ const useIntegrations = ({ dataStreamsClient }: IntegrationsContextDeps) => { const integrations = useSelector(integrationsStateService, (state) => state.context.integrations); - const search = useSelector(integrationsStateService, (state) => - filterSearchParams(state.context.search) - ); - const error = useSelector(integrationsStateService, (state) => state.context.error); const isUninitialized = useSelector(integrationsStateService, (state) => @@ -49,7 +43,10 @@ const useIntegrations = ({ dataStreamsClient }: IntegrationsContextDeps) => { const isLoading = useSelector( integrationsStateService, (state) => - state.matches('loading') || state.matches('loadingMore') || state.matches('debouncingSearch') + state.matches('loading') || + state.matches({ loaded: 'loadingMore' }) || + state.matches({ loaded: 'debounceSearchingIntegrations' }) || + state.matches({ loaded: 'debounceSearchingIntegrationsStreams' }) ); const hasFailedLoading = useSelector(integrationsStateService, (state) => @@ -59,16 +56,37 @@ const useIntegrations = ({ dataStreamsClient }: IntegrationsContextDeps) => { const searchIntegrations: SearchIntegrations = useCallback( (searchParams) => integrationsStateService.send({ - type: 'SEARCH', - delay: search.name !== searchParams.name ? 300 : 0, - search: { - nameQuery: searchParams.name, - strategy: searchParams.strategy, - sortOrder: searchParams.sortOrder, - integrationId: searchParams.integrationId, - }, + type: 'SEARCH_INTEGRATIONS', + search: formatSearchParams(searchParams), }), - [integrationsStateService, search.name] + [integrationsStateService] + ); + + const sortIntegrations: SearchIntegrations = useCallback( + (searchParams) => + integrationsStateService.send({ + type: 'SORT_INTEGRATIONS', + search: formatSearchParams(searchParams), + }), + [integrationsStateService] + ); + + const searchIntegrationsStreams: SearchIntegrations = useCallback( + (searchParams) => + integrationsStateService.send({ + type: 'SEARCH_INTEGRATIONS_STREAMS', + search: formatSearchParams(searchParams), + }), + [integrationsStateService] + ); + + const sortIntegrationsStreams: SearchIntegrations = useCallback( + (searchParams) => + integrationsStateService.send({ + type: 'SORT_INTEGRATIONS_STREAMS', + search: formatSearchParams(searchParams), + }), + [integrationsStateService] ); const reloadIntegrations = useCallback( @@ -95,12 +113,14 @@ const useIntegrations = ({ dataStreamsClient }: IntegrationsContextDeps) => { // Data integrations, - search, // Actions loadMore, reloadIntegrations, searchIntegrations, + sortIntegrations, + searchIntegrationsStreams, + sortIntegrationsStreams, }; }; @@ -109,9 +129,10 @@ export const [IntegrationsProvider, useIntegrationsContext] = createContainer(us /** * Utils */ -const filterSearchParams = ( - search: FindIntegrationsRequestQuery -): Pick => ({ - name: search.nameQuery, - sortOrder: search.sortOrder, +const formatSearchParams = ({ + name, + ...params +}: SearchIntegrationsParams): FindIntegrationsRequestQuery => ({ + nameQuery: name, + ...params, }); diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts index 6ffffd640ba37..523da50314f77 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts @@ -5,10 +5,9 @@ * 2.0. */ -import { catchError, from, map, of, throwError } from 'rxjs'; import { assign, createMachine } from 'xstate'; import { EntityList } from '../../../../common/entity_list'; -import { DataStream, Integration, SearchStrategy } from '../../../../common/data_streams'; +import { DataStream, Integration } from '../../../../common/data_streams'; import { FindIntegrationsResponse, getIntegrationId } from '../../../../common'; import { IDataStreamsClient } from '../../../services/data_streams'; import { DEFAULT_CONTEXT } from './defaults'; @@ -29,9 +28,9 @@ import { export const createPureIntegrationsStateMachine = ( initialContext: DefaultIntegrationsContext = DEFAULT_CONTEXT ) => - /** @xstate-layout N4IgpgJg5mDOIC5QEkB2AXMUBOBDdAlgPaqwB0ArqgdYbgDYEBekAxANoAMAuoqAA5FYBQiT4gAHogDsAZgCsZABxyAbAE4AjOqXrVqzqoA0IAJ6JNS1WWkAWJbfnz98pZs23ZAXy8m0mHHxiUjJ6IlwIGihWABkAeQBBABFkADkAcQB9AGUAVQBhfIBRIqTSrl4kEEFhUVRxKQRNWXV1MnUAJlltXS1PeVsTcwQOzk4bDp0PQ1tVWVlVDp8-DCw8OvIwiKjYxJSMzIAxBOQY8p5xGpFghsR1aUVbJ85NTls398mhxC7bMi7VHZbFoQc5bMsQP41kESJtwpFUFAALJEbBgXbJNJZPKFEplJIVS5Ca5iKqNZqtdpdHqtDwKQZmRDvcZOF6cVqOWxde4QqGBDaheFRFFojH7LLHU7nSoCYl1W4Ie6PZ6vd7PWTfBAtaxTdyaaTSbR2JS81b84JwiJseLJTJIuIAJSKmTSABUiukHQlXcg4qlsoSqld5WSLC02p1ujpaf0GcMFONDJx5JNkx0HMDpKaAusLYKrRBWNkigkHfkABKB2W1G6hprqWyaf7J6ScaTqWQdJ6OTVPaRkRb62SOB6TeQd7PQgUQMAAIyIVAAxlFsmBcNhFwALIslsuVi5BuW10CNLrpsicJSGdROTvSDqagZN1TyTiyK-yTRzUbySfm2FkDO85Liua4btuEiwOg+BgGQuAAGaYNgAAUxalhWmRlDECQAJoAJSsHyuYAUBC6oMuiKruuW5VtUR6kieFgNk2P6tu2nbdvImpKFYZCeN0XIGpo8jzEsviQmaxEhKRIGUWBW6sJB0GYHBiFgChaF7phRTYfhhGSTC0lzmRFFQFR4HsJoMp0TWDGSExcgDgYwnUpwkw9oyCBuHxSjOBonjto2n5-lJ5CwPJm4rugaK4AAtrAO7oeWOQFMUpTSkStn1HW6Ysumqi2NIL7JgY6iaoaShkPIDxyDxHTuDxmghYZYURVFMXxYlWmSmcBIHtWJLZYxIxXlV+WFcVzjso+94Dn276OIYvm-uJREtfmCJQIcuAEPQ1p7HajrOm6Hpej6foBv1NmDQqciKCoCwgro+iGL2hV8a2GgdPojgiSaq0GQKWybdtu1sE6NpJC6qTup63q+v6tHBse9kIHdyhqE9egGMYnnDtYwLCbIl7sTe-0rDm63A1EoN7YWmkVkj9FDaj6MPRoNIvbjwyLOMzQ464kz3J2PjiagRAzvAVRrRsmU3XWAC03OIErF5jOrGvq9VzUClQNDXAwzCQHLIbDU4bRtm+bnFV+ziapYih2NIV46LM9XvFmAOU0DQqIibKPkl+sg2JoHRdEoxOC90mr1Sx7yqG4cwiaMhU63m1OIiKYD+3ZgdzCHYfvpHSjUr27gXqVHaWHYXYrRTU7p-CxuHllCqWLI-ZFfqFIFfcb5vZVodDiOKY3hOXsNyRxmyWZEU5yzp7pv2izAiorZuNIj73GQFJ6NMPGtKoacAeF1GRZR0VrvF88Kly3GNsowlky7rhvsfIQZ1tO10zfdavn8N5qqjHvL5d8m9PIpmDi+DoIlna5U8EfUWQA */ createMachine( { + /** @xstate-layout N4IgpgJg5mDOIC5QEkB2AXMUBOBDdAlgPaqwB0ArqgdYbgDYEBekAxANoAMAuoqAA5FYBQiT4gAHogCMATgDMZABzSATABZpANgDs8nbJ3rZAGhABPRAaVkt0nffkBWJ9KWz1AX09m0mHPjEpGT0RLgQNFCsECRgZDQAbkQA1nF+WHiiwaHhkQiJRADGgSRc3GXigsJZ4lIITqpmlghKumQKOi5Osg2yqqry3r4YGSXZYRGoUWDY2ETYZPz0+ABm8wC2ZOkBWeQ5k1D5qEnFWWUVSCBVIkG1Mlqy0mQ6Wi5KzjqcLo0WiK+KWg0nGkvWk8le0iGIG2mSCewmkHiEHoYFYABkAPIAQQAIgB9ACyGIASgBRPHIAByABVSQBxYlY6nIDGUgDKFwEQhuYkudXUnEUqj06icnE46iUTkBEqaiDFslsWk0-SlWk4WgeUJhY3h4URBGRqLZpKxxIAwgAJCk0+mM5msjk8Srcmp8xACoUisUStWqWW-erqsjaFWqEGdVRanzQkY7OEhBEQJEo1hsknUm20hlMlnszlXV23d0INRg9p9f3ycWcKUauUILTyRXNjWyWSa+RRnrauOwkh6iAGo1p00W61U7P2vNsvFs6lkrEEp28S7XN2gfmCsjC+Si8WS6X+9QNqNOZSubrqdQDJx6LS9-z98b65OG1Pp4mZyd23OOucLqay4FuuxabogSg6KeSiqGQ4LyCoLwfG4siPqMuyJq+mEHAS8yojEqBxAUqRbH2urYYi+yRLh2BgEcJxjOczprkWvLgY24btC86oOIKkqPA24KwdW6g6FG7ifKo7yDDGOoYfslETNReGsDMcwLEsqwbKRT7kQpyZUVMNF0QUpxBExq5ctUYGSDI4aKO2-SCjWdZaA2onqDusicJ0rh9NKTgycMunyUmZBDgARkQVCFGAbJgLg2CFAAFpEclwqOZpWlmv4OvmzFWTyqB3C07w7nuApgmobxOA2gXnq2WjuF20qaGh8YDhRyaRdFqCxfFiUpWlZG7KwEiwOg+BxLgKyYNgAAU1acAAlKw6Wdfp4VgFFMVxQlSWpVM62kCBrHFSWZYOZWzniq5dWPMo3m6E1fTNqoTjtc+g6Ij1u0DQdw0hXCbLoLRuDrLAmXjjlOZ5bO86LsBBWFtZbG2S0orKJ04oeEYDSaA2IJPDWtaag8Dz+qon16WFv19Xtg2HVAx2wCDYMQ2NE1TWQM1zYt4qrSzXVbTt9P-UNR0jcDoMJRDp2o+d7HdOeEpNdKRP2J0hNiXBshSjoDgPOo6p7tToW5FMABiuAECiECsGSmK4jD06OvLRUldoD0vG8HxfA0DYmyGQIgnr7ZfFTUKoEQQ7wJcLMugrJUALSmIGycaGQJPZyTkfBehCZUDQNwMMwkCJx7JYB4GBg6LY9jvcb1Z69oZsJoZUAVxu6PSKK55OMb3kCn38h7vdigD22Ymj4CoptxtSZdzZdTNoHE+53o4Z6zB88vkOb5GkvaN1HIigIboGh9MY9i1YGmicM8nxgh4dhNoCu-fQZSlGXhR+K+jrQbDgjEj5Dwe4lAQPctuCE0gNSD0CjoD+ws6b9X2hLZmUsBx-xKlKGwBgloCjsMYJs90H7uA1BAwEvFISyUwXvH621eqoMZoDAuA42ayzjoVbudR3hPFDLWSUxgxS6FPBqMgPpazdAcETPWSCO7W1tuXFiScLoeFgmea8TZIw3h+M0UUNhtB3nsLoEmehvDeCAA */ context: initialContext, preserveActionOrder: true, predictableActionArguments: true, @@ -41,78 +40,81 @@ export const createPureIntegrationsStateMachine = ( uninitialized: { always: 'loading', }, + loading: { + id: 'loading', invoke: { src: 'loadIntegrations', - }, - on: { - LOADING_SUCCEEDED: { + onDone: { target: 'loaded', actions: ['storeInCache', 'storeIntegrationsResponse', 'storeSearch'], }, - LOADING_FAILED: 'loadingFailed', - }, - }, - loadingMore: { - invoke: { - src: 'loadIntegrations', - }, - on: { - LOADING_SUCCEEDED: { - target: 'loaded', - actions: ['storeInCache', 'appendIntegrations', 'storeSearch'], - }, - LOADING_FAILED: 'loadingFailed', + onError: '#loadingFailed', }, }, + loaded: { - on: { - LOAD_MORE_INTEGRATIONS: { - cond: 'hasMoreIntegrations', - target: 'loadingMore', + id: 'loaded', + initial: 'idle', + states: { + idle: { + on: { + LOAD_MORE_INTEGRATIONS: { + cond: 'hasMoreIntegrations', + target: 'loadingMore', + }, + SEARCH_INTEGRATIONS: 'debounceSearchingIntegrations', + SORT_INTEGRATIONS: { + target: '#loading', + actions: 'storeSearch', + }, + SEARCH_INTEGRATIONS_STREAMS: 'debounceSearchingIntegrationsStreams', + SORT_INTEGRATIONS_STREAMS: { + target: 'idle', + actions: ['storeSearch', 'searchIntegrationsStreams'], + }, + }, }, - SEARCH: 'debouncingSearch', - }, - }, - debouncingSearch: { - entry: 'storeSearch', - on: { - SEARCH: 'debouncingSearch', - }, - after: { - SEARCH_DELAY: [ - { - cond: 'isStreamSearch', - target: 'searchingStreams', + loadingMore: { + invoke: { + src: 'loadIntegrations', + onDone: { + target: 'idle', + actions: ['storeInCache', 'appendIntegrations', 'storeSearch'], + }, + onError: '#loadingFailed', }, - { - target: 'loading', + }, + debounceSearchingIntegrations: { + entry: 'storeSearch', + on: { + SEARCH_INTEGRATIONS: 'debounceSearchingIntegrations', + }, + after: { + 300: '#loading', + }, + }, + debounceSearchingIntegrationsStreams: { + entry: 'storeSearch', + on: { + SEARCH_INTEGRATIONS_STREAMS: 'debounceSearchingIntegrationsStreams', + }, + after: { + 300: { + target: 'idle', + actions: 'searchIntegrationsStreams', + }, }, - ], - }, - }, - searchingStreams: { - invoke: { - src: 'searchStreams', - }, - on: { - SEARCH_SUCCEEDED: { - target: 'loaded', - actions: 'storeIntegrations', }, - SEARCH_FAILED: 'loadingFailed', }, }, + loadingFailed: { + id: 'loadingFailed', entry: ['clearCache', 'storeError'], exit: 'clearError', on: { - LOAD_MORE_INTEGRATIONS: { - cond: 'hasMoreIntegrations', - target: 'loadingMore', - }, - RELOAD_INTEGRATIONS: 'loading', - SEARCH: 'debouncingSearch', + RELOAD_INTEGRATIONS: '#loading', }, }, }, @@ -130,7 +132,7 @@ export const createPureIntegrationsStateMachine = ( }, }), })), - storeIntegrationsResponse: assign((context, event) => + storeIntegrationsResponse: assign((_context, event) => 'data' in event ? { integrationsSource: event.data.items, @@ -139,25 +141,24 @@ export const createPureIntegrationsStateMachine = ( } : {} ), - storeIntegrations: assign((_context, event) => - 'integrations' in event ? { integrations: event.integrations } : {} - ), - storeInCache: assign((context, event) => { - if (event.type !== 'LOADING_SUCCEEDED') return {}; + searchIntegrationsStreams: assign((context) => { + console.log('get here', context.search); - return { - cache: context.cache.set(context.search, event.data), - }; - }), - clearCache: assign((context, event) => { - if (event.type !== 'LOADING_FAILED') return {}; - - return { - cache: context.cache.clear(), - integrationsSource: null, - integrations: null, - }; + if (context.integrationsSource !== null) { + return { + integrations: searchIntegrationStreams(context.integrationsSource, context.search), + }; + } + return {}; }), + storeInCache: assign((context, event) => + 'data' in event ? { cache: context.cache.set(context.search, event.data) } : {} + ), + clearCache: assign((context) => ({ + cache: context.cache.clear(), + integrationsSource: null, + integrations: null, + })), appendIntegrations: assign((context, event) => 'data' in event ? { @@ -167,26 +168,11 @@ export const createPureIntegrationsStateMachine = ( } : {} ), - storeError: assign((_context, event) => - 'error' in event - ? { - error: event.error, - } - : {} - ), + storeError: assign((_context, event) => ('data' in event ? { error: event.data } : {})), clearError: assign((_context) => ({ error: null })), }, guards: { hasMoreIntegrations: (context) => Boolean(context.search.searchAfter), - isStreamSearch: (context) => - context.search.strategy === SearchStrategy.INTEGRATIONS_DATA_STREAMS, - }, - delays: { - SEARCH_DELAY: (_context, event) => { - if (event.type !== 'SEARCH' || !event.delay) return 0; - - return event.delay; - }, }, } ); @@ -205,51 +191,9 @@ export const createIntegrationStateMachine = ({ loadIntegrations: (context) => { const searchParams = context.search; - return from( - context.cache.has(searchParams) - ? Promise.resolve(context.cache.get(searchParams) as FindIntegrationsResponse) - : dataStreamsClient.findIntegrations(searchParams) - ).pipe( - map( - (data): IntegrationsEvent => ({ - type: 'LOADING_SUCCEEDED', - data, - }) - ), - catchError((error) => - of({ - type: 'LOADING_FAILED', - error, - }) - ) - ); - }, - searchStreams: (context) => { - const searchParams = context.search; - - return from( - context.integrationsSource !== null - ? Promise.resolve(searchIntegrationStreams(context.integrationsSource, searchParams)) - : throwError( - () => - new Error( - 'Failed to filter integration streams: No integrations found in context.' - ) - ) - ).pipe( - map( - (integrations): IntegrationsEvent => ({ - type: 'SEARCH_SUCCEEDED', - integrations, - }) - ), - catchError((error) => - of({ - type: 'SEARCH_FAILED', - error, - }) - ) - ); + return context.cache.has(searchParams) + ? Promise.resolve(context.cache.get(searchParams) as FindIntegrationsResponse) + : dataStreamsClient.findIntegrations(searchParams); }, }, }); diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts index e5eb3203f45b3..7be7d2b82acbd 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts @@ -4,15 +4,15 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { DoneInvokeEvent } from 'xstate'; import type { IImmutableCache } from '../../../../common/immutable_cache'; import { FindIntegrationsResponse, SortOrder, SearchAfter } from '../../../../common/latest'; -import { Integration, SearchStrategy } from '../../../../common/data_streams'; +import { Integration } from '../../../../common/data_streams'; export interface IntegrationsSearchParams { nameQuery?: string; searchAfter?: SearchAfter; sortOrder?: SortOrder; - strategy?: SearchStrategy; integrationId?: string; } @@ -65,8 +65,6 @@ type LoadingFailedIntegrationsContext = WithCache & WithSearch & WithError; -type SearchingIntegrationsContext = LoadedIntegrationsContext | LoadingFailedIntegrationsContext; - export type IntegrationTypestate = | { value: 'uninitialized'; @@ -85,45 +83,45 @@ export type IntegrationTypestate = context: LoadingFailedIntegrationsContext; } | { - value: 'loadingMore'; - context: LoadingIntegrationsContext; + value: { loaded: 'idle' }; + context: LoadedIntegrationsContext; + } + | { + value: { loaded: 'loadingMore' }; + context: LoadedIntegrationsContext; } | { - value: 'debouncingSearch'; - context: SearchingIntegrationsContext; + value: { loaded: 'debounceSearchingIntegrations' }; + context: LoadedIntegrationsContext; } | { - value: 'searchingStreams'; - context: SearchingIntegrationsContext; + value: { loaded: 'debounceSearchingIntegrationsStreams' }; + context: LoadedIntegrationsContext; }; export type IntegrationsContext = IntegrationTypestate['context']; export type IntegrationsEvent = | { - type: 'LOADING_SUCCEEDED'; - data: FindIntegrationsResponse; - } - | { - type: 'LOADING_FAILED'; - error: Error; + type: 'LOAD_MORE_INTEGRATIONS'; } | { - type: 'SEARCH_SUCCEEDED'; - integrations: Integration[]; + type: 'RELOAD_INTEGRATIONS'; } | { - type: 'SEARCH_FAILED'; - error: Error; + type: 'SEARCH_INTEGRATIONS'; + search: IntegrationsSearchParams; } | { - type: 'LOAD_MORE_INTEGRATIONS'; + type: 'SORT_INTEGRATIONS'; + search: IntegrationsSearchParams; } | { - type: 'RELOAD_INTEGRATIONS'; + type: 'SEARCH_INTEGRATIONS_STREAMS'; + search: IntegrationsSearchParams; } | { - type: 'SEARCH'; + type: 'SORT_INTEGRATIONS_STREAMS'; search: IntegrationsSearchParams; - delay?: number; - }; + } + | DoneInvokeEvent; From 947cd763c53941b16d0b0f86eb07f3a4bd2ba1c9 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Fri, 26 May 2023 20:16:15 +0200 Subject: [PATCH 114/122] refactor(observability_logs): remove conflicting code --- .../data_stream_selector/data_stream_selector.tsx | 8 -------- 1 file changed, 8 deletions(-) diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx index 76ca629505e25..60d0d4013a6e0 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx @@ -6,11 +6,7 @@ */ import { EuiContextMenu, EuiContextMenuPanel, EuiHorizontalRule } from '@elastic/eui'; -<<<<<<< HEAD import React, { useMemo } from 'react'; -======= -import React, { useCallback, useMemo } from 'react'; ->>>>>>> 982e6faf7008f3dd730341c4f1c8d54aa0e13977 import { dynamic } from '../../../common/dynamic'; import { useIntersectionRef } from '../../hooks/use_intersection_ref'; import { @@ -25,11 +21,7 @@ import { import { useDataStreamSelector } from './state_machine/use_data_stream_selector'; import { DataStreamsPopover } from './sub_components/data_streams_popover'; import { DataStreamSkeleton } from './sub_components/data_streams_skeleton'; -<<<<<<< HEAD import { SearchControls } from './sub_components/search_controls'; -======= -import { SearchControls, useSearchStrategy } from './sub_components/search_controls'; ->>>>>>> 982e6faf7008f3dd730341c4f1c8d54aa0e13977 import { DataStreamSelectorProps } from './types'; import { buildIntegrationsTree, setIntegrationListSpy } from './utils'; From 350d125b361496728249c8bd0da82109b46af64f Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Fri, 26 May 2023 20:36:38 +0200 Subject: [PATCH 115/122] refactor(observability_logs): update data streams machine --- .../custom_data_stream_selector.tsx | 5 +- .../public/hooks/use_data_streams.ts | 40 ++++---- .../data_streams/src/state_machine.ts | 99 +++++-------------- .../state_machines/data_streams/src/types.ts | 25 ++--- .../integrations/src/state_machine.ts | 12 +-- 5 files changed, 62 insertions(+), 119 deletions(-) diff --git a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx index c85f248c80a9b..edb155aa53181 100644 --- a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx @@ -40,6 +40,7 @@ export const CustomDataStreamSelector = withProviders(({ stateContainer }) => { loadDataStreams, reloadDataStreams, searchDataStreams, + sortDataStreams, } = useDataStreamsContext(); const handleStreamSelection: DataStreamSelectionHandler = (dataStream) => { @@ -65,8 +66,8 @@ export const CustomDataStreamSelector = withProviders(({ stateContainer }) => { onIntegrationsSort={sortIntegrations} onIntegrationsStreamsSearch={searchIntegrationsStreams} onIntegrationsStreamsSort={sortIntegrationsStreams} - onUnmanagedStreamsSearch={console.log} - onUnmanagedStreamsSort={console.log} + onUnmanagedStreamsSearch={searchDataStreams} + onUnmanagedStreamsSort={sortDataStreams} onUnmanagedStreamsReload={reloadDataStreams} onStreamSelected={handleStreamSelection} onStreamsEntryClick={loadDataStreams} diff --git a/x-pack/plugins/observability_logs/public/hooks/use_data_streams.ts b/x-pack/plugins/observability_logs/public/hooks/use_data_streams.ts index 561f51dc2a921..b1ea140e285c0 100644 --- a/x-pack/plugins/observability_logs/public/hooks/use_data_streams.ts +++ b/x-pack/plugins/observability_logs/public/hooks/use_data_streams.ts @@ -17,8 +17,8 @@ interface DataStreamsContextDeps { } export interface SearchDataStreamsParams { - name?: string; - sortOrder?: SortOrder; + name: string; + sortOrder: SortOrder; } export type SearchDataStreams = (params: SearchDataStreamsParams) => void; @@ -32,10 +32,6 @@ const useDataStreams = ({ dataStreamsClient }: DataStreamsContextDeps) => { const dataStreams = useSelector(dataStreamsStateService, (state) => state.context.dataStreams); - const search = useSelector(dataStreamsStateService, (state) => - filterSearchParams(state.context.search) - ); - const error = useSelector(dataStreamsStateService, (state) => state.context.error); const isUninitialized = useSelector(dataStreamsStateService, (state) => @@ -44,7 +40,7 @@ const useDataStreams = ({ dataStreamsClient }: DataStreamsContextDeps) => { const isLoading = useSelector( dataStreamsStateService, - (state) => state.matches('loading') || state.matches('debouncingSearch') + (state) => state.matches('loading') || state.matches('debounceSearchingDataStreams') ); const hasFailedLoading = useSelector(dataStreamsStateService, (state) => @@ -64,14 +60,19 @@ const useDataStreams = ({ dataStreamsClient }: DataStreamsContextDeps) => { const searchDataStreams: SearchDataStreams = useCallback( (searchParams) => dataStreamsStateService.send({ - type: 'SEARCH', - delay: search.name !== searchParams.name ? 500 : 0, - search: { - datasetQuery: searchParams.name, - sortOrder: searchParams.sortOrder, - }, + type: 'SEARCH_DATA_STREAMS', + search: formatSearchParams(searchParams), }), - [dataStreamsStateService, search.name] + [dataStreamsStateService] + ); + + const sortDataStreams: SearchDataStreams = useCallback( + (searchParams) => + dataStreamsStateService.send({ + type: 'SORT_DATA_STREAMS', + search: formatSearchParams(searchParams), + }), + [dataStreamsStateService] ); return { @@ -88,12 +89,12 @@ const useDataStreams = ({ dataStreamsClient }: DataStreamsContextDeps) => { // Data dataStreams, - search, // Actions loadDataStreams, reloadDataStreams, searchDataStreams, + sortDataStreams, }; }; @@ -102,7 +103,10 @@ export const [DataStreamsProvider, useDataStreamsContext] = createContainer(useD /** * Utils */ -const filterSearchParams = (search: FindDataStreamsRequestQuery): SearchDataStreamsParams => ({ - name: search.datasetQuery, - sortOrder: search.sortOrder, +const formatSearchParams = ({ + name, + ...params +}: SearchDataStreamsParams): FindDataStreamsRequestQuery => ({ + datasetQuery: name, + ...params, }); diff --git a/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/state_machine.ts b/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/state_machine.ts index fcb23be366bc1..bc7bc83ca58c4 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/state_machine.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/state_machine.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { catchError, from, map, of } from 'rxjs'; import { assign, createMachine } from 'xstate'; import { FindDataStreamsResponse } from '../../../../common'; import { IDataStreamsClient } from '../../../services/data_streams'; @@ -17,16 +16,10 @@ import { DataStreamsTypestate, } from './types'; -/** - * TODO - * - Split search and sort into 2 different events - * - Split states into UI accessible interactions, keep track of current UI state for this - */ - export const createPureDataStreamsStateMachine = ( initialContext: DefaultDataStreamsContext = DEFAULT_CONTEXT ) => - /** @xstate-layout N4IgpgJg5mDOIC5QBECGAXVBldAnMqAtrAHQCuAdgJbXpWoA2VAXpAMQAyA8gILID6yHgBUe-LMIBKAUR4BZLAG0ADAF1EoAA4B7WFTraKGkAA9EAdmUBGEuavKAnAGYArACYHy5QA5l5gDQgAJ6I3jYuXl4AbM7KUS5WrgC+SYFomDj4RKQM2qgQNFCcvMgAkgByAOLiAKoAwnXS0sjNKupIIDp6BkYdZgiWNnaOrh5evgHBiFGJJC6WTm4+1lHeYSlpGNh4BMQkufmFxXwV1QBiPKUcrWrGXfpUhsb95gAsbiQO5vFuVq-eUQWbkCIQQS2UtleDi+Dg8Diir3+GxA6W2WT2Bwg7CwskkdQAEm07roHk8+og3O5Pr81k5AW4nFY3iDEO4IW9oeY3N4nG9LN5kajMrtSFiAEbaSgAY0KWAIuClAAs2DieHjCbcOvces9Wa8bK9vi5XvNFuZnE4WQhER9KV4mfEXPDlG5BVthdkSOLJRQZRQoHLUArlSZYJh0GASKgAGYR3AAClV6sE0g4PAAmgBKNhCnae73S2XypVErUknXkgbWWz2ZzuTzLSag9y2iLWZxMpzKRZujJ5jF5Ar+s6oKgMdgybh8QQiMQSGTyJSarTlx69UD9SkfWFWWn0xnMqYIRbhSIeVZOBxWJZOXtokX7QeFEdj7G4gmllfdNe6sFUnd7lyB6vFaiIOCQLpuK8ygRPqPguC4UQpKkIAUNoWLwB0ubophX6kuupiIAAtFEVpES4nzQpyV4Mr8USUneHp7JQNAPIwLCQMS35khurJOiQ3gON4iI8mEglQVaYSUVRiRuOYTpREhKHYQ+mKFFx+G-g4+okG48QzN2yjvOYixWh43hzJEViwtBThrIijH9jkg6cWW3EEf0iSvCQryrPJ5rmACRlfFa8QWfqikmYkCR0o5OFemAEqFv6gbBhpFa8QgEQ2N216vE4BWdl2IFHlC5iQlRdKRIhSmbH28VqcOo7jhA6U-pW7xWnZFH0VCDguL4vlXshSRAA */ + /** @xstate-layout N4IgpgJg5mDOIC5QBECGAXVBldAnMqAtrAHQCuAdgJbXpWoA2VAXpAMQAyA8gILID6yHgBUe-LMIBKAUR4BZLAG0ADAF1EoAA4B7WFTraKGkAA9EAdmUBWEuYCMygJwAWS1asAmOwGYANCABPRA8rZRI7c0cANisIqPjnR3MADgBfVP80TBx8IlIGbVQIGig2CEMwEhoAN20Aa0qs7DwCYhICopKEGu0AYwwqQxVVYeMdPQMjJFMLO0dbO2Tzc2dvKKSPcyt-IIRkuxJQ5WPnZKsoh3W0jJAmnNb8wuKKUrBcXG1cEk0GDAAzT6EEh3Fp5dpPLo9fqTYajabjfSDKagMwIOyLcwkDzKbwRRLKZKE847YKOea4jyOQkeTaOOwebzpTIYZq5NodCDsLCySQAYQAEoIRGIJDJ5Eo1GNdIjDMZUXYomFnFZcWtTspzLioh4SQh3DYnFFvEsPGcrC5GTcQWzHkUuVxJMIhaJxFJZAo4VppZM5YgFUqVT4ourNQqdYFEN5XCRHIHTdrks5rB5nEzbiz7mDOQAjbSUXpgLAEXC9AAWJWtDzY3J4fMFQhdovdEvU8O9SN9CFWNisyWxzjikXMDN1iQOUUi62WzmcmwuVjTlazYFz+cLxbLFYzoOIbBMsEw6EqqD+R9wAApvMcAJRsJdtHN5igFouoEvll732CekAIn3TVE3AWJxXGsTwfF1Kx7BICckhnbVnG1dZF23G1wU6F4ADFUCoBh2Bkbg+GdEU3XFH8-w7AC-QxLEcTxRwCSJKJdRTZISH2K9aVjKNEgXNMKG0Tl4GmL8pQmSiUUQABaZiIwQKSbDJJTlJUy1mWyHdSEoGhEUYFhIDEmVkRmPUjXYqllQJZQUyiJYWNCGDJxcI06UiNT0w0tCORKQz-0khAXGcGNlSjFUlJTcwWO8eYaWiFZkkcFNziWFDPIedDOQgXyJJM2ddR8NjkmUUDlX2HEtlS1l0sfNdX3fLc0rybLZSort0VsI4IkWdY7Eg6MIjg7xhzmay+PUqqwW8rCcLwrK23Elr-LsbsSApc1rI8Cc7E8FiU1W6JvGilIyS2y10iAA */ createMachine( { context: initialContext, @@ -41,33 +34,32 @@ export const createPureDataStreamsStateMachine = ( }, }, loading: { + id: 'loading', invoke: { src: 'loadDataStreams', - }, - on: { - LOADING_SUCCEEDED: { + onDone: { target: 'loaded', actions: ['storeInCache', 'storeDataStreams', 'storeSearch'], }, - LOADING_FAILED: 'loadingFailed', + onError: 'loadingFailed', }, }, loaded: { on: { - SEARCH: 'debouncingSearch', + SEARCH_DATA_STREAMS: 'debounceSearchingDataStreams', + SORT_DATA_STREAMS: { + target: 'loading', + actions: 'storeSearch', + }, }, }, - debouncingSearch: { + debounceSearchingDataStreams: { entry: 'storeSearch', on: { - SEARCH: { - target: 'debouncingSearch', - }, + SEARCH_DATA_STREAMS: 'debounceSearchingDataStreams', }, after: { - SEARCH_DELAY: { - target: 'loading', - }, + 300: 'loading', }, }, loadingFailed: { @@ -75,7 +67,6 @@ export const createPureDataStreamsStateMachine = ( exit: 'clearError', on: { RELOAD_DATA_STREAMS: 'loading', - SEARCH: 'debouncingSearch', }, }, }, @@ -87,42 +78,18 @@ export const createPureDataStreamsStateMachine = ( ...('search' in event && { search: event.search }), })), storeDataStreams: assign((_context, event) => - 'data' in event - ? { - dataStreams: event.data.items, - } - : {} + 'data' in event ? { dataStreams: event.data.items } : {} ), - storeInCache: assign((context, event) => { - if (event.type !== 'LOADING_SUCCEEDED') return {}; - - return { - cache: context.cache.set(context.search, event.data), - }; - }), - clearCache: assign((context, event) => { - if (event.type !== 'LOADING_FAILED') return {}; - - return { - cache: context.cache.clear(), - }; - }), - storeError: assign((_context, event) => - 'error' in event - ? { - error: event.error, - } - : {} + storeInCache: assign((context, event) => + 'data' in event ? { cache: context.cache.set(context.search, event.data) } : {} ), + clearCache: assign((context) => ({ + cache: context.cache.clear(), + dataStreams: null, + })), + storeError: assign((_context, event) => ('data' in event ? { error: event.data } : {})), clearError: assign((_context) => ({ error: null })), }, - delays: { - SEARCH_DELAY: (_context, event) => { - if (event.type !== 'SEARCH' || !event.delay) return 0; - - return event.delay; - }, - }, } ); @@ -137,28 +104,12 @@ export const createDataStreamsStateMachine = ({ }: DataStreamsStateMachineDependencies) => createPureDataStreamsStateMachine(initialContext).withConfig({ services: { - loadDataStreams: (context, event) => { - const searchParams = - 'search' in event ? { ...context.search, ...event.search } : context.search; + loadDataStreams: (context) => { + const searchParams = context.search; - return from( - context.cache.has(searchParams) - ? Promise.resolve(context.cache.get(searchParams) as FindDataStreamsResponse) - : dataStreamsClient.findDataStreams(searchParams) - ).pipe( - map( - (data): DataStreamsEvent => ({ - type: 'LOADING_SUCCEEDED', - data, - }) - ), - catchError((error) => - of({ - type: 'LOADING_FAILED', - error, - }) - ) - ); + return context.cache.has(searchParams) + ? Promise.resolve(context.cache.get(searchParams) as FindDataStreamsResponse) + : dataStreamsClient.findDataStreams(searchParams); }, }, }); diff --git a/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/types.ts b/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/types.ts index a511c8d4adacf..6cb553ff96c14 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/types.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/types.ts @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { DoneInvokeEvent } from 'xstate'; import type { IImmutableCache } from '../../../../common/immutable_cache'; import { FindDataStreamsResponse, SortOrder } from '../../../../common/latest'; import { DataStream } from '../../../../common/data_streams'; @@ -60,8 +61,6 @@ type LoadingFailedDataStreamsContext = WithCache & WithSearch & WithError; -type SearchingDataStreamsContext = LoadedDataStreamsContext | LoadingFailedDataStreamsContext; - export type DataStreamsTypestate = | { value: 'uninitialized'; @@ -80,29 +79,25 @@ export type DataStreamsTypestate = context: LoadingFailedDataStreamsContext; } | { - value: 'debouncingSearch'; - context: SearchingDataStreamsContext; + value: 'debounceSearchingDataStreams'; + context: LoadedDataStreamsContext; }; -export type DataStreamsContext = DataStreamTypestate['context']; +export type DataStreamsContext = DataStreamsTypestate['context']; export type DataStreamsEvent = | { type: 'LOAD_DATA_STREAMS'; } | { - type: 'LOADING_SUCCEEDED'; - data: FindDataStreamsResponse; - } - | { - type: 'LOADING_FAILED'; - error: Error; + type: 'RELOAD_DATA_STREAMS'; } | { - type: 'RELOAD_DATA_STREAMS'; + type: 'SEARCH_DATA_STREAMS'; + search: DataStreamsSearchParams; } | { - type: 'SEARCH'; + type: 'SORT_DATA_STREAMS'; search: DataStreamsSearchParams; - delay?: number; - }; + } + | DoneInvokeEvent; diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts index 523da50314f77..047fed2febd7b 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts @@ -19,18 +19,12 @@ import { IntegrationTypestate, } from './types'; -/** - * TODO - * - Split search and sort into 2 different events - * - Split states into UI accessible interactions, keep track of current UI state for this - */ - export const createPureIntegrationsStateMachine = ( initialContext: DefaultIntegrationsContext = DEFAULT_CONTEXT ) => createMachine( { - /** @xstate-layout N4IgpgJg5mDOIC5QEkB2AXMUBOBDdAlgPaqwB0ArqgdYbgDYEBekAxANoAMAuoqAA5FYBQiT4gAHogCMATgDMZABzSATABZpANgDs8nbJ3rZAGhABPRAaVkt0nffkBWJ9KWz1AX09m0mHPjEpGT0RLgQNFCsECRgZDQAbkQA1nF+WHiiwaHhkQiJRADGgSRc3GXigsJZ4lIITqpmlghKumQKOi5Osg2yqqry3r4YGSXZYRGoUWDY2ETYZPz0+ABm8wC2ZOkBWeQ5k1D5qEnFWWUVSCBVIkG1Mlqy0mQ6Wi5KzjqcLo0WiK+KWg0nGkvWk8le0iGIG2mSCewmkHiEHoYFYABkAPIAQQAIgB9ACyGIASgBRPHIAByABVSQBxYlY6nIDGUgDKFwEQhuYkudXUnEUqj06icnE46iUTkBEqaiDFslsWk0-SlWk4WgeUJhY3h4URBGRqLZpKxxIAwgAJCk0+mM5msjk8Srcmp8xACoUisUStWqWW-erqsjaFWqEGdVRanzQkY7OEhBEQJEo1hsknUm20hlMlnszlXV23d0INRg9p9f3ycWcKUauUILTyRXNjWyWSa+RRnrauOwkh6iAGo1p00W61U7P2vNsvFs6lkrEEp28S7XN2gfmCsjC+Si8WS6X+9QNqNOZSubrqdQDJx6LS9-z98b65OG1Pp4mZyd23OOucLqay4FuuxabogSg6KeSiqGQ4LyCoLwfG4siPqMuyJq+mEHAS8yojEqBxAUqRbH2urYYi+yRLh2BgEcJxjOczprkWvLgY24btC86oOIKkqPA24KwdW6g6FG7ifKo7yDDGOoYfslETNReGsDMcwLEsqwbKRT7kQpyZUVMNF0QUpxBExq5ctUYGSDI4aKO2-SCjWdZaA2onqDusicJ0rh9NKTgycMunyUmZBDgARkQVCFGAbJgLg2CFAAFpEclwqOZpWlmv4OvmzFWTyqB3C07w7nuApgmobxOA2gXnq2WjuF20qaGh8YDhRyaRdFqCxfFiUpWlZG7KwEiwOg+BxLgKyYNgAAU1acAAlKw6Wdfp4VgFFMVxQlSWpVM62kCBrHFSWZYOZWzniq5dWPMo3m6E1fTNqoTjtc+g6Ij1u0DQdw0hXCbLoLRuDrLAmXjjlOZ5bO86LsBBWFtZbG2S0orKJ04oeEYDSaA2IJPDWtaag8Dz+qon16WFv19Xtg2HVAx2wCDYMQ2NE1TWQM1zYt4qrSzXVbTt9P-UNR0jcDoMJRDp2o+d7HdOeEpNdKRP2J0hNiXBshSjoDgPOo6p7tToW5FMABiuAECiECsGSmK4jD06OvLRUldoD0vG8HxfA0DYmyGQIgnr7ZfFTUKoEQQ7wJcLMugrJUALSmIGycaGQJPZyTkfBehCZUDQNwMMwkCJx7JYB4GBg6LY9jvcb1Z69oZsJoZUAVxu6PSKK55OMb3kCn38h7vdigD22Ymj4CoptxtSZdzZdTNoHE+53o4Z6zB88vkOb5GkvaN1HIigIboGh9MY9i1YGmicM8nxgh4dhNoCu-fQZSlGXhR+K+jrQbDgjEj5Dwe4lAQPctuCE0gNSD0CjoD+ws6b9X2hLZmUsBx-xKlKGwBgloCjsMYJs90H7uA1BAwEvFISyUwXvH621eqoMZoDAuA42ayzjoVbudR3hPFDLWSUxgxS6FPBqMgPpazdAcETPWSCO7W1tuXFiScLoeFgmea8TZIw3h+M0UUNhtB3nsLoEmehvDeCAA */ + /** @xstate-layout N4IgpgJg5mDOIC5QEkB2AXMUBOBDdAlgPaqwB0ArqgdYbgDYEBekAxANoAMAuoqAA5FYBQiT4gAHogCMATgDMZABzSATAFZOANnWzOAFlnr9AGhABPRAHYtVsrP1LO2rbIXrV8gL5ezaTDj4xKRk9ES4EDRQrBAkYGQ0AG5EANbx-lh4oiFhEVEISUQAxkEkXNzl4oLC2eJSCB5mlghK8naqWkqqVk6q0vKGWj5+GJmlOeGRqNFg2NhE2GT89PgAZgsAtmQZgdnkuVNQBajJJdnllUgg1SLBdTKd0mQ26upKskpW-a1KTYif+jI+i0+l0XX0+lUeiUwxAOyywX2k0gCQg9DArAAMgB5ACCABEAPoAWWxACUAKKE5AAOQAKhSAOJk3F05DYmkAZUuAiEtzEV3q+k4im6A3U8k40mk+iskqsfwQsvUQK0nDcatUTnkulh8PGSIiKIIaIxnIpuLJAGEABLU+lMllsjncnhVPm1QWIYWiuWgyXS2XyxUSpRkeQgkFuDpKFRWPWjXaI0LIiCo9GsTnkun2hnM1nsrk864eu5ehBqfr2WR9DpBsG-CyIWxPQyOTpaVScDx9BMBBEkQ0QY2mzMW6122l5p2FzmEzl0ym44mu3hXG6e0BCkVkMX+qUyuWcBVNhDdOxq6Qg7ucbqeby+OGJgcTI1pk0ZrNknNTx0Fl3zouForsWG5llu-wns0qiOGQV7HhKcgxrqj76nsKZvhhhzEgsGKxKg8SFGk2zPgaWEogcUQ4dgYDHKc4wXG666lgKEEIJ2TyyC8Vg2N0OgGIq8hanBUqdse8gyl02h9mM6EHBRkxUbhrCzPMizLGsmwkf2ZHyWmlHTNRtGFGcwSMWuvI1OBkgyH0ijRmoIJyg2IbHkCjjOFYzjaBoqgyUmg7kWmw4AEZEFQRRgJyYC4NgRQABZRGhiJjpatq5n+zpFkxln8qg9wtK0u4DDqsauMCsqKrKgIOFYHxfI5bj+S+Q4oqF4WoJF0WxQlSWkXsrASLA6D4PEuCrJg2AABSSpwACUrDJYFelkO1EVRTFcWJdMS2kKBLH5eWlb2TWjn1h8jbNK8NWyg4spqPoMqyM1umpqtYBhet3VbX1Ol7Jy6A0bgGywKlE4ZfmWVzguS4gTlJZWaxNktKCyhWIYxg1q4shyIq0iaNWbhdEJMFWK86gvXJb1rZ1G09dtUC7bAANAyDg3DaNZDjZNM3OAtTNBe9n2099vU7f1iIszFIP7Yjh1sboKoGJ0XmdBCQnqHjSiApJxhtpwSidpTyYGVAABiuAEOiECsJSOIEhDM4urLeUFVeKjPDobz1d88iXf8nj2JG0hOPjEp+bCqBEMO8BXEz7pywVAC0siKknMFkN5WfZ55xuBVQNC3AwzCQAnrvlo0p42Fo9i3UoesG7YMKoRLy2KdMZebsjMqo8YHZvAMwpaIqrSAgYdXqNKejqvXeevsOECd9Z9TyKnp5aIohhE3VdkKDxFMt39JtvR+YBL0j9RyIofstp2d0eMegk2GQ6i2CK0p+4bMFz61+nt1ARlz7y2RobMMEYvhaBDjoAYKhBKuHcnVVwUohKxi4j-QWNMuqbTFozVupAgEFXrmGHe0Jjx2SsKoEMkoEGyFsAMGU6gejoJWpgumP1xZH0HFLYGsdcpd3qK0J4V5hQULcB-f2Z5tAv28rGOQ5UpTMP-hbK2pdmKJyOjWVQu4vY6BVnIRwioeiyBfu8O+IodRqmbj4IAA */ context: initialContext, preserveActionOrder: true, predictableActionArguments: true, @@ -49,7 +43,7 @@ export const createPureIntegrationsStateMachine = ( target: 'loaded', actions: ['storeInCache', 'storeIntegrationsResponse', 'storeSearch'], }, - onError: '#loadingFailed', + onError: 'loadingFailed', }, }, @@ -142,8 +136,6 @@ export const createPureIntegrationsStateMachine = ( : {} ), searchIntegrationsStreams: assign((context) => { - console.log('get here', context.search); - if (context.integrationsSource !== null) { return { integrations: searchIntegrationStreams(context.integrationsSource, context.search), From 4a75c11d3871114e96e1accad91f5c050fc76359 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Mon, 29 May 2023 13:42:04 +0200 Subject: [PATCH 116/122] refactor(observability_logs): update state machine restore search --- .../common/data_streams/types.ts | 6 -- .../data_stream_selector.tsx | 8 +-- .../state_machine/state_machine.ts | 64 ++++++++----------- .../state_machine/types.ts | 27 +++++++- .../state_machine/use_data_stream_selector.ts | 11 ++-- .../components/data_stream_selector/types.ts | 21 ++++-- .../components/data_stream_selector/utils.tsx | 14 +--- .../custom_data_stream_selector.tsx | 10 +-- .../public/hooks/use_data_streams.ts | 2 + .../public/hooks/use_integrations.ts | 1 + .../integrations/src/defaults.ts | 2 - 11 files changed, 85 insertions(+), 81 deletions(-) diff --git a/x-pack/plugins/observability_logs/common/data_streams/types.ts b/x-pack/plugins/observability_logs/common/data_streams/types.ts index 26d56aca3d632..140bdd3526e6c 100644 --- a/x-pack/plugins/observability_logs/common/data_streams/types.ts +++ b/x-pack/plugins/observability_logs/common/data_streams/types.ts @@ -34,9 +34,3 @@ export const integrationRT = rt.type({ export type DataStream = rt.TypeOf; export type Integration = rt.TypeOf; - -export enum SearchStrategy { - DATA_STREAMS = 'dataStreams', - INTEGRATIONS = 'integrations', - INTEGRATIONS_DATA_STREAMS = 'integrationDataStreams', -} diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx index 60d0d4013a6e0..7a8113a0226c4 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx @@ -33,11 +33,6 @@ const DataStreamsList = dynamic(() => import('./sub_components/data_streams_list }); const IntegrationsListStatus = dynamic(() => import('./sub_components/integrations_list_status')); -/** - * TODO - * - Refactor internal state management to work as a SM - */ - export function DataStreamSelector({ dataStreams, dataStreamsError, @@ -113,6 +108,7 @@ export function DataStreamSelector({ onStreamSelected: selectDataStream, }); + // Set the last item to be a spy for infinite scroll loading setIntegrationListSpy(items, setSpyRef); if (items.length === 0) items.push(createIntegrationStatusItem()); @@ -143,11 +139,11 @@ export function DataStreamSelector({ width: DATA_VIEW_POPOVER_CONTENT_WIDTH, content: ( ), }, diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/state_machine.ts b/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/state_machine.ts index e658e7ec2f7a0..e94f46dd69f7b 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/state_machine.ts +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/state_machine.ts @@ -5,12 +5,13 @@ * 2.0. */ -import { assign, createMachine, send } from 'xstate'; -import { INTEGRATION_PANEL_ID, UNMANAGED_STREAMS_PANEL_ID } from '../constants'; +import { actions, assign, createMachine, send } from 'xstate'; +import { UNMANAGED_STREAMS_PANEL_ID } from '../constants'; import { defaultSearch, DEFAULT_CONTEXT } from './defaults'; import { DataStreamsSelectorContext, DataStreamsSelectorEvent, + DataStreamsSelectorStateMachineDependencies, DataStreamsSelectorTypestate, DefaultDataStreamsSelectorContext, } from './types'; @@ -18,7 +19,7 @@ import { export const createPureDataStreamsSelectorStateMachine = ( initialContext: DefaultDataStreamsSelectorContext = DEFAULT_CONTEXT ) => - /** @xstate-layout N4IgpgJg5mDOIC5QBECGAXVBldAnMqAtrFmADZgDG6A9rgHSVk2yQDEAKgPIDiPAMgFEA2gAYAuolAAHFgEt0cmgDspIAB6IArACYANCACeiABwBGegBYAnLZMmAzNYDstyzucBfTwbSYc+EQk5FS0DDTSYMqcvAIiEmqysApKqkgaiGZa2fSiAGw6upaizpYOeaJaBsYIWnkW+YVmedZ5zo4ODt6+GNh4BMSkFNR09BFR9PiwYWAACqjK5GxikulJKSpqmgg6nblO+ZYmzmbWoqJmztWI7Vr0ZoWiDrrODqVa3SB+fYGDISPhSLKSZwGbzRZkZZmVYyeSKTbpba7Bz7M55I4nM4XK5GRC2O5vLQPSo6URnN6fb4BAbBYZhMZAkHTOhzBZLYQ6GEgdbwtKgJF7J5ojGnc6Xa47M70ExaayWLRlXYy+yU3rUoJDUKjcbAshyaZyZRQACSynQYCguAwqVgbAAwgAJACCADkeIIAPqzV2CfgrRJw1JbUyWZz0BwmHTmAqWGy6fS4hDHHTh5xaURlfEleWq-z9DX-ek6+h6g1G03my3WlS2x2u91en1+6EB5K84NJ0PhyPRnSx6zxiVaRzSh5E9w2UTI3M-GmagEMialxTls0Wq2821YO0AJS4-H4Hu4HuNLo4gh4O6dHGNXBdWA9ACEuBxuABZf1rQMI-naKf0BU8mOI5rE6LI8glPI6nuTEzHsAcgKcGd1T+OltUZZdDRNNcq03NgsEEJ0d0dJ8AE0PRdJ033iLkeSDRFtGcUR6CVDwBweDxhyHBwU2cKD0VsDMYy6HwvjVfNUK1QEl31FdsMrDcbXwrgdw4MiPRU5BBB3T9YTbejf1qdpUxMckdDgrJzIlYprHDWVY2KUl0V2SxkIk2kpMXXVZKwit12rZQUNrZ03U9b0XV9XTuW-PkMgQMwM2Y0odFJSMHCedpuJRBwxx0FoEsKZwvFEql3PnIsMJ81cFICoL8N9QQ7TUrAOB3QiPwSL99J-OKshyRoimzcpKkgswLDqR4ynyWUVRK8Tfg8hdi0w6r-N5OqCKIkjH3IyjqKiuieu2B56hY9L00Q0yEpxGonEse4JuyPsp3yaw3IW8r0Jkst5LW1INpUtSdo0nctJ0zq9I2WLjrykx6FaY4ZsuODyglDwkpsawTBjawzFDESejzD7Cy+7yfoAVWUQgFlQGAIDqutQsbCK-Qh6LuuhzJEvoZLUt2DKTAlSMLERyo8n49K3rmom5xJ6SybkynqeUWnIA2hqmo9Fq2qog6Yo7Pq7gG57SmGqpEwHMNTJlepLIVKd3tltD5ZLKqoCVmm6fVraHXUvaaNbKGDby5isYzPjZRaSw8gcIWnisIC+IqBx3EKHRHYLZ2vNdimqc9tX3OU1T1M07S9Y54OgJ5mULJ4kx8kcSD69yHGmLKZo6m8UTlBoCA4DUUriedwP2wYhAAFprAlce8gzySFyYFhIBHgzerTe6mJy0MTpjuChyr3Gmi0JjLjJEw58WiqohXo7ECcCUUZYzHI3yV4ivyC-PpdqYwTZMgb85kmG6NwRyEnDiUVwWQCZiRlpnTyy03Z+VwjaABHZdB5HoPlYo9RzBkj7BKNMYYCjpijEVKax9P5y2zitX6yCVBBVQWPB4ewiR1HlDoLGHhShDhSgBYhfZdg71ntLWccClqVVzsrVW9N3KMMMswlErD0S6E4WQiUoERZpieCQ3QJgbBd08EAA */ + /** @xstate-layout N4IgpgJg5mDOIC5QBECGAXVBldAnMqAtrFmADZgDG6A9rgHSVk2yQDEAKgPIDiPAMgFEA2gAYAuolAAHFgEt0cmgDspIAB6IArACYANCACeiABwBGeloC+Vg2kw58REuSq0GNaWGWdeAkRJqsrAKSqpIGohmZgDMAJyWZloAbDpaBsYIKRaiqda2IPbYeATEpBTUdPSe3vRkcrCKylAAksroYFC4GGGwbADCABIAggByPIIA+gAKY4L8YpIRwaEqapoIACybFiYxWnExAOzpRohxRyb0x-l2GMVOZa6VHl7KdQ1Nre2d3YoqfSGYwmMzmCzMSxk8n+4VAG22u32hxOGW0yQSuTSNjuDhKznKbiqNXe9UacmabQ6XR6ALYWH6ACUuPx+JNuJMWqMOIIeAzhhwWlxRlhJgAhLgcbgAWUWQWhYXWpiOqIQl029GSRzMJhO2MK90cpRcFXc1TeHzJFJ+1JhfSwgmGDKGYoAmpNRsMpQFISAVjDFQgkur0WZNpjTplkskLDpkiksQUiob8c9TcSLV9Kb8aco7VwGRxXZN88hBAzZct5WsIhszKl6HsdKIYmY0iqtMcGwcbnqk3iniaiebSZnrX8wsniAMRuMprNRvMK1CQv6a4hkvt6GY4h3WxHtNrLHlewb+8bCa9aiPyd8qeOVJO7fNBP1C1gOAyHTLApWVwq14GsQJFoSR5Cq0YWPGtz6rijzni8ZpXp8N5ZjaE5nnSDpOoMRYel6S6+lWsKRAgJibFoGp7N2KJnAg8TqqBCY4g8RoEgh6bXlad45o+dL5oWopuiWZYEX6-5wqYHb0JsOjajRmQ6Ecoj0LqiannBbFpsOyHNAAqsohCoMoqAwBAvFArOoILgsP7LqsxEbBuFHbruba0XEikNqpzGPppQ5IZaUD6YZxmmbx9pCK+kzvp+nqiURAbRPEiRQSqO5HA2cRkT2amwaxqb+SSOlBQZRkmZA4VYc6gnup63pyn+1YSaR5HSTESI3CqJjNtJyQ6tBfYaQVl5FYFwVlWFGFYPxRbCeWtmEY1DmSTE9A6DEJg6OYbmRt19DhjYBTKDQEBwGog35YOuANfZAYALRxCqd3JCeeUpldjDMKwEA3auzWxMljH7lkfX0HEe6vSx70XohxFiU1JHxCq2qrUxMFQwOMPpgAFp8v3iSR0QWKG5EtjtiCbEcyRdsiA3qZdWPaYFqH3rm+MI7W0RbtsrnAyYJjUxDuUY-BWkBaO3Ewo+7PLaRZgZXEfXydoOg6EeaMXdD7FM1842hRVZ4ywG5gK0rwOHLsqk2EAA */ createMachine( { context: initialContext, @@ -30,32 +31,20 @@ export const createPureDataStreamsSelectorStateMachine = ( closed: { id: 'closed', on: { - TOGGLE: 'open', + TOGGLE: 'open.hist', }, }, open: { - initial: 'restorePanel', + initial: 'listingIntegrations', on: { TOGGLE: 'closed', }, states: { - restorePanel: { - always: [ - { - cond: 'isIntegrationsId', - target: 'listingIntegrations', - }, - { - cond: 'isUnmanagedStreamsId', - target: 'listingUnmanagedStreams', - }, - { - target: 'listingIntegrationStreams', - }, - ], + hist: { + type: 'history', }, listingIntegrations: { - entry: ['storePanelId', 'retrieveSearchFromCache', 'restoreSearchResult'], + entry: ['storePanelId', 'retrieveSearchFromCache', maybeRestoreSearchResult], on: { CHANGE_PANEL: [ { @@ -78,7 +67,7 @@ export const createPureDataStreamsSelectorStateMachine = ( }, }, listingIntegrationStreams: { - entry: ['storePanelId', 'retrieveSearchFromCache', 'restoreSearchResult'], + entry: ['storePanelId', 'retrieveSearchFromCache', maybeRestoreSearchResult], on: { CHANGE_PANEL: 'listingIntegrations', SELECT_STREAM: { @@ -94,7 +83,7 @@ export const createPureDataStreamsSelectorStateMachine = ( }, }, listingUnmanagedStreams: { - entry: ['storePanelId', 'retrieveSearchFromCache', 'restoreSearchResult'], + entry: ['storePanelId', 'retrieveSearchFromCache', maybeRestoreSearchResult], on: { CHANGE_PANEL: 'listingIntegrations', SELECT_STREAM: { @@ -132,24 +121,29 @@ export const createPureDataStreamsSelectorStateMachine = ( ? { search: context.searchCache.get(event.panelId) ?? defaultSearch } : {} ), - restoreSearchResult: (context) => send({ type: 'SORT', search: context.search }), }, guards: { - isIntegrationsId: (context, event) => { - const id = 'panelId' in event ? event.panelId : context.panelId; - return id === INTEGRATION_PANEL_ID; - }, - isUnmanagedStreamsId: (context, event) => { - const id = 'panelId' in event ? event.panelId : context.panelId; - return id === UNMANAGED_STREAMS_PANEL_ID; + isUnmanagedStreamsId: (_context, event) => { + return 'panelId' in event && event.panelId === UNMANAGED_STREAMS_PANEL_ID; }, }, } ); -export interface DataStreamsStateMachineDependencies { - initialContext?: DefaultDataStreamsSelectorContext; -} +// Define a conditional action to restore a panel search result when a cached search exists +const maybeRestoreSearchResult = actions.choose< + DefaultDataStreamsSelectorContext, + DataStreamsSelectorEvent +>([ + { + cond: (context, event) => { + if (event.type !== 'CHANGE_PANEL') return false; + + return context.searchCache.has(event.panelId); + }, + actions: send((context) => ({ type: 'SORT_BY_ORDER', search: context.search })), + }, +]); export const createDataStreamsSelectorStateMachine = ({ initialContext, @@ -162,9 +156,8 @@ export const createDataStreamsSelectorStateMachine = ({ onUnmanagedStreamsSearch, onUnmanagedStreamsSort, onStreamSelected, - onPanelChange, onUnmanagedStreamsReload, -}: DataStreamsStateMachineDependencies) => +}: DataStreamsSelectorStateMachineDependencies) => createPureDataStreamsSelectorStateMachine(initialContext).withConfig({ actions: { selectStream: (_context, event) => @@ -185,7 +178,6 @@ export const createDataStreamsSelectorStateMachine = ({ 'search' in event && onUnmanagedStreamsSearch(event.search), sortUnmanagedStreams: (_context, event) => 'search' in event && onUnmanagedStreamsSort(event.search), - changePanel: onPanelChange, reloadUnmanagedStreams: onUnmanagedStreamsReload, }, }); diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/types.ts b/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/types.ts index d8c24e7bd2bcc..f77449e64c10f 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/types.ts +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/types.ts @@ -4,10 +4,17 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { EuiContextMenuPanelId } from '@elastic/eui/src/components/context_menu/context_menu'; +import { ReloadDataStreams, SearchDataStreams } from '../../../hooks/use_data_streams'; +import { + LoadMoreIntegrations, + ReloadIntegrations, + SearchIntegrations, +} from '../../../hooks/use_integrations'; import { DataStream } from '../../../../common/data_streams'; import type { IImmutableCache } from '../../../../common/immutable_cache'; import { SortOrder } from '../../../../common/latest'; -import { PanelId } from '../types'; +import { DataStreamSelectionHandler, PanelId } from '../types'; export interface DataStreamsSelectorSearchParams { name: string; @@ -16,6 +23,8 @@ export interface DataStreamsSelectorSearchParams { export type DataStreamsSelectorSearchHandler = (params: DataStreamsSelectorSearchParams) => void; +export type ChangePanelHandler = ({ panelId }: { panelId: EuiContextMenuPanelId }) => void; + export interface DefaultDataStreamsSelectorContext { panelId: PanelId; searchCache: IImmutableCache; @@ -32,7 +41,7 @@ export type DataStreamsSelectorTypestate = context: DefaultDataStreamsSelectorContext; } | { - value: 'restorePanel'; + value: { open: 'hist' }; context: DefaultDataStreamsSelectorContext; } | { @@ -73,3 +82,17 @@ export type DataStreamsSelectorEvent = type: 'SORT_BY_ORDER'; search: DataStreamsSelectorSearchParams; }; + +export interface DataStreamsSelectorStateMachineDependencies { + initialContext?: DefaultDataStreamsSelectorContext; + onIntegrationsLoadMore: LoadMoreIntegrations; + onIntegrationsReload: ReloadIntegrations; + onIntegrationsSearch: SearchIntegrations; + onIntegrationsSort: SearchIntegrations; + onIntegrationsStreamsSearch: SearchIntegrations; + onIntegrationsStreamsSort: SearchIntegrations; + onUnmanagedStreamsSearch: SearchDataStreams; + onUnmanagedStreamsSort: SearchDataStreams; + onStreamSelected: DataStreamSelectionHandler; + onUnmanagedStreamsReload: ReloadDataStreams; +} diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/use_data_stream_selector.ts b/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/use_data_stream_selector.ts index cc754ccea8d36..11760fa155a7c 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/use_data_stream_selector.ts +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/use_data_stream_selector.ts @@ -5,14 +5,15 @@ * 2.0. */ -import { EuiContextMenuPanelId } from '@elastic/eui/src/components/context_menu/context_menu'; import { useInterpret, useSelector } from '@xstate/react'; import { useCallback } from 'react'; import { DataStreamSelectionHandler, PanelId } from '../types'; import { createDataStreamsSelectorStateMachine } from './state_machine'; -import { DataStreamsSelectorSearchHandler } from './types'; - -type ChangePanelHandler = ({ panelId }: { panelId: EuiContextMenuPanelId }) => void; +import { + ChangePanelHandler, + DataStreamsSelectorSearchHandler, + DataStreamsSelectorStateMachineDependencies, +} from './types'; export const useDataStreamSelector = ({ onIntegrationsLoadMore, @@ -25,7 +26,7 @@ export const useDataStreamSelector = ({ onUnmanagedStreamsSort, onStreamSelected, onUnmanagedStreamsReload, -}) => { +}: DataStreamsSelectorStateMachineDependencies) => { const dataStreamsSelectorStateService = useInterpret(() => createDataStreamsSelectorStateMachine({ onIntegrationsLoadMore, diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/types.ts b/x-pack/plugins/observability_logs/public/components/data_stream_selector/types.ts index f9413c0254cf8..5ac6f30d05b40 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/types.ts +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/types.ts @@ -6,9 +6,17 @@ */ import { IntegrationId, SortOrder } from '../../../common'; -import { DataStream, Integration, SearchStrategy } from '../../../common/data_streams'; -import { SearchDataStreams } from '../../hooks/use_data_streams'; -import { LoadMoreIntegrations, SearchIntegrations } from '../../hooks/use_integrations'; +import { DataStream, Integration } from '../../../common/data_streams'; +import { + LoadDataStreams, + ReloadDataStreams, + SearchDataStreams, +} from '../../hooks/use_data_streams'; +import { + LoadMoreIntegrations, + ReloadIntegrations, + SearchIntegrations, +} from '../../hooks/use_integrations'; import { INTEGRATION_PANEL_ID, UNMANAGED_STREAMS_PANEL_ID } from './constants'; export interface DataStreamSelectorProps { @@ -36,11 +44,11 @@ export interface DataStreamSelectorProps { /* Triggered when we reach the bottom of the integration list and want to load more */ onIntegrationsLoadMore: LoadMoreIntegrations; /* Triggered when we reach the bottom of the integration list and want to load more */ - onIntegrationsReload: LoadMoreIntegrations; + onIntegrationsReload: ReloadIntegrations; /* Triggered when the uncategorized streams entry is selected */ - onStreamsEntryClick: () => void; + onStreamsEntryClick: LoadDataStreams; /* Triggered when retrying to load the data streams */ - onUnmanagedStreamsReload: () => void; + onUnmanagedStreamsReload: ReloadDataStreams; /* Triggered when a data stream entry is selected */ onStreamSelected: DataStreamSelectionHandler; } @@ -54,7 +62,6 @@ export interface SearchParams { integrationId?: PanelId; name?: string; sortOrder?: SortOrder; - strategy: SearchStrategy; } export type SearchControlsParams = Pick; diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/utils.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/utils.tsx index ea5004bb7e0e8..66c2429c19493 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/utils.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/utils.tsx @@ -9,12 +9,8 @@ import { EuiContextMenuPanelDescriptor, EuiContextMenuPanelItemDescriptor } from import { PackageIcon } from '@kbn/fleet-plugin/public'; import React, { RefCallback } from 'react'; import { getIntegrationId } from '../../../common'; -import { Integration, SearchStrategy } from '../../../common/data_streams'; -import { - DATA_VIEW_POPOVER_CONTENT_WIDTH, - INTEGRATION_PANEL_ID, - UNMANAGED_STREAMS_PANEL_ID, -} from './constants'; +import { Integration } from '../../../common/data_streams'; +import { DATA_VIEW_POPOVER_CONTENT_WIDTH } from './constants'; import { PanelId, DataStreamSelectionHandler } from './types'; export const getPopoverButtonStyles = ({ fullWidth }: { fullWidth?: boolean }) => ({ @@ -72,9 +68,3 @@ export const setIntegrationListSpy = ( lastItem.buttonRef = spyRef; } }; - -export const getSearchStrategy = (panelId: PanelId) => { - if (panelId === UNMANAGED_STREAMS_PANEL_ID) return SearchStrategy.DATA_STREAMS; - if (panelId === INTEGRATION_PANEL_ID) return SearchStrategy.INTEGRATIONS; - return SearchStrategy.INTEGRATIONS_DATA_STREAMS; -}; diff --git a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx index edb155aa53181..3238f273c2f33 100644 --- a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx @@ -20,7 +20,7 @@ interface CustomDataStreamSelectorProps { export const CustomDataStreamSelector = withProviders(({ stateContainer }) => { // Container component, here goes all the state management and custom logic usage to keep the DataStreamSelector presentational. const dataView = useDataView(); - + console.log('render'); const { error: integrationsError, integrations, @@ -28,8 +28,8 @@ export const CustomDataStreamSelector = withProviders(({ stateContainer }) => { loadMore, reloadIntegrations, searchIntegrations, - sortIntegrations, searchIntegrationsStreams, + sortIntegrations, sortIntegrationsStreams, } = useIntegrationsContext(); @@ -66,11 +66,11 @@ export const CustomDataStreamSelector = withProviders(({ stateContainer }) => { onIntegrationsSort={sortIntegrations} onIntegrationsStreamsSearch={searchIntegrationsStreams} onIntegrationsStreamsSort={sortIntegrationsStreams} - onUnmanagedStreamsSearch={searchDataStreams} - onUnmanagedStreamsSort={sortDataStreams} - onUnmanagedStreamsReload={reloadDataStreams} onStreamSelected={handleStreamSelection} onStreamsEntryClick={loadDataStreams} + onUnmanagedStreamsReload={reloadDataStreams} + onUnmanagedStreamsSearch={searchDataStreams} + onUnmanagedStreamsSort={sortDataStreams} title={dataView.getName()} /> ); diff --git a/x-pack/plugins/observability_logs/public/hooks/use_data_streams.ts b/x-pack/plugins/observability_logs/public/hooks/use_data_streams.ts index b1ea140e285c0..12855ceeb9909 100644 --- a/x-pack/plugins/observability_logs/public/hooks/use_data_streams.ts +++ b/x-pack/plugins/observability_logs/public/hooks/use_data_streams.ts @@ -22,6 +22,8 @@ export interface SearchDataStreamsParams { } export type SearchDataStreams = (params: SearchDataStreamsParams) => void; +export type LoadDataStreams = () => void; +export type ReloadDataStreams = () => void; const useDataStreams = ({ dataStreamsClient }: DataStreamsContextDeps) => { const dataStreamsStateService = useInterpret(() => diff --git a/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts b/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts index fede1fa6384dd..c9c0db81bbbc2 100644 --- a/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts +++ b/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts @@ -23,6 +23,7 @@ export interface SearchIntegrationsParams { } export type SearchIntegrations = (params: SearchIntegrationsParams) => void; +export type ReloadIntegrations = () => void; export type LoadMoreIntegrations = () => void; const useIntegrations = ({ dataStreamsClient }: IntegrationsContextDeps) => { diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/defaults.ts b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/defaults.ts index 30d4bbac03a1a..347b0ca648338 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/defaults.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/defaults.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { SearchStrategy } from '../../../../common/data_streams'; import { ImmutableCache } from '../../../../common/immutable_cache'; import { DefaultIntegrationsContext } from './types'; @@ -17,6 +16,5 @@ export const DEFAULT_CONTEXT: DefaultIntegrationsContext = { search: { nameQuery: '', sortOrder: 'asc', - strategy: SearchStrategy.INTEGRATIONS, }, }; From bd598e5cb69441aabc29472ef4f67ae617557a53 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Mon, 29 May 2023 15:51:56 +0200 Subject: [PATCH 117/122] refactor(observability_logs): update storybook --- .../data_stream_selector.stories.tsx | 18 +++++-- .../state_machine/state_machine.ts | 54 +++++++++++++------ .../components/data_stream_selector/utils.tsx | 4 +- .../custom_data_stream_selector.tsx | 2 +- .../public/customizations/index.tsx | 2 +- .../public/hooks/use_data_streams.ts | 10 ---- .../public/hooks/use_integrations.ts | 10 ---- 7 files changed, 55 insertions(+), 45 deletions(-) diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.stories.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.stories.tsx index ca98f87f403e6..d56241ed1bc87 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.stories.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.stories.tsx @@ -24,6 +24,10 @@ export default { options: [null, { message: 'Failed to fetch data streams' }], control: { type: 'radio' }, }, + integrationsError: { + options: [null, { message: 'Failed to fetch data integrations' }], + control: { type: 'radio' }, + }, }, }; @@ -59,13 +63,17 @@ const DataStreamSelectorTemplate: Story = (args) => { return ( ); }; @@ -73,10 +81,12 @@ const DataStreamSelectorTemplate: Story = (args) => { export const Basic = DataStreamSelectorTemplate.bind({}); Basic.args = { dataStreamsError: null, + integrationsError: null, isLoadingIntegrations: false, isLoadingStreams: false, + onIntegrationsReload: () => alert('Reload integrations...'), onStreamsEntryClick: () => console.log('Load uncategorized streams...'), - onStreamsReload: () => alert('Reloading streams...'), + onUnmanagedStreamsReload: () => alert('Reloading streams...'), }; const mockIntegrations: Integration[] = [ diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/state_machine.ts b/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/state_machine.ts index e94f46dd69f7b..0986527600a13 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/state_machine.ts +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/state_machine.ts @@ -19,7 +19,7 @@ import { export const createPureDataStreamsSelectorStateMachine = ( initialContext: DefaultDataStreamsSelectorContext = DEFAULT_CONTEXT ) => - /** @xstate-layout N4IgpgJg5mDOIC5QBECGAXVBldAnMqAtrFmADZgDG6A9rgHSVk2yQDEAKgPIDiPAMgFEA2gAYAuolAAHFgEt0cmgDspIAB6IArACYANCACeiABwBGeloC+Vg2kw58REuSq0GNaWGWdeAkRJqsrAKSqpIGohmZgDMAJyWZloAbDpaBsYIKRaiqda2IPbYeATEpBTUdPSe3vRkcrCKylAAksroYFC4GGGwbADCABIAggByPIIA+gAKY4L8YpIRwaEqapoIACybFiYxWnExAOzpRohxRyb0x-l2GMVOZa6VHl7KdQ1Nre2d3YoqfSGYwmMzmCzMSxk8n+4VAG22u32hxOGW0yQSuTSNjuDhKznKbiqNXe9UacmabQ6XR6ALYWH6ACUuPx+JNuJMWqMOIIeAzhhwWlxRlhJgAhLgcbgAWUWQWhYXWpiOqIQl029GSRzMJhO2MK90cpRcFXc1TeHzJFJ+1JhfSwgmGDKGYoAmpNRsMpQFISAVjDFQgkur0WZNpjTplkskLDpkiksQUiob8c9TcSLV9Kb8aco7VwGRxXZN88hBAzZct5WsIhszKl6HsdKIYmY0iqtMcGwcbnqk3iniaiebSZnrX8wsniAMRuMprNRvMK1CQv6a4hkvt6GY4h3WxHtNrLHlewb+8bCa9aiPyd8qeOVJO7fNBP1C1gOAyHTLApWVwq14GsQJFoSR5Cq0YWPGtz6rijzni8ZpXp8N5ZjaE5nnSDpOoMRYel6S6+lWsKRAgJibFoGp7N2KJnAg8TqqBCY4g8RoEgh6bXlad45o+dL5oWopuiWZYEX6-5wqYHb0JsOjajRmQ6Ecoj0LqiannBbFpsOyHNAAqsohCoMoqAwBAvFArOoILgsP7LqsxEbBuFHbruba0XEikNqpzGPppQ5IZaUD6YZxmmbx9pCK+kzvp+nqiURAbRPEiRQSqO5HA2cRkT2amwaxqb+SSOlBQZRkmZA4VYc6gnup63pyn+1YSaR5HSTESI3CqJjNtJyQ6tBfYaQVl5FYFwVlWFGFYPxRbCeWtmEY1DmSTE9A6DEJg6OYbmRt19DhjYBTKDQEBwGog35YOuANfZAYALRxCqd3JCeeUpldjDMKwEA3auzWxMljH7lkfX0HEe6vSx70XohxFiU1JHxCq2qrUxMFQwOMPpgAFp8v3iSR0QWKG5EtjtiCbEcyRdsiA3qZdWPaYFqH3rm+MI7W0RbtsrnAyYJjUxDuUY-BWkBaO3Ewo+7PLaRZgZXEfXydoOg6EeaMXdD7FM1842hRVZ4ywG5gK0rwOHLsqk2EAA */ + /** @xstate-layout N4IgpgJg5mDOIC5QBECGAXVBldAnMqAtrFmADZgDG6A9rgHSVk2yQDEAKgPIDiPAMgFEA2gAYAuolAAHFgEt0cmgDspIAB6IAjACYALAFZ6WgGyitBkzoMAaEAE9te+qIDsenVoAcrgwF8-OzRMHHwiEnIqWgYaaTBlTl4BEQk1WVgFJVUkDW03LXoTPS89AGZfO0cEPQBOHXoarRq6718AoIxsPAJiUgpqOnpY+PoyOVhFZSgASWV0MChcDCzYNgBhAAkAQQA5HkEAfQAFXcF+MUkc9MyVNU0ELXdCy2tKxAMvUvodGpMff0CIGCXTCvUiAxicWUo3GkxmcwWS0UKlWm12+2Op3OWkuMnkyOyoHunmcekev3+b2qWgKNVqnn+7SBnVCPQi-WiQyhMImcims3mi2WKLYWDWACUuPx+AduAdpjsOIIeOKthxplwdlgDgAhLgcbgAWQuaXxWTuiAAtNYCq5RKVdLYHIgdF4ag1fqIauUAR0Qt1wn0ooNhtCxrz+QihQTVlhBFtxZtdQBNA47LaGlK4kDXAkWhCWx71LyiNxeR1UgwGVz0Axlx1M4GswPgzmhnlwgWI4XKWNccUcFMHfvIQTik1XM23HL3S0Okz0XyucuvZ3VPSiWtkjdWX3M-2g9nByEjcOdqNIrLN4jrbZ7Q4nHZnCd4jJ5mcunRfEyuVpOqr6F49AlnofxtICTYBmCHIhtyZ58vCgqXio16xmcghrIOWAcOK8bGqkk5vuaH4PKI1jGDUBgOquAE6KIjYslBR4Qlyp6wghXbRleTGivGiYbEO6aZi+OZToSuQIHaRh0i0lJriWxiMhBjGHkGLHtvBkZIT2qGiv2g46qmI5jiJubEUSiBkjWxReK6FbyZuf4MQebJqW2cHsVMACqyiEKgyioDAEC6Wi96Yk+5wEa+NzicSX6FL+clVK4JhGKYO7WM5IKua2sFsRGUA+X5AVBbpcZCJhBzYbhGamWJ+ZaGRaWUdR-6IO4XylKIXgGN64F+tlLYwSeYaeYVvn+YFkBlXxSaGWmGZZqaRHThZkkeMBv5ej6VI+PU+hmH1e6QapuUjR2CFFZNpU8Vg+lDsZ45RaJK2xZZ+j0Hohitbtrj7aB23gYCyg0BAcBqCdOXDctMX5paZL1EuK5tQWpj1I81bI1lqFuYMTAsJAMPvmtjrfolFRrsUXw1KIoFKQNONnax4lmatElzkUi6Y-ZVSWk8P7c5lykuUNx7M-QAAWsJE+Z7OPDWFhfmUFNVLowH6Ay-X7oN0FixpY2cchvYy2zs40u6RQlDta6lJRxhFKIu7Y0xuPnZpiHdgSqEm29CBUZulgO7uVJNO6XV01rkOi+pHkFVdJXTUxPv5lWRh6ClLwo399QmFopR2ULARAA */ createMachine( { context: initialContext, @@ -160,24 +160,44 @@ export const createDataStreamsSelectorStateMachine = ({ }: DataStreamsSelectorStateMachineDependencies) => createPureDataStreamsSelectorStateMachine(initialContext).withConfig({ actions: { - selectStream: (_context, event) => - 'dataStream' in event && onStreamSelected(event.dataStream), + selectStream: (_context, event) => { + if ('dataStream' in event) { + return onStreamSelected(event.dataStream); + } + }, loadMoreIntegrations: onIntegrationsLoadMore, relaodIntegrations: onIntegrationsReload, - // Search actions - searchIntegrations: (_context, event) => - 'search' in event && onIntegrationsSearch(event.search), - sortIntegrations: (_context, event) => 'search' in event && onIntegrationsSort(event.search), - searchIntegrationsStreams: (context, event) => - 'search' in event && - onIntegrationsStreamsSearch({ ...event.search, integrationId: context.panelId }), - sortIntegrationsStreams: (context, event) => - 'search' in event && - onIntegrationsStreamsSort({ ...event.search, integrationId: context.panelId }), - searchUnmanagedStreams: (_context, event) => - 'search' in event && onUnmanagedStreamsSearch(event.search), - sortUnmanagedStreams: (_context, event) => - 'search' in event && onUnmanagedStreamsSort(event.search), reloadUnmanagedStreams: onUnmanagedStreamsReload, + // Search actions + searchIntegrations: (_context, event) => { + if ('search' in event) { + onIntegrationsSearch(event.search); + } + }, + sortIntegrations: (_context, event) => { + if ('search' in event) { + onIntegrationsSort(event.search); + } + }, + searchIntegrationsStreams: (context, event) => { + if ('search' in event) { + onIntegrationsStreamsSearch({ ...event.search, integrationId: context.panelId }); + } + }, + sortIntegrationsStreams: (context, event) => { + if ('search' in event) { + onIntegrationsStreamsSort({ ...event.search, integrationId: context.panelId }); + } + }, + searchUnmanagedStreams: (_context, event) => { + if ('search' in event) { + onUnmanagedStreamsSearch(event.search); + } + }, + sortUnmanagedStreams: (_context, event) => { + if ('search' in event) { + onUnmanagedStreamsSearch(event.search); + } + }, }, }); diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/utils.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/utils.tsx index 66c2429c19493..5679f9b0c2202 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/utils.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/utils.tsx @@ -5,13 +5,13 @@ * 2.0. */ +import React, { RefCallback } from 'react'; import { EuiContextMenuPanelDescriptor, EuiContextMenuPanelItemDescriptor } from '@elastic/eui'; import { PackageIcon } from '@kbn/fleet-plugin/public'; -import React, { RefCallback } from 'react'; import { getIntegrationId } from '../../../common'; import { Integration } from '../../../common/data_streams'; import { DATA_VIEW_POPOVER_CONTENT_WIDTH } from './constants'; -import { PanelId, DataStreamSelectionHandler } from './types'; +import { DataStreamSelectionHandler, PanelId } from './types'; export const getPopoverButtonStyles = ({ fullWidth }: { fullWidth?: boolean }) => ({ maxWidth: fullWidth ? undefined : DATA_VIEW_POPOVER_CONTENT_WIDTH, diff --git a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx index 3238f273c2f33..a33ad462e84ed 100644 --- a/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/customizations/custom_data_stream_selector.tsx @@ -20,7 +20,7 @@ interface CustomDataStreamSelectorProps { export const CustomDataStreamSelector = withProviders(({ stateContainer }) => { // Container component, here goes all the state management and custom logic usage to keep the DataStreamSelector presentational. const dataView = useDataView(); - console.log('render'); + const { error: integrationsError, integrations, diff --git a/x-pack/plugins/observability_logs/public/customizations/index.tsx b/x-pack/plugins/observability_logs/public/customizations/index.tsx index 706e203f78396..4138fe9a1af7b 100644 --- a/x-pack/plugins/observability_logs/public/customizations/index.tsx +++ b/x-pack/plugins/observability_logs/public/customizations/index.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { dynamic } from '../../common/dynamic'; -import type { CustomDataStreamSelectorBuilderProps } from './custom_data_stream_selector'; +import { CustomDataStreamSelectorBuilderProps } from './custom_data_stream_selector'; const LazyCustomDataStreamSelector = dynamic(() => import('./custom_data_stream_selector')); diff --git a/x-pack/plugins/observability_logs/public/hooks/use_data_streams.ts b/x-pack/plugins/observability_logs/public/hooks/use_data_streams.ts index 12855ceeb9909..e328fd795313a 100644 --- a/x-pack/plugins/observability_logs/public/hooks/use_data_streams.ts +++ b/x-pack/plugins/observability_logs/public/hooks/use_data_streams.ts @@ -36,19 +36,11 @@ const useDataStreams = ({ dataStreamsClient }: DataStreamsContextDeps) => { const error = useSelector(dataStreamsStateService, (state) => state.context.error); - const isUninitialized = useSelector(dataStreamsStateService, (state) => - state.matches('uninitialized') - ); - const isLoading = useSelector( dataStreamsStateService, (state) => state.matches('loading') || state.matches('debounceSearchingDataStreams') ); - const hasFailedLoading = useSelector(dataStreamsStateService, (state) => - state.matches('loadingFailed') - ); - const loadDataStreams = useCallback( () => dataStreamsStateService.send({ type: 'LOAD_DATA_STREAMS' }), [dataStreamsStateService] @@ -83,10 +75,8 @@ const useDataStreams = ({ dataStreamsClient }: DataStreamsContextDeps) => { // Failure states error, - hasFailedLoading, // Loading states - isUninitialized, isLoading, // Data diff --git a/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts b/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts index c9c0db81bbbc2..e97945c3ac2b1 100644 --- a/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts +++ b/x-pack/plugins/observability_logs/public/hooks/use_integrations.ts @@ -37,10 +37,6 @@ const useIntegrations = ({ dataStreamsClient }: IntegrationsContextDeps) => { const error = useSelector(integrationsStateService, (state) => state.context.error); - const isUninitialized = useSelector(integrationsStateService, (state) => - state.matches('uninitialized') - ); - const isLoading = useSelector( integrationsStateService, (state) => @@ -50,10 +46,6 @@ const useIntegrations = ({ dataStreamsClient }: IntegrationsContextDeps) => { state.matches({ loaded: 'debounceSearchingIntegrationsStreams' }) ); - const hasFailedLoading = useSelector(integrationsStateService, (state) => - state.matches('loadingFailed') - ); - const searchIntegrations: SearchIntegrations = useCallback( (searchParams) => integrationsStateService.send({ @@ -106,10 +98,8 @@ const useIntegrations = ({ dataStreamsClient }: IntegrationsContextDeps) => { // Failure states error, - hasFailedLoading, // Loading states - isUninitialized, isLoading, // Data From 2fdde3bf291478f4b45edc2bf3c1cc7335bd2820 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Mon, 29 May 2023 17:18:52 +0200 Subject: [PATCH 118/122] refactor(observability_logs): small adjustments --- .../data_stream_selector.stories.tsx | 12 ++++-- .../data_stream_selector.tsx | 41 +++++++++---------- .../state_machine/defaults.ts | 3 +- .../state_machine/state_machine.ts | 2 +- .../state_machine/types.ts | 17 ++------ .../state_machine/use_data_stream_selector.ts | 11 ++--- .../sub_components/data_streams_list.tsx | 5 ++- .../sub_components/data_streams_popover.tsx | 38 ++++++++++++++--- .../integrations_list_status.tsx | 13 ++++-- .../sub_components/search_controls.tsx | 6 +-- .../components/data_stream_selector/types.ts | 38 +++++++++-------- .../components/data_stream_selector/utils.tsx | 12 ++++++ .../data_streams/src/state_machine.ts | 2 +- 13 files changed, 120 insertions(+), 80 deletions(-) diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.stories.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.stories.tsx index d56241ed1bc87..6161a089ed374 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.stories.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.stories.tsx @@ -9,13 +9,13 @@ import React, { useState } from 'react'; import { I18nProvider } from '@kbn/i18n-react'; -import { Story } from '@storybook/react'; +import type { Meta, Story } from '@storybook/react'; import { IndexPatternType } from '@kbn/io-ts-utils'; import { DataStream, Integration } from '../../../common/data_streams'; import { DataStreamSelector } from './data_stream_selector'; -import { DataStreamSelectorProps, SearchControlsParams } from './types'; +import { DataStreamSelectorProps, DataStreamsSelectorSearchParams } from './types'; -export default { +const meta: Meta = { component: DataStreamSelector, title: 'observability_logs/DataStreamSelector', decorators: [(wrappedStory) => {wrappedStory()}], @@ -30,10 +30,14 @@ export default { }, }, }; +export default meta; const DataStreamSelectorTemplate: Story = (args) => { const [title, setTitle] = useState(mockIntegrations[0].dataStreams[0].title as string); - const [search, setSearch] = useState({ sortOrder: 'asc', name: '' }); + const [search, setSearch] = useState({ + sortOrder: 'asc', + name: '', + }); const [integrations, setIntegrations] = useState(() => mockIntegrations.slice(0, 10)); const onIntegrationsLoadMore = () => { diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx index 7a8113a0226c4..57f73aaf0ddbc 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import { EuiContextMenu, EuiContextMenuPanel, EuiHorizontalRule } from '@elastic/eui'; import React, { useMemo } from 'react'; +import { EuiContextMenu, EuiHorizontalRule } from '@elastic/eui'; import { dynamic } from '../../../common/dynamic'; import { useIntersectionRef } from '../../hooks/use_intersection_ref'; import { @@ -14,7 +14,6 @@ import { DATA_VIEW_POPOVER_CONTENT_WIDTH, integrationsLabel, INTEGRATION_PANEL_ID, - selectDatasetLabel, uncategorizedLabel, UNMANAGED_STREAMS_PANEL_ID, } from './constants'; @@ -46,11 +45,11 @@ export function DataStreamSelector({ onIntegrationsSort, onIntegrationsStreamsSearch, onIntegrationsStreamsSort, - onUnmanagedStreamsSearch, - onUnmanagedStreamsSort, - onUnmanagedStreamsReload, onStreamSelected, onStreamsEntryClick, + onUnmanagedStreamsReload, + onUnmanagedStreamsSearch, + onUnmanagedStreamsSort, title, }: DataStreamSelectorProps) { const { @@ -157,23 +156,21 @@ export function DataStreamSelector({ closePopover={togglePopover} onClick={togglePopover} > - - - - - + + + ); } diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/defaults.ts b/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/defaults.ts index 984e850103c0d..dc4599b6fcdd5 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/defaults.ts +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/defaults.ts @@ -7,7 +7,8 @@ import { ImmutableCache } from '../../../../common/immutable_cache'; import { INTEGRATION_PANEL_ID } from '../constants'; -import { DataStreamsSelectorSearchParams, DefaultDataStreamsSelectorContext } from './types'; +import { DataStreamsSelectorSearchParams } from '../types'; +import { DefaultDataStreamsSelectorContext } from './types'; export const defaultSearch: DataStreamsSelectorSearchParams = { name: '', diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/state_machine.ts b/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/state_machine.ts index 0986527600a13..78524c93436bf 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/state_machine.ts +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/state_machine.ts @@ -196,7 +196,7 @@ export const createDataStreamsSelectorStateMachine = ({ }, sortUnmanagedStreams: (_context, event) => { if ('search' in event) { - onUnmanagedStreamsSearch(event.search); + onUnmanagedStreamsSort(event.search); } }, }, diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/types.ts b/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/types.ts index f77449e64c10f..42e1d91524506 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/types.ts +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/types.ts @@ -4,7 +4,6 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { EuiContextMenuPanelId } from '@elastic/eui/src/components/context_menu/context_menu'; import { ReloadDataStreams, SearchDataStreams } from '../../../hooks/use_data_streams'; import { LoadMoreIntegrations, @@ -13,17 +12,7 @@ import { } from '../../../hooks/use_integrations'; import { DataStream } from '../../../../common/data_streams'; import type { IImmutableCache } from '../../../../common/immutable_cache'; -import { SortOrder } from '../../../../common/latest'; -import { DataStreamSelectionHandler, PanelId } from '../types'; - -export interface DataStreamsSelectorSearchParams { - name: string; - sortOrder: SortOrder; -} - -export type DataStreamsSelectorSearchHandler = (params: DataStreamsSelectorSearchParams) => void; - -export type ChangePanelHandler = ({ panelId }: { panelId: EuiContextMenuPanelId }) => void; +import { DataStreamSelectionHandler, DataStreamsSelectorSearchParams, PanelId } from '../types'; export interface DefaultDataStreamsSelectorContext { panelId: PanelId; @@ -91,8 +80,8 @@ export interface DataStreamsSelectorStateMachineDependencies { onIntegrationsSort: SearchIntegrations; onIntegrationsStreamsSearch: SearchIntegrations; onIntegrationsStreamsSort: SearchIntegrations; - onUnmanagedStreamsSearch: SearchDataStreams; - onUnmanagedStreamsSort: SearchDataStreams; onStreamSelected: DataStreamSelectionHandler; onUnmanagedStreamsReload: ReloadDataStreams; + onUnmanagedStreamsSearch: SearchDataStreams; + onUnmanagedStreamsSort: SearchDataStreams; } diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/use_data_stream_selector.ts b/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/use_data_stream_selector.ts index 11760fa155a7c..8f31c3706cbae 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/use_data_stream_selector.ts +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/use_data_stream_selector.ts @@ -5,15 +5,16 @@ * 2.0. */ -import { useInterpret, useSelector } from '@xstate/react'; import { useCallback } from 'react'; -import { DataStreamSelectionHandler, PanelId } from '../types'; -import { createDataStreamsSelectorStateMachine } from './state_machine'; +import { useInterpret, useSelector } from '@xstate/react'; import { ChangePanelHandler, + DataStreamSelectionHandler, DataStreamsSelectorSearchHandler, - DataStreamsSelectorStateMachineDependencies, -} from './types'; + PanelId, +} from '../types'; +import { createDataStreamsSelectorStateMachine } from './state_machine'; +import { DataStreamsSelectorStateMachineDependencies } from './types'; export const useDataStreamSelector = ({ onIntegrationsLoadMore, diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/data_streams_list.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/data_streams_list.tsx index fe5465a95a496..19f0bd8df4d98 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/data_streams_list.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/data_streams_list.tsx @@ -8,6 +8,7 @@ import React from 'react'; import { EuiButton, EuiContextMenuItem, EuiEmptyPrompt, EuiText, EuiToolTip } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; +import { ReloadDataStreams } from '../../../hooks/use_data_streams'; import { errorLabel, noDataStreamsDescriptionLabel, @@ -22,16 +23,16 @@ interface DataStreamListProps { dataStreams: DataStream[] | null; error: Error | null; isLoading: boolean; + onRetry: ReloadDataStreams; onStreamClick: DataStreamSelectionHandler; - onRetry: () => void; } export const DataStreamsList = ({ dataStreams, error, isLoading, - onStreamClick, onRetry, + onStreamClick, }: DataStreamListProps) => { const isEmpty = dataStreams == null || dataStreams.length <= 0; const hasError = error !== null; diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/data_streams_popover.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/data_streams_popover.tsx index 20c21f8f47b0f..dec20ba8c8548 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/data_streams_popover.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/data_streams_popover.tsx @@ -6,17 +6,32 @@ */ import React from 'react'; -import { EuiButton, EuiPopover, EuiPopoverProps, useIsWithinBreakpoints } from '@elastic/eui'; -import { POPOVER_ID } from '../constants'; +import { + EuiButton, + EuiHorizontalRule, + EuiPanel, + EuiPopover, + EuiPopoverProps, + EuiTitle, + useIsWithinBreakpoints, +} from '@elastic/eui'; +import styled from '@emotion/styled'; +import { DATA_VIEW_POPOVER_CONTENT_WIDTH, POPOVER_ID, selectDatasetLabel } from '../constants'; import { getPopoverButtonStyles } from '../utils'; +const panelStyle = { width: DATA_VIEW_POPOVER_CONTENT_WIDTH }; interface DataStreamsPopoverProps extends Omit { + children: React.ReactNode; onClick: () => void; title: string; - children: React.ReactNode; } -export const DataStreamsPopover = ({ title, onClick, ...props }: DataStreamsPopoverProps) => { +export const DataStreamsPopover = ({ + children, + onClick, + title, + ...props +}: DataStreamsPopoverProps) => { const isMobile = useIsWithinBreakpoints(['xs', 's']); const buttonStyles = getPopoverButtonStyles({ fullWidth: isMobile }); @@ -39,6 +54,19 @@ export const DataStreamsPopover = ({ title, onClick, ...props }: DataStreamsPopo buffer={8} {...(isMobile && { display: 'block' })} {...props} - /> + > + + + <span>{selectDatasetLabel}</span> + + + {children} + +
); }; + +const Title = styled(EuiTitle)` + padding: 12px; + display: block; +`; diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/integrations_list_status.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/integrations_list_status.tsx index c045fbd4e219a..4eb4d7736c29a 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/integrations_list_status.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/integrations_list_status.tsx @@ -5,10 +5,11 @@ * 2.0. */ +import React from 'react'; import { EuiButton, EuiEmptyPrompt, EuiText, EuiToolTip } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import React from 'react'; import { Integration } from '../../../../common/data_streams'; +import { ReloadIntegrations } from '../../../hooks/use_integrations'; import { errorLabel, noDataRetryLabel, @@ -16,13 +17,17 @@ import { noIntegrationsLabel, } from '../constants'; -interface DataStreamListProps { +interface IntegrationsListStatus { integrations: Integration[] | null; error: Error | null; - onRetry: () => void; + onRetry: ReloadIntegrations; } -export const IntegrationsListStatus = ({ integrations, error, onRetry }: DataStreamListProps) => { +export const IntegrationsListStatus = ({ + integrations, + error, + onRetry, +}: IntegrationsListStatus) => { const isEmpty = integrations == null || integrations.length <= 0; const hasError = error !== null; diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/search_controls.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/search_controls.tsx index 03dbeea3f457c..7ab9a7f8bd0a0 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/search_controls.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/search_controls.tsx @@ -9,10 +9,7 @@ import React from 'react'; import { EuiButtonGroup, EuiFieldSearch, EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; import { SortOrder } from '../../../../common'; import { DATA_VIEW_POPOVER_CONTENT_WIDTH, sortOptions, sortOrdersLabel } from '../constants'; -import { - DataStreamsSelectorSearchHandler, - DataStreamsSelectorSearchParams, -} from '../state_machine'; +import { DataStreamsSelectorSearchHandler, DataStreamsSelectorSearchParams } from '../types'; interface SearchControlsProps { isLoading: boolean; @@ -34,6 +31,7 @@ export const SearchControls = ({ search, onSearch, onSort, isLoading }: SearchCo const newSearch = { ...search, sortOrder: id as DataStreamsSelectorSearchParams['sortOrder'] }; onSort(newSearch); }; + return ( diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/types.ts b/x-pack/plugins/observability_logs/public/components/data_stream_selector/types.ts index 5ac6f30d05b40..584631b8e903f 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/types.ts +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/types.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { EuiContextMenuPanelId } from '@elastic/eui/src/components/context_menu/context_menu'; import { IntegrationId, SortOrder } from '../../../common'; import { DataStream, Integration } from '../../../common/data_streams'; import { @@ -20,20 +21,21 @@ import { import { INTEGRATION_PANEL_ID, UNMANAGED_STREAMS_PANEL_ID } from './constants'; export interface DataStreamSelectorProps { - /* The human-readable name of the currently selected view */ - title: string; - /* The integrations list, each integration includes its data streams */ - integrations: Integration[] | null; - /* Any error occurred to show when the user preview the integrations */ - integrationsError?: Error | null; /* The generic data stream list */ dataStreams: DataStream[] | null; /* Any error occurred to show when the user preview the generic data streams */ dataStreamsError?: Error | null; - /* Flag for loading/searching integrations */ + /* The integrations list, each integration includes its data streams */ + integrations: Integration[] | null; + /* Any error occurred to show when the user preview the integrations */ + integrationsError?: Error | null; + /* Flags for loading/searching integrations or data streams*/ isLoadingIntegrations: boolean; - /* Flag for loading/searching generic streams */ isLoadingStreams: boolean; + /* Triggered when we reach the bottom of the integration list and want to load more */ + onIntegrationsLoadMore: LoadMoreIntegrations; + /* Triggered when the user reload the list after an error */ + onIntegrationsReload: ReloadIntegrations; /* Triggered when a search or sorting is performed */ onIntegrationsSearch: SearchIntegrations; onIntegrationsSort: SearchIntegrations; @@ -41,16 +43,14 @@ export interface DataStreamSelectorProps { onIntegrationsStreamsSort: SearchIntegrations; onUnmanagedStreamsSearch: SearchDataStreams; onUnmanagedStreamsSort: SearchDataStreams; - /* Triggered when we reach the bottom of the integration list and want to load more */ - onIntegrationsLoadMore: LoadMoreIntegrations; - /* Triggered when we reach the bottom of the integration list and want to load more */ - onIntegrationsReload: ReloadIntegrations; - /* Triggered when the uncategorized streams entry is selected */ - onStreamsEntryClick: LoadDataStreams; /* Triggered when retrying to load the data streams */ onUnmanagedStreamsReload: ReloadDataStreams; + /* Triggered when the uncategorized streams entry is selected */ + onStreamsEntryClick: LoadDataStreams; /* Triggered when a data stream entry is selected */ onStreamSelected: DataStreamSelectionHandler; + /* The human-readable name of the currently selected view */ + title: string; } export type PanelId = @@ -60,10 +60,14 @@ export type PanelId = export interface SearchParams { integrationId?: PanelId; - name?: string; - sortOrder?: SortOrder; + name: string; + sortOrder: SortOrder; } -export type SearchControlsParams = Pick; +export type DataStreamsSelectorSearchParams = Pick; + +export type DataStreamsSelectorSearchHandler = (params: DataStreamsSelectorSearchParams) => void; + +export type ChangePanelHandler = ({ panelId }: { panelId: EuiContextMenuPanelId }) => void; export type DataStreamSelectionHandler = (stream: DataStream) => void; diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/utils.tsx b/x-pack/plugins/observability_logs/public/components/data_stream_selector/utils.tsx index 5679f9b0c2202..c34d3ccb3dd54 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/utils.tsx +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/utils.tsx @@ -27,6 +27,14 @@ interface IntegrationsTree { panels: EuiContextMenuPanelDescriptor[]; } +/** + * The `EuiContextMenu` component receives a list of panels, + * each one with a pointer id which is used as a reference for the items to know + * what panel they refer to. + * This helper function, starting from a list of integrations, + * generate the necessary item entries for each integration, + * and also create a related panel that render the list of data streams for the integration. + */ export const buildIntegrationsTree = ({ integrations, onStreamSelected, @@ -59,6 +67,10 @@ export const buildIntegrationsTree = ({ ); }; +/** + * Take a list of EuiContextMenuPanelItemDescriptor and, if exists, + * attach the passed reference to the last item. + */ export const setIntegrationListSpy = ( items: EuiContextMenuPanelItemDescriptor[], spyRef: RefCallback diff --git a/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/state_machine.ts b/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/state_machine.ts index bc7bc83ca58c4..f6edb3a632588 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/state_machine.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/state_machine.ts @@ -19,7 +19,7 @@ import { export const createPureDataStreamsStateMachine = ( initialContext: DefaultDataStreamsContext = DEFAULT_CONTEXT ) => - /** @xstate-layout N4IgpgJg5mDOIC5QBECGAXVBldAnMqAtrAHQCuAdgJbXpWoA2VAXpAMQAyA8gILID6yHgBUe-LMIBKAUR4BZLAG0ADAF1EoAA4B7WFTraKGkAA9EAdmUBWEuYCMygJwAWS1asAmOwGYANCABPRA8rZRI7c0cANisIqPjnR3MADgBfVP80TBx8IlIGbVQIGig2CEMwEhoAN20Aa0qs7DwCYhICopKEGu0AYwwqQxVVYeMdPQMjJFMLO0dbO2Tzc2dvKKSPcyt-IIRkuxJQ5WPnZKsoh3W0jJAmnNb8wuKKUrBcXG1cEk0GDAAzT6EEh3Fp5dpPLo9fqTYajabjfSDKagMwIOyLcwkDzKbwRRLKZKE847YKOea4jyOQkeTaOOwebzpTIYZq5NodCDsLCySQAYQAEoIRGIJDJ5Eo1GNdIjDMZUXYomFnFZcWtTspzLioh4SQh3DYnFFvEsPGcrC5GTcQWzHkUuVxJMIhaJxFJZAo4VppZM5YgFUqVT4ourNQqdYFEN5XCRHIHTdrks5rB5nEzbiz7mDOQAjbSUXpgLAEXC9AAWJWtDzY3J4fMFQhdovdEvU8O9SN9CFWNisyWxzjikXMDN1iQOUUi62WzmcmwuVjTlazYFz+cLxbLFYzoOIbBMsEw6EqqD+R9wAApvMcAJRsJdtHN5igFouoEvll732CekAIn3TVE3AWJxXGsTwfF1Kx7BICckhnbVnG1dZF23G1wU6F4ADFUCoBh2Bkbg+GdEU3XFH8-w7AC-QxLEcTxRwCSJKJdRTZISH2K9aVjKNEgXNMKG0Tl4GmL8pQmSiUUQABaZiIwQKSbDJJTlJUy1mWyHdSEoGhEUYFhIDEmVkRmPUjXYqllQJZQUyiJYWNCGDJxcI06UiNT0w0tCORKQz-0khAXGcGNlSjFUlJTcwWO8eYaWiFZkkcFNziWFDPIedDOQgXyJJM2ddR8NjkmUUDlX2HEtlS1l0sfNdX3fLc0rybLZSort0VsI4IkWdY7Eg6MIjg7xhzmay+PUqqwW8rCcLwrK23Elr-LsbsSApc1rI8Cc7E8FiU1W6JvGilIyS2y10iAA */ + /** @xstate-layout N4IgpgJg5mDOIC5QBECGAXVBldAnMqAtrAHQCuAdgJbXpWoA2VAXpAMQAyA8gILID6yHgBUe-LMIBKAUR4BZLAG0ADAF1EoAA4B7WFTraKGkAA9EAdmUBWEuYCMygJxWAHABYrdqwDYANCABPRAAmK2USO3NHczcXb1d3R2VvAF8U-zRMHHwiUgZtVAgaKDYIQzASGgA3bQBrCszsPAJiEnzC4oRq7QBjDCpDFVUh4x09AyMkUws7R1s7FxdQxxXlF3N-IIQXOxIw5QPgtyi7AGY7I7SMjCac1vaiihKwXFxtXBJNBgwAM3fCEiNbItPIFR5QLoUGp9CZDEZTMb6AaTUBmBB2BbmEjBZSnI6uOzeI4XTYhFYkc6nXHmYKnbzeFyOdZXEBA5q5Npg9hYWSSADCAAlBCIxBIZPIlGpRrokYZjGjCeEPOdggtlMFzJr3KSEFY9SQnN43MEjo5vE5PCy2XdQYVuVxJMJhaJxFJZAp4VoZRN5YhFSRlRc1RqtW4dadjiRnOcrG5nBr3Kl0qybsCORAwAAjbSUHpgLAEXA9AAWxWtILYPJ4-KFQhdYvdkvUCO9yN9CDcpxsrhxYSspySji8YcCiDju28UUsk6HVmidjcVtT7NaGezufzhZLZeXNrYJlgmHQFVQP2PuAAFFTlABKNjl9NZnMUPMF1BF0tPB-ET0gRE+qY0UsGx7AtdxPB8HUrHsEgZ3MPEB0Jc07CXLIV1tcEADFUCoBh2Bkbg+GdUU3QlX9-zbQC-UxbFcXxHYiTcElRwQI4XBIHYqROXFHCY8wk2TChtAzeApm-USvXGSjUUQABaPwWPk1DbhBchqFoegmFYCBpSkuUqN1bxTg4pk3HNYJnG8DENhY0Jwjg5RLAxLx3BcZS03uMFil02UUWmBBeLcKMPDcdUmM8RkdVpOYTXnHjoKiE13PQzk7R0ls9L8tFjR1M52JcZRjkiY4pwXNzk3Ekg12fV8t0-KBxJ8gCZI7DFbDjFZQljCyXCsKDI0iaJYlVaJgiM5KbVSrCcLw9LJN89sFy7CkLlcIbYgXEcthNIKByM3qdn7KyBJSIA */ createMachine( { context: initialContext, From 0715488bf9df04f54b3268d195cea0f117178994 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Tue, 30 May 2023 09:46:25 +0200 Subject: [PATCH 119/122] refactor(observability_logs): minor changes --- .../common/data_streams/v1/common.ts | 3 +++ .../observability_logs/common/dynamic.tsx | 6 ++++++ .../observability_logs/common/runtime_types.ts | 13 ------------- .../state_machine/state_machine.ts | 2 +- .../observability_logs/public/plugin.tsx | 8 ++++---- .../data_streams/src/state_machine.ts | 10 ++++------ .../state_machines/data_streams/src/types.ts | 18 +++--------------- .../integrations/src/state_machine.ts | 15 +++++---------- .../state_machines/integrations/src/types.ts | 14 +++++++------- 9 files changed, 33 insertions(+), 56 deletions(-) diff --git a/x-pack/plugins/observability_logs/common/data_streams/v1/common.ts b/x-pack/plugins/observability_logs/common/data_streams/v1/common.ts index 552814c5224dd..2536f41b414ff 100644 --- a/x-pack/plugins/observability_logs/common/data_streams/v1/common.ts +++ b/x-pack/plugins/observability_logs/common/data_streams/v1/common.ts @@ -39,6 +39,9 @@ function stringifyByProp( return mapValues(obj, (val, key) => (props.includes(key) ? JSON.stringify(val) : val)); } +/** + * Format the integrations and data streams search request into the required API format + */ export const formatSearch = ( search: FindIntegrationsRequestQuery | FindDataStreamsRequestQuery ) => { diff --git a/x-pack/plugins/observability_logs/common/dynamic.tsx b/x-pack/plugins/observability_logs/common/dynamic.tsx index 47d87c863f8f8..0971c628b07de 100644 --- a/x-pack/plugins/observability_logs/common/dynamic.tsx +++ b/x-pack/plugins/observability_logs/common/dynamic.tsx @@ -13,6 +13,12 @@ interface DynamicOptions { fallback?: React.ReactNode; } +/** + * Lazy load and wrap with Suspense any component. + * + * @example + * const Header = dynamic(() => import('./components/header')) + */ export function dynamic(loader: LoadableComponent, options: DynamicOptions = {}) { const Component = lazy(loader); diff --git a/x-pack/plugins/observability_logs/common/runtime_types.ts b/x-pack/plugins/observability_logs/common/runtime_types.ts index 71d0031a3ed4b..d6d0336eafdd7 100644 --- a/x-pack/plugins/observability_logs/common/runtime_types.ts +++ b/x-pack/plugins/observability_logs/common/runtime_types.ts @@ -51,16 +51,3 @@ export const decodeOrThrow = ) => (inputValue: InputValue) => pipe(runtimeType.decode(inputValue), fold(throwErrors(createError), identity)); - -export const createValidationFunction = - ( - runtimeType: Type - ): RouteValidationFunction => - (inputValue, { badRequest, ok }) => - pipe( - runtimeType.decode(inputValue), - fold>( - (errors: Errors) => badRequest(formatErrors(errors)), - (result: DecodedValue) => ok(result) - ) - ); diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/state_machine.ts b/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/state_machine.ts index 78524c93436bf..4c04f5132666a 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/state_machine.ts +++ b/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/state_machine.ts @@ -19,7 +19,7 @@ import { export const createPureDataStreamsSelectorStateMachine = ( initialContext: DefaultDataStreamsSelectorContext = DEFAULT_CONTEXT ) => - /** @xstate-layout N4IgpgJg5mDOIC5QBECGAXVBldAnMqAtrFmADZgDG6A9rgHSVk2yQDEAKgPIDiPAMgFEA2gAYAuolAAHFgEt0cmgDspIAB6IAjACYALAFZ6WgGyitBkzoMAaEAE9te+qIDsenVoAcrgwF8-OzRMHHwiEnIqWgYaaTBlTl4BEQk1WVgFJVUkDW03LXoTPS89AGZfO0cEPQBOHXoarRq6718AoIxsPAJiUgpqOnpY+PoyOVhFZSgASWV0MChcDCzYNgBhAAkAQQA5HkEAfQAFXcF+MUkc9MyVNU0ELXdCy2tKxAMvUvodGpMff0CIGCXTCvUiAxicWUo3GkxmcwWS0UKlWm12+2Op3OWkuMnkyOyoHunmcekev3+b2qWgKNVqnn+7SBnVCPQi-WiQyhMImcims3mi2WKLYWDWACUuPx+AduAdpjsOIIeOKthxplwdlgDgAhLgcbgAWQuaXxWTuiAAtNYCq5RKVdLYHIgdF4ag1fqIauUAR0Qt1wn0ooNhtCxrz+QihQTVlhBFtxZtdQBNA47LaGlK4kDXAkWhCWx71LyiNxeR1UgwGVz0Axlx1M4GswPgzmhnlwgWI4XKWNccUcFMHfvIQTik1XM23HL3S0Okz0XyucuvZ3VPSiWtkjdWX3M-2g9nByEjcOdqNIrLN4jrbZ7Q4nHZnCd4jJ5mcunRfEyuVpOqr6F49AlnofxtICTYBmCHIhtyZ58vCgqXio16xmcghrIOWAcOK8bGqkk5vuaH4PKI1jGDUBgOquAE6KIjYslBR4Qlyp6wghXbRleTGivGiYbEO6aZi+OZToSuQIHaRh0i0lJriWxiMhBjGHkGLHtvBkZIT2qGiv2g46qmI5jiJubEUSiBkjWxReK6FbyZuf4MQebJqW2cHsVMACqyiEKgyioDAEC6Wi96Yk+5wEa+NzicSX6FL+clVK4JhGKYO7WM5IKua2sFsRGUA+X5AVBbpcZCJhBzYbhGamWJ+ZaGRaWUdR-6IO4XylKIXgGN64F+tlLYwSeYaeYVvn+YFkBlXxSaGWmGZZqaRHThZkkeMBv5ej6VI+PU+hmH1e6QapuUjR2CFFZNpU8Vg+lDsZ45RaJK2xZZ+j0Hohitbtrj7aB23gYCyg0BAcBqCdOXDctMX5paZL1EuK5tQWpj1I81bI1lqFuYMTAsJAMPvmtjrfolFRrsUXw1KIoFKQNONnax4lmatElzkUi6Y-ZVSWk8P7c5lykuUNx7M-QAAWsJE+Z7OPDWFhfmUFNVLowH6Ay-X7oN0FixpY2cchvYy2zs40u6RQlDta6lJRxhFKIu7Y0xuPnZpiHdgSqEm29CBUZulgO7uVJNO6XV01rkOi+pHkFVdJXTUxPv5lWRh6ClLwo399QmFopR2ULARAA */ + /** @xstate-layout N4IgpgJg5mDOIC5QBECGAXVBldAnMqAtrFmADZgDG6A9rgHSVk2yQDEAKgPIDiPAMgFEA2gAYAuolAAHFgEt0cmgDspIAB6IAjAGYA7AA56AFgOjjANgN6ArABoQAT0Smd9AJzmrtgL4+HaJg4+EQk5FS0DDTSYMqcvAIiEmqysApKqkga2qJ6WvQWpsb69k4u7gBMHlrulVrWNn4BGNh4BMSkFNR09NGx9GRysIrKUACSyuhgULgYGbBsAMIAEgCCAHI8ggD6AAobgvxiklmp6SpqmghaesYFNhYVpc4INgZuFe7ejf4gga0hDrhbpRGLKAZDEbjSbTWaKFQLFYbLZ7A5HLQnGTyeGZUBXCpaO7GG5fBoOF7E-LuYx1BpNP4tYLtMJdSK9MEQ4ZyUYTKYzOYIthYRYAJS4-H42242zG6w4gh4ItWHDGXHWWG2ACEuBxuABZY4pbEZS6IAC0T3yelEOi0T3JiAqBncHi+oncJXp-yZoU6ER6fXBgy5PJh-JxCywglWIpWWoAmtt1qs9UlMSAzjjTQgzTcqmZcgY7c9EDYbHp6DZC8WvYy2r7gWzA5yobzYQLlJGuCKOAntt3kIIRYbTsaLlkrmbbRZ6LZDMWHQhjMZRJXiSvHj9mkF60DWQGOcHW2G4RkfcQlmtNjt9utDiOsWksxPHRU3BY8mSyggKqZ6GZLDpX5vV3Fl-VBfoj25aE+VPFRz0jQ5BEWXssA4EVowNZJRyfE0X2uUQnnoGobFte1vwqCpRFrHdATAkF2UgyFoLbcMz1AoVo1jZY+2TVMHwzMdcWyBBrRsDwaQJL8XjMYigO3AFmT9Bjmyg0NYI7BChW7XtNUTAchwEzM8LxFwbhMAwDCdBdv1k+pfGAus6OUptD2Y0YAFVlEIVBlFQGAIC0pFr1RO8jmwx9zmE-E3wKT9bEXPQLHErRClETcaMUht9wgoN3KgLyfL8gKtKjIQUO2NCMJTIyhOzLRCJS9xSJsl5bjcHRRAMGwPQchSEJcg8mJDArvN8-zIFKri4z0pMUzTI1cPHUzROMfM8ndEpF2sKpfwsTa+oZWilMbIa8pGwrxpKjisB0vsDOHCLBKW6KXF-ExjBa8iZL0XbLAOn5fmUGgIDgNQQOc07cEWqLszNYkqjnItvvNVKqhuctka3I6sr3cDGGYVgIBh58VuLd94pLJd3g8Lx5JxgaocY4TjOWkSp0KWdMda81bji7mnkyxmcuZ+gAAtIRJkz2ZuCstBsN9igS787X-X8pMOiGTpF1T8tYuDOyltnJy0GoCiKLbvx0ZriLSjLHOO7L8d1kb9c00Cjde15OsrCw7ZR65anoTrAM1pztedtyLrG4rJo9nDYfwstxOMJKHgD36qgsXRrMFvwfCAA */ createMachine( { context: initialContext, diff --git a/x-pack/plugins/observability_logs/public/plugin.tsx b/x-pack/plugins/observability_logs/public/plugin.tsx index a3f08326b4ad6..3d10c0e480330 100644 --- a/x-pack/plugins/observability_logs/public/plugin.tsx +++ b/x-pack/plugins/observability_logs/public/plugin.tsx @@ -18,7 +18,7 @@ export class ObservabilityLogsPlugin implements ObservabilityLogsClientPluginCla this.dataStreamsService = new DataStreamsService(); } - public setup() {} + public setup() { } public start(core: CoreStart, plugins: ObservabilityLogsStartDeps) { const { discover } = plugins; @@ -31,12 +31,12 @@ export class ObservabilityLogsPlugin implements ObservabilityLogsClientPluginCla dataStreamsService, }; - /** - * Replace the DataViewPicker with a custom DataStreamSelector to access only integrations streams - */ discover.customize( OBSERVABILITY_LOGS_PROFILE_ID, async ({ customizations, stateContainer }) => { + /** + * Replace the DataViewPicker with a custom `DataStreamSelector` to pick integrations streams + */ customizations.set({ id: 'search_bar', CustomDataViewPicker: createLazyCustomDataStreamSelector({ diff --git a/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/state_machine.ts b/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/state_machine.ts index f6edb3a632588..c42097b8eb1f0 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/state_machine.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/state_machine.ts @@ -19,7 +19,7 @@ import { export const createPureDataStreamsStateMachine = ( initialContext: DefaultDataStreamsContext = DEFAULT_CONTEXT ) => - /** @xstate-layout N4IgpgJg5mDOIC5QBECGAXVBldAnMqAtrAHQCuAdgJbXpWoA2VAXpAMQAyA8gILID6yHgBUe-LMIBKAUR4BZLAG0ADAF1EoAA4B7WFTraKGkAA9EAdmUBWEuYCMygJxWAHABYrdqwDYANCABPRAAmK2USO3NHczcXb1d3R2VvAF8U-zRMHHwiUgZtVAgaKDYIQzASGgA3bQBrCszsPAJiEnzC4oRq7QBjDCpDFVUh4x09AyMkUws7R1s7FxdQxxXlF3N-IIQXOxIw5QPgtyi7AGY7I7SMjCac1vaiihKwXFxtXBJNBgwAM3fCEiNbItPIFR5QLoUGp9CZDEZTMb6AaTUBmBB2BbmEjBZSnI6uOzeI4XTYhFYkc6nXHmYKnbzeFyOdZXEBA5q5Npg9hYWSSADCAAlBCIxBIZPIlGpRrokYZjGjCeEPOdggtlMFzJr3KSEFY9SQnN43MEjo5vE5PCy2XdQYVuVxJMJhaJxFJZAp4VoZRN5YhFSRlRc1RqtW4dadjiRnOcrG5nBr3Kl0qybsCORAwAAjbSUHpgLAEXA9AAWxWtILYPJ4-KFQhdYvdkvUCO9yN9CDcpxsrhxYSspySji8YcCiDju28UUsk6HVmidjcVtT7NaGezufzhZLZeXNrYJlgmHQFVQP2PuAAFFTlABKNjl9NZnMUPMF1BF0tPB-ET0gRE+qY0UsGx7AtdxPB8HUrHsEgZ3MPEB0Jc07CXLIV1tcEADFUCoBh2Bkbg+GdUU3QlX9-zbQC-UxbFcXxHYiTcElRwQI4XBIHYqROXFHCY8wk2TChtAzeApm-USvXGSjUUQABaPwWPk1DbhBchqFoegmFYCBpSkuUqN1bxTg4pk3HNYJnG8DENhY0Jwjg5RLAxLx3BcZS03uMFil02UUWmBBeLcKMPDcdUmM8RkdVpOYTXnHjoKiE13PQzk7R0ls9L8tFjR1M52JcZRjkiY4pwXNzk3Ekg12fV8t0-KBxJ8gCZI7DFbDjFZQljCyXCsKDI0iaJYlVaJgiM5KbVSrCcLw9LJN89sFy7CkLlcIbYgXEcthNIKByM3qdn7KyBJSIA */ + /** @xstate-layout N4IgpgJg5mDOIC5QBECGAXVBldAnMqAtrAHQCuAdgJbXpWoA2VAXpAMQAyA8gILID6yHgBUe-LMIBKAUR4BZLAG0ADAF1EoAA4B7WFTraKGkAA9EAdmUBWEuYCMygJwBmc1YA0IAJ6IATFeUSO3NHewAOZWCrczC7XwBfeM80TBx8IlIGbVQIGig2CEMwEhoAN20Aa2KU7DwCYhIsnLyEMu0AYwwqQxVVXuMdPQMjJFMLO0dbOzC3Tx8EWJIA5WVnayiYuMTkjFr0hqbcinywXFxtXBJNBgwAMwvCEhq0+szso6hWinLO4d7+0aDfTdEagMwIOzTcwkXyrOIebx+RyTZx2NZWNEBRyQgAsVm2IGedQyjXe7CwskkAGEABKCERiCQyeRKNQDXTAwzGcF2ABsgTxqP8c0QVjFJCcvOcYRxvJlvhxOMcBKJ+zeOXJXEkwnponEUlkCgBWg5w25iD5AqsQoR82cOOhjmtdjFGLcmxVuxeJIgYAARtpKO0wFgCLh2gALPKq15sCk8al0oR6pmG1nqQGmkHmhA45w2KxhWE2kW57EkXkhcKRd2xT2pYkNX0BoMhsOR6NexuwNgmWCYdDFVC3Qe4AAUa2UAEo2DGff7AxRg6HUOGo8c58RjSAgWbRuDLDZ7E5XLbRfYK1XpjXonWkoSu2rSc1jgAxVBUBjsGTcPi6xkGiy267tm+4WlCMJwsKiIIAqYQkLE6KYk4uJhIk94UNovrwKMm44SaQygWCiAALS8qWZH1nsrzkNQtD0EwrAQOyhFcmBCBWFKCGODKp6lv4gSVqE14bLECT3nhz4fCxnKgmMCCOIqJCKc60HzL4ziTL4vjCREol2DiVHegcZLMZmrFyeCOK+KWaLwREDoibWWwSY+NHNouy7tuuUB4TJe7EbmkK2GeHEOkEV56c5RndlJeTvp+kD+UR8kGfmJAljBvgurYOnVvphnoUAA */ createMachine( { context: initialContext, @@ -63,7 +63,7 @@ export const createPureDataStreamsStateMachine = ( }, }, loadingFailed: { - entry: ['clearCache', 'storeError'], + entry: ['clearCache', 'clearData', 'storeError'], exit: 'clearError', on: { RELOAD_DATA_STREAMS: 'loading', @@ -83,11 +83,9 @@ export const createPureDataStreamsStateMachine = ( storeInCache: assign((context, event) => 'data' in event ? { cache: context.cache.set(context.search, event.data) } : {} ), - clearCache: assign((context) => ({ - cache: context.cache.clear(), - dataStreams: null, - })), storeError: assign((_context, event) => ('data' in event ? { error: event.data } : {})), + clearCache: assign((context) => ({ cache: context.cache.clear() })), + clearData: assign((_context) => ({ dataStreams: null })), clearError: assign((_context) => ({ error: null })), }, } diff --git a/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/types.ts b/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/types.ts index 6cb553ff96c14..274b74f5e68c8 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/types.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/types.ts @@ -23,7 +23,7 @@ interface WithSearch { } interface WithDataStreams { - dataStreams: DataStream[] | null; + dataStreams: DataStream[]; } interface WithNullishDataStreams { @@ -38,10 +38,6 @@ interface WithNullishError { error: null; } -interface WithTotal { - total: number; -} - export type DefaultDataStreamsContext = WithCache & WithNullishDataStreams & WithSearch & @@ -49,17 +45,9 @@ export type DefaultDataStreamsContext = WithCache & type LoadingDataStreamsContext = DefaultDataStreamsContext; -type LoadedDataStreamsContext = WithCache & - WithDataStreams & - WithTotal & - WithSearch & - WithNullishError; +type LoadedDataStreamsContext = WithCache & WithDataStreams & WithSearch & WithNullishError; -type LoadingFailedDataStreamsContext = WithCache & - WithDataStreams & - Partial & - WithSearch & - WithError; +type LoadingFailedDataStreamsContext = WithCache & WithNullishDataStreams & WithSearch & WithError; export type DataStreamsTypestate = | { diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts index 047fed2febd7b..fad3065754436 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts @@ -24,7 +24,7 @@ export const createPureIntegrationsStateMachine = ( ) => createMachine( { - /** @xstate-layout N4IgpgJg5mDOIC5QEkB2AXMUBOBDdAlgPaqwB0ArqgdYbgDYEBekAxANoAMAuoqAA5FYBQiT4gAHogCMATgDMZABzSATAFZOANnWzOAFlnr9AGhABPRAHYtVsrP1LO2rbIXrV8gL5ezaTDj4xKRk9ES4EDRQrBAkYGQ0AG5EANbx-lh4oiFhEVEISUQAxkEkXNzl4oLC2eJSCB5mlghK8naqWkqqVk6q0vKGWj5+GJmlOeGRqNFg2NhE2GT89PgAZgsAtmQZgdnkuVNQBajJJdnllUgg1SLBdTKd0mQ26upKskpW-a1KTYif+jI+i0+l0XX0+lUeiUwxAOyywX2k0gCQg9DArAAMgB5ACCABEAPoAWWxACUAKKE5AAOQAKhSAOJk3F05DYmkAZUuAiEtzEV3q+k4im6A3U8k40mk+iskqsfwQsvUQK0nDcatUTnkulh8PGSIiKIIaIxnIpuLJAGEABLU+lMllsjncnhVPm1QWIYWiuWgyXS2XyxUSpRkeQgkFuDpKFRWPWjXaI0LIiCo9GsTnkun2hnM1nsrk864eu5ehBqfr2WR9DpBsG-CyIWxPQyOTpaVScDx9BMBBEkQ0QY2mzMW6122l5p2FzmEzl0ym44mu3hXG6e0BCkVkMX+qUyuWcBVNhDdOxq6Qg7ucbqeby+OGJgcTI1pk0ZrNknNTx0Fl3zouForsWG5llu-wns0qiOGQV7HhKcgxrqj76nsKZvhhhzEgsGKxKg8SFGk2zPgaWEogcUQ4dgYDHKc4wXG666lgKEEIJ2TyyC8Vg2N0OgGIq8hanBUqdse8gyl02h9mM6EHBRkxUbhrCzPMizLGsmwkf2ZHyWmlHTNRtGFGcwSMWuvI1OBkgyH0ijRmoIJyg2IbHkCjjOFYzjaBoqgyUmg7kWmw4AEZEFQRRgJyYC4NgRQABZRGhiJjpatq5n+zpFkxln8qg9wtK0u4DDqsauMCsqKrKgIOFYHxfI5bj+S+Q4oqF4WoJF0WxQlSWkXsrASLA6D4PEuCrJg2AABSSpwACUrDJYFelkO1EVRTFcWJdMS2kKBLH5eWlb2TWjn1h8jbNK8NWyg4spqPoMqyM1umpqtYBhet3VbX1Ol7Jy6A0bgGywKlE4ZfmWVzguS4gTlJZWaxNktKCyhWIYxg1q4shyIq0iaNWbhdEJMFWK86gvXJb1rZ1G09dtUC7bAANAyDg3DaNZDjZNM3OAtTNBe9n2099vU7f1iIszFIP7Yjh1sboKoGJ0XmdBCQnqHjSiApJxhtpwSidpTyYGVAABiuAEOiECsJSOIEhDM4urLeUFVeKjPDobz1d88iXf8nj2JG0hOPjEp+bCqBEMO8BXEz7pywVAC0siKknMFkN5WfZ55xuBVQNC3AwzCQAnrvlo0p42Fo9i3UoesG7YMKoRLy2KdMZebsjMqo8YHZvAMwpaIqrSAgYdXqNKejqvXeevsOECd9Z9TyKnp5aIohhE3VdkKDxFMt39JtvR+YBL0j9RyIofstp2d0eMegk2GQ6i2CK0p+4bMFz61+nt1ARlz7y2RobMMEYvhaBDjoAYKhBKuHcnVVwUohKxi4j-QWNMuqbTFozVupAgEFXrmGHe0Jjx2SsKoEMkoEGyFsAMGU6gejoJWpgumP1xZH0HFLYGsdcpd3qK0J4V5hQULcB-f2Z5tAv28rGOQ5UpTMP-hbK2pdmKJyOjWVQu4vY6BVnIRwioeiyBfu8O+IodRqmbj4IAA */ + /** @xstate-layout N4IgpgJg5mDOIC5QEkB2AXMUBOBDdAlgPaqwB0ArqgdYbgDYEBekAxANoAMAuoqAA5FYBQiT4gAHogCMAJmkB2Mp1kKAzGukBWDQE5ZWgDQgAnol0AWC2QAc0-Vtm7tANheOAvh+NpMOfMSkZPREuBA0UKwQJGBkNABuRADWsb5YeKJBIWERCAlEAMYBJFzcpeKCwpniUggAtHJkljpyRqbmCjZk9hb2LtIWLjZqshZePhjpxVmh4aiRYNjYRNhk-PT4AGYrALZkaf6Z5NlzUHmoiUWZpeVIIJUigTUy0tKcZGpaipwKWgrNFjUxjMCBcFi0ZEBAwsqhsqk4al04xABwygWOs0gcQg9DArAAMgB5ACCABEAPoAWUJACUAKLk5AAOQAKnSAOI04ks5CEpkAZVuAiEjzEd1qiNkZE6XxcPxcul0nRcwMQkqaFhsnE4Ni+StkLmRqOmGLCWIIOLx-LpxJpAGEABKM1kcrk8vmCngVEXVcWIA1S2R2EZqTpvA0KVUIOG6aWfYYWfQKBT2I2TQ7o4KYiDY3Gsfm0lnOtmc7m8gVC+4+p5+hAG94uX42cGyTiK9S6KNglzdThQ8HDA1gtN+NEkU0Qc2W-M2+1O5klt3l-nk-ks+nEyme3h3B6+0C1OoGNSQmxwhSNtTa+SAqPyKUDThDTRuaRa2SyEdTI5Zs05i15gWNJFgurplh6q7rjaW6VnuNYHog-y2Fouihm0IKjNIZAuBomoaAiF5PmM3goumY4zH+v6nJSKx4tEqCxPkKT7GRJpUViJwRDR2BgOclzTDcXq7tWYoIQgiJdBYF7uMqzYGjYKrtAg4JKJwridAoUlfGoX4ZuO7E5px8zcXiizLKs6xbLsLGjmxJwcbMXG0XxhQCTwsEiagzzRroEIxlowzaFoXxRloT4aloUK-G+gzERMtk-vZOaTgARkQVAFGA-JgLg2AFAAFhExpHDOtqOsWYHuhWQnClU8GSIgfwQm+vwBfKWpqVGmldI2PwqP0mnyAounkROWKpelqCZdluUFUVrElRIsDoPgsS4JsmDYAAFFenAAJSsMVmZJWQE0ZVlOV5YV8xHeOHl1aJDUIKhWHBR+bhaEMrzuFGMbdAoPzJp9-wKnFpEJcd2anWAaXnTNV3zRD478ugPG4DssClXOFWllVK5rhuME1VWD1ebWdQKpCuhyh+iY2CmF5RoitiNq2YbNs2Ulg7dFGTslMOTdNl1zTdC3oijaMY6wS0rZgZDrZtO3agdPNjfzsNTRds3XVAqsSzlGP3aKZNiXUIyxkqbYtW8-z0xYTNqU0YL6KzPyONII12Y58wAGK4AQuIQKw9JEmSONLh6Rv7k9rTWO4LgGihuoKtIXWathAMAwaigwooXgkagRCTvAdyq96pPeXUMIQpbzj0zbuh21GDSfN0Iz-PIdhfMFOkkarlDULQBAMMwkDl8b3mvM43Rgk+BhRuGWHfW2OpyMGHt92L+lGVA4-R7UryKGQwVSa22ruJqXWKmQH5XsGnB-BJnuJdme-1YeoZKLX1s-I3mnN1JbC7d9Bvm0EFA0z9IaUQAmAN+j0D4KRvoMHU1NfI2EVGeJmF4PiKh+OgkYOEGaQO3lDHeJk4EmyemCE8uoe6oUGLoJ8nYlJSSwgpVsYVqEIgGMQ3m40BZw2FjrMuwkK61mCu8TS-R0FaV6AqX6fZ-o-DPmeGEqheFq2hhrIW2tEbfnFqjA2JdaoT3Jm8HsBhkyJlBpYemikQRvGsLIK8nCdC2M+p+TeSNeYRD9gHMeojTFiUPjQ6Q7g4RqVznIKM4JYxfCsBYFQdM+z5w8EAA */ context: initialContext, preserveActionOrder: true, predictableActionArguments: true, @@ -34,9 +34,9 @@ export const createPureIntegrationsStateMachine = ( uninitialized: { always: 'loading', }, - loading: { id: 'loading', + invoke: { src: 'loadIntegrations', onDone: { @@ -46,7 +46,6 @@ export const createPureIntegrationsStateMachine = ( onError: 'loadingFailed', }, }, - loaded: { id: 'loaded', initial: 'idle', @@ -102,10 +101,9 @@ export const createPureIntegrationsStateMachine = ( }, }, }, - loadingFailed: { id: 'loadingFailed', - entry: ['clearCache', 'storeError'], + entry: ['clearCache', 'clearData', 'storeError'], exit: 'clearError', on: { RELOAD_INTEGRATIONS: '#loading', @@ -146,11 +144,6 @@ export const createPureIntegrationsStateMachine = ( storeInCache: assign((context, event) => 'data' in event ? { cache: context.cache.set(context.search, event.data) } : {} ), - clearCache: assign((context) => ({ - cache: context.cache.clear(), - integrationsSource: null, - integrations: null, - })), appendIntegrations: assign((context, event) => 'data' in event ? { @@ -161,6 +154,8 @@ export const createPureIntegrationsStateMachine = ( : {} ), storeError: assign((_context, event) => ('data' in event ? { error: event.data } : {})), + clearCache: assign((context) => ({ cache: context.cache.clear() })), + clearData: assign((_context) => ({ integrationsSource: null, integrations: null })), clearError: assign((_context) => ({ error: null })), }, guards: { diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts index 7be7d2b82acbd..e64c642d070ff 100644 --- a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts +++ b/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts @@ -25,8 +25,8 @@ interface WithSearch { } interface WithIntegrations { - integrationsSource: Integration[] | null; - integrations: Integration[] | null; + integrationsSource: Integration[]; + integrations: Integration[]; } interface WithNullishIntegrations { @@ -60,7 +60,7 @@ type LoadedIntegrationsContext = WithCache & WithNullishError; type LoadingFailedIntegrationsContext = WithCache & - WithIntegrations & + WithNullishIntegrations & Partial & WithSearch & WithError; @@ -74,14 +74,14 @@ export type IntegrationTypestate = value: 'loading'; context: LoadingIntegrationsContext; } - | { - value: 'loaded'; - context: LoadedIntegrationsContext; - } | { value: 'loadingFailed'; context: LoadingFailedIntegrationsContext; } + | { + value: 'loaded'; + context: LoadedIntegrationsContext; + } | { value: { loaded: 'idle' }; context: LoadedIntegrationsContext; From 61c68f35be9b16a4046494b36cef966303495d38 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 30 May 2023 07:51:16 +0000 Subject: [PATCH 120/122] [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' --- x-pack/plugins/observability_logs/public/plugin.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/observability_logs/public/plugin.tsx b/x-pack/plugins/observability_logs/public/plugin.tsx index 3d10c0e480330..ce0c1adca2066 100644 --- a/x-pack/plugins/observability_logs/public/plugin.tsx +++ b/x-pack/plugins/observability_logs/public/plugin.tsx @@ -18,7 +18,7 @@ export class ObservabilityLogsPlugin implements ObservabilityLogsClientPluginCla this.dataStreamsService = new DataStreamsService(); } - public setup() { } + public setup() {} public start(core: CoreStart, plugins: ObservabilityLogsStartDeps) { const { discover } = plugins; From 577ba68fd150f49e75d5d11d5f75c726aa958e95 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Wed, 7 Jun 2023 11:25:37 +0200 Subject: [PATCH 121/122] refactor(discover-log-explorer): rename plugin --- .../.storybook/__mocks__/package_icon.tsx | 0 .../.storybook/main.js | 0 .../.storybook/preview.js | 0 .../README.md | 2 +- .../common/data_streams/errors.ts | 0 .../common/data_streams/index.ts | 0 .../common/data_streams/types.ts | 0 .../common/data_streams/v1/common.ts | 0 .../data_streams/v1/find_data_streams.ts | 0 .../data_streams/v1/find_integrations.ts | 0 .../common/data_streams/v1/index.ts | 0 .../common/dynamic.tsx | 0 .../common/entity_list.ts | 0 .../common/immutable_cache.ts | 0 .../common/index.ts | 2 +- .../common/latest.ts | 0 .../common/runtime_types.ts | 0 .../jest.config.js | 6 +- .../kibana.jsonc | 6 +- .../data_stream_selector/constants.tsx | 20 +- .../data_stream_selector.stories.tsx | 2 +- .../data_stream_selector.test.ts | 0 .../data_stream_selector.tsx | 0 .../components/data_stream_selector/index.ts | 0 .../state_machine/defaults.ts | 0 .../state_machine/index.ts | 0 .../state_machine/state_machine.ts | 0 .../state_machine/types.ts | 0 .../state_machine/use_data_stream_selector.ts | 0 .../sub_components/data_streams_list.tsx | 2 +- .../sub_components/data_streams_popover.tsx | 0 .../sub_components/data_streams_skeleton.tsx | 0 .../integrations_list_status.tsx | 2 +- .../sub_components/search_controls.tsx | 0 .../components/data_stream_selector/types.ts | 0 .../components/data_stream_selector/utils.tsx | 0 .../custom_data_stream_selector.tsx | 0 .../public/customizations/index.tsx | 0 .../public/hooks/use_data_streams.ts | 0 .../public/hooks/use_data_view.ts | 0 .../public/hooks/use_integrations.ts | 0 .../public/hooks/use_intersection_ref.ts | 0 .../public/index.ts | 6 +- .../public/plugin.tsx | 10 +- .../data_streams/data_streams_client.mock.ts | 0 .../data_streams/data_streams_client.ts | 0 .../data_streams/data_streams_service.mock.ts | 0 .../data_streams/data_streams_service.ts | 0 .../public/services/data_streams/index.ts | 0 .../public/services/data_streams/types.ts | 0 .../state_machines/data_streams/index.ts | 0 .../data_streams/src/defaults.ts | 0 .../state_machines/data_streams/src/index.ts | 0 .../data_streams/src/state_machine.ts | 0 .../state_machines/data_streams/src/types.ts | 0 .../state_machines/integrations/index.ts | 0 .../integrations/src/defaults.ts | 0 .../state_machines/integrations/src/index.ts | 0 .../integrations/src/state_machine.ts | 0 .../state_machines/integrations/src/types.ts | 0 .../public/types.ts | 12 +- .../server/index.ts | 6 +- .../server/plugin.ts | 10 +- .../server/routes/index.ts | 453 ++++++++++++++++ .../server/types.ts | 4 +- .../tsconfig.json | 0 .../observability_logs/server/routes/index.ts | 488 ------------------ 67 files changed, 498 insertions(+), 533 deletions(-) rename x-pack/plugins/{observability_logs => discover_log_explorer}/.storybook/__mocks__/package_icon.tsx (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/.storybook/main.js (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/.storybook/preview.js (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/README.md (68%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/common/data_streams/errors.ts (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/common/data_streams/index.ts (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/common/data_streams/types.ts (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/common/data_streams/v1/common.ts (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/common/data_streams/v1/find_data_streams.ts (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/common/data_streams/v1/find_integrations.ts (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/common/data_streams/v1/index.ts (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/common/dynamic.tsx (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/common/entity_list.ts (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/common/immutable_cache.ts (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/common/index.ts (85%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/common/latest.ts (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/common/runtime_types.ts (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/jest.config.js (70%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/kibana.jsonc (71%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/public/components/data_stream_selector/constants.tsx (72%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/public/components/data_stream_selector/data_stream_selector.stories.tsx (99%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/public/components/data_stream_selector/data_stream_selector.test.ts (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/public/components/data_stream_selector/data_stream_selector.tsx (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/public/components/data_stream_selector/index.ts (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/public/components/data_stream_selector/state_machine/defaults.ts (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/public/components/data_stream_selector/state_machine/index.ts (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/public/components/data_stream_selector/state_machine/state_machine.ts (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/public/components/data_stream_selector/state_machine/types.ts (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/public/components/data_stream_selector/state_machine/use_data_stream_selector.ts (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/public/components/data_stream_selector/sub_components/data_streams_list.tsx (96%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/public/components/data_stream_selector/sub_components/data_streams_popover.tsx (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/public/components/data_stream_selector/sub_components/data_streams_skeleton.tsx (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/public/components/data_stream_selector/sub_components/integrations_list_status.tsx (96%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/public/components/data_stream_selector/sub_components/search_controls.tsx (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/public/components/data_stream_selector/types.ts (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/public/components/data_stream_selector/utils.tsx (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/public/customizations/custom_data_stream_selector.tsx (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/public/customizations/index.tsx (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/public/hooks/use_data_streams.ts (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/public/hooks/use_data_view.ts (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/public/hooks/use_integrations.ts (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/public/hooks/use_intersection_ref.ts (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/public/index.ts (59%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/public/plugin.tsx (81%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/public/services/data_streams/data_streams_client.mock.ts (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/public/services/data_streams/data_streams_client.ts (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/public/services/data_streams/data_streams_service.mock.ts (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/public/services/data_streams/data_streams_service.ts (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/public/services/data_streams/index.ts (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/public/services/data_streams/types.ts (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/public/state_machines/data_streams/index.ts (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/public/state_machines/data_streams/src/defaults.ts (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/public/state_machines/data_streams/src/index.ts (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/public/state_machines/data_streams/src/state_machine.ts (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/public/state_machines/data_streams/src/types.ts (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/public/state_machines/integrations/index.ts (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/public/state_machines/integrations/src/defaults.ts (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/public/state_machines/integrations/src/index.ts (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/public/state_machines/integrations/src/state_machine.ts (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/public/state_machines/integrations/src/types.ts (100%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/public/types.ts (64%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/server/index.ts (70%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/server/plugin.ts (71%) create mode 100644 x-pack/plugins/discover_log_explorer/server/routes/index.ts rename x-pack/plugins/{observability_logs => discover_log_explorer}/server/types.ts (79%) rename x-pack/plugins/{observability_logs => discover_log_explorer}/tsconfig.json (100%) delete mode 100644 x-pack/plugins/observability_logs/server/routes/index.ts diff --git a/x-pack/plugins/observability_logs/.storybook/__mocks__/package_icon.tsx b/x-pack/plugins/discover_log_explorer/.storybook/__mocks__/package_icon.tsx similarity index 100% rename from x-pack/plugins/observability_logs/.storybook/__mocks__/package_icon.tsx rename to x-pack/plugins/discover_log_explorer/.storybook/__mocks__/package_icon.tsx diff --git a/x-pack/plugins/observability_logs/.storybook/main.js b/x-pack/plugins/discover_log_explorer/.storybook/main.js similarity index 100% rename from x-pack/plugins/observability_logs/.storybook/main.js rename to x-pack/plugins/discover_log_explorer/.storybook/main.js diff --git a/x-pack/plugins/observability_logs/.storybook/preview.js b/x-pack/plugins/discover_log_explorer/.storybook/preview.js similarity index 100% rename from x-pack/plugins/observability_logs/.storybook/preview.js rename to x-pack/plugins/discover_log_explorer/.storybook/preview.js diff --git a/x-pack/plugins/observability_logs/README.md b/x-pack/plugins/discover_log_explorer/README.md similarity index 68% rename from x-pack/plugins/observability_logs/README.md rename to x-pack/plugins/discover_log_explorer/README.md index 188d81b1ce4cc..1a14d75cafe70 100755 --- a/x-pack/plugins/observability_logs/README.md +++ b/x-pack/plugins/discover_log_explorer/README.md @@ -1,3 +1,3 @@ -# Observability Logs +# Discover Log Explorer This plugin exposes and registers Logs+ features. diff --git a/x-pack/plugins/observability_logs/common/data_streams/errors.ts b/x-pack/plugins/discover_log_explorer/common/data_streams/errors.ts similarity index 100% rename from x-pack/plugins/observability_logs/common/data_streams/errors.ts rename to x-pack/plugins/discover_log_explorer/common/data_streams/errors.ts diff --git a/x-pack/plugins/observability_logs/common/data_streams/index.ts b/x-pack/plugins/discover_log_explorer/common/data_streams/index.ts similarity index 100% rename from x-pack/plugins/observability_logs/common/data_streams/index.ts rename to x-pack/plugins/discover_log_explorer/common/data_streams/index.ts diff --git a/x-pack/plugins/observability_logs/common/data_streams/types.ts b/x-pack/plugins/discover_log_explorer/common/data_streams/types.ts similarity index 100% rename from x-pack/plugins/observability_logs/common/data_streams/types.ts rename to x-pack/plugins/discover_log_explorer/common/data_streams/types.ts diff --git a/x-pack/plugins/observability_logs/common/data_streams/v1/common.ts b/x-pack/plugins/discover_log_explorer/common/data_streams/v1/common.ts similarity index 100% rename from x-pack/plugins/observability_logs/common/data_streams/v1/common.ts rename to x-pack/plugins/discover_log_explorer/common/data_streams/v1/common.ts diff --git a/x-pack/plugins/observability_logs/common/data_streams/v1/find_data_streams.ts b/x-pack/plugins/discover_log_explorer/common/data_streams/v1/find_data_streams.ts similarity index 100% rename from x-pack/plugins/observability_logs/common/data_streams/v1/find_data_streams.ts rename to x-pack/plugins/discover_log_explorer/common/data_streams/v1/find_data_streams.ts diff --git a/x-pack/plugins/observability_logs/common/data_streams/v1/find_integrations.ts b/x-pack/plugins/discover_log_explorer/common/data_streams/v1/find_integrations.ts similarity index 100% rename from x-pack/plugins/observability_logs/common/data_streams/v1/find_integrations.ts rename to x-pack/plugins/discover_log_explorer/common/data_streams/v1/find_integrations.ts diff --git a/x-pack/plugins/observability_logs/common/data_streams/v1/index.ts b/x-pack/plugins/discover_log_explorer/common/data_streams/v1/index.ts similarity index 100% rename from x-pack/plugins/observability_logs/common/data_streams/v1/index.ts rename to x-pack/plugins/discover_log_explorer/common/data_streams/v1/index.ts diff --git a/x-pack/plugins/observability_logs/common/dynamic.tsx b/x-pack/plugins/discover_log_explorer/common/dynamic.tsx similarity index 100% rename from x-pack/plugins/observability_logs/common/dynamic.tsx rename to x-pack/plugins/discover_log_explorer/common/dynamic.tsx diff --git a/x-pack/plugins/observability_logs/common/entity_list.ts b/x-pack/plugins/discover_log_explorer/common/entity_list.ts similarity index 100% rename from x-pack/plugins/observability_logs/common/entity_list.ts rename to x-pack/plugins/discover_log_explorer/common/entity_list.ts diff --git a/x-pack/plugins/observability_logs/common/immutable_cache.ts b/x-pack/plugins/discover_log_explorer/common/immutable_cache.ts similarity index 100% rename from x-pack/plugins/observability_logs/common/immutable_cache.ts rename to x-pack/plugins/discover_log_explorer/common/immutable_cache.ts diff --git a/x-pack/plugins/observability_logs/common/index.ts b/x-pack/plugins/discover_log_explorer/common/index.ts similarity index 85% rename from x-pack/plugins/observability_logs/common/index.ts rename to x-pack/plugins/discover_log_explorer/common/index.ts index 79e0ce88d3e22..fae55d538bc97 100644 --- a/x-pack/plugins/observability_logs/common/index.ts +++ b/x-pack/plugins/discover_log_explorer/common/index.ts @@ -7,7 +7,7 @@ /* eslint-disable @kbn/eslint/no_export_all */ -export const OBSERVABILITY_LOGS_PROFILE_ID = 'observability-logs'; +export const DISCOVER_LOG_EXPLORER_PROFILE_ID = 'discover-log-explorer'; /** * Exporting versioned APIs types diff --git a/x-pack/plugins/observability_logs/common/latest.ts b/x-pack/plugins/discover_log_explorer/common/latest.ts similarity index 100% rename from x-pack/plugins/observability_logs/common/latest.ts rename to x-pack/plugins/discover_log_explorer/common/latest.ts diff --git a/x-pack/plugins/observability_logs/common/runtime_types.ts b/x-pack/plugins/discover_log_explorer/common/runtime_types.ts similarity index 100% rename from x-pack/plugins/observability_logs/common/runtime_types.ts rename to x-pack/plugins/discover_log_explorer/common/runtime_types.ts diff --git a/x-pack/plugins/observability_logs/jest.config.js b/x-pack/plugins/discover_log_explorer/jest.config.js similarity index 70% rename from x-pack/plugins/observability_logs/jest.config.js rename to x-pack/plugins/discover_log_explorer/jest.config.js index e99928a12c80b..729e90216094a 100644 --- a/x-pack/plugins/observability_logs/jest.config.js +++ b/x-pack/plugins/discover_log_explorer/jest.config.js @@ -8,10 +8,10 @@ module.exports = { preset: '@kbn/test', rootDir: '../../..', - roots: ['/x-pack/plugins/observability_logs'], - coverageDirectory: '/target/kibana-coverage/jest/x-pack/plugins/observability_logs', + roots: ['/x-pack/plugins/discover_log_explorer'], + coverageDirectory: '/target/kibana-coverage/jest/x-pack/plugins/discover_log_explorer', coverageReporters: ['text', 'html'], collectCoverageFrom: [ - '/x-pack/plugins/observability_logs/{common,public,server}/**/*.{ts,tsx}', + '/x-pack/plugins/discover_log_explorer/{common,public,server}/**/*.{ts,tsx}', ], }; diff --git a/x-pack/plugins/observability_logs/kibana.jsonc b/x-pack/plugins/discover_log_explorer/kibana.jsonc similarity index 71% rename from x-pack/plugins/observability_logs/kibana.jsonc rename to x-pack/plugins/discover_log_explorer/kibana.jsonc index 106515a333a78..40f4801ff940b 100644 --- a/x-pack/plugins/observability_logs/kibana.jsonc +++ b/x-pack/plugins/discover_log_explorer/kibana.jsonc @@ -1,13 +1,13 @@ { "type": "plugin", - "id": "@kbn/observability-logs-plugin", + "id": "@kbn/discover-log-explorer-plugin", "owner": "@elastic/infra-monitoring-ui", "description": "This plugin exposes and registers Logs+ features.", "plugin": { - "id": "observabilityLogs", + "id": "discoverLogExplorer", "server": true, "browser": true, - "configPath": ["xpack", "observability_logs"], + "configPath": ["xpack", "discover_log_explorer"], "requiredPlugins": ["discover", "fleet", "kibanaReact", "kibanaUtils"], "optionalPlugins": [], "requiredBundles": [] diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/constants.tsx b/x-pack/plugins/discover_log_explorer/public/components/data_stream_selector/constants.tsx similarity index 72% rename from x-pack/plugins/observability_logs/public/components/data_stream_selector/constants.tsx rename to x-pack/plugins/discover_log_explorer/public/components/data_stream_selector/constants.tsx index aa2e84f064693..c795b8f891437 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/constants.tsx +++ b/x-pack/plugins/discover_log_explorer/public/components/data_stream_selector/constants.tsx @@ -16,32 +16,32 @@ export const DATA_VIEW_POPOVER_CONTENT_WIDTH = 300; export const contextMenuStyles = { maxHeight: 440 }; export const selectDatasetLabel = i18n.translate( - 'xpack.observabilityLogs.dataStreamSelector.selectDataset', + 'xpack.discoverLogExplorer.dataStreamSelector.selectDataset', { defaultMessage: 'Select dataset' } ); export const integrationsLabel = i18n.translate( - 'xpack.observabilityLogs.dataStreamSelector.integrations', + 'xpack.discoverLogExplorer.dataStreamSelector.integrations', { defaultMessage: 'Integrations' } ); export const uncategorizedLabel = i18n.translate( - 'xpack.observabilityLogs.dataStreamSelector.uncategorized', + 'xpack.discoverLogExplorer.dataStreamSelector.uncategorized', { defaultMessage: 'Uncategorized' } ); export const sortOrdersLabel = i18n.translate( - 'xpack.observabilityLogs.dataStreamSelector.sortOrders', + 'xpack.discoverLogExplorer.dataStreamSelector.sortOrders', { defaultMessage: 'Sort directions' } ); export const noDataStreamsLabel = i18n.translate( - 'xpack.observabilityLogs.dataStreamSelector.noDataStreams', + 'xpack.discoverLogExplorer.dataStreamSelector.noDataStreams', { defaultMessage: 'No data streams found' } ); export const noDataStreamsDescriptionLabel = i18n.translate( - 'xpack.observabilityLogs.dataStreamSelector.noDataStreamsDescription', + 'xpack.discoverLogExplorer.dataStreamSelector.noDataStreamsDescription', { defaultMessage: "Looks like you don't have data stream or your search does not match any of them.", @@ -49,24 +49,24 @@ export const noDataStreamsDescriptionLabel = i18n.translate( ); export const noIntegrationsLabel = i18n.translate( - 'xpack.observabilityLogs.dataStreamSelector.noIntegrations', + 'xpack.discoverLogExplorer.dataStreamSelector.noIntegrations', { defaultMessage: 'No integrations found' } ); export const noIntegrationsDescriptionLabel = i18n.translate( - 'xpack.observabilityLogs.dataStreamSelector.noIntegrationsDescription', + 'xpack.discoverLogExplorer.dataStreamSelector.noIntegrationsDescription', { defaultMessage: "Looks like you don't have integrations or your search does not match any of them.", } ); -export const errorLabel = i18n.translate('xpack.observabilityLogs.dataStreamSelector.error', { +export const errorLabel = i18n.translate('xpack.discoverLogExplorer.dataStreamSelector.error', { defaultMessage: 'error', }); export const noDataRetryLabel = i18n.translate( - 'xpack.observabilityLogs.dataStreamSelector.noDataRetry', + 'xpack.discoverLogExplorer.dataStreamSelector.noDataRetry', { defaultMessage: 'Retry', } diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.stories.tsx b/x-pack/plugins/discover_log_explorer/public/components/data_stream_selector/data_stream_selector.stories.tsx similarity index 99% rename from x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.stories.tsx rename to x-pack/plugins/discover_log_explorer/public/components/data_stream_selector/data_stream_selector.stories.tsx index 6161a089ed374..568f2604e839c 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.stories.tsx +++ b/x-pack/plugins/discover_log_explorer/public/components/data_stream_selector/data_stream_selector.stories.tsx @@ -17,7 +17,7 @@ import { DataStreamSelectorProps, DataStreamsSelectorSearchParams } from './type const meta: Meta = { component: DataStreamSelector, - title: 'observability_logs/DataStreamSelector', + title: 'discover_log_explorer/DataStreamSelector', decorators: [(wrappedStory) => {wrappedStory()}], argTypes: { dataStreamsError: { diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.test.ts b/x-pack/plugins/discover_log_explorer/public/components/data_stream_selector/data_stream_selector.test.ts similarity index 100% rename from x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.test.ts rename to x-pack/plugins/discover_log_explorer/public/components/data_stream_selector/data_stream_selector.test.ts diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx b/x-pack/plugins/discover_log_explorer/public/components/data_stream_selector/data_stream_selector.tsx similarity index 100% rename from x-pack/plugins/observability_logs/public/components/data_stream_selector/data_stream_selector.tsx rename to x-pack/plugins/discover_log_explorer/public/components/data_stream_selector/data_stream_selector.tsx diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/index.ts b/x-pack/plugins/discover_log_explorer/public/components/data_stream_selector/index.ts similarity index 100% rename from x-pack/plugins/observability_logs/public/components/data_stream_selector/index.ts rename to x-pack/plugins/discover_log_explorer/public/components/data_stream_selector/index.ts diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/defaults.ts b/x-pack/plugins/discover_log_explorer/public/components/data_stream_selector/state_machine/defaults.ts similarity index 100% rename from x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/defaults.ts rename to x-pack/plugins/discover_log_explorer/public/components/data_stream_selector/state_machine/defaults.ts diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/index.ts b/x-pack/plugins/discover_log_explorer/public/components/data_stream_selector/state_machine/index.ts similarity index 100% rename from x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/index.ts rename to x-pack/plugins/discover_log_explorer/public/components/data_stream_selector/state_machine/index.ts diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/state_machine.ts b/x-pack/plugins/discover_log_explorer/public/components/data_stream_selector/state_machine/state_machine.ts similarity index 100% rename from x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/state_machine.ts rename to x-pack/plugins/discover_log_explorer/public/components/data_stream_selector/state_machine/state_machine.ts diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/types.ts b/x-pack/plugins/discover_log_explorer/public/components/data_stream_selector/state_machine/types.ts similarity index 100% rename from x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/types.ts rename to x-pack/plugins/discover_log_explorer/public/components/data_stream_selector/state_machine/types.ts diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/use_data_stream_selector.ts b/x-pack/plugins/discover_log_explorer/public/components/data_stream_selector/state_machine/use_data_stream_selector.ts similarity index 100% rename from x-pack/plugins/observability_logs/public/components/data_stream_selector/state_machine/use_data_stream_selector.ts rename to x-pack/plugins/discover_log_explorer/public/components/data_stream_selector/state_machine/use_data_stream_selector.ts diff --git a/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/data_streams_list.tsx b/x-pack/plugins/discover_log_explorer/public/components/data_stream_selector/sub_components/data_streams_list.tsx similarity index 96% rename from x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/data_streams_list.tsx rename to x-pack/plugins/discover_log_explorer/public/components/data_stream_selector/sub_components/data_streams_list.tsx index 19f0bd8df4d98..138233dc96684 100644 --- a/x-pack/plugins/observability_logs/public/components/data_stream_selector/sub_components/data_streams_list.tsx +++ b/x-pack/plugins/discover_log_explorer/public/components/data_stream_selector/sub_components/data_streams_list.tsx @@ -51,7 +51,7 @@ export const DataStreamsList = ({ titleSize="s" body={ { /** * Replace the DataViewPicker with a custom `DataStreamSelector` to pick integrations streams diff --git a/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_client.mock.ts b/x-pack/plugins/discover_log_explorer/public/services/data_streams/data_streams_client.mock.ts similarity index 100% rename from x-pack/plugins/observability_logs/public/services/data_streams/data_streams_client.mock.ts rename to x-pack/plugins/discover_log_explorer/public/services/data_streams/data_streams_client.mock.ts diff --git a/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_client.ts b/x-pack/plugins/discover_log_explorer/public/services/data_streams/data_streams_client.ts similarity index 100% rename from x-pack/plugins/observability_logs/public/services/data_streams/data_streams_client.ts rename to x-pack/plugins/discover_log_explorer/public/services/data_streams/data_streams_client.ts diff --git a/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_service.mock.ts b/x-pack/plugins/discover_log_explorer/public/services/data_streams/data_streams_service.mock.ts similarity index 100% rename from x-pack/plugins/observability_logs/public/services/data_streams/data_streams_service.mock.ts rename to x-pack/plugins/discover_log_explorer/public/services/data_streams/data_streams_service.mock.ts diff --git a/x-pack/plugins/observability_logs/public/services/data_streams/data_streams_service.ts b/x-pack/plugins/discover_log_explorer/public/services/data_streams/data_streams_service.ts similarity index 100% rename from x-pack/plugins/observability_logs/public/services/data_streams/data_streams_service.ts rename to x-pack/plugins/discover_log_explorer/public/services/data_streams/data_streams_service.ts diff --git a/x-pack/plugins/observability_logs/public/services/data_streams/index.ts b/x-pack/plugins/discover_log_explorer/public/services/data_streams/index.ts similarity index 100% rename from x-pack/plugins/observability_logs/public/services/data_streams/index.ts rename to x-pack/plugins/discover_log_explorer/public/services/data_streams/index.ts diff --git a/x-pack/plugins/observability_logs/public/services/data_streams/types.ts b/x-pack/plugins/discover_log_explorer/public/services/data_streams/types.ts similarity index 100% rename from x-pack/plugins/observability_logs/public/services/data_streams/types.ts rename to x-pack/plugins/discover_log_explorer/public/services/data_streams/types.ts diff --git a/x-pack/plugins/observability_logs/public/state_machines/data_streams/index.ts b/x-pack/plugins/discover_log_explorer/public/state_machines/data_streams/index.ts similarity index 100% rename from x-pack/plugins/observability_logs/public/state_machines/data_streams/index.ts rename to x-pack/plugins/discover_log_explorer/public/state_machines/data_streams/index.ts diff --git a/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/defaults.ts b/x-pack/plugins/discover_log_explorer/public/state_machines/data_streams/src/defaults.ts similarity index 100% rename from x-pack/plugins/observability_logs/public/state_machines/data_streams/src/defaults.ts rename to x-pack/plugins/discover_log_explorer/public/state_machines/data_streams/src/defaults.ts diff --git a/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/index.ts b/x-pack/plugins/discover_log_explorer/public/state_machines/data_streams/src/index.ts similarity index 100% rename from x-pack/plugins/observability_logs/public/state_machines/data_streams/src/index.ts rename to x-pack/plugins/discover_log_explorer/public/state_machines/data_streams/src/index.ts diff --git a/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/state_machine.ts b/x-pack/plugins/discover_log_explorer/public/state_machines/data_streams/src/state_machine.ts similarity index 100% rename from x-pack/plugins/observability_logs/public/state_machines/data_streams/src/state_machine.ts rename to x-pack/plugins/discover_log_explorer/public/state_machines/data_streams/src/state_machine.ts diff --git a/x-pack/plugins/observability_logs/public/state_machines/data_streams/src/types.ts b/x-pack/plugins/discover_log_explorer/public/state_machines/data_streams/src/types.ts similarity index 100% rename from x-pack/plugins/observability_logs/public/state_machines/data_streams/src/types.ts rename to x-pack/plugins/discover_log_explorer/public/state_machines/data_streams/src/types.ts diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/index.ts b/x-pack/plugins/discover_log_explorer/public/state_machines/integrations/index.ts similarity index 100% rename from x-pack/plugins/observability_logs/public/state_machines/integrations/index.ts rename to x-pack/plugins/discover_log_explorer/public/state_machines/integrations/index.ts diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/defaults.ts b/x-pack/plugins/discover_log_explorer/public/state_machines/integrations/src/defaults.ts similarity index 100% rename from x-pack/plugins/observability_logs/public/state_machines/integrations/src/defaults.ts rename to x-pack/plugins/discover_log_explorer/public/state_machines/integrations/src/defaults.ts diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/index.ts b/x-pack/plugins/discover_log_explorer/public/state_machines/integrations/src/index.ts similarity index 100% rename from x-pack/plugins/observability_logs/public/state_machines/integrations/src/index.ts rename to x-pack/plugins/discover_log_explorer/public/state_machines/integrations/src/index.ts diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts b/x-pack/plugins/discover_log_explorer/public/state_machines/integrations/src/state_machine.ts similarity index 100% rename from x-pack/plugins/observability_logs/public/state_machines/integrations/src/state_machine.ts rename to x-pack/plugins/discover_log_explorer/public/state_machines/integrations/src/state_machine.ts diff --git a/x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts b/x-pack/plugins/discover_log_explorer/public/state_machines/integrations/src/types.ts similarity index 100% rename from x-pack/plugins/observability_logs/public/state_machines/integrations/src/types.ts rename to x-pack/plugins/discover_log_explorer/public/state_machines/integrations/src/types.ts diff --git a/x-pack/plugins/observability_logs/public/types.ts b/x-pack/plugins/discover_log_explorer/public/types.ts similarity index 64% rename from x-pack/plugins/observability_logs/public/types.ts rename to x-pack/plugins/discover_log_explorer/public/types.ts index 1f3d1390b6005..4e64116c4b1d2 100644 --- a/x-pack/plugins/observability_logs/public/types.ts +++ b/x-pack/plugins/discover_log_explorer/public/types.ts @@ -8,16 +8,16 @@ import { Plugin } from '@kbn/core/public'; import { DiscoverStart } from '@kbn/discover-plugin/public'; import { DataStreamsServiceStart } from './services/data_streams'; -export type ObservabilityLogsPluginSetup = void; -export interface ObservabilityLogsPluginStart { +export type DiscoverLogExplorerPluginSetup = void; +export interface DiscoverLogExplorerPluginStart { dataStreamsService: DataStreamsServiceStart; } -export interface ObservabilityLogsStartDeps { +export interface DiscoverLogExplorerStartDeps { discover: DiscoverStart; } -export type ObservabilityLogsClientPluginClass = Plugin< - ObservabilityLogsPluginSetup, - ObservabilityLogsPluginStart +export type DiscoverLogExplorerClientPluginClass = Plugin< + DiscoverLogExplorerPluginSetup, + DiscoverLogExplorerPluginStart >; diff --git a/x-pack/plugins/observability_logs/server/index.ts b/x-pack/plugins/discover_log_explorer/server/index.ts similarity index 70% rename from x-pack/plugins/observability_logs/server/index.ts rename to x-pack/plugins/discover_log_explorer/server/index.ts index aad18dee3f1a9..8b8187e37fdd7 100644 --- a/x-pack/plugins/observability_logs/server/index.ts +++ b/x-pack/plugins/discover_log_explorer/server/index.ts @@ -6,13 +6,13 @@ */ import { PluginInitializerContext } from '@kbn/core/server'; -import { ObservabilityLogsPlugin } from './plugin'; +import { DiscoverLogExplorerPlugin } from './plugin'; // This exports static code and TypeScript types, // as well as, Kibana Platform `plugin()` initializer. export function plugin(initializerContext: PluginInitializerContext) { - return new ObservabilityLogsPlugin(initializerContext); + return new DiscoverLogExplorerPlugin(initializerContext); } -export type { ObservabilityLogsPluginSetup, ObservabilityLogsPluginStart } from './types'; +export type { DiscoverLogExplorerPluginSetup, DiscoverLogExplorerPluginStart } from './types'; diff --git a/x-pack/plugins/observability_logs/server/plugin.ts b/x-pack/plugins/discover_log_explorer/server/plugin.ts similarity index 71% rename from x-pack/plugins/observability_logs/server/plugin.ts rename to x-pack/plugins/discover_log_explorer/server/plugin.ts index a35ac28f3f91d..f5e8b3baecce6 100644 --- a/x-pack/plugins/observability_logs/server/plugin.ts +++ b/x-pack/plugins/discover_log_explorer/server/plugin.ts @@ -7,11 +7,11 @@ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin, Logger } from '@kbn/core/server'; -import { ObservabilityLogsPluginSetup, ObservabilityLogsPluginStart } from './types'; +import { DiscoverLogExplorerPluginSetup, DiscoverLogExplorerPluginStart } from './types'; import { defineRoutes } from './routes'; -export class ObservabilityLogsPlugin - implements Plugin +export class DiscoverLogExplorerPlugin + implements Plugin { private readonly logger: Logger; @@ -20,7 +20,7 @@ export class ObservabilityLogsPlugin } public setup(core: CoreSetup) { - this.logger.debug('observabilityLogs: Setup'); + this.logger.debug('discoverLogExplorer: Setup'); const router = core.http.createRouter(); // Register server side APIs @@ -30,7 +30,7 @@ export class ObservabilityLogsPlugin } public start(core: CoreStart) { - this.logger.debug('observabilityLogs: Started'); + this.logger.debug('discoverLogExplorer: Started'); return {}; } diff --git a/x-pack/plugins/discover_log_explorer/server/routes/index.ts b/x-pack/plugins/discover_log_explorer/server/routes/index.ts new file mode 100644 index 0000000000000..a2d9569496a96 --- /dev/null +++ b/x-pack/plugins/discover_log_explorer/server/routes/index.ts @@ -0,0 +1,453 @@ +/* + * 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. + */ + +/* eslint-disable prefer-const */ + +import { IRouter } from '@kbn/core/server'; +import { schema } from '@kbn/config-schema'; +import { FindIntegrationsRequestQuery } from '../../common'; + +export function defineRoutes(router: IRouter) { + router.get( + { + path: '/api/fleet/epm/packages/installed', + validate: { + query: schema.object({ + dataStreamType: schema.maybe( + schema.oneOf([ + schema.literal('logs'), + schema.literal('metrics'), + schema.literal('traces'), + schema.literal('synthetics'), + schema.literal('profiling'), + ]) + ), + nameQuery: schema.maybe(schema.string()), + searchAfter: schema.maybe( + schema.arrayOf(schema.oneOf([schema.string(), schema.number()])) + ), + perPage: schema.number({ defaultValue: 10 }), + sortOrder: schema.oneOf([schema.literal('asc'), schema.literal('desc')], { + defaultValue: 'asc', + }), + }), + }, + }, + async (_context, request, response) => { + await new Promise((res) => setTimeout(res, 500)); + let { + nameQuery = '', + perPage = 10, + searchAfter = '', + sortOrder, + } = request.query as FindIntegrationsRequestQuery; + + let filteredPackages = items.filter((pkg) => pkg.name.includes(nameQuery)); + if (sortOrder === 'desc') { + filteredPackages.sort((a, b) => b.name.localeCompare(a.name)); + } else { + filteredPackages.sort((a, b) => a.name.localeCompare(b.name)); + } + + const searchAfterIndex = searchAfter[0] + ? filteredPackages.findIndex((pkg) => pkg.name === searchAfter[0]) + : -1; + if (searchAfterIndex >= 0) { + filteredPackages = filteredPackages.slice( + searchAfterIndex + 1, + searchAfterIndex + perPage + 1 + ); + } else { + filteredPackages = filteredPackages.slice(0, perPage); + } + + return response.ok({ + body: { + total: items.length, + searchAfter: filteredPackages.length + ? [filteredPackages[filteredPackages.length - 1].name] + : undefined, + items: filteredPackages, + }, + }); + } + ); + + router.get( + { + path: '/api/fleet/epm/data_streams', + validate: { + query: schema.object({ + type: schema.maybe( + schema.oneOf([ + schema.literal('logs'), + schema.literal('metrics'), + schema.literal('traces'), + schema.literal('synthetics'), + schema.literal('profiling'), + ]) + ), + datasetQuery: schema.maybe(schema.string()), + sortOrder: schema.oneOf([schema.literal('asc'), schema.literal('desc')], { + defaultValue: 'asc', + }), + uncategorisedOnly: schema.boolean({ defaultValue: false }), + }), + }, + }, + async (_context, request, response) => { + await new Promise((res) => setTimeout(res, 1000)); + const { datasetQuery = '', sortOrder } = request.query; + + const filteredPackages = streams.filter((pkg) => pkg.name.includes(datasetQuery)); + if (sortOrder === 'desc') { + filteredPackages.sort((a, b) => b.name.localeCompare(a.name)); + } else { + filteredPackages.sort((a, b) => a.name.localeCompare(b.name)); + } + + return response.ok({ + body: { + items: filteredPackages, + }, + }); + } + ); +} + +const items = [ + { + name: 'apm', + version: '8.9.0-SNAPSHOT', + status: 'installed', + dataStreams: [ + { + name: 'logs-apm.app.*-*', + title: 'app_logs', + }, + { + name: 'logs-apm.error-*', + title: 'error_logs', + }, + ], + }, + { + name: 'cloud_security_posture', + version: '1.3.0', + status: 'installed', + dataStreams: [ + { + name: 'logs-cloud_security_posture.findings-*', + title: 'findings', + }, + ], + }, + { + name: 'elastic_agent', + version: '1.8.0', + status: 'installed', + dataStreams: [ + { + name: 'logs-elastic_agent.apm_server-*', + title: 'apm_server_logs', + }, + { + name: 'logs-elastic_agent.auditbeat-*', + title: 'auditbeat_logs', + }, + { + name: 'logs-elastic_agent.cloud_defend-*', + title: 'cloud_defend_logs', + }, + { + name: 'logs-elastic_agent.cloudbeat-*', + title: 'cloudbeat_logs', + }, + { + name: 'logs-elastic_agent-*', + title: 'elastic_agent_logs', + }, + { + name: 'logs-elastic_agent.endpoint_security-*', + title: 'endpoint_sercurity_logs', + }, + { + name: 'logs-elastic_agent.filebeat_input-*', + title: 'filebeat_input_logs', + }, + { + name: 'logs-elastic_agent.filebeat-*', + title: 'filebeat_logs', + }, + { + name: 'logs-elastic_agent.fleet_server-*', + title: 'fleet_server_logs', + }, + { + name: 'logs-elastic_agent.heartbeat-*', + title: 'heartbeat_logs', + }, + { + name: 'logs-elastic_agent.metricbeat-*', + title: 'metricbeat_logs', + }, + { + name: 'logs-elastic_agent.osquerybeat-*', + title: 'osquerybeat_logs', + }, + { + name: 'logs-elastic_agent.packetbeat-*', + title: 'packetbeat_logs', + }, + ], + }, + { + name: 'endpoint', + version: '8.8.0', + status: 'installed', + dataStreams: [ + { + name: 'logs-endpoint.alerts-*', + title: 'alerts', + }, + { + name: 'logs-endpoint.events.api-*', + title: 'api', + }, + { + name: 'logs-endpoint.events.file-*', + title: 'file', + }, + { + name: 'logs-endpoint.events.library-*', + title: 'library', + }, + { + name: 'logs-endpoint.events.network-*', + title: 'network', + }, + { + name: 'logs-endpoint.events.process-*', + title: 'process', + }, + { + name: 'logs-endpoint.events.registry-*', + title: 'registry', + }, + { + name: 'logs-endpoint.events.security-*', + title: 'security', + }, + ], + }, + { + name: 'kubernetes', + version: '1.41.0', + status: 'installed', + dataStreams: [ + { + name: 'logs-kubernetes.audit_logs-*', + title: 'audit_logs', + }, + { + name: 'logs-kubernetes.container_logs-*', + title: 'container_logs', + }, + ], + }, + { + name: 'log', + version: '2.0.0', + status: 'installed', + dataStreams: [], + }, + { + name: 'network_traffic', + version: '1.18.0', + status: 'installed', + dataStreams: [ + { + name: 'logs-network_traffic.amqp-*', + title: 'amqp', + }, + { + name: 'logs-network_traffic.cassandra-*', + title: 'cassandra', + }, + { + name: 'logs-network_traffic.dhcpv4-*', + title: 'dhcpv4', + }, + { + name: 'logs-network_traffic.dns-*', + title: 'dns', + }, + { + name: 'logs-network_traffic.flow-*', + title: 'flow', + }, + { + name: 'logs-network_traffic.http-*', + title: 'http', + }, + { + name: 'logs-network_traffic.icmp-*', + title: 'icmp', + }, + { + name: 'logs-network_traffic.memcached-*', + title: 'memcached', + }, + { + name: 'logs-network_traffic.mongodb-*', + title: 'mongodb', + }, + { + name: 'logs-network_traffic.mysql-*', + title: 'mysql', + }, + { + name: 'logs-network_traffic.nfs-*', + title: 'nfs', + }, + { + name: 'logs-network_traffic.pgsql-*', + title: 'pgsql', + }, + { + name: 'logs-network_traffic.redis-*', + title: 'redis', + }, + { + name: 'logs-network_traffic.sip-*', + title: 'sip', + }, + { + name: 'logs-network_traffic.thrift-*', + title: 'thrift', + }, + { + name: 'logs-network_traffic.tls-*', + title: 'tls', + }, + ], + }, + { + name: 'system', + version: '1.31.1', + status: 'installed', + dataStreams: [ + { + name: 'logs-system.application-*', + title: 'application', + }, + { + name: 'logs-system.auth-*', + title: 'auth', + }, + { + name: 'logs-system.security-*', + title: 'security', + }, + { + name: 'logs-system.syslog-*', + title: 'syslog', + }, + { + name: 'logs-system.system-*', + title: 'system', + }, + ], + }, +]; + +const streams = [ + { + name: 'logs-1password.item_usages-default', + }, + { + name: 'logs-1password.signin_attempts-default', + }, + { + name: 'logs-apache.access-default', + }, + { + name: 'logs-apache.error-default', + }, + { + name: 'logs-apm.app-default', + }, + { + name: 'logs-apm.error-default', + }, + { + name: 'logs-elastic_agent-default', + }, + { + name: 'logs-elastic_agent.apm_server-default', + }, + { + name: 'logs-elastic_agent.endpoint_security-default', + }, + { + name: 'logs-elastic_agent.filebeat-default', + }, + { + name: 'logs-elastic_agent.heartbeat-default', + }, + { + name: 'logs-elastic_agent.metricbeat-default', + }, + { + name: 'logs-endpoint.alerts-default', + }, + { + name: 'logs-endpoint.events.api-default', + }, + { + name: 'logs-endpoint.events.file-default', + }, + { + name: 'logs-endpoint.events.library-default', + }, + { + name: 'logs-endpoint.events.network-default', + }, + { + name: 'logs-endpoint.events.process-default', + }, + { + name: 'logs-endpoint.events.registry-default', + }, + { + name: 'logs-endpoint.events.security-default', + }, + { + name: 'logs-felixbarny-default', + }, + { + name: 'logs-generic-pods-default', + }, + { + name: 'logs-mydata-default', + }, + { + name: 'logs-system.application-default', + }, + { + name: 'logs-system.auth-default', + }, + { + name: 'logs-system.security-default', + }, + { + name: 'logs-system.syslog-default', + }, + { + name: 'logs-system.system-default', + }, +]; diff --git a/x-pack/plugins/observability_logs/server/types.ts b/x-pack/plugins/discover_log_explorer/server/types.ts similarity index 79% rename from x-pack/plugins/observability_logs/server/types.ts rename to x-pack/plugins/discover_log_explorer/server/types.ts index 8f0148a170206..587b3fdfd14b6 100644 --- a/x-pack/plugins/observability_logs/server/types.ts +++ b/x-pack/plugins/discover_log_explorer/server/types.ts @@ -6,6 +6,6 @@ */ // eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface ObservabilityLogsPluginSetup {} +export interface DiscoverLogExplorerPluginSetup {} // eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface ObservabilityLogsPluginStart {} +export interface DiscoverLogExplorerPluginStart {} diff --git a/x-pack/plugins/observability_logs/tsconfig.json b/x-pack/plugins/discover_log_explorer/tsconfig.json similarity index 100% rename from x-pack/plugins/observability_logs/tsconfig.json rename to x-pack/plugins/discover_log_explorer/tsconfig.json diff --git a/x-pack/plugins/observability_logs/server/routes/index.ts b/x-pack/plugins/observability_logs/server/routes/index.ts deleted file mode 100644 index d3f93ae892ac2..0000000000000 --- a/x-pack/plugins/observability_logs/server/routes/index.ts +++ /dev/null @@ -1,488 +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. - */ - -/* eslint-disable prefer-const */ - -import { IRouter } from '@kbn/core/server'; -import { schema } from '@kbn/config-schema'; -import { FindIntegrationsRequestQuery } from '../../common'; - -export function defineRoutes(router: IRouter) { - router.get( - { - path: '/api/fleet/epm/packages/installed', - validate: { - query: schema.object({ - dataStreamType: schema.maybe( - schema.oneOf([ - schema.literal('logs'), - schema.literal('metrics'), - schema.literal('traces'), - schema.literal('synthetics'), - schema.literal('profiling'), - ]) - ), - nameQuery: schema.maybe(schema.string()), - searchAfter: schema.maybe( - schema.arrayOf(schema.oneOf([schema.string(), schema.number()])) - ), - perPage: schema.number({ defaultValue: 10 }), - sortOrder: schema.oneOf([schema.literal('asc'), schema.literal('desc')], { - defaultValue: 'asc', - }), - }), - }, - }, - async (_context, request, response) => { - await new Promise((res) => setTimeout(res, 500)); - let { - nameQuery = '', - perPage = 10, - searchAfter = '', - sortOrder, - } = request.query as FindIntegrationsRequestQuery; - - let filteredPackages = items.filter((pkg) => pkg.name.includes(nameQuery)); - if (sortOrder === 'desc') { - filteredPackages.sort((a, b) => b.name.localeCompare(a.name)); - } else { - filteredPackages.sort((a, b) => a.name.localeCompare(b.name)); - } - - const searchAfterIndex = searchAfter[0] - ? filteredPackages.findIndex((pkg) => pkg.name === searchAfter[0]) - : -1; - if (searchAfterIndex >= 0) { - filteredPackages = filteredPackages.slice( - searchAfterIndex + 1, - searchAfterIndex + perPage + 1 - ); - } else { - filteredPackages = filteredPackages.slice(0, perPage); - } - - return response.ok({ - body: { - total: items.length, - searchAfter: filteredPackages.length - ? [filteredPackages[filteredPackages.length - 1].name] - : undefined, - items: filteredPackages, - }, - }); - } - ); - - router.get( - { - path: '/api/fleet/epm/data_streams', - validate: { - query: schema.object({ - type: schema.maybe( - schema.oneOf([ - schema.literal('logs'), - schema.literal('metrics'), - schema.literal('traces'), - schema.literal('synthetics'), - schema.literal('profiling'), - ]) - ), - datasetQuery: schema.maybe(schema.string()), - sortOrder: schema.oneOf([schema.literal('asc'), schema.literal('desc')], { - defaultValue: 'asc', - }), - uncategorisedOnly: schema.boolean({ defaultValue: false }), - }), - }, - }, - async (_context, request, response) => { - await new Promise((res) => setTimeout(res, 1000)); - const { datasetQuery = '', sortOrder } = request.query; - - const filteredPackages = streams.filter((pkg) => pkg.name.includes(datasetQuery)); - if (sortOrder === 'desc') { - filteredPackages.sort((a, b) => b.name.localeCompare(a.name)); - } else { - filteredPackages.sort((a, b) => a.name.localeCompare(b.name)); - } - - return response.ok({ - body: { - items: filteredPackages, - }, - }); - } - ); -} - -const items = [ - { - name: 'system', - version: '1.25.2', - status: 'installed', - dataStreams: [ - { - title: 'System metrics stream', - name: 'system-metrics-*', - }, - { - title: 'System logs stream', - name: 'system-logs-*', - }, - ], - }, - { - name: 'kubernetes', - version: '1.35.0', - status: 'installed', - dataStreams: [ - { - title: 'Kubernetes metrics stream', - name: 'k8s-metrics-*', - }, - { - title: 'Kubernetes logs stream', - name: 'k8s-logs-*', - }, - ], - }, - { - name: 'mysql', - version: '1.11.0', - status: 'installed', - dataStreams: [ - { - title: 'MySQL metrics stream', - name: 'mysql-metrics-*', - }, - { - title: 'MySQL slow logs stream', - name: 'mysql-slow-logs-*', - }, - { - title: 'MySQL error logs stream', - name: 'mysql-error-logs-*', - }, - ], - }, - { - name: 'apache', - version: '1.12.0', - status: 'installed', - dataStreams: [ - { - title: 'Apache metrics stream', - name: 'apache-metrics-*', - }, - { - title: 'Apache logs stream', - name: 'apache-logs-*', - }, - { - title: 'Apache error logs stream', - name: 'apache-error-logs-*', - }, - ], - }, - { - name: 'nginx', - version: '1.11.1', - status: 'installed', - dataStreams: [ - { - title: 'Nginx metrics stream', - name: 'nginx-metrics-*', - }, - { - title: 'Nginx access logs stream', - name: 'nginx-access-logs-*', - }, - ], - }, - { - name: 'postgresql', - version: '1.13.0', - status: 'installed', - dataStreams: [ - { - title: 'PostgreSQL metrics stream', - name: 'postgresql-metrics-*', - }, - { - title: 'PostgreSQL slow query logs stream', - name: 'postgresql-slow-query-logs-*', - }, - { - title: 'PostgreSQL error logs stream', - name: 'postgresql-error-logs-*', - }, - ], - }, - { - name: 'rabbitmq', - version: '1.8.8', - status: 'installed', - dataStreams: [ - { - title: 'RabbitMQ metrics stream', - name: 'rabbitmq-metrics-*', - }, - { - title: 'RabbitMQ queues stream', - name: 'rabbitmq-queues-*', - }, - { - title: 'RabbitMQ error logs stream', - name: 'rabbitmq-error-logs-*', - }, - ], - }, - { - name: 'redis', - version: '1.9.2', - status: 'installed', - dataStreams: [ - { - title: 'Redis metrics stream', - name: 'redis-metrics-*', - }, - { - title: 'Redis slow logs stream', - name: 'redis-slow-logs-*', - }, - ], - }, - { - name: 'elasticsearch', - version: '1.5.0', - status: 'installed', - dataStreams: [ - { - title: 'Elasticsearch metrics stream', - name: 'elasticsearch-metrics-*', - }, - { - title: 'Elasticsearch indices stream', - name: 'elasticsearch-indices-*', - }, - ], - }, - { - name: 'mongodb', - version: '1.9.3', - status: 'installed', - dataStreams: [ - { - title: 'MongoDB metrics stream', - name: 'mongodb-metrics-*', - }, - { - title: 'MongoDB slow query logs stream', - name: 'mongodb-slow-query-logs-*', - }, - ], - }, - { - name: 'prometheus', - version: '1.3.2', - status: 'installed', - dataStreams: [ - { - title: 'Prometheus metrics stream', - name: 'prometheus-metrics-*', - }, - ], - }, - { - name: 'haproxy', - version: '1.5.1', - status: 'installed', - dataStreams: [ - { - title: 'HAProxy metrics stream', - name: 'haproxy-metrics-*', - }, - { - title: 'HAProxy logs stream', - name: 'haproxy-logs-*', - }, - ], - }, - { - name: 'atlassian_jira', - version: '1.10.0', - status: 'installed', - dataStreams: [ - { title: 'Atlassian metrics stream', name: 'metrics-*' }, - { title: 'Atlassian secondary', name: 'metrics-*' }, - ], - }, - { - name: 'atlassian_confluence', - version: '1.10.0', - status: 'installed', - dataStreams: [ - { title: 'Atlassian metrics stream', name: 'metrics-*' }, - { title: 'Atlassian secondary', name: 'metrics-*' }, - ], - }, - { - name: 'atlassian_bitbucket', - version: '1.9.0', - status: 'installed', - dataStreams: [ - { title: 'Atlassian metrics stream', name: 'metrics-*' }, - { title: 'Atlassian secondary', name: 'metrics-*' }, - ], - }, - { - name: 'docker', - version: '2.4.3', - status: 'installed', - dataStreams: [ - { title: 'Docker container logs', name: 'docker-*' }, - { title: 'Docker daemon logs', name: 'docker-daemon-*' }, - ], - }, - { - name: 'aws', - version: '1.36.3', - status: 'installed', - dataStreams: [ - { title: 'AWS S3 object access logs', name: 'aws-s3-access-' }, - { title: 'AWS S3 bucket access logs', name: 'aws-s3-bucket-access-' }, - ], - }, - { - name: 'cassandra', - version: '1.6.0', - status: 'installed', - dataStreams: [ - { title: 'Cassandra server logs', name: 'cassandra-' }, - { title: 'Cassandra slow queries', name: 'cassandra-slow-' }, - { title: 'Cassandra errors', name: 'cassandra-errors-' }, - ], - }, - { - name: 'nginx_ingress_controller', - version: '1.7.1', - status: 'installed', - dataStreams: [{ title: 'Nginx ingress logs', name: 'nginx-ingress-' }], - }, - { - name: 'gcp', - version: '2.20.1', - status: 'installed', - dataStreams: [{ title: 'GCP Stackdriver logs', name: 'gcp-stackdriver-*' }], - }, - { - name: 'kafka', - version: '1.5.6', - status: 'installed', - dataStreams: [{ title: 'Kafka server logs', name: 'kafka-*' }], - }, - { - name: 'kibana', - version: '2.3.4', - status: 'installed', - dataStreams: [{ title: 'Kibana server logs', name: 'kibana-*' }], - }, -]; - -const streams = [ - { name: 'logs-*' }, - { name: 'logs-*-*' }, - { name: 'logs-nginx-*' }, - { name: 'logs-apache-*' }, - { name: 'logs-security-*' }, - { name: 'logs-error-*' }, - { name: 'logs-access-*' }, - { name: 'logs-firewall-*' }, - { name: 'logs-application-*' }, - { name: 'logs-debug-*' }, - { name: 'logs-transaction-*' }, - { name: 'logs-server-*' }, - { name: 'logs-database-*' }, - { name: 'logs-event-*' }, - { name: 'logs-auth-*' }, - { name: 'logs-billing-*' }, - { name: 'logs-network-*' }, - { name: 'logs-performance-*' }, - { name: 'logs-email-*' }, - { name: 'logs-job-*' }, - { name: 'logs-task-*' }, - { name: 'logs-user-*' }, - { name: 'logs-request-*' }, - { name: 'logs-payment-*' }, - { name: 'logs-inventory-*' }, - { name: 'logs-debugging-*' }, - { name: 'logs-scheduler-*' }, - { name: 'logs-diagnostic-*' }, - { name: 'logs-cluster-*' }, - { name: 'logs-service-*' }, - { name: 'logs-framework-*' }, - { name: 'logs-api-*' }, - { name: 'logs-reporting-*' }, - { name: 'logs-backend-*' }, - { name: 'logs-frontend-*' }, - { name: 'logs-chat-*' }, - { name: 'logs-api.gateway-*' }, - { name: 'logs-event.service-*' }, - { name: 'logs-notification.service-*' }, - { name: 'logs-search.service-*' }, - { name: 'logs-logging-service-*' }, - { name: 'logs-performance.service-*' }, - { name: 'logs-load-testing-*' }, - { name: 'logs-mobile-app-*' }, - { name: 'logs-web-app-*' }, - { name: 'logs-stream-processing-*' }, - { name: 'logs-batch-processing-*' }, - { name: 'logs-cloud-service-*' }, - { name: 'logs-container-*' }, - { name: 'logs-serverless-*' }, - { name: 'logs-server.administration-*' }, - { name: 'logs-application.deployment-*' }, - { name: 'logs-webserver-*' }, - { name: 'logs-pipeline-*' }, - { name: 'logs-frontend-service-*' }, - { name: 'logs-backend-service-*' }, - { name: 'logs-resource-monitoring-*' }, - { name: 'logs-logging-aggregation-*' }, - { name: 'logs-container-orchestration-*' }, - { name: 'logs-audit-*' }, - { name: 'logs-management-*' }, - { name: 'logs-mesh-*' }, - { name: 'logs-processing-*' }, - { name: 'logs-science-*' }, - { name: 'logs-machine.learning-*' }, - { name: 'logs-experimentation-*' }, - { name: 'logs-visualization-*' }, - { name: 'logs-cleaning-*' }, - { name: 'logs-transformation-*' }, - { name: 'logs-analysis-*' }, - { name: 'logs-storage-*' }, - { name: 'logs-retrieval-*' }, - { name: 'logs-warehousing-*' }, - { name: 'logs-modeling-*' }, - { name: 'logs-integration-*' }, - { name: 'logs-quality-*' }, - { name: 'logs-encryption-*' }, - { name: 'logs-governance-*' }, - { name: 'logs-compliance-*' }, - { name: 'logs-privacy-*' }, - { name: 'logs-auditing-*' }, - { name: 'logs-discovery-*' }, - { name: 'logs-protection-*' }, - { name: 'logs-archiving-*' }, - { name: 'logs-backup-*' }, - { name: 'logs-recovery-*' }, - { name: 'logs-replication-*' }, - { name: 'logs-synchronization-*' }, - { name: 'logs-migration-*' }, - { name: 'logs-load-balancing-*' }, - { name: 'logs-scaling-*' }, -]; From 49016b69bb371edac970ab447c8fa605f1f4200b Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 7 Jun 2023 10:02:11 +0000 Subject: [PATCH 122/122] [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' --- package.json | 2 +- tsconfig.base.json | 4 ++-- yarn.lock | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 904344602be69..473bf3ea98876 100644 --- a/package.json +++ b/package.json @@ -350,6 +350,7 @@ "@kbn/developer-examples-plugin": "link:examples/developer_examples", "@kbn/discover-enhanced-plugin": "link:x-pack/plugins/discover_enhanced", "@kbn/discover-extender-plugin": "link:examples/discover_extender", + "@kbn/discover-log-explorer-plugin": "link:x-pack/plugins/discover_log_explorer", "@kbn/discover-plugin": "link:src/plugins/discover", "@kbn/doc-links": "link:packages/kbn-doc-links", "@kbn/dom-drag-drop": "link:packages/kbn-dom-drag-drop", @@ -493,7 +494,6 @@ "@kbn/object-versioning": "link:packages/kbn-object-versioning", "@kbn/observability-alert-details": "link:x-pack/packages/observability/alert_details", "@kbn/observability-fixtures-plugin": "link:x-pack/test/cases_api_integration/common/plugins/observability", - "@kbn/observability-logs-plugin": "link:x-pack/plugins/observability_logs", "@kbn/observability-plugin": "link:x-pack/plugins/observability", "@kbn/observability-shared-plugin": "link:x-pack/plugins/observability_shared", "@kbn/oidc-provider-plugin": "link:x-pack/test/security_api_integration/plugins/oidc_provider", diff --git a/tsconfig.base.json b/tsconfig.base.json index 24fad8fdf4d00..a13a04fa88b83 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -614,6 +614,8 @@ "@kbn/discover-enhanced-plugin/*": ["x-pack/plugins/discover_enhanced/*"], "@kbn/discover-extender-plugin": ["examples/discover_extender"], "@kbn/discover-extender-plugin/*": ["examples/discover_extender/*"], + "@kbn/discover-log-explorer-plugin": ["x-pack/plugins/discover_log_explorer"], + "@kbn/discover-log-explorer-plugin/*": ["x-pack/plugins/discover_log_explorer/*"], "@kbn/discover-plugin": ["src/plugins/discover"], "@kbn/discover-plugin/*": ["src/plugins/discover/*"], "@kbn/doc-links": ["packages/kbn-doc-links"], @@ -950,8 +952,6 @@ "@kbn/observability-alert-details/*": ["x-pack/packages/observability/alert_details/*"], "@kbn/observability-fixtures-plugin": ["x-pack/test/cases_api_integration/common/plugins/observability"], "@kbn/observability-fixtures-plugin/*": ["x-pack/test/cases_api_integration/common/plugins/observability/*"], - "@kbn/observability-logs-plugin": ["x-pack/plugins/observability_logs"], - "@kbn/observability-logs-plugin/*": ["x-pack/plugins/observability_logs/*"], "@kbn/observability-plugin": ["x-pack/plugins/observability"], "@kbn/observability-plugin/*": ["x-pack/plugins/observability/*"], "@kbn/observability-shared-plugin": ["x-pack/plugins/observability_shared"], diff --git a/yarn.lock b/yarn.lock index 861b7bb8ef894..4eda0c7b3a319 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3966,6 +3966,10 @@ version "0.0.0" uid "" +"@kbn/discover-log-explorer-plugin@link:x-pack/plugins/discover_log_explorer": + version "0.0.0" + uid "" + "@kbn/discover-plugin@link:src/plugins/discover": version "0.0.0" uid "" @@ -4638,10 +4642,6 @@ version "0.0.0" uid "" -"@kbn/observability-logs-plugin@link:x-pack/plugins/observability_logs": - version "0.0.0" - uid "" - "@kbn/observability-plugin@link:x-pack/plugins/observability": version "0.0.0" uid ""