-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Lens] Implement types for reference-based operations #83603
Conversation
Pinging @elastic/kibana-app (Team:KibanaApp) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Took a look and this looks pretty good to me. Most of my comments can be tackled later I think (the first one should probably be resolved on this PR)
I will start building the time scale logic on top of this to minimize conflicts.
column && | ||
!column.isBucketed && | ||
!isReferenced( | ||
// Fake layer, but works with real columns |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about just passing in the real layer? This seems brittle because the synthetic layer is not consistent (e.g. if isReferenced
will start using columnOrder
or indexPatternId
later, it will break)(
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
let tempLayer = { ...layer }; | ||
|
||
if (previousDefinition.input === 'fullReference') { | ||
// @ts-expect-error references are not statically analyzed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those will go away once we have a first reference-based operation, right? If yes I think we can live with it for now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, once we have the first reference-based operation we will be required to delete the comment. It will fail the type checks if it's not an error.
let tempLayer = { ...layer }; | ||
const referenceIds = operationDefinition.requiredReferences.map((validation) => { | ||
const validOperations = Object.values(operationDefinitionMap).filter(({ type }) => | ||
isOperationAllowedAsReference({ validation, operationType: type }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess we should pass in the field we get via parameter as well here? Otherwise isOperationAllowedAsReference
will never be called with a field
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll clarify this in a comment in code, but basically I'm not expecting the field
to be passed in while constructing a reference-based operation. However I would expect that the operationSupportMatrix
would be used here to look for cases where there's only one possible field or operation.
const metadata = operationDefinition.getPossibleOperation(); | ||
hasValidMetadata = Boolean(metadata) && validation.validateMetadata(metadata!); | ||
} else { | ||
// TODO: How can we validate the metadata without a specific field? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a very good question and we have to answer it in some way for the UI so we can provide a list of sub-functions. One option would be to go through all fields of the current index pattern and return true if one of them matches.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exactly, this can't be done purely in the code here.
columnEntries.forEach(([colId, col]) => { | ||
const def = operationDefinitionMap[col.operationType]; | ||
if (def.input === 'fullReference') { | ||
expressions.push(...def.toExpression(layer, colId, indexPattern)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we be sure the columns are in order so all calculations col
depends on will be earlier in the list than col
itself? Otherwise it can happen an expression function tries to act on a column with doesn't exist yet.
We either have to make sure column order is always right or we add a check here and walk the graph of references in direction of the pointers. It's not relevant for single level calculations, but I guess we will run into this in the future.
It could probably be part of the getColumnOrder
as well
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed by updating the getColumnOrder function and calling it in a few more places.
💚 Build SucceededMetrics [docs]Module Count
Async chunks
History
To update your PR or re-run it, just comment with: |
@@ -134,7 +134,7 @@ export const validateDatasourceAndVisualization = ( | |||
? currentVisualization?.getErrorMessages(currentVisualizationState, frameAPI) | |||
: undefined; | |||
|
|||
if (datasourceValidationErrors || visualizationValidationErrors) { | |||
if (datasourceValidationErrors?.length || visualizationValidationErrors?.length) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if it makes sense to get rid of the undefined
here completely, and just work with array. I cannot foresee particular differences between the no current visualization/datasource available
case and the no error
one in this context.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I considered making this change in this PR, but decided not to because it actually affects the signature of all of the visualizations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this PR is fine for now, didn't test again. I think we need to re-revisit the column ordering later but we don't have to solve that problem now.
); | ||
// If a reference has another reference as input, put it last in sort order |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure this catches all possible cases for longer chains of references (e.g. what if b doesn't reference a directly, but it references c which in turn references a?), but as we won't have them in the first version, I think it's fine for now if we leave a comment to revisit once we get more complex here.
One way to solve it for all possible cases is to build an inverted index of reference targets (Record<string, string[]>
- for each column, there's a list of other columns pointing to it), then pushing all columns without references to the expression, removing them from the inverted index, pushing all columns without references and so on until all of them are gone.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Edit: I originally wrote something incorrect.
I think this is catching all possible cases, including the one you mentioned. If the order is b -> c -> a
like you said, then the sorting logic will detect that b references c, so c comes first
and also that c references a, so a comes first
. This is basically topological sorting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code LGTM
Friendly reminder: Looks like this PR hasn’t been backported yet. |
* [Lens] Implement types for reference-based operations * Update from review feedback
This PR introduces the
fullReference
operation type and adds unit tests for this type. The full list of functionality for this operation type is here:getDefaultName
function which isn't actually used in this PRSplit into a separate PR:
incompleteColumns
object in the UIgetDefaultName
function in the UI, to allow resetting the nameChecklist
Delete any items that are not applicable to this PR.