diff --git a/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.filter.md b/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.filter.md
index 900f8e333f337..2c20fe2dab00f 100644
--- a/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.filter.md
+++ b/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.filter.md
@@ -7,5 +7,5 @@
Signature:
```typescript
-filter?: string;
+filter?: string | KueryNode;
```
diff --git a/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.md b/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.md
index ebd0a99531755..903462ac3039d 100644
--- a/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.md
+++ b/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.md
@@ -17,7 +17,7 @@ export interface SavedObjectsFindOptions
| --- | --- | --- |
| [defaultSearchOperator](./kibana-plugin-core-public.savedobjectsfindoptions.defaultsearchoperator.md) | 'AND' | 'OR'
| |
| [fields](./kibana-plugin-core-public.savedobjectsfindoptions.fields.md) | string[]
| An array of fields to include in the results |
-| [filter](./kibana-plugin-core-public.savedobjectsfindoptions.filter.md) | string
| |
+| [filter](./kibana-plugin-core-public.savedobjectsfindoptions.filter.md) | string | KueryNode
| |
| [hasReference](./kibana-plugin-core-public.savedobjectsfindoptions.hasreference.md) | {
type: string;
id: string;
}
| |
| [namespaces](./kibana-plugin-core-public.savedobjectsfindoptions.namespaces.md) | string[]
| |
| [page](./kibana-plugin-core-public.savedobjectsfindoptions.page.md) | number
| |
diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.filter.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.filter.md
index ae7b7a28bcd09..c98a4fe5e8796 100644
--- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.filter.md
+++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.filter.md
@@ -7,5 +7,5 @@
Signature:
```typescript
-filter?: string;
+filter?: string | KueryNode;
```
diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.md
index 15a9d99b3d062..804c83f7c1b48 100644
--- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.md
+++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.md
@@ -17,7 +17,7 @@ export interface SavedObjectsFindOptions
| --- | --- | --- |
| [defaultSearchOperator](./kibana-plugin-core-server.savedobjectsfindoptions.defaultsearchoperator.md) | 'AND' | 'OR'
| |
| [fields](./kibana-plugin-core-server.savedobjectsfindoptions.fields.md) | string[]
| An array of fields to include in the results |
-| [filter](./kibana-plugin-core-server.savedobjectsfindoptions.filter.md) | string
| |
+| [filter](./kibana-plugin-core-server.savedobjectsfindoptions.filter.md) | string | KueryNode
| |
| [hasReference](./kibana-plugin-core-server.savedobjectsfindoptions.hasreference.md) | {
type: string;
id: string;
}
| |
| [namespaces](./kibana-plugin-core-server.savedobjectsfindoptions.namespaces.md) | string[]
| |
| [page](./kibana-plugin-core-server.savedobjectsfindoptions.page.md) | number
| |
diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md
index 570732fa6e5d6..bacbd6e757114 100644
--- a/src/core/public/public.api.md
+++ b/src/core/public/public.api.md
@@ -1189,8 +1189,10 @@ export interface SavedObjectsFindOptions {
// (undocumented)
defaultSearchOperator?: 'AND' | 'OR';
fields?: string[];
+ // Warning: (ae-forgotten-export) The symbol "KueryNode" needs to be exported by the entry point index.d.ts
+ //
// (undocumented)
- filter?: string;
+ filter?: string | KueryNode;
// (undocumented)
hasReference?: {
type: string;
diff --git a/src/core/server/saved_objects/service/lib/filter_utils.test.ts b/src/core/server/saved_objects/service/lib/filter_utils.test.ts
index 4d9bcdda3c8ae..60e8aa0afdda4 100644
--- a/src/core/server/saved_objects/service/lib/filter_utils.test.ts
+++ b/src/core/server/saved_objects/service/lib/filter_utils.test.ts
@@ -83,7 +83,19 @@ const mockMappings = {
describe('Filter Utils', () => {
describe('#validateConvertFilterToKueryNode', () => {
- test('Validate a simple filter', () => {
+ test('Empty string filters are ignored', () => {
+ expect(validateConvertFilterToKueryNode(['foo'], '', mockMappings)).toBeUndefined();
+ });
+ test('Validate a simple KQL KueryNode filter', () => {
+ expect(
+ validateConvertFilterToKueryNode(
+ ['foo'],
+ esKuery.nodeTypes.function.buildNode('is', `foo.attributes.title`, 'best', true),
+ mockMappings
+ )
+ ).toEqual(esKuery.fromKueryExpression('foo.title: "best"'));
+ });
+ test('Validate a simple KQL expression filter', () => {
expect(
validateConvertFilterToKueryNode(['foo'], 'foo.attributes.title: "best"', mockMappings)
).toEqual(esKuery.fromKueryExpression('foo.title: "best"'));
diff --git a/src/core/server/saved_objects/service/lib/filter_utils.ts b/src/core/server/saved_objects/service/lib/filter_utils.ts
index 5fbe62a074b29..d19f06d74e419 100644
--- a/src/core/server/saved_objects/service/lib/filter_utils.ts
+++ b/src/core/server/saved_objects/service/lib/filter_utils.ts
@@ -28,11 +28,12 @@ const astFunctionType = ['is', 'range', 'nested'];
export const validateConvertFilterToKueryNode = (
allowedTypes: string[],
- filter: string,
+ filter: string | KueryNode,
indexMapping: IndexMapping
): KueryNode | undefined => {
- if (filter && filter.length > 0 && indexMapping) {
- const filterKueryNode = esKuery.fromKueryExpression(filter);
+ if (filter && indexMapping) {
+ const filterKueryNode =
+ typeof filter === 'string' ? esKuery.fromKueryExpression(filter) : filter;
const validationFilterKuery = validateFilterKueryNode({
astFilter: filterKueryNode,
diff --git a/src/core/server/saved_objects/service/lib/repository.test.js b/src/core/server/saved_objects/service/lib/repository.test.js
index 39433981dfd59..b1d6028465713 100644
--- a/src/core/server/saved_objects/service/lib/repository.test.js
+++ b/src/core/server/saved_objects/service/lib/repository.test.js
@@ -25,6 +25,8 @@ import { encodeHitVersion } from '../../version';
import { SavedObjectTypeRegistry } from '../../saved_objects_type_registry';
import { DocumentMigrator } from '../../migrations/core/document_migrator';
import { elasticsearchClientMock } from '../../../elasticsearch/client/mocks';
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { nodeTypes } from '../../../../../plugins/data/common/es_query';
jest.mock('./search_dsl/search_dsl', () => ({ getSearchDsl: jest.fn() }));
@@ -2529,7 +2531,7 @@ describe('SavedObjectsRepository', () => {
expect(getSearchDslNS.getSearchDsl).toHaveBeenCalledWith(mappings, registry, relevantOpts);
});
- it(`accepts KQL filter and passes kueryNode to getSearchDsl`, async () => {
+ it(`accepts KQL expression filter and passes KueryNode to getSearchDsl`, async () => {
const findOpts = {
namespace,
search: 'foo*',
@@ -2570,6 +2572,47 @@ describe('SavedObjectsRepository', () => {
`);
});
+ it(`accepts KQL KueryNode filter and passes KueryNode to getSearchDsl`, async () => {
+ const findOpts = {
+ namespace,
+ search: 'foo*',
+ searchFields: ['foo'],
+ type: ['dashboard'],
+ sortField: 'name',
+ sortOrder: 'desc',
+ defaultSearchOperator: 'AND',
+ hasReference: {
+ type: 'foo',
+ id: '1',
+ },
+ indexPattern: undefined,
+ filter: nodeTypes.function.buildNode('is', `dashboard.attributes.otherField`, '*'),
+ };
+
+ await findSuccess(findOpts, namespace);
+ const { kueryNode } = getSearchDslNS.getSearchDsl.mock.calls[0][2];
+ expect(kueryNode).toMatchInlineSnapshot(`
+ Object {
+ "arguments": Array [
+ Object {
+ "type": "literal",
+ "value": "dashboard.otherField",
+ },
+ Object {
+ "type": "wildcard",
+ "value": "@kuery-wildcard@",
+ },
+ Object {
+ "type": "literal",
+ "value": false,
+ },
+ ],
+ "function": "is",
+ "type": "function",
+ }
+ `);
+ });
+
it(`supports multiple types`, async () => {
const types = ['config', 'index-pattern'];
await findSuccess({ type: types });
diff --git a/src/core/server/saved_objects/types.ts b/src/core/server/saved_objects/types.ts
index edbdbe4d16784..000153cd542fa 100644
--- a/src/core/server/saved_objects/types.ts
+++ b/src/core/server/saved_objects/types.ts
@@ -39,6 +39,9 @@ import { SavedObjectUnsanitizedDoc } from './serialization';
import { SavedObjectsMigrationLogger } from './migrations/core/migration_logger';
import { SavedObject } from '../../types';
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { KueryNode } from '../../../plugins/data/common';
+
export {
SavedObjectAttributes,
SavedObjectAttribute,
@@ -89,7 +92,7 @@ export interface SavedObjectsFindOptions {
rootSearchFields?: string[];
hasReference?: { type: string; id: string };
defaultSearchOperator?: 'AND' | 'OR';
- filter?: string;
+ filter?: string | KueryNode;
namespaces?: string[];
/** An optional ES preference value to be used for the query **/
preference?: string;
diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md
index fb4e4494801ed..05afad5a4f7a4 100644
--- a/src/core/server/server.api.md
+++ b/src/core/server/server.api.md
@@ -2320,8 +2320,10 @@ export interface SavedObjectsFindOptions {
// (undocumented)
defaultSearchOperator?: 'AND' | 'OR';
fields?: string[];
+ // Warning: (ae-forgotten-export) The symbol "KueryNode" needs to be exported by the entry point index.d.ts
+ //
// (undocumented)
- filter?: string;
+ filter?: string | KueryNode;
// (undocumented)
hasReference?: {
type: string;
diff --git a/x-pack/plugins/ingest_manager/server/services/agents/actions.ts b/x-pack/plugins/ingest_manager/server/services/agents/actions.ts
index 8d1b320c89ae6..cd0dd92131230 100644
--- a/x-pack/plugins/ingest_manager/server/services/agents/actions.ts
+++ b/x-pack/plugins/ingest_manager/server/services/agents/actions.ts
@@ -9,6 +9,7 @@ import { Agent, AgentAction, AgentActionSOAttributes } from '../../../common/typ
import { AGENT_ACTION_SAVED_OBJECT_TYPE } from '../../../common/constants';
import { savedObjectToAgentAction } from './saved_objects';
import { appContextService } from '../app_context';
+import { nodeTypes } from '../../../../../../src/plugins/data/common';
export async function createAgentAction(
soClient: SavedObjectsClientContract,
@@ -29,9 +30,24 @@ export async function getAgentActionsForCheckin(
soClient: SavedObjectsClientContract,
agentId: string
): Promise {
+ const filter = nodeTypes.function.buildNode('and', [
+ nodeTypes.function.buildNode(
+ 'not',
+ nodeTypes.function.buildNode(
+ 'is',
+ `${AGENT_ACTION_SAVED_OBJECT_TYPE}.attributes.sent_at`,
+ '*'
+ )
+ ),
+ nodeTypes.function.buildNode(
+ 'is',
+ `${AGENT_ACTION_SAVED_OBJECT_TYPE}.attributes.agent_id`,
+ agentId
+ ),
+ ]);
const res = await soClient.find({
type: AGENT_ACTION_SAVED_OBJECT_TYPE,
- filter: `not ${AGENT_ACTION_SAVED_OBJECT_TYPE}.attributes.sent_at: * and ${AGENT_ACTION_SAVED_OBJECT_TYPE}.attributes.agent_id:${agentId}`,
+ filter,
});
return Promise.all(
@@ -78,9 +94,26 @@ export async function getAgentActionByIds(
}
export async function getNewActionsSince(soClient: SavedObjectsClientContract, timestamp: string) {
+ const filter = nodeTypes.function.buildNode('and', [
+ nodeTypes.function.buildNode(
+ 'not',
+ nodeTypes.function.buildNode(
+ 'is',
+ `${AGENT_ACTION_SAVED_OBJECT_TYPE}.attributes.sent_at`,
+ '*'
+ )
+ ),
+ nodeTypes.function.buildNode(
+ 'range',
+ `${AGENT_ACTION_SAVED_OBJECT_TYPE}.attributes.created_at`,
+ {
+ gte: timestamp,
+ }
+ ),
+ ]);
const res = await soClient.find({
type: AGENT_ACTION_SAVED_OBJECT_TYPE,
- filter: `not ${AGENT_ACTION_SAVED_OBJECT_TYPE}.attributes.sent_at: * AND ${AGENT_ACTION_SAVED_OBJECT_TYPE}.attributes.created_at >= "${timestamp}"`,
+ filter,
});
return res.saved_objects.map(savedObjectToAgentAction);