Skip to content

Commit

Permalink
Add KQL functionality in the find function of the saved objects
Browse files Browse the repository at this point in the history
  • Loading branch information
XavierM committed Jul 15, 2019
1 parent 7731e5b commit d6e65c6
Show file tree
Hide file tree
Showing 11 changed files with 383 additions and 131 deletions.
40 changes: 40 additions & 0 deletions src/core/server/saved_objects/service/lib/kuery_utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query';
import { isEmpty } from 'lodash';
import { StaticIndexPattern } from 'ui/index_patterns';

export const isFromKueryExpressionValid = (expression: string): boolean => {
try {
fromKueryExpression(expression);
} catch (err) {
throw err;
}
return true;
};

export const convertKueryToElasticSearchQuery = (
kueryExpression: string | undefined,
indexPattern: StaticIndexPattern | undefined
) => {
return kueryExpression && indexPattern && !isEmpty(kueryExpression)
? [toElasticsearchQuery(fromKueryExpression(kueryExpression), indexPattern)]
: [];
};
52 changes: 50 additions & 2 deletions src/core/server/saved_objects/service/lib/repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@
* under the License.
*/

import { omit } from 'lodash';
import { isEmpty, omit } from 'lodash';
import { CallCluster } from 'src/legacy/core_plugins/elasticsearch';
import { IndexPatternsService } from 'src/legacy/server/index_patterns';
import { FieldDescriptor } from 'src/legacy/server/index_patterns/service/index_patterns_service';
import { StaticIndexPattern } from 'ui/index_patterns';
import { getRootPropertiesObjects, IndexMapping } from '../../mappings';
import { getSearchDsl } from './search_dsl';
import { includedFields } from './included_fields';
Expand All @@ -42,6 +45,7 @@ import {
SavedObjectsUpdateOptions,
SavedObjectsUpdateResponse,
} from '../saved_objects_client';
import { isFromKueryExpressionValid } from './kuery_utils';

// BEWARE: The SavedObjectClient depends on the implementation details of the SavedObjectsRepository
// so any breaking changes to this repository are considered breaking changes to the SavedObjectsClient.
Expand All @@ -64,6 +68,7 @@ const isLeft = <L, R>(either: Either<L, R>): either is Left<L> => {

export interface SavedObjectsRepositoryOptions {
index: string;
indexPatternsService: IndexPatternsService;
mappings: IndexMapping;
callCluster: CallCluster;
schema: SavedObjectsSchema;
Expand All @@ -80,6 +85,7 @@ export interface IncrementCounterOptions extends SavedObjectsBaseOptions {
export class SavedObjectsRepository {
private _migrator: KibanaMigrator;
private _index: string;
private _indexPatternsService: IndexPatternsService;
private _mappings: IndexMapping;
private _schema: SavedObjectsSchema;
private _allowedTypes: string[];
Expand All @@ -90,6 +96,7 @@ export class SavedObjectsRepository {
constructor(options: SavedObjectsRepositoryOptions) {
const {
index,
indexPatternsService,
mappings,
callCluster,
schema,
Expand All @@ -108,6 +115,7 @@ export class SavedObjectsRepository {
// to returning them.
this._migrator = migrator;
this._index = index;
this._indexPatternsService = indexPatternsService;
this._mappings = mappings;
this._schema = schema;
if (allowedTypes.length === 0) {
Expand Down Expand Up @@ -397,6 +405,7 @@ export class SavedObjectsRepository {
fields,
namespace,
type,
kql,
}: SavedObjectsFindOptions): Promise<SavedObjectsFindResponse<T>> {
if (!type) {
throw new TypeError(`options.type must be a string or an array of strings`);
Expand All @@ -405,6 +414,8 @@ export class SavedObjectsRepository {
const types = Array.isArray(type) ? type : [type];
const allowedTypes = types.filter(t => this._allowedTypes.includes(t));
if (allowedTypes.length === 0) {
// Why not throwing an error that your types is not allowed instead of swallowing the error
// throw new TypeError(`options.type (${type.join(', ')}) is not allowed`)
return {
page,
per_page: perPage,
Expand All @@ -421,6 +432,28 @@ export class SavedObjectsRepository {
throw new TypeError('options.fields must be an array');
}

// No need to go further if you do NOT have a valid kql expression
if (kql && kql !== '' && !isFromKueryExpressionValid(kql)) {
throw new TypeError('options.kql is not a valid expression');
}

const fieldsDescriptor: FieldDescriptor[] | null =
kql && kql !== ''
? await this._getFieldsDescriptor(this.getIndicesForTypes(allowedTypes))
: null;
const indexPattern: StaticIndexPattern | undefined =
fieldsDescriptor && !isEmpty(fieldsDescriptor)
? {
fields: fieldsDescriptor.map(field => ({
aggregatable: field.aggregatable,
name: field.name,
searchable: field.searchable,
type: field.type,
})),
title: types.join(','),
}
: undefined;

const esOptions = {
index: this.getIndicesForTypes(allowedTypes),
size: perPage,
Expand All @@ -439,6 +472,8 @@ export class SavedObjectsRepository {
sortOrder,
namespace,
hasReference,
indexPattern,
kql,
}),
},
};
Expand Down Expand Up @@ -762,10 +797,23 @@ export class SavedObjectsRepository {

// The internal representation of the saved object that the serializer returns
// includes the namespace, and we use this for migrating documents. However, we don't
// want the namespcae to be returned from the repository, as the repository scopes each
// want the namespace to be returned from the repository, as the repository scopes each
// method transparently to the specified namespace.
private _rawToSavedObject(raw: RawDoc): SavedObject {
const savedObject = this._serializer.rawToSavedObject(raw);
return omit(savedObject, 'namespace');
}

// TO DO - I would like to memoize the index patter since it should not change like that
// private readonly getFieldsDescriptor = memoizeLast(this._getFieldsDescriptor);

private async _getFieldsDescriptor(indices: string[]): Promise<FieldDescriptor[]> {
try {
return await this._indexPatternsService.getFieldsForWildcard({
pattern: this.getIndicesForTypes(indices),
});
} catch (err) {
throw new Error(`Index Pattern Error - ${err.message}`);
}
}
}
Loading

0 comments on commit d6e65c6

Please sign in to comment.