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

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

Merged
merged 6 commits into from
Mar 8, 2022
Merged
Show file tree
Hide file tree
Changes from all 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
73 changes: 73 additions & 0 deletions src/core/server/saved_objects/service/lib/filter_utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ const mockMappings = {
},
bar: {
properties: {
_id: {
type: 'keyword',
},
foo: {
type: 'text',
},
Expand Down Expand Up @@ -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(
Expand Down
29 changes: 19 additions & 10 deletions src/core/server/saved_objects/service/lib/filter_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down