Skip to content

Commit

Permalink
Merge branch 'master' into eui/16.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
thompsongl committed Nov 19, 2019
2 parents b755742 + 3baa6cc commit f0f5c5e
Show file tree
Hide file tree
Showing 27 changed files with 724 additions and 121 deletions.
41 changes: 29 additions & 12 deletions docs/discover/kuery.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,13 @@ they appear. This means documents with "quick brown fox" will match, but so will
to search for a phrase.

The query parser will no longer split on whitespace. Multiple search terms must be separated by explicit
boolean operators. Note that boolean operators are not case sensitive.
boolean operators. Lucene will combine search terms with an `or` by default, so `response:200 extension:php` would
become `response:200 or extension:php` in KQL. This will match documents where response matches 200, extension matches php, or both.
Note that boolean operators are not case sensitive.

`response:200 extension:php` in lucene would become `response:200 and extension:php`.
This will match documents where response matches 200 and extension matches php.
We can make terms required by using `and`.

We can make terms optional by using `or`.

`response:200 or extension:php` will match documents where response matches 200, extension matches php, or both.
`response:200 and extension:php` will match documents where response matches 200 and extension matches php.

By default, `and` has a higher precedence than `or`.

Expand Down Expand Up @@ -73,7 +72,7 @@ set these terms will be matched against all fields. For example, a query for `re
in the response field, but a query for just `200` will search for 200 across all fields in your index.
============

===== Nested Field Support
==== Nested Field Support

KQL supports querying on {ref}/nested.html[nested fields] through a special syntax. You can query nested fields in subtly different
ways, depending on the results you want, so crafting nested queries requires extra thought.
Expand All @@ -85,7 +84,8 @@ There are two main approaches to take:
* *Parts of the query can match different nested documents.* This is how a regular object field works.
Although generally less useful, there might be occasions where you want to query a nested field in this way.

Let's take a look at the first approach. In the following document, `items` is a nested field:
Let's take a look at the first approach. In the following document, `items` is a nested field. Each document in the nested
field contains a name, stock, and category.

[source,json]
----------------------------------
Expand Down Expand Up @@ -116,21 +116,38 @@ Let's take a look at the first approach. In the following document, `items` is a
}
----------------------------------

===== Match a single nested document

To find stores that have more than 10 bananas in stock, you would write a query like this:

`items:{ name:banana and stock > 10 }`

`items` is the "nested path". Everything inside the curly braces (the "nested group") must match a single document.
For example, `items:{ name:banana and stock:9 }` does not match because there isn't a single nested document that
matches the entire query in the nested group.
`items` is the "nested path". Everything inside the curly braces (the "nested group") must match a single nested document.

The following example returns no matches because no single nested document has bananas with a stock of 9.

`items:{ name:banana and stock:9 }`

==== Match different nested documents

What if you want to find a store with more than 10 bananas that *also* stocks vegetables? This is the second way of querying a nested field, and you can do it like this:
The subqueries in this example are in separate nested groups and can match different nested documents.

`items:{ name:banana } and items:{ stock:9 }`

`name:banana` matches the first document in the array and `stock:9` matches the third document in the array.

==== Combine approaches

You can combine these two approaches to create complex queries. What if you wanted to find a store with more than 10
bananas that *also* stocks vegetables? You could do this:

`items:{ name:banana and stock > 10 } and items:{ category:vegetable }`

The first nested group (`name:banana and stock > 10`) must still match a single document, but the `category:vegetables`
subquery can match a different nested document because it is in a separate group.

==== Nested fields inside other nested fields

KQL's syntax also supports nested fields inside of other nested fields—you simply have to specify the full path. Suppose you
have a document where `level1` and `level2` are both nested fields:

Expand Down
24 changes: 24 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 @@ -405,5 +405,29 @@ describe('Filter Utils', () => {
},
]);
});

test('Return Error if filter is using an non-existing key null key', () => {
const validationObject = validateFilterKueryNode(
fromKueryExpression('foo.attributes.description: hello AND bye'),
['foo'],
mockMappings
);
expect(validationObject).toEqual([
{
astPath: 'arguments.0',
error: null,
isSavedObjectAttr: false,
key: 'foo.attributes.description',
type: 'foo',
},
{
astPath: 'arguments.1',
error: 'The key is empty and needs to be wrapped by a saved object type like foo',
isSavedObjectAttr: false,
key: null,
type: null,
},
]);
});
});
});
12 changes: 8 additions & 4 deletions src/core/server/saved_objects/service/lib/filter_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,8 @@ export const validateFilterKueryNode = (
}, []);
};

const getType = (key: string) => (key.includes('.') ? key.split('.')[0] : null);
const getType = (key: string | undefined | null) =>
key != null && key.includes('.') ? key.split('.')[0] : null;

/**
* Is this filter key referring to a a top-level SavedObject attribute such as
Expand All @@ -137,8 +138,8 @@ const getType = (key: string) => (key.includes('.') ? key.split('.')[0] : null);
* @param key
* @param indexMapping
*/
export const isSavedObjectAttr = (key: string, indexMapping: IndexMapping) => {
const keySplit = key.split('.');
export const isSavedObjectAttr = (key: string | null | undefined, indexMapping: IndexMapping) => {
const keySplit = key != null ? key.split('.') : [];
if (keySplit.length === 1 && fieldDefined(indexMapping, keySplit[0])) {
return true;
} else if (keySplit.length === 2 && fieldDefined(indexMapping, keySplit[1])) {
Expand All @@ -149,10 +150,13 @@ export const isSavedObjectAttr = (key: string, indexMapping: IndexMapping) => {
};

export const hasFilterKeyError = (
key: string,
key: string | null | undefined,
types: string[],
indexMapping: IndexMapping
): string | null => {
if (key == null) {
return `The key is empty and needs to be wrapped by a saved object type like ${types.join()}`;
}
if (!key.includes('.')) {
return `This key '${key}' need to be wrapped by a saved object type like ${types.join()}`;
} else if (key.includes('.')) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ describe('getNotifyUserAboutOptInDefault: get a flag that describes if the user
getNotifyUserAboutOptInDefault({
allowChangingOptInStatus: true,
telemetrySavedObject: { userHasSeenNotice: false },
telemetryOptedIn: null,
telemetryOptedIn: true,
configTelemetryOptIn: true,
})
).toBe(true);
Expand All @@ -40,50 +40,37 @@ describe('getNotifyUserAboutOptInDefault: get a flag that describes if the user
configTelemetryOptIn: false,
})
).toBe(false);
});

it('should return false if user has seen notice', () => {
expect(
getNotifyUserAboutOptInDefault({
allowChangingOptInStatus: true,
telemetrySavedObject: { userHasSeenNotice: true },
telemetryOptedIn: false,
allowChangingOptInStatus: false,
telemetrySavedObject: null,
telemetryOptedIn: true,
configTelemetryOptIn: true,
})
).toBe(false);
});

it('should return false if user has seen notice', () => {
expect(
getNotifyUserAboutOptInDefault({
allowChangingOptInStatus: true,
telemetrySavedObject: { userHasSeenNotice: true },
telemetryOptedIn: true,
configTelemetryOptIn: true,
telemetryOptedIn: false,
configTelemetryOptIn: false,
})
).toBe(false);
});

it('not show notice for users already opted in and has not seen notice yet', () => {
expect(
getNotifyUserAboutOptInDefault({
allowChangingOptInStatus: true,
telemetrySavedObject: { userHasSeenNotice: false },
telemetrySavedObject: { userHasSeenNotice: true },
telemetryOptedIn: true,
configTelemetryOptIn: true,
})
).toBe(false);
});

it('should see notice if they are merely opted in by default and have not yet seen the notice', () => {
expect(
getNotifyUserAboutOptInDefault({
allowChangingOptInStatus: true,
telemetrySavedObject: { userHasSeenNotice: false },
telemetryOptedIn: null,
configTelemetryOptIn: true,
})
).toBe(true);
});

it('should return false if user is opted out', () => {
expect(
getNotifyUserAboutOptInDefault({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,5 @@ export function getNotifyUserAboutOptInDefault({
return false;
}

if (telemetryOptedIn !== null) {
return false; // they were not defaulted in
}

return configTelemetryOptIn;
return telemetryOptedIn === true && configTelemetryOptIn === true;
}
14 changes: 7 additions & 7 deletions src/plugins/data/common/es_query/filters/phrase_filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,19 @@ export type PhraseFilterMeta = FilterMeta & {
params?: {
query: string; // The unformatted value
};
field?: any;
index?: any;
};

export type PhraseFilter = Filter & {
meta: PhraseFilterMeta;
script?: {
script: {
source?: any;
lang?: string;
params: any;
};
};
field?: any;
index?: any;
};

export type PhraseFilter = Filter & {
meta: PhraseFilterMeta;
};

type PhraseFilterValue = string | number | boolean;
Expand Down Expand Up @@ -79,7 +79,7 @@ export const buildPhraseFilter = (
return {
meta: { index: indexPattern.id, field: field.name } as PhraseFilterMeta,
script: getPhraseScript(field, value),
} as PhraseFilter;
};
} else {
return {
meta: { index: indexPattern.id },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ function getExistingFilter(
}

if (esFilters.isScriptedPhraseFilter(filter)) {
return filter.meta.field === fieldName && filter.meta.script!.script.params.value === value;
return filter.meta.field === fieldName && filter.script!.script.params.value === value;
}
});
}
Expand Down
3 changes: 2 additions & 1 deletion x-pack/legacy/plugins/alerting/server/alerts_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,15 @@ interface ConstructorOptions {
createAPIKey: () => Promise<CreateAPIKeyResult>;
}

interface FindOptions {
export interface FindOptions {
options?: {
perPage?: number;
page?: number;
search?: string;
defaultSearchOperator?: 'AND' | 'OR';
searchFields?: string[];
sortField?: string;
sortOrder?: string;
hasReference?: {
type: string;
id: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ import {
getScrubber as scrubber,
getScrubberSlideContainer as scrubberContainer,
getPageControlsCenter as center,
getSettingsTrigger as trigger,
getContextMenuItems as menuItems,
// getAutoplayTextField as autoplayText,
// getAutoplayCheckbox as autoplayCheck,
// getAutoplaySubmit as autoplaySubmit,
Expand All @@ -30,6 +28,7 @@ import {
getPageControlsPrevious as previous,
getPageControlsNext as next,
} from '../../test/selectors';
import { openSettings, selectMenuItem } from '../../test/interactions';

// Mock the renderers
jest.mock('../../supported_renderers');
Expand Down Expand Up @@ -102,13 +101,9 @@ describe('<App />', () => {

test('autohide footer functions on mouseEnter + Leave', async () => {
const wrapper = getWrapper();
trigger(wrapper).simulate('click');
await tick(20);
menuItems(wrapper)
.at(1)
.simulate('click');
await tick(20);
wrapper.update();
await openSettings(wrapper);
await selectMenuItem(wrapper, 1);

expect(footer(wrapper).prop('isHidden')).toEqual(false);
expect(footer(wrapper).prop('isAutohide')).toEqual(false);
toolbarCheck(wrapper).simulate('click');
Expand All @@ -125,13 +120,9 @@ describe('<App />', () => {
expect(scrubber(wrapper).prop('isScrubberVisible')).toEqual(true);

// Open the menu and activate toolbar hiding.
trigger(wrapper).simulate('click');
await tick(20);
menuItems(wrapper)
.at(1)
.simulate('click');
await tick(20);
wrapper.update();
await openSettings(wrapper);
await selectMenuItem(wrapper, 1);

toolbarCheck(wrapper).simulate('click');
await tick(20);

Expand Down
Loading

0 comments on commit f0f5c5e

Please sign in to comment.