Skip to content

Commit 677e798

Browse files
committed
[Graph] Fix graph saved object references (#85295)
1 parent f5ae464 commit 677e798

15 files changed

Lines changed: 238 additions & 35 deletions

File tree

x-pack/plugins/graph/public/services/persistence/deserialize.test.ts

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
* you may not use this file except in compliance with the Elastic License.
55
*/
66

7-
import { GraphWorkspaceSavedObject, Workspace } from '../../types';
8-
import { savedWorkspaceToAppState } from './deserialize';
7+
import { GraphWorkspaceSavedObject, IndexPatternSavedObject, Workspace } from '../../types';
8+
import { migrateLegacyIndexPatternRef, savedWorkspaceToAppState } from './deserialize';
99
import { createWorkspace } from '../../angular/graph_client_workspace';
1010
import { outlinkEncoders } from '../../helpers/outlink_encoders';
1111
import { IndexPattern } from '../../../../../../src/plugins/data/public';
@@ -21,7 +21,7 @@ describe('deserialize', () => {
2121
numLinks: 2,
2222
numVertices: 4,
2323
wsState: JSON.stringify({
24-
indexPattern: 'Testindexpattern',
24+
indexPattern: '123',
2525
selectedFields: [
2626
{ color: 'black', name: 'field1', selected: true, iconClass: 'a' },
2727
{ color: 'black', name: 'field2', selected: true, iconClass: 'b' },
@@ -208,4 +208,32 @@ describe('deserialize', () => {
208208
expect(workspace.edges[1].source).toBe(workspace.nodes[2]);
209209
expect(workspace.edges[1].target).toBe(workspace.nodes[4]);
210210
});
211+
212+
describe('migrateLegacyIndexPatternRef', () => {
213+
it('should migrate legacy index pattern ref', () => {
214+
const workspacePayload = { ...savedWorkspace, legacyIndexPatternRef: 'Testpattern' };
215+
const success = migrateLegacyIndexPatternRef(workspacePayload, [
216+
{ id: '678', attributes: { title: 'Testpattern' } } as IndexPatternSavedObject,
217+
{ id: '123', attributes: { title: 'otherpattern' } } as IndexPatternSavedObject,
218+
]);
219+
expect(success).toEqual({ success: true });
220+
expect(workspacePayload.legacyIndexPatternRef).toBeUndefined();
221+
expect(JSON.parse(workspacePayload.wsState).indexPattern).toBe('678');
222+
});
223+
224+
it('should return false if migration fails', () => {
225+
const workspacePayload = { ...savedWorkspace, legacyIndexPatternRef: 'Testpattern' };
226+
const success = migrateLegacyIndexPatternRef(workspacePayload, [
227+
{ id: '123', attributes: { title: 'otherpattern' } } as IndexPatternSavedObject,
228+
]);
229+
expect(success).toEqual({ success: false, missingIndexPattern: 'Testpattern' });
230+
});
231+
232+
it('should not modify migrated workspaces', () => {
233+
const workspacePayload = { ...savedWorkspace };
234+
const success = migrateLegacyIndexPatternRef(workspacePayload, []);
235+
expect(success).toEqual({ success: true });
236+
expect(workspacePayload).toEqual(savedWorkspace);
237+
});
238+
});
211239
});

x-pack/plugins/graph/public/services/persistence/deserialize.ts

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -61,19 +61,39 @@ function deserializeUrlTemplate({
6161
return template;
6262
}
6363

64-
// returns the id of the index pattern, lookup is done in app.js
65-
export function lookupIndexPattern(
64+
/**
65+
* Migrates `savedWorkspace` to use the id instead of the title of the referenced index pattern.
66+
* Returns a status indicating successful migration or failure to look up the index pattern by title.
67+
* If the workspace is migrated already, a success status is returned as well.
68+
* @param savedWorkspace The workspace saved object to migrate. The migration will happen in-place and mutate the passed in object
69+
* @param indexPatterns All index patterns existing in the current space
70+
*/
71+
export function migrateLegacyIndexPatternRef(
6672
savedWorkspace: GraphWorkspaceSavedObject,
6773
indexPatterns: IndexPatternSavedObject[]
68-
) {
74+
): { success: true } | { success: false; missingIndexPattern: string } {
75+
const legacyIndexPatternRef = savedWorkspace.legacyIndexPatternRef;
76+
if (!legacyIndexPatternRef) {
77+
return { success: true };
78+
}
79+
const indexPatternId = indexPatterns.find(
80+
(pattern) => pattern.attributes.title === legacyIndexPatternRef
81+
)?.id;
82+
if (!indexPatternId) {
83+
return { success: false, missingIndexPattern: legacyIndexPatternRef };
84+
}
6985
const serializedWorkspaceState: SerializedWorkspaceState = JSON.parse(savedWorkspace.wsState);
70-
const indexPattern = indexPatterns.find(
71-
(pattern) => pattern.attributes.title === serializedWorkspaceState.indexPattern
72-
);
86+
serializedWorkspaceState.indexPattern = indexPatternId!;
87+
savedWorkspace.wsState = JSON.stringify(serializedWorkspaceState);
88+
delete savedWorkspace.legacyIndexPatternRef;
89+
return { success: true };
90+
}
7391

74-
if (indexPattern) {
75-
return indexPattern;
76-
}
92+
// returns the id of the index pattern, lookup is done in app.js
93+
export function lookupIndexPatternId(savedWorkspace: GraphWorkspaceSavedObject) {
94+
const serializedWorkspaceState: SerializedWorkspaceState = JSON.parse(savedWorkspace.wsState);
95+
96+
return serializedWorkspaceState.indexPattern;
7797
}
7898

7999
// returns all graph fields mapped out of the index pattern

x-pack/plugins/graph/public/services/persistence/serialize.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ describe('serialize', () => {
184184
"timeoutMillis": 5000,
185185
"useSignificance": true,
186186
},
187-
"indexPattern": "Testindexpattern",
187+
"indexPattern": "123",
188188
"links": Array [
189189
Object {
190190
"label": "",

x-pack/plugins/graph/public/services/persistence/serialize.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ export function appStateToSavedWorkspace(
109109
const mappedUrlTemplates = urlTemplates.map(serializeUrlTemplate);
110110

111111
const persistedWorkspaceState: SerializedWorkspaceState = {
112-
indexPattern: selectedIndex.title,
112+
indexPattern: selectedIndex.id,
113113
selectedFields: selectedFields.map(serializeField),
114114
blocklist,
115115
vertices,

x-pack/plugins/graph/public/state_management/datasource.sagas.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,10 @@ export const datasourceSaga = ({
4444
yield put(setDatasource({ type: 'none' }));
4545
notifications.toasts.addDanger(
4646
i18n.translate('xpack.graph.loadWorkspace.missingIndexPatternErrorMessage', {
47-
defaultMessage: 'Index pattern not found',
47+
defaultMessage: 'Index pattern "{name}" not found',
48+
values: {
49+
name: action.payload.title,
50+
},
4851
})
4952
);
5053
}

x-pack/plugins/graph/public/state_management/mocks.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ export function createMockGraphStore({
6161
getWorkspace: jest.fn(() => workspaceMock),
6262
getSavedWorkspace: jest.fn(() => savedWorkspace),
6363
indexPatternProvider: {
64-
get: jest.fn(() => Promise.resolve(({} as unknown) as IndexPattern)),
64+
get: jest.fn(() =>
65+
Promise.resolve(({ id: '123', title: 'test-pattern' } as unknown) as IndexPattern)
66+
),
6567
},
6668
indexPatterns: [
6769
({ id: '123', attributes: { title: 'test-pattern' } } as unknown) as IndexPatternSavedObject,

x-pack/plugins/graph/public/state_management/persistence.test.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@ import { IndexpatternDatasource, datasourceSelector } from './datasource';
1111
import { fieldsSelector } from './fields';
1212
import { metaDataSelector, updateMetaData } from './meta_data';
1313
import { templatesSelector } from './url_templates';
14-
import { lookupIndexPattern, appStateToSavedWorkspace } from '../services/persistence';
14+
import { migrateLegacyIndexPatternRef, appStateToSavedWorkspace } from '../services/persistence';
1515
import { settingsSelector } from './advanced_settings';
1616
import { openSaveModal } from '../services/save_modal';
1717

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

2020
jest.mock('../services/persistence', () => ({
21-
lookupIndexPattern: jest.fn(() => ({ id: '123', attributes: { title: 'test-pattern' } })),
21+
lookupIndexPatternId: jest.fn(() => ({ id: '123', attributes: { title: 'test-pattern' } })),
22+
migrateLegacyIndexPatternRef: jest.fn(() => ({ success: true })),
2223
savedWorkspaceToAppState: jest.fn(() => ({
2324
urlTemplates: [
2425
{
@@ -67,7 +68,7 @@ describe('persistence sagas', () => {
6768
});
6869

6970
it('should warn with a toast and abort if index pattern is not found', async () => {
70-
(lookupIndexPattern as jest.Mock).mockReturnValueOnce(undefined);
71+
(migrateLegacyIndexPatternRef as jest.Mock).mockReturnValueOnce({ success: false });
7172
env.store.dispatch(loadSavedWorkspace({} as GraphWorkspaceSavedObject));
7273
await waitForPromise();
7374
expect(env.mockedDeps.notifications.toasts.addDanger).toHaveBeenCalled();

x-pack/plugins/graph/public/state_management/persistence.ts

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@ import { loadFields, selectedFieldsSelector } from './fields';
1515
import { updateSettings, settingsSelector } from './advanced_settings';
1616
import { loadTemplates, templatesSelector } from './url_templates';
1717
import {
18-
lookupIndexPattern,
18+
migrateLegacyIndexPatternRef,
1919
savedWorkspaceToAppState,
2020
appStateToSavedWorkspace,
21+
lookupIndexPatternId,
2122
} from '../services/persistence';
2223
import { updateMetaData, metaDataSelector } from './meta_data';
2324
import { openSaveModal, SaveWorkspaceHandler } from '../services/save_modal';
@@ -43,23 +44,28 @@ export const loadingSaga = ({
4344
indexPatternProvider,
4445
}: GraphStoreDependencies) => {
4546
function* deserializeWorkspace(action: Action<GraphWorkspaceSavedObject>) {
46-
const selectedIndex = lookupIndexPattern(action.payload, indexPatterns);
47-
if (!selectedIndex) {
47+
const workspacePayload = action.payload;
48+
const migrationStatus = migrateLegacyIndexPatternRef(workspacePayload, indexPatterns);
49+
if (!migrationStatus.success) {
4850
notifications.toasts.addDanger(
4951
i18n.translate('xpack.graph.loadWorkspace.missingIndexPatternErrorMessage', {
50-
defaultMessage: 'Index pattern not found',
52+
defaultMessage: 'Index pattern "{name}" not found',
53+
values: {
54+
name: migrationStatus.missingIndexPattern,
55+
},
5156
})
5257
);
5358
return;
5459
}
5560

56-
const indexPattern = yield call(indexPatternProvider.get, selectedIndex.id);
61+
const selectedIndexPatternId = lookupIndexPatternId(workspacePayload);
62+
const indexPattern = yield call(indexPatternProvider.get, selectedIndexPatternId);
5763
const initialSettings = settingsSelector(yield select());
5864

59-
createWorkspace(selectedIndex.attributes.title, initialSettings);
65+
createWorkspace(indexPattern.title, initialSettings);
6066

6167
const { urlTemplates, advancedSettings, allFields } = savedWorkspaceToAppState(
62-
action.payload,
68+
workspacePayload,
6369
indexPattern,
6470
// workspace won't be null because it's created in the same call stack
6571
getWorkspace()!
@@ -68,16 +74,16 @@ export const loadingSaga = ({
6874
// put everything in the store
6975
yield put(
7076
updateMetaData({
71-
title: action.payload.title,
72-
description: action.payload.description,
73-
savedObjectId: action.payload.id,
77+
title: workspacePayload.title,
78+
description: workspacePayload.description,
79+
savedObjectId: workspacePayload.id,
7480
})
7581
);
7682
yield put(
7783
setDatasource({
7884
type: 'indexpattern',
79-
id: selectedIndex.id,
80-
title: selectedIndex.attributes.title,
85+
id: indexPattern.id,
86+
title: indexPattern.title,
8187
})
8288
);
8389
yield put(loadFields(allFields));

x-pack/plugins/graph/public/types/persistence.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,14 @@ export interface GraphWorkspaceSavedObject {
2727
type: string;
2828
version?: number;
2929
wsState: string;
30+
// the title of the index pattern used by this workspace.
31+
// Only set for legacy saved objects.
32+
legacyIndexPatternRef?: string;
3033
_source: Record<string, unknown>;
3134
}
3235

3336
export interface SerializedWorkspaceState {
37+
// the id of the index pattern saved object
3438
indexPattern: string;
3539
selectedFields: SerializedField[];
3640
blocklist: SerializedNode[];

x-pack/plugins/graph/server/plugin.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export class GraphPlugin implements Plugin {
6060
all: ['graph-workspace'],
6161
read: ['index-pattern'],
6262
},
63-
ui: ['save', 'delete'],
63+
ui: ['save', 'delete', 'show'],
6464
},
6565
read: {
6666
app: ['graph', 'kibana'],
@@ -69,7 +69,7 @@ export class GraphPlugin implements Plugin {
6969
all: [],
7070
read: ['index-pattern', 'graph-workspace'],
7171
},
72-
ui: [],
72+
ui: ['show'],
7373
},
7474
},
7575
});

0 commit comments

Comments
 (0)