Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Graph] Fix graph saved object references #85295

Merged
merged 3 commits into from
Dec 10, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { GraphWorkspaceSavedObject, Workspace } from '../../types';
import { savedWorkspaceToAppState } from './deserialize';
import { GraphWorkspaceSavedObject, IndexPatternSavedObject, Workspace } from '../../types';
import { migrateLegacyIndexPatternRef, savedWorkspaceToAppState } from './deserialize';
import { createWorkspace } from '../../angular/graph_client_workspace';
import { outlinkEncoders } from '../../helpers/outlink_encoders';
import { IndexPattern } from '../../../../../../src/plugins/data/public';
Expand All @@ -21,7 +21,7 @@ describe('deserialize', () => {
numLinks: 2,
numVertices: 4,
wsState: JSON.stringify({
indexPattern: 'Testindexpattern',
indexPattern: '123',
selectedFields: [
{ color: 'black', name: 'field1', selected: true, iconClass: 'a' },
{ color: 'black', name: 'field2', selected: true, iconClass: 'b' },
Expand Down Expand Up @@ -208,4 +208,32 @@ describe('deserialize', () => {
expect(workspace.edges[1].source).toBe(workspace.nodes[2]);
expect(workspace.edges[1].target).toBe(workspace.nodes[4]);
});

describe('migrateLegacyIndexPatternRef', () => {
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,
]);
expect(success).toEqual({ success: true });
expect(workspacePayload.legacyIndexPatternRef).toBeUndefined();
expect(JSON.parse(workspacePayload.wsState).indexPattern).toBe('678');
});

it('should return false if migration fails', () => {
const workspacePayload = { ...savedWorkspace, legacyIndexPatternRef: 'Testpattern' };
const success = migrateLegacyIndexPatternRef(workspacePayload, [
{ id: '123', attributes: { title: 'otherpattern' } } as IndexPatternSavedObject,
]);
expect(success).toEqual({ success: false, missingIndexPattern: 'Testpattern' });
});

it('should not modify migrated workspaces', () => {
const workspacePayload = { ...savedWorkspace };
const success = migrateLegacyIndexPatternRef(workspacePayload, []);
expect(success).toEqual({ success: true });
expect(workspacePayload).toEqual(savedWorkspace);
});
});
});
38 changes: 29 additions & 9 deletions x-pack/plugins/graph/public/services/persistence/deserialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,19 +61,39 @@ function deserializeUrlTemplate({
return template;
}

// returns the id of the index pattern, lookup is done in app.js
export function lookupIndexPattern(
/**
* Migrates `savedWorkspace` to use the id instead of the title of the referenced index pattern.
* 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
*/
export function migrateLegacyIndexPatternRef(
flash1293 marked this conversation as resolved.
Show resolved Hide resolved
savedWorkspace: GraphWorkspaceSavedObject,
indexPatterns: IndexPatternSavedObject[]
) {
): { success: true } | { success: false; missingIndexPattern: string } {
const legacyIndexPatternRef = savedWorkspace.legacyIndexPatternRef;
if (!legacyIndexPatternRef) {
return { success: true };
}
const indexPatternId = indexPatterns.find(
flash1293 marked this conversation as resolved.
Show resolved Hide resolved
(pattern) => pattern.attributes.title === legacyIndexPatternRef
)?.id;
if (!indexPatternId) {
return { success: false, missingIndexPattern: legacyIndexPatternRef };
}
const serializedWorkspaceState: SerializedWorkspaceState = JSON.parse(savedWorkspace.wsState);
const indexPattern = indexPatterns.find(
(pattern) => pattern.attributes.title === serializedWorkspaceState.indexPattern
);
serializedWorkspaceState.indexPattern = indexPatternId!;
savedWorkspace.wsState = JSON.stringify(serializedWorkspaceState);
delete savedWorkspace.legacyIndexPatternRef;
return { success: true };
}

if (indexPattern) {
return indexPattern;
}
// returns the id of the index pattern, lookup is done in app.js
export function lookupIndexPatternId(savedWorkspace: GraphWorkspaceSavedObject) {
const serializedWorkspaceState: SerializedWorkspaceState = JSON.parse(savedWorkspace.wsState);

return serializedWorkspaceState.indexPattern;
}

// returns all graph fields mapped out of the index pattern
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ describe('serialize', () => {
"timeoutMillis": 5000,
"useSignificance": true,
},
"indexPattern": "Testindexpattern",
"indexPattern": "123",
"links": Array [
Object {
"label": "",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export function appStateToSavedWorkspace(
const mappedUrlTemplates = urlTemplates.map(serializeUrlTemplate);

const persistedWorkspaceState: SerializedWorkspaceState = {
indexPattern: selectedIndex.title,
indexPattern: selectedIndex.id,
selectedFields: selectedFields.map(serializeField),
blocklist,
vertices,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@ export const datasourceSaga = ({
yield put(setDatasource({ type: 'none' }));
notifications.toasts.addDanger(
i18n.translate('xpack.graph.loadWorkspace.missingIndexPatternErrorMessage', {
defaultMessage: 'Index pattern not found',
defaultMessage: 'Index pattern "{name}" not found',
values: {
name: action.payload.title,
},
})
);
}
Expand Down
4 changes: 3 additions & 1 deletion x-pack/plugins/graph/public/state_management/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ export function createMockGraphStore({
getWorkspace: jest.fn(() => workspaceMock),
getSavedWorkspace: jest.fn(() => savedWorkspace),
indexPatternProvider: {
get: jest.fn(() => Promise.resolve(({} as unknown) as IndexPattern)),
get: jest.fn(() =>
Promise.resolve(({ id: '123', title: 'test-pattern' } as unknown) as IndexPattern)
),
},
indexPatterns: [
({ id: '123', attributes: { title: 'test-pattern' } } as unknown) as IndexPatternSavedObject,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@ import { IndexpatternDatasource, datasourceSelector } from './datasource';
import { fieldsSelector } from './fields';
import { metaDataSelector, updateMetaData } from './meta_data';
import { templatesSelector } from './url_templates';
import { lookupIndexPattern, appStateToSavedWorkspace } from '../services/persistence';
import { migrateLegacyIndexPatternRef, appStateToSavedWorkspace } from '../services/persistence';
import { settingsSelector } from './advanced_settings';
import { openSaveModal } from '../services/save_modal';

const waitForPromise = () => new Promise((r) => setTimeout(r));

jest.mock('../services/persistence', () => ({
lookupIndexPattern: jest.fn(() => ({ id: '123', attributes: { title: 'test-pattern' } })),
lookupIndexPatternId: jest.fn(() => ({ id: '123', attributes: { title: 'test-pattern' } })),
migrateLegacyIndexPatternRef: jest.fn(() => ({ success: true })),
savedWorkspaceToAppState: jest.fn(() => ({
urlTemplates: [
{
Expand Down Expand Up @@ -67,7 +68,7 @@ describe('persistence sagas', () => {
});

it('should warn with a toast and abort if index pattern is not found', async () => {
(lookupIndexPattern as jest.Mock).mockReturnValueOnce(undefined);
(migrateLegacyIndexPatternRef as jest.Mock).mockReturnValueOnce({ success: false });
env.store.dispatch(loadSavedWorkspace({} as GraphWorkspaceSavedObject));
await waitForPromise();
expect(env.mockedDeps.notifications.toasts.addDanger).toHaveBeenCalled();
Expand Down
30 changes: 18 additions & 12 deletions x-pack/plugins/graph/public/state_management/persistence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ import { loadFields, selectedFieldsSelector } from './fields';
import { updateSettings, settingsSelector } from './advanced_settings';
import { loadTemplates, templatesSelector } from './url_templates';
import {
lookupIndexPattern,
migrateLegacyIndexPatternRef,
savedWorkspaceToAppState,
appStateToSavedWorkspace,
lookupIndexPatternId,
} from '../services/persistence';
import { updateMetaData, metaDataSelector } from './meta_data';
import { openSaveModal, SaveWorkspaceHandler } from '../services/save_modal';
Expand All @@ -43,23 +44,28 @@ export const loadingSaga = ({
indexPatternProvider,
}: GraphStoreDependencies) => {
function* deserializeWorkspace(action: Action<GraphWorkspaceSavedObject>) {
const selectedIndex = lookupIndexPattern(action.payload, indexPatterns);
if (!selectedIndex) {
const workspacePayload = action.payload;
const migrationStatus = migrateLegacyIndexPatternRef(workspacePayload, indexPatterns);
if (!migrationStatus.success) {
notifications.toasts.addDanger(
i18n.translate('xpack.graph.loadWorkspace.missingIndexPatternErrorMessage', {
defaultMessage: 'Index pattern not found',
defaultMessage: 'Index pattern "{name}" not found',
values: {
name: migrationStatus.missingIndexPattern,
},
})
);
return;
}

const indexPattern = yield call(indexPatternProvider.get, selectedIndex.id);
const selectedIndexPatternId = lookupIndexPatternId(workspacePayload);
const indexPattern = yield call(indexPatternProvider.get, selectedIndexPatternId);
const initialSettings = settingsSelector(yield select());

createWorkspace(selectedIndex.attributes.title, initialSettings);
createWorkspace(indexPattern.title, initialSettings);

const { urlTemplates, advancedSettings, allFields } = savedWorkspaceToAppState(
action.payload,
workspacePayload,
indexPattern,
// workspace won't be null because it's created in the same call stack
getWorkspace()!
Expand All @@ -68,16 +74,16 @@ export const loadingSaga = ({
// put everything in the store
yield put(
updateMetaData({
title: action.payload.title,
description: action.payload.description,
savedObjectId: action.payload.id,
title: workspacePayload.title,
description: workspacePayload.description,
savedObjectId: workspacePayload.id,
})
);
yield put(
setDatasource({
type: 'indexpattern',
id: selectedIndex.id,
title: selectedIndex.attributes.title,
id: indexPattern.id,
title: indexPattern.title,
})
);
yield put(loadFields(allFields));
Expand Down
4 changes: 4 additions & 0 deletions x-pack/plugins/graph/public/types/persistence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,14 @@ export interface GraphWorkspaceSavedObject {
type: string;
version?: number;
wsState: string;
// the title of the index pattern used by this workspace.
// Only set for legacy saved objects.
legacyIndexPatternRef?: string;
_source: Record<string, unknown>;
}

export interface SerializedWorkspaceState {
// the id of the index pattern saved object
indexPattern: string;
selectedFields: SerializedField[];
blocklist: SerializedNode[];
Expand Down
4 changes: 2 additions & 2 deletions x-pack/plugins/graph/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export class GraphPlugin implements Plugin {
all: ['graph-workspace'],
read: ['index-pattern'],
},
ui: ['save', 'delete'],
ui: ['save', 'delete', 'show'],
},
read: {
app: ['graph', 'kibana'],
Expand All @@ -69,7 +69,7 @@ export class GraphPlugin implements Plugin {
all: [],
read: ['index-pattern', 'graph-workspace'],
},
ui: [],
ui: ['show'],
},
},
});
Expand Down
18 changes: 18 additions & 0 deletions x-pack/plugins/graph/server/saved_objects/graph_workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,20 @@ export const graphWorkspace: SavedObjectsType = {
name: 'graph-workspace',
namespaceType: 'single',
hidden: false,
management: {
icon: 'graphApp',
defaultSearchField: 'title',
importableAndExportable: true,
getTitle(obj) {
return obj.attributes.title;
},
getInAppUrl(obj) {
return {
path: `/app/graph#/workspace/${encodeURIComponent(obj.id)}`,
uiCapabilitiesPath: 'graph.show',
};
},
},
migrations: graphMigrations,
mappings: {
properties: {
Expand Down Expand Up @@ -38,6 +52,10 @@ export const graphWorkspace: SavedObjectsType = {
wsState: {
type: 'text',
},
legacyIndexPatternRef: {
type: 'text',
index: false,
},
},
},
};
Loading