[Expressions] Use table column ID instead of name when set#99724
[Expressions] Use table column ID instead of name when set#99724wylieconlon merged 10 commits intoelastic:masterfrom
Conversation
|
Pinging @elastic/kibana-app-services (Team:AppServices) |
| const existingColumnIndex = columns.findIndex(({ name }) => name === args.name); | ||
| const existingColumnIndex = columns.findIndex(({ id, name }) => { | ||
| // Columns that have IDs are allowed to have duplicate names, for example esaggs | ||
| if (id) { |
There was a problem hiding this comment.
should we also check whether args.id is set as well here?
There was a problem hiding this comment.
@flash1293 @poffdeluxe I've just changed the logic here since your last review. I'm now checking id === args.name if the column has an ID, which makes sense to me as a fallback. What do you all think of this change?
poffdeluxe
left a comment
There was a problem hiding this comment.
Tested this across several workpads using mapcolumn and math and everything worked great still
| const existingColumnIndex = columns.findIndex(({ id, name }) => { | ||
| // Columns that have IDs are allowed to have duplicate names, for example esaggs | ||
| if (args.id) { | ||
| return id === args.id; |
There was a problem hiding this comment.
If the args.id is passed but the column does not have id is it ok to return false?
I didn't see any test about this. I assume it's ok, but probably it's better to make it clear somewhere (either with a test or documentation).
There was a problem hiding this comment.
@dej611 good catch, I thought I had a test for this but I will add one.
dej611
left a comment
There was a problem hiding this comment.
Left only a comment about some edge case clarification.
Apart from that, LGTM.
flash1293
left a comment
There was a problem hiding this comment.
LGTM
Paraphrasing the new logic to make sure I understood correctly:
- If id is set, id is used to find a column
- If id is not available, name is used as fallback
- It's not possible to reference by name if an id is present
|
@elasticmachine merge upstream |
streamich
left a comment
There was a problem hiding this comment.
AppServices changes LGTM.
|
Sorry to all the code reviewers, but I've changed the behavior of this PR to make it much simpler. It turns out that I was making a bad assumption about the lack of I have written a much more detailed set of examples in the PR description up at the top. I hope you will all take some time to re-review as I think this is in a much better state now. |
flash1293
left a comment
There was a problem hiding this comment.
I have two nits, but otherwise this looks good to me after having played around with it a bit.
As names aren't unique anymore, it's a bit weird to only reference a column by name now - first one wins which makes kind of sense, but maybe we can introduce a breaking change in 8.0 to require the id to simplify this logic. I think it's fine for now though.
|
|
||
| if (existingColumnIndex === -1) { | ||
| columns.push(newColumn); | ||
| columns[columnLength] = newColumn; |
There was a problem hiding this comment.
What is wrong with using .push here?
There was a problem hiding this comment.
Previously the existingColumnIndex was updated for each row, now it's updated once. So if I used .push it would add a new column for each row.
There was a problem hiding this comment.
Why is this executed once per row? It seems like only https://github.com/elastic/kibana/pull/99724/files/9f4d6474708ba8f11375fc81893cc153bb87ba7c#diff-30a63ac89404e0e4c5cebad82ffb721533d9aae11810cb2d982d1b45d6fecbbfR102-R109 is executed per row, the rest of the code is running only once per mapColumn invocation. Promise.all returns a single promise which is resolved once.
There was a problem hiding this comment.
Oh yeah, I think I just misread this part. Will update.
| ? pivotObjectArray( | ||
| input.rows, | ||
| input.columns.map((col) => col.name) | ||
| input.columns.map((col) => col.id ?? col.name) |
There was a problem hiding this comment.
Is this necessary? If we can be sure there is an id now, we can simply use input.columns.map((col) => col.id), right?
| help: i18n.translate('expressions.functions.mapColumn.args.idHelpText', { | ||
| defaultMessage: | ||
| 'An optional id of the resulting column. When `null` the name/column argument is used as id.', | ||
| 'An optional id of the resulting column. When no id is provided, the id will be looked up from the existing column and default to the name.', |
There was a problem hiding this comment.
I had trouble inferring everything that happens from this description. Maybe like this? ... will be looked up from the existing column by the provided name argument. If no column with this name exists yet, a new column with this name and an identical id will be added to the table
|
Thanks for the changes, looks great to me! |
|
@elasticmachine merge upstream |
| return name === args.name; | ||
| }); | ||
| const columnId = | ||
| existingColumnIndex === -1 ? args.id ?? args.name : columns[existingColumnIndex].id; |
There was a problem hiding this comment.
ids should be unique, if we find existing column with requested new column id we should throw
There was a problem hiding this comment.
imo the logic should be:
const columnId = args.id ?? args.name;
if(columns.find({id} => id === columnId)) throw('id already exists');
There was a problem hiding this comment.
@ppisljar Column IDs are guaranteed to be unique with this logic, I am not seeing any case where you could get a duplicate ID. Also, this logic lets you rewrite a column by ID, this is shown in some of the test cases. For example mapColumn id="existing" name="New name" expression={math "existing + 2"} is a valid expression that updates the data in place.
| ? pivotObjectArray( | ||
| input.rows, | ||
| input.columns.map((col) => col.name) | ||
| input.columns.map((col) => col.id) |
There was a problem hiding this comment.
what is meant by fallback to names ? the fact that name will be used for columnId when id was not provided (to map_column for example ?)
There was a problem hiding this comment.
Yes, if no id is provided we find the first column with the name. This is required for backwards compatibility.
|
@elasticmachine merge upstream |
💚 Build SucceededMetrics [docs]Page load bundle
Unknown metric groupsReferences to deprecated APIs
History
To update your PR or re-run it, just comment with: |
…9724) * [Expressions] Use table column ID instead of name when set * Update ID matching to match by name sometimes * Add an extra case to prevent insertion of duplicate column * Simplify logic and add test for output ID * Respond to review comments Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
…101110) * [Expressions] Use table column ID instead of name when set * Update ID matching to match by name sometimes * Add an extra case to prevent insertion of duplicate column * Simplify logic and add test for output ID * Respond to review comments Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
…sens/kibana into reporting/new-png-pdf-report-type * 'reporting/new-png-pdf-report-type' of github.com:jloleysens/kibana: (46 commits) [Security Solution] Add Ransomware canary advanced policy option (elastic#101068) [Exploratory view] Core web vitals (elastic#100320) [Security solution][Endpoint] Add unit tests for fleet event filters/trusted apps cards (elastic#101034) [Lens] Use a setter function for the dimension panel (elastic#101123) [Index Patterns] Fix return saved index pattern object (elastic#101051) [CI] For PRs, build TS refs before public api docs check (elastic#100791) [Maps] fix line and polygon label regression (elastic#101085) Migrate CCR to new ES JS client. (elastic#100131) [Canvas] Switch Canvas to use React Router (elastic#100579) [Expressions] Use table column ID instead of name when set (elastic#99724) [DOCS] Updates docs landing page (elastic#100749) [DOCS] Corrects typo in step 3 (elastic#101079) [DOCS] Updates runtime example in Discover (elastic#100926) Migrate kibana.autocomplete config to data plugin (elastic#100586) [Uptime] New width/delay definition for waterfall sidebar item tooltip (elastic#100147) [FTR] Use importExport for saved_object/basic archive (elastic#100244) [Fleet] Better input for multi text input in agent policy builder (elastic#101020) [CI] Buildkite support with Baseline pipeline (elastic#100492) [Reporting/Telemetry] Do not send telemetry if we are in screenshot mode (elastic#100388) Create API keys with metadata (elastic#100682) ...
Lens uses the
esaggsexpression function which always has a different columnidandname. Theidof the column represents the JSON key where the data is stored in each row, for example{ 'col-0-a': 0 }has an ID ofcol-0-aand a name ofbytes.This PR fixes the
mapColumnbehavior when the optionalidparameter is set. In a previous iteration of this PR, the fix was to introduce a complex set of lookups. This is no longer the approach, instead the logic is simple, and can easily be copied as a fix for another set of bugs in Canvas. The new behavior is:mapColumnis called without an ID, look up the first matching column by name, and use that ID. Example:{ "datatable": { "type": "datatable", "columns": [ { "id": "col-0-a", "name": "Sum of bytes", "meta": { "type": "number" } } ], "rows": [{ "col-0-a": 79725690 }] } }idandname, look up only by id. In this example, the name is changed by the mapColumn call, but the ID is kept:{ "datatable": { "type": "datatable", "columns": [ { "id": "col-0-a", "name": "Total bytes", "meta": { "type": "number" } } ], "rows": [{ "col-0-a": 79725690 }] } }Previous version of this PR
For code reviewers who previously reviewed this change, the behavior described above is not what was originally implemented. The main reason for this is that all datatable columns have an ID property, which I had missed due to the way I was testing. Also, @cqliu1 fixed the last remaining datatable that was missing an ID property in #98561. You can verify this by opening canvas and typing
essql "select bytes from kibana_sample_data_logs| render as="debug", which produces a table with a matching ID and name. Because we can now guarantee thatid` is set on the table, we don't need the complex fallback logic.Checklist