Skip to content

Commit

Permalink
[Graph] Switch to SavedObjectClient.resolve (elastic#109617)
Browse files Browse the repository at this point in the history
  • Loading branch information
mbondyra authored and kibanamachine committed Sep 10, 2021
1 parent b2bb127 commit 5c46181
Show file tree
Hide file tree
Showing 11 changed files with 325 additions and 57 deletions.
27 changes: 22 additions & 5 deletions x-pack/plugins/graph/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,29 @@
"kibanaVersion": "kibana",
"server": true,
"ui": true,
"requiredPlugins": ["licensing", "data", "navigation", "savedObjects", "kibanaLegacy"],
"optionalPlugins": ["home", "features"],
"configPath": ["xpack", "graph"],
"requiredBundles": ["kibanaUtils", "kibanaReact", "home"],
"requiredPlugins": [
"licensing",
"data",
"navigation",
"savedObjects",
"kibanaLegacy"
],
"optionalPlugins": [
"home",
"features",
"spaces"
],
"configPath": [
"xpack",
"graph"
],
"requiredBundles": [
"kibanaUtils",
"kibanaReact",
"home"
],
"owner": {
"name": "Data Discovery",
"githubTeam": "kibana-data-discovery"
}
}
}
2 changes: 2 additions & 0 deletions x-pack/plugins/graph/public/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import './index.scss';
import { SavedObjectsStart } from '../../../../src/plugins/saved_objects/public';
import { GraphSavePolicy } from './types';
import { graphRouter } from './router';
import { SpacesApi } from '../../spaces/public';

/**
* These are dependencies of the Graph app besides the base dependencies
Expand Down Expand Up @@ -63,6 +64,7 @@ export interface GraphDependencies {
setHeaderActionMenu: AppMountParameters['setHeaderActionMenu'];
uiSettings: IUiSettingsClient;
history: ScopedHistory<unknown>;
spaces?: SpacesApi;
}

export type GraphServices = Omit<GraphDependencies, 'kibanaLegacy' | 'element' | 'history'>;
Expand Down
16 changes: 10 additions & 6 deletions x-pack/plugins/graph/public/apps/workspace_route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import React, { useMemo, useRef, useState } from 'react';
import { I18nProvider } from '@kbn/i18n/react';
import { Provider } from 'react-redux';
import { useHistory, useLocation } from 'react-router-dom';
import { useHistory } from 'react-router-dom';
import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public';
import { showSaveModal } from '../../../../../src/plugins/saved_objects/public';
import { Workspace } from '../types';
Expand Down Expand Up @@ -40,6 +40,7 @@ export const WorkspaceRoute = ({
getBasePath,
addBasePath,
setHeaderActionMenu,
spaces,
indexPatterns: getIndexPatternProvider,
},
}: WorkspaceRouteProps) => {
Expand All @@ -56,7 +57,6 @@ export const WorkspaceRoute = ({
*/
const [renderCounter, setRenderCounter] = useState(0);
const history = useHistory();
const urlQuery = new URLSearchParams(useLocation().search).get('query');

const indexPatternProvider = useMemo(
() => createCachedIndexPatternProvider(getIndexPatternProvider.get),
Expand Down Expand Up @@ -114,22 +114,27 @@ export const WorkspaceRoute = ({
})
);

const { savedWorkspace, indexPatterns } = useWorkspaceLoader({
const loaded = useWorkspaceLoader({
workspaceRef,
store,
savedObjectsClient,
toastNotifications,
spaces,
coreStart,
});

if (!savedWorkspace || !indexPatterns) {
if (!loaded) {
return null;
}

const { savedWorkspace, indexPatterns, sharingSavedObjectProps } = loaded;

return (
<I18nProvider>
<KibanaContextProvider services={services}>
<Provider store={store}>
<WorkspaceLayout
spaces={spaces}
sharingSavedObjectProps={sharingSavedObjectProps}
renderCounter={renderCounter}
workspace={workspaceRef.current}
loading={loading}
Expand All @@ -143,7 +148,6 @@ export const WorkspaceRoute = ({
indexPatterns={indexPatterns}
savedWorkspace={savedWorkspace}
indexPatternProvider={indexPatternProvider}
urlQuery={urlQuery}
/>
</Provider>
</KibanaContextProvider>
Expand Down
Original file line number Diff line number Diff line change
@@ -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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React from 'react';
import { shallow } from 'enzyme';
import { WorkspaceLayoutComponent } from '.';
import { coreMock } from 'src/core/public/mocks';
import { spacesPluginMock } from '../../../../spaces/public/mocks';
import { NavigationPublicPluginStart as NavigationStart } from '../../../../../../src/plugins/navigation/public';
import { GraphSavePolicy, GraphWorkspaceSavedObject, IndexPatternProvider } from '../../types';
import { OverlayStart, Capabilities } from 'kibana/public';
import { SharingSavedObjectProps } from '../../helpers/use_workspace_loader';

jest.mock('react-router-dom', () => {
const useLocation = () => ({
search: '?query={}',
});
return {
useLocation,
};
});

describe('workspace_layout', () => {
const defaultProps = {
renderCounter: 1,
loading: false,
savedWorkspace: { id: 'test' } as GraphWorkspaceSavedObject,
hasFields: true,
overlays: {} as OverlayStart,
workspaceInitialized: true,
indexPatterns: [],
indexPatternProvider: {} as IndexPatternProvider,
capabilities: {} as Capabilities,
coreStart: coreMock.createStart(),
graphSavePolicy: 'configAndDataWithConsent' as GraphSavePolicy,
navigation: {} as NavigationStart,
canEditDrillDownUrls: true,
setHeaderActionMenu: jest.fn(),
sharingSavedObjectProps: {
outcome: 'exactMatch',
aliasTargetId: '',
} as SharingSavedObjectProps,
spaces: spacesPluginMock.createStartContract(),
};
it('should display conflict notification if outcome is conflict', () => {
shallow(
<WorkspaceLayoutComponent
{...defaultProps}
sharingSavedObjectProps={{ outcome: 'conflict', aliasTargetId: 'conflictId' }}
/>
);
expect(defaultProps.spaces.ui.components.getLegacyUrlConflict).toHaveBeenCalledWith({
currentObjectId: 'test',
objectNoun: 'Graph',
otherObjectId: 'conflictId',
otherObjectPath: '#/workspace/conflictId?query={}',
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import React, { Fragment, memo, useCallback, useRef, useState } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiSpacer } from '@elastic/eui';
import { connect } from 'react-redux';
import { useLocation } from 'react-router-dom';
import { SearchBar } from '../search_bar';
import {
GraphState,
Expand All @@ -33,6 +34,8 @@ import { GraphServices } from '../../application';
import { ControlPanel } from '../control_panel';
import { GraphVisualization } from '../graph_visualization';
import { colorChoices } from '../../helpers/style_choices';
import { SharingSavedObjectProps } from '../../helpers/use_workspace_loader';
import { getEditUrl } from '../../services/url';

/**
* Each component, which depends on `worksapce`
Expand All @@ -51,22 +54,23 @@ type WorkspaceLayoutProps = Pick<
| 'coreStart'
| 'canEditDrillDownUrls'
| 'overlays'
| 'spaces'
> & {
renderCounter: number;
workspace?: Workspace;
loading: boolean;
indexPatterns: IndexPatternSavedObject[];
savedWorkspace: GraphWorkspaceSavedObject;
indexPatternProvider: IndexPatternProvider;
urlQuery: string | null;
sharingSavedObjectProps?: SharingSavedObjectProps;
};

interface WorkspaceLayoutStateProps {
workspaceInitialized: boolean;
hasFields: boolean;
}

const WorkspaceLayoutComponent = ({
export const WorkspaceLayoutComponent = ({
renderCounter,
workspace,
loading,
Expand All @@ -81,15 +85,20 @@ const WorkspaceLayoutComponent = ({
graphSavePolicy,
navigation,
canEditDrillDownUrls,
urlQuery,
setHeaderActionMenu,
sharingSavedObjectProps,
spaces,
}: WorkspaceLayoutProps & WorkspaceLayoutStateProps) => {
const [currentIndexPattern, setCurrentIndexPattern] = useState<IndexPattern>();
const [showInspect, setShowInspect] = useState(false);
const [pickerOpen, setPickerOpen] = useState(false);
const [mergeCandidates, setMergeCandidates] = useState<TermIntersect[]>([]);
const [control, setControl] = useState<ControlType>('none');
const selectedNode = useRef<WorkspaceNode | undefined>(undefined);

const search = useLocation().search;
const urlQuery = new URLSearchParams(search).get('query');

const isInitialized = Boolean(workspaceInitialized || savedWorkspace.id);

const selectSelected = useCallback((node: WorkspaceNode) => {
Expand Down Expand Up @@ -154,6 +163,27 @@ const WorkspaceLayoutComponent = ({
[]
);

const getLegacyUrlConflictCallout = useCallback(() => {
// This function returns a callout component *if* we have encountered a "legacy URL conflict" scenario
const currentObjectId = savedWorkspace.id;
if (spaces && sharingSavedObjectProps?.outcome === 'conflict' && currentObjectId) {
// We have resolved to one object, but another object has a legacy URL alias associated with this ID/page. We should display a
// callout with a warning for the user, and provide a way for them to navigate to the other object.
const otherObjectId = sharingSavedObjectProps?.aliasTargetId!; // This is always defined if outcome === 'conflict'
const otherObjectPath =
getEditUrl(coreStart.http.basePath.prepend, { id: otherObjectId }) + search;
return spaces.ui.components.getLegacyUrlConflict({
objectNoun: i18n.translate('xpack.graph.legacyUrlConflict.objectNoun', {
defaultMessage: 'Graph',
}),
currentObjectId,
otherObjectId,
otherObjectPath,
});
}
return null;
}, [savedWorkspace.id, sharingSavedObjectProps, spaces, coreStart.http, search]);

return (
<Fragment>
<WorkspaceTopNavMenu
Expand All @@ -176,7 +206,6 @@ const WorkspaceLayoutComponent = ({
lastResponse={workspace?.lastResponse}
indexPattern={currentIndexPattern}
/>

{isInitialized && <GraphTitle />}
<div className="gphGraph__bar">
<SearchBar
Expand All @@ -190,6 +219,7 @@ const WorkspaceLayoutComponent = ({
<EuiSpacer size="s" />
<FieldManagerMemoized pickerOpen={pickerOpen} setPickerOpen={setPickerOpen} />
</div>
{getLegacyUrlConflictCallout()}
{!isInitialized && (
<div>
<GuidancePanelMemoized
Expand Down
46 changes: 32 additions & 14 deletions x-pack/plugins/graph/public/helpers/saved_workspace_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,28 +82,38 @@ export function findSavedWorkspace(
});
}

export function getEmptyWorkspace() {
return {
savedObject: {
displayName: 'graph workspace',
getEsType: () => savedWorkspaceType,
...defaultsProps,
} as GraphWorkspaceSavedObject,
};
}

export async function getSavedWorkspace(
savedObjectsClient: SavedObjectsClientContract,
id?: string
id: string
) {
const savedObject = {
id,
displayName: 'graph workspace',
getEsType: () => savedWorkspaceType,
} as { [key: string]: any };

if (!id) {
assign(savedObject, defaultsProps);
return Promise.resolve(savedObject);
}
const resolveResult = await savedObjectsClient.resolve<Record<string, unknown>>(
savedWorkspaceType,
id
);

const resp = await savedObjectsClient.get<Record<string, unknown>>(savedWorkspaceType, id);
savedObject._source = cloneDeep(resp.attributes);
const resp = resolveResult.saved_object;

if (!resp._version) {
throw new SavedObjectNotFound(savedWorkspaceType, id || '');
}

const savedObject = {
id,
displayName: 'graph workspace',
getEsType: () => savedWorkspaceType,
_source: cloneDeep(resp.attributes),
} as GraphWorkspaceSavedObject;

// assign the defaults to the response
defaults(savedObject._source, defaultsProps);

Expand All @@ -120,7 +130,15 @@ export async function getSavedWorkspace(
injectReferences(savedObject, resp.references);
}

return savedObject as GraphWorkspaceSavedObject;
const sharingSavedObjectProps = {
outcome: resolveResult.outcome,
aliasTargetId: resolveResult.alias_target_id,
};

return {
savedObject,
sharingSavedObjectProps,
};
}

export function deleteSavedWorkspace(
Expand Down
Loading

0 comments on commit 5c46181

Please sign in to comment.