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.