-
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
[data.search] Clean up arguments to esaggs. #84973
Conversation
ad36054
to
6b2eb4c
Compare
schema: 'metric', | ||
params: { | ||
toEsAggsFn: (column, columnId) => { | ||
return buildExpressionFunction<AggFunctionsMapping['aggCardinality']>('aggCardinality', { |
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.
This approach looks great to me, seems like that's just as maintainable as the old version 👍
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.
seems like that's just as maintainable as the old version
Yeah after our discussion I realized that, once converting to use the function builder, you gain a lot more type safety than you had previously, so overall I think that will help prevent any future issues 🙂
ec49568
to
efb40b0
Compare
it('does not stringify arrays which are not objects', () => { | ||
const ac = new AggConfigs(indexPattern, [], { typesRegistry }); | ||
const configStates = { | ||
type: 'percentiles', | ||
params: { | ||
field: 'bytes', | ||
percents: [1, 25, 50, 75, 99], | ||
}, | ||
}; | ||
const aggConfig = ac.createAggConfig(configStates); | ||
const percents = aggConfig.toExpressionAst()?.chain[0].arguments.percents; | ||
expect(percents).toEqual([1, 25, 50, 75, 99]); | ||
}); |
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.
Uncovered a bug in toExpressionAst
where the percents
arg was incorrectly stringified when we should have preserved it, so I updated the logic to handle this case where a param is already an array, but not an object array.
*/ | ||
toExpressionAst(): ExpressionAstFunction | undefined { | ||
toExpressionAst(): ExpressionAstExpression | undefined { |
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.
Discovered that returning an ExpressionAstFunction
was not very useful since using this as a subexpression requires it to be ExpressionAstExpression
, so I updated accordingly.
import { getMovingAvgMetricAgg } from './metrics/moving_avg'; | ||
import { getSerialDiffMetricAgg } from './metrics/serial_diff'; | ||
import * as buckets from './buckets'; | ||
import * as metrics from './metrics'; |
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.
No changes here, this file was just cleanup.
@@ -72,7 +72,7 @@ export const aggHistogram = (): FunctionDefinition => ({ | |||
}), | |||
}, | |||
interval: { | |||
types: ['string'], | |||
types: ['number', 'string'], |
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.
We had incorrectly typed this as string
only (since you can do auto
), but in most cases the interval is actually a number when explicitly provided. Keeping this as a string caused some functional test failures where an incorrect interval got applied when the histogram expression function cast the provided number to a string instead of keeping its type.
let calculatedInterval = isAuto | ||
? 0 | ||
: typeof interval !== 'number' | ||
? parseFloat(interval) | ||
: interval; |
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.
Had to tweak logic here to make TS happy once updating interval
to be string or number. Now we need to check for the case where it's already a number.
Technically this doesn't change anything at runtime (since passing a number to parseFloat
will still return the same number), but TS expects parseFloat
to only accept a number.
const aggConfigsState = JSON.parse(args.aggConfigs); | ||
const indexPattern = await indexPatterns.get(args.index); | ||
const aggConfigs = aggs.createAggConfigs(indexPattern, aggConfigsState); | ||
const indexPattern = await indexPatterns.create(args.index.value, true); |
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 used indexPatterns.create
here, because manually creating new IndexPattern()
still requires you to provide the field formats service, plus some UI settings... which means adding more dependencies to esaggs
(currently ui settings is not required).
However we could still refactor to use new IndexPattern()
directly if desired; this was just a simpler change.
const configStr = JSON.stringify(visConfig).replace(/\\/g, `\\\\`).replace(/'/g, `\\'`); | ||
const visTypeXy = buildExpressionFunction<VisTypeVislibExpressionFunctionDefinition>( | ||
vislibVisName, | ||
{ | ||
type: vis.type.name, | ||
visConfig: configStr, |
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.
There was a regression here which AFAICT was introduced by #80744.
It surfaced as a bug where creating a vislib vis with a serial diff agg threw an error... Because the custom label that got created had incorrectly double-escaped backslashes, e.g.: "Blah blah serial diff of \\"bytes\\""
This would cause JSON.parse
to throw because the double-escaped quotes were accidentally kept in place and interpreted as part of the json, unexpectly ending the line early.
I know this escaping was copied over from build_pipeline
and TBH I'm not 100% sure why it isn't needed anymore, but thought I'd provide some background. I only found this bug because I was testing visualizations using every agg type to validate this PR, and ran into trouble with serial diff.
Also worth pointing out that the to_ast
for pie charts still has the escaping in place, because i couldn't find a situation where it was broken. Might be worth testing that further to verify.
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 tested both and I think that they both work as they should. I tested all the aggs on all vislib visualizations. Can't find any regressions.
@@ -4,7 +4,6 @@ | |||
* you may not use this file except in compliance with the Elastic License. | |||
*/ | |||
|
|||
import { ExpressionFunctionAST } from '@kbn/interpreter/common'; |
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.
As an aside -- At some point y'all should consider removing all of the old @kbn/interpreter
imports from Lens since you can access the same types (with slightly different names) from expressions.
I just did so in the files I touched here
field: column.sourceField, | ||
missing: 0, |
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 don't believe missing
is a valid param for any of these agg types so I removed it; please LMK if you know otherwise.
maxBars: params.maxBars === AUTO_BARS ? undefined : params.maxBars, | ||
interval: 'auto', | ||
has_extended_bounds: false, | ||
min_doc_count: true, |
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.
Important: Please confirm that the update I made to min_doc_count
doesn't have any unexpected effects on Lens, because I think it might actually be changing the behavior...
According to the histogram agg type, min_doc_count
should be a boolean
(not a number). It writes out the param as follows:
if (aggConfig.params.min_doc_count) {
output.params.min_doc_count = 0;
} else {
output.params.min_doc_count = 1;
}
So as you can see, setting it to true
will return a value of 0
, which I why I changed it to true
here as it was previously set to 0
.
However, since the check is just confirming whether min_doc_count
is truthy, 0
might instead cause it to actually be set to 1
(or rather, the expressions interpreter could be casting it to false). So it's possible that lens was inadvertently relying on this behavior, which is why I'd appreciate a second set of eyes on this one.
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.
Pinging @elastic/kibana-app-services (Team:AppServices) |
efb40b0
to
7d2c3db
Compare
a668d50
to
bf57656
Compare
bf57656
to
18f62e0
Compare
const getNewAbortController = (): AbortController => { | ||
try { | ||
return new AbortController(); | ||
} catch (error) { | ||
// eslint-disable-next-line @typescript-eslint/no-var-requires | ||
const polyfill = require('abortcontroller-polyfill/dist/cjs-ponyfill'); | ||
return new polyfill.AbortController(); | ||
} |
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.
This was a bug uncovered by functional tests which we hadn't found yet: Since AbortController
isn't supported till Node 15 (and we are currently on v14), we need to manually apply a polyfill on the server.
Since this code is in common
, I'm hacking around the polyfill with a try/catch.
@@ -254,8 +254,7 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> { | |||
const searchSourceDependencies: SearchSourceDependencies = { | |||
getConfig: <T = any>(key: string): T => uiSettingsCache[key], | |||
search: asScoped(request).search, | |||
// onResponse isn't used on the server, so we just return the original value | |||
onResponse: (req, res) => res, | |||
onResponse: (req, res) => shimHitsTotal(res), |
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 discovered that shimHitsTotal
is applied in the low-level search routes, but not in the actual service that's available on the plugin contract, so we need to apply it here for search source.
(This is only necessary on the server, as the client is going through the http routes)
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.
cc @lizozom @lukasolson Whenever shimHitsTotal
is updated to be applied at the search strategy level instead of the route level, this change can be reverted.
let expectExpression: ExpectExpression; | ||
|
||
const expectClientToMatchServer = async (title: string, expression: string) => { |
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 added a function to compare results from client & server. Longer term we might consider updating helpers.ts
to have expectExpressionServer
or similar, which would make it even easier to run expressions on the server.
|
||
router.post( | ||
{ | ||
path: '/api/interpreter_functional/run_expression', |
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.
All this route does is take an expression & optional input, call expressions.run
from the server contract, and return the result.
This way we can gradually build out a suite that tests the same expressions on both client & server.
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
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.
Found some small things, but overall it looks good
index: [indexPattern.id], | ||
metricsAtAllLevels: [false], | ||
partialRows: [false], | ||
includeFormatHints: [true], |
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.
Just to make sure - did the flag includeFormatHints
become unnecessary?
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, includeFormatHints
isn't even returned in the esaggs response on master -- the argument itself just never got removed.
x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx
Show resolved
Hide resolved
@elasticmachine merge upstream |
💚 Build SucceededMetrics [docs]Module Count
Async chunks
Distributable file count
Page load bundle
History
To update your PR or re-run it, just comment with: |
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.
LGTM, thanks for making this change and giving us some type safety around agg building :) There are some JSON-serialized parts in there I guess you will revisit them later? Let me know if the Lens team can help here.
I thought a bit more about the histogram min_doc_count thing and I think we have to do some fixes here, but for this PR it makes sense to keep the existing behavior even if it's not perfect.
I focused on Lens, would appreciate a look at the Visualize bits from @stratoula |
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.
Legacy visualizations code review LGTM, I tested all of them with various aggs and I can't find any regression. Thanx @lukeelmers for fixing the serial Diff bug 🙂
@flash1293 Yep we have #67908 to track further work in this area. I'm sure the team would love any help you can provide 😄 Each agg param type now has its own |
Closes #61768
Closes #65067
Part of #46902
Summary
This updates the arguments of
esaggs
a part of the project to clean up semantics across all expression functions by removing blobs of stringified json. The following changes have been made here:index
arg inesaggs
to require an argument of typeindex_pattern
which is only returnable byindexPatternLoad
aggConfigs
arg to be anaggs
multiarg, which accepts typeagg_type
which is only returnable by each of the various agg type functionsesaggs
in both Lens & Visualize to ensure the function is called correctlytoEsAggsConfig
datasource method to betoEsAggsFn
and return the function AST. This is basically what @wylieconlon did with his exploration in [Lens] Use agg functions instead of string #77565build_pipeline
as well as each of the vis types that already had their owntoExpressionAst
definedaggConfig.toExpressionAst()
esaggs
implementation & testsesaggs
&requestHandler
.Testing
This PR introduces no functional changes, however it makes significant changes to the underlying infrastructure for generating aggregation requests that are run for Visualize & Lens. The best things to test are various configurations of agg types in different visualizations to make sure things are behaving as expected. Ultimately no user-facing behavior should change as a result of this PR.
Plugin API Changes
The
esaggs
expression function, which is the default method of requesting aggregations for visualizations, has had some changes to the arguments it accepts.