From 7bdf57249b6662d4240a3dcdb9b8812196d64f1f Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Tue, 28 Jun 2022 17:00:23 +0200 Subject: [PATCH] [Graph] Migrate from savedObjectsClient to dataViews and fix the displayed data view name (#135142) * [Graph] Migrate from savedObjectsClient to dataViews * [Graph] Update tests * [Graph] Fix "no data views" view Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../graph/public/apps/workspace_route.tsx | 4 +- .../guidance_panel/guidance_panel.tsx | 32 +++++----- .../public/components/search_bar.test.tsx | 4 +- .../graph/public/components/search_bar.tsx | 2 +- .../workspace_layout/workspace_layout.tsx | 15 +---- .../helpers/use_workspace_loader.test.tsx | 9 ++- .../public/helpers/use_workspace_loader.ts | 60 +++++-------------- .../services/persistence/deserialize.test.ts | 10 ++-- .../services/persistence/deserialize.ts | 15 +++-- .../public/state_management/persistence.ts | 10 ++-- 10 files changed, 62 insertions(+), 99 deletions(-) diff --git a/x-pack/plugins/graph/public/apps/workspace_route.tsx b/x-pack/plugins/graph/public/apps/workspace_route.tsx index e8850e4817d504a..64c85bc06750dec 100644 --- a/x-pack/plugins/graph/public/apps/workspace_route.tsx +++ b/x-pack/plugins/graph/public/apps/workspace_route.tsx @@ -120,13 +120,14 @@ export const WorkspaceRoute = ({ savedObjectsClient, spaces, coreStart, + data, }); if (!loaded) { return null; } - const { savedWorkspace, indexPatterns, sharingSavedObjectProps } = loaded; + const { savedWorkspace, sharingSavedObjectProps } = loaded; return ( @@ -145,7 +146,6 @@ export const WorkspaceRoute = ({ coreStart={coreStart} canEditDrillDownUrls={canEditDrillDownUrls} overlays={overlays} - indexPatterns={indexPatterns} savedWorkspace={savedWorkspace} indexPatternProvider={indexPatternProvider} /> diff --git a/x-pack/plugins/graph/public/components/guidance_panel/guidance_panel.tsx b/x-pack/plugins/graph/public/components/guidance_panel/guidance_panel.tsx index e6cf2eacc0d87f3..77a56eadee526f6 100644 --- a/x-pack/plugins/graph/public/components/guidance_panel/guidance_panel.tsx +++ b/x-pack/plugins/graph/public/components/guidance_panel/guidance_panel.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { ReactNode } from 'react'; +import React, { ReactNode, useEffect, useState } from 'react'; import { EuiPanel, EuiFlexGroup, @@ -38,7 +38,6 @@ export interface GuidancePanelProps { hasDatasource: boolean; hasFields: boolean; onIndexPatternSelected: (indexPattern: IndexPatternSavedObject) => void; - noIndexPatterns: boolean; } function ListItem({ @@ -73,18 +72,21 @@ function ListItem({ } function GuidancePanelComponent(props: GuidancePanelProps) { - const { - onFillWorkspace, - onOpenFieldPicker, - onIndexPatternSelected, - hasDatasource, - hasFields, - noIndexPatterns, - } = props; + const { onFillWorkspace, onOpenFieldPicker, onIndexPatternSelected, hasDatasource, hasFields } = + props; const kibana = useKibana(); const { services, overlays } = kibana; - const { savedObjects, uiSettings, application } = services; + const { savedObjects, uiSettings, application, data } = services; + const [hasDataViews, setHasDataViews] = useState(true); + + useEffect(() => { + const checkIfDataViewsExist = async () => { + setHasDataViews(await data.dataViews.hasData.hasUserDataView()); + }; + checkIfDataViewsExist(); + }, [setHasDataViews, data.dataViews]); + if (!overlays || !application) return null; const onOpenDatasourcePicker = () => { @@ -146,9 +148,9 @@ function GuidancePanelComponent(props: GuidancePanelProps) { ); - if (noIndexPatterns) { - const indexPatternUrl = application.getUrlForApp('management', { - path: '/kibana/indexPatterns', + if (!hasDataViews) { + const dataViewManagementUrl = application.getUrlForApp('management', { + path: '/kibana/dataViews', }); const sampleDataUrl = `${application.getUrlForApp('home')}#/tutorial_directory/sampleData`; content = ( @@ -174,7 +176,7 @@ function GuidancePanelComponent(props: GuidancePanelProps) { defaultMessage="No data sources found. Go to {managementIndexPatternsLink} and create a data view for your Elasticsearch indices." values={{ managementIndexPatternsLink: ( - + { const defaultProps = { isLoading: false, indexPatternProvider: { - get: jest.fn(() => Promise.resolve({ fields: [] } as unknown as DataView)), + get: jest.fn(() => + Promise.resolve({ fields: [], getName: () => 'Test Name' } as unknown as DataView) + ), }, confirmWipeWorkspace: (callback: () => void) => { callback(); diff --git a/x-pack/plugins/graph/public/components/search_bar.tsx b/x-pack/plugins/graph/public/components/search_bar.tsx index 4eccff31c644b28..30f3fad82dafc91 100644 --- a/x-pack/plugins/graph/public/components/search_bar.tsx +++ b/x-pack/plugins/graph/public/components/search_bar.tsx @@ -142,7 +142,7 @@ export function SearchBarComponent(props: SearchBarStateProps & SearchBarProps) }} > {currentIndexPattern - ? currentIndexPattern.title + ? currentIndexPattern.getName() : // This branch will be shown if the user exits the // initial picker modal i18n.translate('xpack.graph.bar.pickSourceLabel', { diff --git a/x-pack/plugins/graph/public/components/workspace_layout/workspace_layout.tsx b/x-pack/plugins/graph/public/components/workspace_layout/workspace_layout.tsx index ecbe8f676f50acf..6cfbbcf7d9105f1 100644 --- a/x-pack/plugins/graph/public/components/workspace_layout/workspace_layout.tsx +++ b/x-pack/plugins/graph/public/components/workspace_layout/workspace_layout.tsx @@ -18,13 +18,7 @@ import { workspaceInitializedSelector, } from '../../state_management'; import { FieldManager } from '../field_manager'; -import { - ControlType, - IndexPatternProvider, - IndexPatternSavedObject, - TermIntersect, - WorkspaceNode, -} from '../../types'; +import { ControlType, IndexPatternProvider, TermIntersect, WorkspaceNode } from '../../types'; import { WorkspaceTopNavMenu } from './workspace_top_nav_menu'; import { InspectPanel } from '../inspect_panel'; import { GuidancePanel } from '../guidance_panel'; @@ -59,7 +53,6 @@ type WorkspaceLayoutProps = Pick< renderCounter: number; workspace?: Workspace; loading: boolean; - indexPatterns: IndexPatternSavedObject[]; savedWorkspace: GraphWorkspaceSavedObject; indexPatternProvider: IndexPatternProvider; sharingSavedObjectProps?: SharingSavedObjectProps; @@ -78,7 +71,6 @@ export const WorkspaceLayoutComponent = ({ hasFields, overlays, workspaceInitialized, - indexPatterns, indexPatternProvider, capabilities, coreStart, @@ -222,10 +214,7 @@ export const WorkspaceLayoutComponent = ({ {getLegacyUrlConflictCallout()} {!isInitialized && (
- +
)} diff --git a/x-pack/plugins/graph/public/helpers/use_workspace_loader.test.tsx b/x-pack/plugins/graph/public/helpers/use_workspace_loader.test.tsx index bfb373712acd63f..af35a837350365f 100644 --- a/x-pack/plugins/graph/public/helpers/use_workspace_loader.test.tsx +++ b/x-pack/plugins/graph/public/helpers/use_workspace_loader.test.tsx @@ -7,10 +7,12 @@ import { useWorkspaceLoader, UseWorkspaceLoaderProps } from './use_workspace_loader'; import { coreMock } from '@kbn/core/public/mocks'; import { spacesPluginMock } from '@kbn/spaces-plugin/public/mocks'; +import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { createMockGraphStore } from '../state_management/mocks'; import { Workspace } from '../types'; import { SavedObjectsClientCommon } from '@kbn/data-views-plugin/public'; import { renderHook, act, RenderHookOptions } from '@testing-library/react-hooks'; +import type { SavedObjectsClientContract } from '@kbn/core/public'; jest.mock('react-router-dom', () => { const useLocation = () => ({ @@ -40,13 +42,14 @@ const mockSavedObjectsClient = { } as unknown as SavedObjectsClientCommon; describe('use_workspace_loader', () => { - const defaultProps = { + const defaultProps: UseWorkspaceLoaderProps = { workspaceRef: { current: {} as Workspace }, store: createMockGraphStore({}).store, - savedObjectsClient: mockSavedObjectsClient, + savedObjectsClient: mockSavedObjectsClient as unknown as SavedObjectsClientContract, coreStart: coreMock.createStart(), spaces: spacesPluginMock.createStartContract(), - } as unknown as UseWorkspaceLoaderProps; + data: dataPluginMock.createStartContract(), + }; it('should not redirect if outcome is exactMatch', async () => { await act(async () => { diff --git a/x-pack/plugins/graph/public/helpers/use_workspace_loader.ts b/x-pack/plugins/graph/public/helpers/use_workspace_loader.ts index 75b4cf9584f31f8..1e50a951e16b191 100644 --- a/x-pack/plugins/graph/public/helpers/use_workspace_loader.ts +++ b/x-pack/plugins/graph/public/helpers/use_workspace_loader.ts @@ -5,23 +5,27 @@ * 2.0. */ -import { SavedObjectsClientContract } from '@kbn/core/public'; -import type { SavedObjectsResolveResponse } from '@kbn/core/public'; import { useEffect, useState } from 'react'; import { useHistory, useLocation, useParams } from 'react-router-dom'; +import type { SavedObjectsClientContract } from '@kbn/core/public'; +import type { SavedObjectsResolveResponse } from '@kbn/core/public'; +import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import { i18n } from '@kbn/i18n'; import { CoreStart } from '@kbn/core/public'; import { SpacesApi } from '@kbn/spaces-plugin/public'; +import type { DataViewListItem } from '@kbn/data-views-plugin/common'; import { GraphStore } from '../state_management'; -import { GraphWorkspaceSavedObject, IndexPatternSavedObject, Workspace } from '../types'; +import { GraphWorkspaceSavedObject, Workspace } from '../types'; import { getEmptyWorkspace, getSavedWorkspace } from './saved_workspace_utils'; import { getEditUrl } from '../services/url'; + export interface UseWorkspaceLoaderProps { store: GraphStore; workspaceRef: React.MutableRefObject; savedObjectsClient: SavedObjectsClientContract; coreStart: CoreStart; spaces?: SpacesApi; + data: DataPublicPluginStart; } interface WorkspaceUrlParams { @@ -35,7 +39,6 @@ export interface SharingSavedObjectProps { interface WorkspaceLoadedState { savedWorkspace: GraphWorkspaceSavedObject; - indexPatterns: IndexPatternSavedObject[]; sharingSavedObjectProps?: SharingSavedObjectProps; } @@ -45,6 +48,7 @@ export const useWorkspaceLoader = ({ workspaceRef, store, savedObjectsClient, + data, }: UseWorkspaceLoaderProps) => { const [state, setState] = useState(); const { replace: historyReplace } = useHistory(); @@ -60,13 +64,13 @@ export const useWorkspaceLoader = ({ function loadWorkspace( fetchedSavedWorkspace: GraphWorkspaceSavedObject, - fetchedIndexPatterns: IndexPatternSavedObject[] + dataViews: DataViewListItem[] ) { store.dispatch({ type: 'x-pack/graph/LOAD_WORKSPACE', payload: { savedWorkspace: fetchedSavedWorkspace, - indexPatterns: fetchedIndexPatterns, + dataViews, urlQuery, }, }); @@ -76,43 +80,6 @@ export const useWorkspaceLoader = ({ store.dispatch({ type: 'x-pack/graph/RESET' }); } - async function* pageThroughIndexPatterns() { - let perPage = 1000; - let total = 0; - let savedObjects: IndexPatternSavedObject[] = []; - - async function* makeRequest(page: number): AsyncGenerator { - await savedObjectsClient - .find<{ title: string }>({ - type: 'index-pattern', - fields: ['title', 'type'], - perPage, - page, - }) - .then((response) => { - perPage = response.perPage; - total = response.total; - savedObjects = response.savedObjects; - }); - - yield savedObjects; - - if (total > page * perPage) { - yield* makeRequest(++page); - } - } - yield* makeRequest(1); - } - - async function fetchIndexPatterns() { - const result = pageThroughIndexPatterns(); - let fetchedIndexPatterns: IndexPatternSavedObject[] = []; - for await (const page of result) { - fetchedIndexPatterns = fetchedIndexPatterns.concat(page); - } - return fetchedIndexPatterns; - } - async function fetchSavedWorkspace(): Promise<{ savedObject: GraphWorkspaceSavedObject; sharingSavedObjectProps?: SharingSavedObjectProps; @@ -132,7 +99,6 @@ export const useWorkspaceLoader = ({ } async function initializeWorkspace() { - const fetchedIndexPatterns = await fetchIndexPatterns(); const { savedObject: fetchedSavedWorkspace, sharingSavedObjectProps: fetchedSharingSavedObjectProps, @@ -152,18 +118,19 @@ export const useWorkspaceLoader = ({ return null; } + const dataViews = await data.dataViews.getIdsWithTitle(); + /** * Deal with situation of request to open saved workspace. Otherwise clean up store, * when navigating to a new workspace from existing one. */ if (fetchedSavedWorkspace.id) { - loadWorkspace(fetchedSavedWorkspace, fetchedIndexPatterns); + loadWorkspace(fetchedSavedWorkspace, dataViews); } else if (workspaceRef.current) { clearStore(); } setState({ savedWorkspace: fetchedSavedWorkspace, - indexPatterns: fetchedIndexPatterns, sharingSavedObjectProps: fetchedSharingSavedObjectProps, }); } @@ -179,6 +146,7 @@ export const useWorkspaceLoader = ({ coreStart, workspaceRef, spaces, + data.dataViews, ]); return state; diff --git a/x-pack/plugins/graph/public/services/persistence/deserialize.test.ts b/x-pack/plugins/graph/public/services/persistence/deserialize.test.ts index 38efc43e502dcc4..736c79b4ef25444 100644 --- a/x-pack/plugins/graph/public/services/persistence/deserialize.test.ts +++ b/x-pack/plugins/graph/public/services/persistence/deserialize.test.ts @@ -5,11 +5,11 @@ * 2.0. */ -import { GraphWorkspaceSavedObject, IndexPatternSavedObject, Workspace } from '../../types'; +import { GraphWorkspaceSavedObject, Workspace } from '../../types'; import { migrateLegacyIndexPatternRef, savedWorkspaceToAppState, mapFields } from './deserialize'; import { createWorkspace } from '../workspace/graph_client_workspace'; import { outlinkEncoders } from '../../helpers/outlink_encoders'; -import type { DataView } from '@kbn/data-views-plugin/public'; +import type { DataView, DataViewListItem } from '@kbn/data-views-plugin/public'; describe('deserialize', () => { let savedWorkspace: GraphWorkspaceSavedObject; @@ -214,8 +214,8 @@ describe('deserialize', () => { it('should migrate legacy index pattern ref', () => { const workspacePayload = { ...savedWorkspace, legacyIndexPatternRef: 'Testpattern' }; const success = migrateLegacyIndexPatternRef(workspacePayload, [ - { id: '678', attributes: { title: 'Testpattern' } } as IndexPatternSavedObject, - { id: '123', attributes: { title: 'otherpattern' } } as IndexPatternSavedObject, + { id: '678', title: 'Testpattern' } as DataViewListItem, + { id: '123', title: 'otherpattern' } as DataViewListItem, ]); expect(success).toEqual({ success: true }); expect(workspacePayload.legacyIndexPatternRef).toBeUndefined(); @@ -225,7 +225,7 @@ describe('deserialize', () => { it('should return false if migration fails', () => { const workspacePayload = { ...savedWorkspace, legacyIndexPatternRef: 'Testpattern' }; const success = migrateLegacyIndexPatternRef(workspacePayload, [ - { id: '123', attributes: { title: 'otherpattern' } } as IndexPatternSavedObject, + { id: '123', title: 'otherpattern' } as DataViewListItem, ]); expect(success).toEqual({ success: false, missingIndexPattern: 'Testpattern' }); }); diff --git a/x-pack/plugins/graph/public/services/persistence/deserialize.ts b/x-pack/plugins/graph/public/services/persistence/deserialize.ts index 0a30907f7e1aada..181fa4a6d7e4bab 100644 --- a/x-pack/plugins/graph/public/services/persistence/deserialize.ts +++ b/x-pack/plugins/graph/public/services/persistence/deserialize.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { indexPatterns as indexPatternsUtils } from '@kbn/data-plugin/public'; +import { DataViewListItem, indexPatterns as indexPatternsUtils } from '@kbn/data-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public'; import { SerializedNode, @@ -14,7 +14,6 @@ import { WorkspaceField, GraphWorkspaceSavedObject, SerializedWorkspaceState, - IndexPatternSavedObject, AdvancedSettings, GraphData, Workspace, @@ -65,24 +64,24 @@ function deserializeUrlTemplate({ * Returns a status indicating successful migration or failure to look up the index pattern by title. * If the workspace is migrated already, a success status is returned as well. * @param savedWorkspace The workspace saved object to migrate. The migration will happen in-place and mutate the passed in object - * @param indexPatterns All index patterns existing in the current space + * @param dataViews All data views existing in the current space */ export function migrateLegacyIndexPatternRef( savedWorkspace: GraphWorkspaceSavedObject, - indexPatterns: IndexPatternSavedObject[] + dataViews: DataViewListItem[] ): { success: true } | { success: false; missingIndexPattern: string } { const legacyIndexPatternRef = savedWorkspace.legacyIndexPatternRef; if (!legacyIndexPatternRef) { return { success: true }; } - const indexPatternId = indexPatterns.find( - (pattern) => pattern.attributes.title === legacyIndexPatternRef + const dataViewId = dataViews.find( + (dataViewItem) => dataViewItem.title === legacyIndexPatternRef )?.id; - if (!indexPatternId) { + if (!dataViewId) { return { success: false, missingIndexPattern: legacyIndexPatternRef }; } const serializedWorkspaceState: SerializedWorkspaceState = JSON.parse(savedWorkspace.wsState); - serializedWorkspaceState.indexPattern = indexPatternId!; + serializedWorkspaceState.indexPattern = dataViewId!; savedWorkspace.wsState = JSON.stringify(serializedWorkspaceState); delete savedWorkspace.legacyIndexPatternRef; return { success: true }; diff --git a/x-pack/plugins/graph/public/state_management/persistence.ts b/x-pack/plugins/graph/public/state_management/persistence.ts index 2df2c98a10d77b3..aa460d247ac7c02 100644 --- a/x-pack/plugins/graph/public/state_management/persistence.ts +++ b/x-pack/plugins/graph/public/state_management/persistence.ts @@ -8,8 +8,8 @@ import actionCreatorFactory, { Action } from 'typescript-fsa'; import { i18n } from '@kbn/i18n'; import { takeLatest, call, put, select, cps } from 'redux-saga/effects'; -import type { DataView } from '@kbn/data-views-plugin/public'; -import { GraphWorkspaceSavedObject, IndexPatternSavedObject, Workspace } from '../types'; +import type { DataView, DataViewListItem } from '@kbn/data-views-plugin/public'; +import { GraphWorkspaceSavedObject, Workspace } from '../types'; import { GraphStoreDependencies, GraphState, submitSearch } from '.'; import { datasourceSelector } from './datasource'; import { setDatasource, IndexpatternDatasource } from './datasource'; @@ -28,7 +28,7 @@ import { getEditPath } from '../services/url'; import { saveSavedWorkspace } from '../helpers/saved_workspace_utils'; export interface LoadSavedWorkspacePayload { - indexPatterns: IndexPatternSavedObject[]; + dataViews: DataViewListItem[]; savedWorkspace: GraphWorkspaceSavedObject; urlQuery: string | null; } @@ -51,8 +51,8 @@ export const loadingSaga = ({ indexPatternProvider, }: GraphStoreDependencies) => { function* deserializeWorkspace(action: Action): Generator { - const { indexPatterns, savedWorkspace, urlQuery } = action.payload; - const migrationStatus = migrateLegacyIndexPatternRef(savedWorkspace, indexPatterns); + const { dataViews, savedWorkspace, urlQuery } = action.payload; + const migrationStatus = migrateLegacyIndexPatternRef(savedWorkspace, dataViews); if (!migrationStatus.success) { notifications.toasts.addDanger( i18n.translate('xpack.graph.loadWorkspace.missingDataViewErrorMessage', {