Skip to content

Conversation

@Bargs
Copy link
Contributor

@Bargs Bargs commented Jun 30, 2017

Replaces #11915 (changes from that PR are in the first commit)
Fixes #12282
Part of #10789

See the issue description for an up to date reference for the new syntax. This PR has everything in it that I think is necessary for 6.0 beta1.

To enable query language switching, go to Advanced Settings and enable search:queryLanguage:switcher:enable. You can also set kuery as the default language by updating search:queryLanguage.

Some caveats:

  • Doesn’t support filters created via filters agg. This would be a nice to have, but since we only just started supporting filters from the filter agg at all, I don't see it as a blocker. Need some time to think about how we should do it. I don't really want to add support for query_string_query and raw query DSL natively in kuery. Including them as aliased sub-queries might be one option.
  • There's no way to migrate saved searches written in lucene.
  • Geo queries can get quite long very quickly. It's pretty obvious we'll need multi-line support. I'd like to explore this in another PR in the near future.

TODO:

  • Add a crapload of tests

Release Note: This PR adds a new experimental query language to Kibana. The new language is turned off by default but can be enabled in the Management > Advanced Settings via the search:queryLanguage:switcher:enable option. Changing this setting to true will add a language selector to the query bar that allows users to choose the new language for their query. Details of the new query language can be found in the issue description (#12282).

screen shot 2017-07-13 at 3 17 21 pm

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved all the filter handling logic into the query_manager module, but there's still a bit of tech debt here with this watch. I've got another branch where I'm working on removing the need for a watch, but I don't think it's necessary for this PR.

@weltenwort
Copy link
Member

After playing around with it a bit I have to say it already feels more solid that the lucene query syntax 😉 Here are a few observations though:

  • Maybe being more lenient with whitespace would be good: ( range:[1 to 10] ) is allowed but (range:[ 1 to 10 ]) or range:[1 to 10] is not
  • At first I was confused that (is(extension, jpg)) (and) (is(response, 200)) is valid until I realized it is interpreted as is(extension, jpg) and is(<ANYFIELD>, and) and is(response, 200). Maybe we should provide some kind of feedback to the user to make that clear. I was thinking of maybe automatically reformatting the query to a canonical, unambiguous form. But maybe that is too invasive?
  • It was surprising to notice that *:"win xp" and machine.os:"win xp" are interpreted differently (the former is a query_string query with all its syntax and the latter a match_phrase query). This allows to escape kuery using quotes: "_exists_:host". Is that on purpose?
  • To me the range query shorthand a:[b TO c] feels like it should be inclusive, i.e. equivalent to range(a, gte=b, lte=c).

Overall it feels quite intuitive so far. I will dig into the code next.

@Bargs
Copy link
Contributor Author

Bargs commented Jul 3, 2017

Good points @weltenwort, you've touched on some important and thorny issues.

At first I was confused that (is(extension, jpg)) (and) (is(response, 200)) is valid until I realized it is interpreted as is(extension, jpg) and is(, and) and is(response, 200). Maybe we should provide some kind of feedback to the user to make that clear. I was thinking of maybe automatically reformatting the query to a canonical, unambiguous form. But maybe that is too invasive?

This is a tricky subject for sure. I'm a bit wary of automatic reformatting. As a user I'm always a bit surprised and confused when an app does this to me. An option to manually kick off an auto-reformat would be cool, but maybe that's a feature for another PR.

I think the simplest option for now would be to consider and and or keywords of the query language. The example you provided would then throw a parse error. If the user actually wanted to query for the word "and" or "or", they would need to wrap it in double quotes. How does that sound?

The nuclear option would be to require double quotes around all search terms, as you mentioned in the last PR. I'm not quite ready to pull that trigger yet since no-one likes typing double quotes constantly.

It was surprising to notice that *:"win xp" and machine.os:"win xp" are interpreted differently (the former is a query_string query with all its syntax and the latter a match_phrase query). This allows to escape kuery using quotes: "exists:host". Is that on purpose?

Not on purpose :/ I obviously didn't think this one all the way through. I'm just abusing the query_string_query for its all_fields mode. I initially tried using the multi_match query with "fields": ["*"] but got the error The _source field is not searchable, so I switched to the query_string_query. Any idea how to execute a match query across all fields? My research is coming up nil, so I may need to reach out to the ES team on this one. Perhaps @dakrone could impart some wisdom.

To me the range query shorthand a:[b TO c] feels like it should be inclusive, i.e. equivalent to range(a, gte=b, lte=c)

Me too, this is a bug :) good catch!

@Bargs
Copy link
Contributor Author

Bargs commented Jul 3, 2017

@weltenwort I've addressed everything except for the query_sting_query issue

@weltenwort
Copy link
Member

Regarding the phrase search across all fields: I imagine we could use a query_string_query and wrap it in quotes (\-escaping user-supplied quotes) to force a phrase search. I cannot judge whether there are other caveats with that approach though.

@dakrone
Copy link
Member

dakrone commented Jul 5, 2017

I initially tried using the multi_match query with "fields": ["*"] but got the error The _source field is not searchable, so I switched to the query_string_query. Any idea how to execute a match query across all fields? My research is coming up nil, so I may need to reach out to the ES team on this one. Perhaps @dakrone could impart some wisdom.

I am familiar with this, I tried this initially when working on the all_fields mode with the same problem, the issue is that a blanket wildcard includes some fields that are not (and should not) be searchable.

Adrien opened an issue to allow fields:* queries elastic/elasticsearch#25551, which internally can rewrite to the correct combination of fields (we do this now with all_fields by having a list of "valid" fields and field-types that should be included in the field list)

@Bargs
Copy link
Contributor Author

Bargs commented Jul 5, 2017

@dakrone thanks for the info, you guys are great!

@weltenwort I'll try your suggestion as a temporary measure, I think it should work alright until we can do a proper match query across all fields.

@Bargs
Copy link
Contributor Author

Bargs commented Jul 6, 2017

Note: function syntax (e.g. is(response, 200)) is slightly broken at the moment, I'm in the middle of extracting the AST node creation code out of the grammar file and writing grammar tests. I'll get this finished up tomorrow.

Copy link
Member

@weltenwort weltenwort left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I though I would already submit this first half of the code review in the interest of not losing any unsubmitted comments. 😉 More to come later today.

return function (filter) {
if (filter.geo_polygon) {
return courier
.indexPatterns
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The style guide prescribes indentation of chained method calls for readability:
(https://github.com/elastic/kibana/blob/master/style_guides/js_style_guide.md#chaining-operations)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have to object here. I'm following the existing style of all the other files in this module. Changing just this one file makes it inconsistent and harder to read in context. We either need to revert this indentation rule (which was thoughtlessly changed) or fix the entire codebase in one go, otherwise it's just making things worse. I'll open a separate issue to discuss this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}
// These are handled by the filter bar directive when lucene is the query language
Promise.all(filters.map(queryManager.addLegacyFilter))
.then(updateState)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The style guide prescribes indentation of chained method calls for readability:
(https://github.com/elastic/kibana/blob/master/style_guides/js_style_guide.md#chaining-operations)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated this spot to maintain consistency with the rest of this particular file

30029eb


return negate ? nodeTypes.function.buildNode('not', node) : node;
}
export { filterToKueryAST } from './lib/filter_to_kuery';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The path public/kuery/lib/filter_migrations/lib/filter_to_kuery means it is a lib inside a lib inside the public lib folder. public already is a library folder itself, so the libraries inside could have sibling relations instead of nesting them further. Attempting to reduce the nesting depth seems advisable to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like shoving everything into the top level public directory makes it very difficult to understand the most important relationships between modules. For example, ask a newcomer to infer the relationships of all the vislib code scattered throughout public and they're gonna have a bad time.

Let me propose a couple options that cut down on the /lib/lib/libness while still grouping all the kuery code in one spot.

Option 1

screen shot 2017-07-10 at 11 03 50 am

Option 2

screen shot 2017-07-10 at 11 00 46 am

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 for the second option

I agree that putting everything on the top level would make code paths too hard to trace.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 0a83211

expect(result.arguments[0].value).to.be('foo');
});

it('should return undefined if the given filter is not of type "exists"', function () {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the api design point of view I would expect the function to throw an exception in that case. I have seen that other code uses the undefined to conditionally chain calls, but it might be worth expressing that differently to keep the apis clean.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returning undefined and throwing an exception for a non-exceptional scenario both seem equally hacky to me. Since these are private functions used only by filterToKueryAST, it doesn't seem terribly important, does it?

If you're strongly against returning undefined, perhaps I should add a supports function to each converter module that returns true/false depending on whether that module supports the given filter?

Copy link
Member

@weltenwort weltenwort Jul 10, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we had a type system passing something different from an "exists" filter as an argument would be a type error. So I would absolutely call this an exceptional scenario. Returning undefined just has no intrinsic meaning and can be caused by all kind of accidental mistakes in JS. I would be in favor of clearly signalling "here is your converted filter" and "I can not convert that". Adding a supports function vs throwing an exception essentially comes down to the "ask permission rather than forgiveness" pattern. I would be fine with either.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But we don't have a type system :) an exceptional scenario in one language is another language's bread and butter. Anyway, it doesn't matter much to me since this isn't a public API. I'll just switch them to exceptions since it'll be easier than adding a new function.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks 😄 just want to point out that even though there is no way in javascript to write down function signatures or even enforce them, the functions still have one.

Personally, as a developer who has to reconstruct those signatures in his head on the fly, I always find undefineds a major pain. Every piece of code that works with the results has to check for it and whenever I return to the code I have to derive the meaning of undefined again. I appreciate this freeing up of cognitive capacity. 🦄

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose it's just one of those things that comes down to personal preference. I have the exact same feelings about exceptions XD

If only javascript had proper interfaces and polymorphism it would be a non-issue because we wouldn't have to write this hacky switching logic ourselves.

const result = filterToKueryAST(filter);

expect(result).to.have.property('type', 'function');
expect(result).to.have.property('function', 'not');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we maybe also test that the argument is passed to the not function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 aa2c478

}

export function toKueryExpression(node) {
if (node.serializeStyle !== 'operator') {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't the type be shorthand here? (With foo:bar being shorthand, is(foo, bar) being function and foo is bar being operator.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

foo is bar isn't valid. I think of : as a binary operator. I think my use of the term shorthand for the range alias :[] may be confusing things here. I didn't use the term operator there because it's a weird syntax that sort of half wraps the arguments, I don't think most people would think of it as an operator. But we could call it an operator and get rid of the shorthand term entirely, if you think that would be less confusing.

Copy link
Member

@weltenwort weltenwort Jul 10, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know foo is bar is not valid in the current implementation. But one might expect it to be. In any case, I just tried to delineate the different cases by giving examples.

}

export function toKueryExpression(node) {
if (node.serializeStyle !== 'operator') {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a shorthand to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a unary operator in prefix notation.

Copy link
Member

@weltenwort weltenwort Jul 10, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could make the same argument for :. (being a binary operator in infix notation instead of a "shorthand")

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's exactly the argument I made about : above :) I wonder if you're looking at an outdated version of the PR? I think at one point I categorized : as shorthand, but I've since updated it to operator.

I'll try to sum up my thoughts here since I can't do an inline reply to your review summary below. I don't believe I've used these terms inconsistently. I think we're just operating with different definitions in mind. Here's how I categorize things:

  • implicit (just and)
  • operator (foo:bar, foo or bar, foo and bar, -foo) (basically anything the grammar has a special case for)
  • function (is(foo, bar))
  • shorthand (just range bytes:[1000 to 8000])

I wouldn't be surprised if these categorizations evolved along with the language, but they seem to work ok for now.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, just having the definitions written down does a lot for the understanding. 👍

I still don't get why : and :[ to ] are different categories, but maybe future additions will make that distinction clearer to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:[ to ] just seemed weird to me... it felt wrong to call it an operator since it's not strictly prefix or infix. However, after giving it some more thought and perusing the Operator Wikipedia page, maybe it's ok. It's not much weirder than the ternary operator. I thought calling it shorthand instead of operator would reduce confusion, but it seems that's not the case ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

expect(ast.toKueryExpression()).to.be('');

const node = nodeTypes.function.buildNode('exists', 'foo');
delete node.type;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about a type that is given but does not match any known ast node type?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 7b1c4e9


expect(parseResult).to.eql(ast);
const node = nodeTypes.function.buildNode('exists', 'foo');
delete node.type;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about a type that is given but does not match any known ast node type?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 7b1c4e9

describe('symmetry of to/fromKueryExpression', function () {

expect(parseResult).to.eql(ast);
it('converting from an expression to an AST and back again should result in the same expression', function () {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one could say "toKueryExpression and fromKueryExpression should be inverse operations"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 293279e

Copy link
Member

@weltenwort weltenwort left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So from what I saw is that we have 4 serialization styles:

  • implicit (only the implicit and)
  • operator (e.g. foo or bar, other languages call this "infix notation")
  • function (e.g. is(foo, bar), otherwise called "prefix notation")
  • shorthand (e.g. foo:bar)

As I commented inline, there seem to be some inconsistencies in assigning these labels to the styles. In addition, I think we should choose a consistent style to use by default and in toKueryExpression.

Another inconsistency that slightly bugs me is the varying use of literal and named arguments in the AST. Is there something keeping us from always using named arguments? I expect that would make maintaining backwards-compatibility easier when we expand the language if the order-dependent logic is restricted to only the topmost parsing layer.

In the individual toKueryExpression functions there seems to be a lot of duplication. And asymmetric looking code. Maybe some kind of mapping from serialization style to a formatting function could make that more readable. But that's obviously a nice-to-have that can be delayed.


describe('toKueryExpression', function () {

it('should serialize "and" nodes with an implicit syntax by default', function () {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Explicit is better than implicit

is one of my favourite lines from the zen of python. I think we should promote being explicit and unambiguous whenever possible.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 a6f80f1

expect(result.simple_query_string.query).to.be('"+response"');
});

it('should return an ES exists query when no value is provided', function () {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That means there are two ways of creating an exists clause: The explicit exists(foo) and the implicit is(foo). The latter opens up opportunities for misunderstanding and wrong expectations (e.g. to me this reads as if foo is evaluated as a boolean). Maybe we should require the value argument for the is type?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, both arguments should be required. I think this was left over from some intermediate implementation.

b73bd2b

const result = range.buildNodeParams('bytes', givenParams);
const { arguments: [ , ...params ] } = result;

params.map((param) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If params was empty no expectation would be executed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 07804f2

bool: {
filter: children.map((child) => {
if (child.type === 'literal') {
child = nodeTypes.function.buildNode('is', '*', child.value);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this is actually the right place to build that node. Shouldn't that have been created in buildNodeParams?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so, that would alter the AST built from query string provided by the user. Then when we call toKueryExpression on this node we won't know if the original query string contained an explicit is function, or just a literal.

In my experience so far, it seems best to read the query string as-is with minimal "interpretations" at parse time and leave things like interpreting the meaning of a literal argument to the individual serializers.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, or we could introduce an "implicit" style for is, which would preserve that formatting while still having an unambiguous AST. I don't have enough experience with building languages to judge the pros and cons of either solution. My intuition is that we should either create a fully "interpreted" AST as early as possible or as late as possible, but not in between.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have enough experience with building languages to judge the pros and cons of either solution.

Nor do I ¯\(ツ)

Usually when faced with such uncertainty I'll just move ahead with whatever works and assume time will reveal a better solution. Let's leave as-is for now if you're ok with it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed, just something to keep in mind

export function buildNodeParams(fieldName, params) {
params = _.pick(params, 'topLeft', 'bottomRight');
const fieldNameArg = nodeTypes.literal.buildNode(fieldName);
const args = _.pairs(params).map((argument) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The callback given to _.map() on an object actually receives the value and the key, so pairs should not be required.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 a54274a

}

function isDoubleQuoted(str) {
return (_.first(str) === '"') && (_.last(str) === '"');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works, but using str.startsWith('"') might be more idiomatic.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 69447bf

}
};
}
else if (fieldName !== '*' && _.isUndefined(value)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As commented earlier, I am not sure this is a good idea.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 b73bd2b

export function toElasticsearchQuery(node, indexPattern) {
let [ argument ] = node.arguments;
if (argument.type === 'literal') {
argument = nodeTypes.function.buildNode('is', '*', argument.value);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the other case, I feel this should have happened earlier (probably in buildNodeParams).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment above

const [ argument ] = node.arguments;
const queryString = ast.toKueryExpression(argument);

if (argument.function && (argument.function === 'and' || argument.function === 'or')) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be less coupled to and and or specifically if this looked at the serialization style to decide whether it is an infix that needs grouping or whether it is a "self-grouping" prefix style expression. That way it would also leave out the parentheses with prefix-style ands and ors.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you're right, this will need to become smarter eventually. I don't have the granularity required in serializeStyle for what you describe yet, and I'm not quite ready to dive into overhauling that. I think the best solution will become more clear to me over time, so I'd like to defer this until later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forgot to mention: I updated this condition to check for the function serializeStyle, so that in the meantime we won't run into the unnecessary wrapping you described.

03ed922


export function toElasticsearchQuery(node, indexPattern) {
const [ fieldNameArg, ...args ] = node.arguments;
const fieldName = ast.toElasticsearchQuery(fieldNameArg);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In other places the fieldName is hardcoded to be a literal. Is this case different on purpose?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No real reason, it should probably be literal everywhere
ec1ae47


return function (state) {

function add(field, values = [], operation, index) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And put your optional arguments at the end.

-- Kibana JavaScript Style Guide

Aside from that, maybe we should take this chance to improve this function's signature? Or is it meant to be a compatibility adapter?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea is that this will become the API we pass to clients who want to manipulate the query/filters. In particular, the vis team needs an API to pass to visualizations. Mostly I was being lazy here because I'm going to have to revisit and expand this API before handing it to the vis team. I figured I'd get a better idea of what the function signatures should look like once I start working on the rest of the API.

Copy link
Contributor

@lukasolson lukasolson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this is just me, but seems like we're missing the outline on the drop-down here:

image

Copy link
Contributor

@lukasolson lukasolson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When your search doesn't return results, if you have Kuery selected, the message still says the message about the query bar using Lucene syntax with examples. Not exactly sure if we want to just hide it or show how to use Kuery there.

Copy link
Contributor

@lukasolson lukasolson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I create a visualization and use the filters bucket aggregation, I get errors. If I have an empty input, I get this error:

TypeError: Cannot convert undefined or null to object
    at Function.keys (<anonymous>)
    at _abstract.js:394
    at Array.forEach (<anonymous>)
    at recurse (_abstract.js:387)
    at _abstract.js:401
    at processQueue (angular.js:14745)
    at angular.js:14761
    at Scope.$eval (angular.js:15989)
    at Scope.$digest (angular.js:15800)
    at angular.js:16028

And when I type something into the filters input, I get this:

Error: [parsing_exception] [_na] query malformed, must start with start_object, with { line=1 & col=89 }
    at respond (https://localhost:5601/pni/bundles/kibana.bundle.js?v=8467:16995:17)
    at checkRespForFailure (https://localhost:5601/pni/bundles/kibana.bundle.js?v=8467:16954:9)
    at https://localhost:5601/pni/bundles/kibana.bundle.js?v=8467:1823:9
    at processQueue (https://localhost:5601/pni/bundles/commons.bundle.js?v=8467:37815:29)
    at https://localhost:5601/pni/bundles/commons.bundle.js?v=8467:37831:28
    at Scope.$eval (https://localhost:5601/pni/bundles/commons.bundle.js?v=8467:39059:29)
    at Scope.$digest (https://localhost:5601/pni/bundles/commons.bundle.js?v=8467:38870:32)
    at Scope.$apply (https://localhost:5601/pni/bundles/commons.bundle.js?v=8467:39167:25)
    at done (https://localhost:5601/pni/bundles/commons.bundle.js?v=8467:33616:48)
    at completeRequest (https://localhost:5601/pni/bundles/commons.bundle.js?v=8467:33814:8)

@thomasneirynck thomasneirynck removed their request for review July 11, 2017 19:49
@Bargs
Copy link
Contributor Author

Bargs commented Jul 11, 2017

@weltenwort I believe I've updated or responded to all of your inline feedback. A couple of things you mentioned in your summary:

Another inconsistency that slightly bugs me is the varying use of literal and named arguments in the AST. Is there something keeping us from always using named arguments? I expect that would make maintaining backwards-compatibility easier when we expand the language if the order-dependent logic is restricted to only the topmost parsing layer.

Are you saying we shouldn't support unnamed parameters in the language, or that unnamed parameters in a query string should still turn into a namedArg node in the AST?

If the former: Less verbosity and more fluid typing are the main arguments for unnamed parameters, I think. is(foo, bar) is a lot easier to type than is(fieldName=foo, value=bar)

If the latter: It's a possibility I've considered. I suppose we could give namedArg nodes a serializeStyle like functions have. It just doesn't seem terribly necessary at the moment. I figured I'd play around with the idea in a future refactoring.

In the individual toKueryExpression functions there seems to be a lot of duplication. And asymmetric looking code. Maybe some kind of mapping from serialization style to a formatting function could make that more readable. But that's obviously a nice-to-have that can be delayed.

Agreed, it would be nice to DRY up these functions in a future refactoring after the syntax has seen some use and we're sure this is the way we want to go.

Thanks for all the feedback so far, keep it coming!

@weltenwort
Copy link
Member

Are you saying we shouldn't support unnamed parameters in the language, or that unnamed parameters in a query string should still turn into a namedArg node in the AST?

I mean the latter. That way the interpretation of the position can happen early and only once. The idea of having a single "argument" AST node type with a style property sounds good to me, probably worth trying out in a subsequent PR.

@Bargs
Copy link
Contributor Author

Bargs commented Jul 12, 2017

@lukasolson thanks for the feedback, I've made some updates:

When your search doesn't return results, if you have Kuery selected, the message still says the message about the query bar using Lucene syntax with examples.

Good catch. For now I've simply updated the no-results page to hide the bit about the lucene query syntax if kuery is selected in the query bar. Eventually I do think we'll want to fill this out with some details about kuery.

When I create a visualization and use the filters bucket aggregation, I get errors.

This was due to my changes to from_user.js. I didn't realize the filter agg was using our parse-query directive. I've updated the filter agg's serializer to correctly wrap lucene query strings in json just like we do when building a query from the query bar.

Not sure if this is just me, but seems like we're missing the outline on the drop-down here

This seems to be part of the UI framework (see screenshots from the framework docs below). If it's a mistake (cc @cjcenizal) we should probably fix it in a separate PR.

screen shot 2017-07-12 at 10 06 19 am

I believe this addresses all of your points. Please take another look!

Copy link
Contributor

@lukasolson lukasolson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty sure this is related to the match_all changes, but if you create a filters aggregation, the label in the visualization will be {"match_all":{}}

image

@Bargs
Copy link
Contributor Author

Bargs commented Jul 20, 2017

@lukasolson I fixed the issues with the range filter and the filters agg label. or and and are both case insensitive. Can you share the query you're having trouble with? Perhaps there's something else odd going on.

@Bargs
Copy link
Contributor Author

Bargs commented Jul 20, 2017

Test failure is unrelated to this PR

@lukasolson
Copy link
Contributor

Can you share the query you're having trouble with? Perhaps there's something else odd going on.

It's a simple OR with conditions on either side:

jul-20-2017 10-02-57

@Bargs
Copy link
Contributor Author

Bargs commented Jul 20, 2017

@lukasolson ah yep, I see what's wrong, looks like I was deceiving myself into thinking it was working 😅

Should be fixed now with my latest commit

Copy link
Contributor

@lukasolson lukasolson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm seeing a bit of a funky behavior. Here's the steps to reproduce:

  1. Select lucene as the query language
  2. Create a bar chart and bucket by terms
  3. Switch to kuery
  4. Click on one of the bars to filter by the term
  5. Click back, then back again

When you click back the second time, you'll be back to lucene, but the filter will be applied.

jul-20-2017 11-25-17

@Bargs
Copy link
Contributor Author

Bargs commented Jul 20, 2017

@lukasolson I've run into that before, it's because of how the silly $newFilters scope variable is used. When I tried to fix it in the past I just ended up creating an infinite digest loop. I just gave it another shot and I think I got it this time.

Copy link
Contributor

@lukasolson lukasolson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! 💯 🎆 🥇 🎈

@Bargs
Copy link
Contributor Author

Bargs commented Jul 21, 2017

The test that failed passed locally.... probably a timing issue

jenkins, test this

@Bargs Bargs merged commit d379e9a into elastic:master Jul 21, 2017
@ppisljar
Copy link
Contributor

🎉

@jimgoodwin
Copy link

@Bargs Please update the description above to have at least one high quality screen shot and a Release Note: paragraph at the end with a user readable summary.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants