[CORE] Allow find saved object to query on _id (#126933)

* allow find saved object to query on _id

* rename

* more test and some cleaning

* review to use id instead of _id to match saved object definition

* add more test after talking to Rudolf
This commit is contained in:
Xavier Mouligneau 2022-03-08 13:59:15 -05:00 committed by GitHub
parent 2bb237fc4f
commit 7a00d24fd6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 92 additions and 10 deletions

View file

@ -36,6 +36,9 @@ const mockMappings = {
},
bar: {
properties: {
_id: {
type: 'keyword',
},
foo: {
type: 'text',
},
@ -193,6 +196,76 @@ describe('Filter Utils', () => {
).toEqual(esKuery.fromKueryExpression('alert.params.foo:bar'));
});
test('Assemble filter with just "id" and one type', () => {
expect(validateConvertFilterToKueryNode(['foo'], 'foo.id: 0123456789', mockMappings)).toEqual(
esKuery.fromKueryExpression('type: foo and _id: 0123456789')
);
});
test('Assemble filter with saved object attribute "id" and one type and more', () => {
expect(
validateConvertFilterToKueryNode(
['foo'],
'foo.id: 0123456789 and (foo.updated_at: 5678654567 or foo.attributes.bytes > 1000)',
mockMappings
)
).toEqual(
esKuery.fromKueryExpression(
'(type: foo and _id: 0123456789) and ((type: foo and updated_at: 5678654567) or foo.bytes > 1000)'
)
);
});
test('Assemble filter with saved object attribute "id" and multi type and more', () => {
expect(
validateConvertFilterToKueryNode(
['foo', 'bar'],
'foo.id: 0123456789 and bar.id: 9876543210',
mockMappings
)
).toEqual(
esKuery.fromKueryExpression(
'(type: foo and _id: 0123456789) and (type: bar and _id: 9876543210)'
)
);
});
test('Allow saved object type to defined "_id" attributes and filter on it', () => {
expect(
validateConvertFilterToKueryNode(
['foo', 'bar'],
'foo.id: 0123456789 and bar.attributes._id: 9876543210',
mockMappings
)
).toEqual(
esKuery.fromKueryExpression('(type: foo and _id: 0123456789) and (bar._id: 9876543210)')
);
});
test('Lets make sure that we are throwing an exception if we are using id outside of saved object attribute when it does not belong', () => {
expect(() => {
validateConvertFilterToKueryNode(
['foo'],
'foo.attributes.id: 0123456789 and (foo.updated_at: 5678654567 or foo.attributes.bytes > 1000)',
mockMappings
);
}).toThrowErrorMatchingInlineSnapshot(
`"This key 'foo.attributes.id' does NOT exist in foo saved object index patterns: Bad Request"`
);
});
test('Lets make sure that we are throwing an exception if we are using _id', () => {
expect(() => {
validateConvertFilterToKueryNode(
['foo'],
'foo._id: 0123456789 and (foo.updated_at: 5678654567 or foo.attributes.bytes > 1000)',
mockMappings
);
}).toThrowErrorMatchingInlineSnapshot(
`"This key 'foo._id' does NOT exist in foo saved object index patterns: Bad Request"`
);
});
test('Lets make sure that we are throwing an exception if we get an error', () => {
expect(() => {
validateConvertFilterToKueryNode(

View file

@ -22,7 +22,7 @@ export const validateConvertFilterToKueryNode = (
indexMapping: IndexMapping
): KueryNode | undefined => {
if (filter && indexMapping) {
const filterKueryNode =
let filterKueryNode =
typeof filter === 'string' ? esKuery.fromKueryExpression(filter) : cloneDeep(filter);
const validationFilterKuery = validateFilterKueryNode({
@ -54,17 +54,20 @@ export const validateConvertFilterToKueryNode = (
const existingKueryNode: KueryNode =
path.length === 0 ? filterKueryNode : get(filterKueryNode, path);
if (item.isSavedObjectAttr) {
existingKueryNode.arguments[0].value = existingKueryNode.arguments[0].value.split('.')[1];
const keySavedObjectAttr = existingKueryNode.arguments[0].value.split('.')[1];
existingKueryNode.arguments[0].value =
keySavedObjectAttr === 'id' ? '_id' : keySavedObjectAttr;
const itemType = allowedTypes.filter((t) => t === item.type);
if (itemType.length === 1) {
set(
filterKueryNode,
path,
esKuery.nodeTypes.function.buildNode('and', [
esKuery.nodeTypes.function.buildNode('is', 'type', itemType[0]),
existingKueryNode,
])
);
const kueryToAdd = esKuery.nodeTypes.function.buildNode('and', [
esKuery.nodeTypes.function.buildNode('is', 'type', itemType[0]),
existingKueryNode,
]);
if (path.length > 0) {
set(filterKueryNode, path, kueryToAdd);
} else {
filterKueryNode = kueryToAdd;
}
}
} else {
existingKueryNode.arguments[0].value = existingKueryNode.arguments[0].value.replace(
@ -171,6 +174,8 @@ export const isSavedObjectAttr = (key: string | null | undefined, indexMapping:
const keySplit = key != null ? key.split('.') : [];
if (keySplit.length === 1 && fieldDefined(indexMapping, keySplit[0])) {
return true;
} else if (keySplit.length === 2 && keySplit[1] === 'id') {
return true;
} else if (keySplit.length === 2 && fieldDefined(indexMapping, keySplit[1])) {
return true;
} else {
@ -219,6 +224,10 @@ export const fieldDefined = (indexMappings: IndexMapping, key: string): boolean
return true;
}
if (mappingKey === 'properties.id') {
return true;
}
// If the `mappingKey` does not match a valid path, before returning false,
// we want to check and see if the intended path was for a multi-field
// such as `x.attributes.field.text` where `field` is mapped to both text