Where is canvas expression documentation?

Hi

Well, my situation is simple. I've run into some trouble using Canvas expressions and in the docs all I've found is this: https://www.elastic.co/guide/en/kibana/master/canvas-function-reference.html

It's just a very brief function reference that doesn't even explain basic concepts like what exactly is a context or how it's passed around (and doesn't even have all the functions documented). Some of the functions are explained in a way that presumes you already have some knowledge about it and they have no examples. Like the any function. It doesn't say anywhere how to actually pass several values to it, if I do:

any condition={getCell "Test" getCell "Test2"}

Only the value of "Test2" is evaluated. "Test" is completely ignored.

But there are people out there who have a good knowlegde about how expressions work, so I guess there is documentation somewhere, just I'm not able to find it.

So my question is: Is there any docs about it? Or will I have to study the interpreter's code?

1 Like

Hi @jpazsedano,

I don't think we have any Canvas specific documentation other than you've already found. I know the team is working hard on improving documentation since Canvas API/feature surface is quite big, you can track progress here.

In the meantime, @Joe_Fleming can you please help to answer questions above?

Best,
Oleg

Thanks! I didn't know that the feature was in such an early stage, that explains a lot. I will take a deeper look at the github to see if I can find the info I'm looking for. Maybe I can even contribute to the docs.

It's just a very brief function reference that doesn't even explain basic concepts like what exactly is a context or how it's passed around (and doesn't even have all the functions documented). Some of the functions are explained in a way that presumes you already have some knowledge about it and they have no examples.

Examples are something we're working on providing more of for sure. It's been an ask for a very long time, we're just a bit behind on the documentation part, sadly. I can try to explain it here though.

First, let's talk about context. Every function in the expression is stateless, it gets two pieces of input and produces an output. The first type of input is arguments, which you seem to understand already, and "context", which is just a fancy way to refer to the output of the previous function. For example, given this expression:

demodata
| table

The context that is provided to demodata is null, since there is no input proceeding that function. It produces a bunch of data, in a "datatable" format (basic two-dimensional data, rows and columns, like in a speadsheet) and then is piped to the table function. The datatable produced from the demodata function is now the "context" for the table function, which just renders that data as a table vis.

Next, let's talk sub-expressions, which are expressions wrapped in { and } and are generally used to provide dynamic values for function arguments based on data you already have. You can also nest sub-expressions inside of other sub-expressions, which allows you to compose functions to produce values. Sub-expressions are executed before the top-level function, from the most nested to the least, and they are passed the same context value. So, taking your example expression, and pre-pending demodata just as an example...

demodata
| any condition={getCell "Test"}

First demodata is run and returns a bunch of data. Then, that data is provided as the context to the getCell function (sub-expressions run first, remember), which is executed, and the "resolved value" becomes the value of the condition argument for the any function. Then the any function is also given the output of demodata as its context, and then run to produce some new output. If there was another function being piped to, the output of any would become the context for that function.

...I need to break up this response because apparently there's a 7000 word limit here :frowning:.

1 Like

Like the any function. It doesn't say anywhere how to actually pass several values to it, if I do:

any condition={getCell "Test" getCell "Test2"}

Only the value of "Test2" is evaluated. "Test" is completely ignored.

That's correct, because that argument for the getCell function is not a "multi arg", you can only provide the argument one time. That's something the docs are entirely missing, unfortunately; whether or not an argument is a multi arg. The way it deals with argument values when they are specified multiple times but are not multi args is that last value wins ("Test2" in your case).

Since the docs are a little incomplete, the best way to look up how a function is used is to look at the code for it, since the arguments are defined pretty clearly: https://github.com/elastic/kibana/blob/56cafd3a859c1a45c1d00b0ff9980b774df678d3/x-pack/plugins/canvas/canvas_plugin_src/functions/common/getCell.js. The unnamed arg (denoted with the _ alias) is not defined as "multi: true", so it's just a single value (the default).

For comparison, take a look at the any function, defined here: https://github.com/elastic/kibana/blob/56cafd3a859c1a45c1d00b0ff9980b774df678d3/x-pack/plugins/canvas/canvas_plugin_src/functions/common/any.js. The condition argument (also unnamed, note the _ alias) is a multi arg (multi: true), so it can be provided multiple times. And as the help text explains, it will "[r]eturn true if any of the conditions are true."

It doesn't say anywhere how to actually pass several values to it

Yes, but knowing what you know now, you can hopefully make sense of how this works; you use getCell multiple times in the any function, instead of trying to select multiple column values via a single getCell call. Let's review why:

  • Context is simply the value produced from the previous function
  • Sub-expressions are run first to produce a value
  • Every sub-expression gets the same context as the function it's used in
  • The condition argument of the any function is a multi arg
  • The any function produces a boolean value, which is true if any of the condition values is true

So let's tweak your example a little to add a comparison to the value we're reading from the cells:

| any {getCell "Test" | eq 10} {getCell "Test2" | gt 20}

Note that I left off the condition= part, since the argument has an unnamed alias (again, the _ in the docs). I'm just using eq and gt to compare values and produce some boolean value, since the value of the condition argument needs to be a boolean or null (noted by types: ['boolean', 'null'], in the argument's definition).

If you think about this in steps that the interpreter performs, sub-expressions are run first and resolve to some value, which becomes the value of the argument. So we get two boolean values, after they are resolved. Let's say it produces something like this:

| any true false

true and false here are made up resolved values for the two sub-expressions that were originally there. Then that function is executed, and since one of the values is true, any's output is also true.

I suspect what you're really trying to do here is add a new column to your data though, otherwise this will simply reduce your entire data structure into a true/false value based on just the values in the first row (the default row for getCell). To do this for every row, you'd need to map over each row and perform this check, which is exactly what mapColumn is for. So, using a little pseudo-data source, you might do something like this:

<however you're getting your data>
| mapColumn "my new column" fn={any {getCell "Test" | eq 10} {getCell "Test2" | gt 20}}

Let's assume you have data like this already:

Test Test2
2 50
12 13
42 42

mapColumn will run the fn argument's function with each row in your data by passing the rows in one by one as context to the subexpression, take whatever output is generated, and put it into a new column ("my new column" from the expression above). This would result in this updated data:

Test Test2 my new column
2 50 true
12 13 false
10 2 true

The first row is true because "Test2" is greater than 20, and the last row matches because "Test" is equal to 10. Hopefully you can see how I've composed a bunch of functions together to produce the output I wanted here.

But hold on, I said earlier that sub-expressions get the output of the previous function as context, but in mapColumn the context is each row, so how is that possible? The answer to that lies in the argument definition in the mapColumn function: https://github.com/elastic/kibana/blob/56cafd3a859c1a45c1d00b0ff9980b774df678d3/x-pack/plugins/canvas/canvas_plugin_src/functions/common/mapColumn.js

See the expression argument there, and note that is has resolve: false. Normally argument values are resolved directly, and that's when they get the context value. But in this case, we tell the interpreter not to resolve it, but instead to pass the expression itself into the function, and it's up to the function to do something with the expression. This can vary for each function, but in this case, every row is split out and passed in as context to the sub-expression, and the value it produces is simply appended as a new column on each row.

So, now you know basically everything you need to know to make sense of function and argument definitions:

  • Context
  • Sub-expressions
  • Multi args
  • Resolved values (and resolve: false)

Types are also kind of important, but from what I've seen, users have been really successful with the expression even without that knowledge, so I usually don't bother diving into that part. It's more of an implementation detail in practice, you just need to make sure your argument value's type is one of the types that it accepts, and is usually some primitive type (number, sting, boolean...).


But there are people out there who have a good knowledge about how expressions work, so I guess there is documentation somewhere , just I'm not able to find it.

So my question is: Is there any docs about it? Or will I have to study the interpreter's code?

Ironically, there used to be better documentation, and videos that explained a bunch of the concepts (like all the stuff I just added here), back when Canvas was still just a technical preview, but all that content was taken down and the only parts that remain are the function docs. You don't really need to dig into the interpreter code, you just need to understand how context and sub-expressions work and then looking at the function definitions is usually enough to put things together. And yes, we're working on bringing all this information back to the docs.

That was a lot to take in, but I hope it was helpful. If you have a more concrete example of what you're trying to do, I can try to help you write an expression for it.

3 Likes

Amazing! I wasn't expecting such a detailed answer. Well, I've already sorted out my problem with this, but just for the info:

I have a series of samples that I wanted to represent as a gaussian curve (well, bars, actually), and I wanted to paint the bars in different colors depending on the range of X they are (one color if they are between mean-var and mean+var and another if they are outside that range).

This topic was automatically closed 28 days after the last reply. New replies are no longer allowed.