Skip to content

Commit 6095de6

Browse files
committed
[SOM] Preserve saved object references when saving the object (#66584)
* create field for references and add comments * add FTR test * remove comments * address comments * use real reference in dataset and assert against it.
1 parent e18895b commit 6095de6

File tree

6 files changed

+143
-5
lines changed

6 files changed

+143
-5
lines changed

src/plugins/saved_objects_management/public/lib/create_field_list.test.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ describe('createFieldList', () => {
5959
"type": "boolean",
6060
"value": true,
6161
},
62+
Object {
63+
"name": "references",
64+
"type": "array",
65+
"value": "[]",
66+
},
6267
]
6368
`);
6469
});
@@ -76,6 +81,11 @@ describe('createFieldList', () => {
7681
\\"data\\": \\"value\\"
7782
}",
7883
},
84+
Object {
85+
"name": "references",
86+
"type": "array",
87+
"value": "[]",
88+
},
7989
]
8090
`);
8191
});
@@ -93,6 +103,48 @@ describe('createFieldList', () => {
93103
1,
94104
2,
95105
3
106+
]",
107+
},
108+
Object {
109+
"name": "references",
110+
"type": "array",
111+
"value": "[]",
112+
},
113+
]
114+
`);
115+
});
116+
117+
it(`generates a field for the object's references`, () => {
118+
const obj = createObject(
119+
{
120+
someString: 'foo',
121+
},
122+
[
123+
{ id: 'ref1', type: 'type', name: 'Ref 1' },
124+
{ id: 'ref12', type: 'other-type', name: 'Ref 2' },
125+
]
126+
);
127+
expect(createFieldList(obj)).toMatchInlineSnapshot(`
128+
Array [
129+
Object {
130+
"name": "someString",
131+
"type": "text",
132+
"value": "foo",
133+
},
134+
Object {
135+
"name": "references",
136+
"type": "array",
137+
"value": "[
138+
{
139+
\\"id\\": \\"ref1\\",
140+
\\"type\\": \\"type\\",
141+
\\"name\\": \\"Ref 1\\"
142+
},
143+
{
144+
\\"id\\": \\"ref12\\",
145+
\\"type\\": \\"other-type\\",
146+
\\"name\\": \\"Ref 2\\"
147+
}
96148
]",
97149
},
98150
]
@@ -126,6 +178,11 @@ describe('createFieldList', () => {
126178
"type": "text",
127179
"value": "B",
128180
},
181+
Object {
182+
"name": "references",
183+
"type": "array",
184+
"value": "[]",
185+
},
129186
]
130187
`);
131188
});

src/plugins/saved_objects_management/public/lib/create_field_list.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,15 @@ export function createFieldList(
2929
object: SimpleSavedObject,
3030
service?: SavedObjectLoader
3131
): ObjectField[] {
32-
const fields = Object.entries(object.attributes as Record<string, any>).reduce(
32+
let fields = Object.entries(object.attributes as Record<string, any>).reduce(
3333
(objFields, [key, value]) => {
34-
return [...objFields, ...recursiveCreateFields(key, value)];
34+
return [...objFields, ...createFields(key, value)];
3535
},
3636
[] as ObjectField[]
3737
);
38+
// Special handling for references which isn't within "attributes"
39+
fields = [...fields, ...createFields('references', object.references)];
40+
3841
if (service && (service as any).Class) {
3942
addFieldsFromClass((service as any).Class, fields);
4043
}
@@ -53,7 +56,7 @@ export function createFieldList(
5356
* @param {array} parents The parent keys to the field
5457
* @returns {array}
5558
*/
56-
const recursiveCreateFields = (key: string, value: any, parents: string[] = []): ObjectField[] => {
59+
const createFields = (key: string, value: any, parents: string[] = []): ObjectField[] => {
5760
const path = [...parents, key];
5861
if (path.length > maxRecursiveIterations) {
5962
return [];
@@ -78,7 +81,7 @@ const recursiveCreateFields = (key: string, value: any, parents: string[] = []):
7881
} else if (isPlainObject(field.value)) {
7982
let fields: ObjectField[] = [];
8083
forOwn(field.value, (childValue, childKey) => {
81-
fields = [...fields, ...recursiveCreateFields(childKey as string, childValue, path)];
84+
fields = [...fields, ...createFields(childKey as string, childValue, path)];
8285
});
8386
return fields;
8487
}

src/plugins/saved_objects_management/public/management_section/object_view/components/field.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ export class Field extends PureComponent<FieldProps> {
120120
return (
121121
<div data-test-subj={`savedObjects-editField-${name}`}>
122122
<EuiCodeEditor
123+
name={`savedObjects-editField-${name}-aceEditor`}
123124
mode="json"
124125
theme="textmate"
125126
value={currentValue}

src/plugins/saved_objects_management/public/management_section/object_view/components/form.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ export class Form extends Component<FormProps, FormState> {
171171
set(source, field.name, value);
172172
});
173173

174+
// we extract the `references` field that does not belong to attributes
174175
const { references, ...attributes } = source;
175176

176177
await onSave({ attributes, references });

test/functional/apps/saved_objects_management/edit_saved_object.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ export default function({ getPageObjects, getService }: FtrProviderContext) {
2626
const esArchiver = getService('esArchiver');
2727
const testSubjects = getService('testSubjects');
2828
const PageObjects = getPageObjects(['common', 'settings']);
29+
const browser = getService('browser');
30+
const find = getService('find');
2931

3032
const setFieldValue = async (fieldName: string, value: string) => {
3133
return testSubjects.setValue(`savedObjects-editField-${fieldName}`, value);
@@ -35,6 +37,26 @@ export default function({ getPageObjects, getService }: FtrProviderContext) {
3537
return testSubjects.getAttribute(`savedObjects-editField-${fieldName}`, 'value');
3638
};
3739

40+
const setAceEditorFieldValue = async (fieldName: string, fieldValue: string) => {
41+
const editorId = `savedObjects-editField-${fieldName}-aceEditor`;
42+
await find.clickByCssSelector(`#${editorId}`);
43+
return browser.execute(
44+
(editor: string, value: string) => {
45+
return (window as any).ace.edit(editor).setValue(value);
46+
},
47+
editorId,
48+
fieldValue
49+
);
50+
};
51+
52+
const getAceEditorFieldValue = async (fieldName: string) => {
53+
const editorId = `savedObjects-editField-${fieldName}-aceEditor`;
54+
await find.clickByCssSelector(`#${editorId}`);
55+
return browser.execute((editor: string) => {
56+
return (window as any).ace.edit(editor).getValue() as string;
57+
}, editorId);
58+
};
59+
3860
const focusAndClickButton = async (buttonSubject: string) => {
3961
const button = await testSubjects.find(buttonSubject);
4062
await button.scrollIntoViewIfNecessary();
@@ -99,5 +121,52 @@ export default function({ getPageObjects, getService }: FtrProviderContext) {
99121
const objects = await PageObjects.settings.getSavedObjectsInTable();
100122
expect(objects.includes('A Dashboard')).to.be(false);
101123
});
124+
125+
it('preserves the object references when saving', async () => {
126+
const testVisualizationUrl =
127+
'/management/kibana/objects/savedVisualizations/75c3e060-1e7c-11e9-8488-65449e65d0ed';
128+
const visualizationRefs = [
129+
{
130+
name: 'kibanaSavedObjectMeta.searchSourceJSON.index',
131+
type: 'index-pattern',
132+
id: 'logstash-*',
133+
},
134+
];
135+
136+
await PageObjects.settings.navigateTo();
137+
await PageObjects.settings.clickKibanaSavedObjects();
138+
139+
const objects = await PageObjects.settings.getSavedObjectsInTable();
140+
expect(objects.includes('A Pie')).to.be(true);
141+
142+
await PageObjects.common.navigateToActualUrl('kibana', testVisualizationUrl);
143+
144+
await testSubjects.existOrFail('savedObjectEditSave');
145+
146+
let displayedReferencesValue = await getAceEditorFieldValue('references');
147+
148+
expect(JSON.parse(displayedReferencesValue)).to.eql(visualizationRefs);
149+
150+
await focusAndClickButton('savedObjectEditSave');
151+
152+
await PageObjects.settings.getSavedObjectsInTable();
153+
154+
await PageObjects.common.navigateToActualUrl('kibana', testVisualizationUrl);
155+
156+
// Parsing to avoid random keys ordering issues in raw string comparison
157+
expect(JSON.parse(await getAceEditorFieldValue('references'))).to.eql(visualizationRefs);
158+
159+
await setAceEditorFieldValue('references', JSON.stringify([], undefined, 2));
160+
161+
await focusAndClickButton('savedObjectEditSave');
162+
163+
await PageObjects.settings.getSavedObjectsInTable();
164+
165+
await PageObjects.common.navigateToActualUrl('kibana', testVisualizationUrl);
166+
167+
displayedReferencesValue = await getAceEditorFieldValue('references');
168+
169+
expect(JSON.parse(displayedReferencesValue)).to.eql([]);
170+
});
102171
});
103172
}

test/functional/fixtures/es_archiver/saved_objects_management/edit_saved_object/data.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,14 @@
3636
},
3737
"type": "visualization",
3838
"updated_at": "2019-01-22T19:32:31.206Z"
39-
}
39+
},
40+
"references" : [
41+
{
42+
"name" : "kibanaSavedObjectMeta.searchSourceJSON.index",
43+
"type" : "index-pattern",
44+
"id" : "logstash-*"
45+
}
46+
]
4047
}
4148
}
4249

0 commit comments

Comments
 (0)