[Metrics][Discover] internal/search/esql_async returns 200 but METRICS_INFO responds with error#260237
Conversation
…ync-returns-200-but-metrics_info-responds-with-error
|
Pinging @elastic/obs-exploration-team (Team:obs-exploration) |
justinkambic
left a comment
There was a problem hiding this comment.
This is looking good. One main thing I'd ask for would be some unit tests to cover what we're adding here with getErrorMessageFromEsqlResponse.
- error w/
typeorreason - error with only
root_cause - empty error
- successful happy path run
I am OK if we just export the function for the sake of simplified testing, but you could also add the tests to the existing test file for this module.
| } | ||
| const rc = e.root_cause?.[0]; | ||
| const fromRoot = [rc?.type, rc?.reason].filter((x): x is string => Boolean(x?.trim())).join(': '); | ||
| return fromRoot || 'Elasticsearch returned an error'; |
There was a problem hiding this comment.
One thing I'd like to call out here. If Elasticsearch returns an object missing all the properties that we care about, is this a case where we want to throw? If ES for some reason sends:
{
error: { }
}
Do we care about throwing an error for that? Would that happen if we didn't? Just telling the user "ES threw an unknown error" is not going to be super helpful for them.
@kpatticha since you're more aware of the actual situation we are addressing here, do you have a thought on a case where the error has none of the keys we're filtering for here?
There was a problem hiding this comment.
When it comes to the "why", even though it might be rare for ES to thrown an empty error, it can happen. We got a similar behaviour here, where we fallback to a default message
const message = causeReason ? causeReason : i18n.translate('searchErrors.esError.unknownRootCause', { defaultMessage: 'unknown' });
There was a problem hiding this comment.
since you're more aware of the actual situation we are addressing here, do you have a thought on a case where the error has none of the keys we're filtering for here?
Good call out @justinkambic . I’m not aware of a specific case, but I think we should align with the pattern used in Discover here.
lucaslopezf
left a comment
There was a problem hiding this comment.
Great Jorge! A few comments!
| uiSettings: IUiSettingsClient; | ||
| } | ||
|
|
||
| function getErrorMessageFromEsqlResponse(response: object): string | undefined { |
There was a problem hiding this comment.
Thanks for taking this Jorge!
I didn't test it, but a few early suggestions
- Try to reuse type, taking a quickly look maybe we can reuse ErrorCause from
@elastic/elasticsearchinstead of the inline cast{ type?; reason?; root_cause? }. - Returning
string | undefinedis ok but maybe we want to make some logic in the future and it will depends of the reason, type, etc. Instead of manage with strings what if we create a Class with properties? - Instead of checking the response inline after
getESQLResults, a small wrapper could wrap the API call and guarantee the response is clean soexecuteEsqlQuerystays linear. - Add tests to cover new functions
- One concern: since we're throwing an error, we should make sure every consumer of executeEsqlQuery handles it properly. Today we're safe because useAsyncFn in useFetchMetricsData catches everything. But if a future consumer calls
executeEsqlQuerywithout a try/catch or useAsyncFn, will we break the experience?
I put together one possible approach to illustrate the idea, just a starting point:
// esql_response_error.ts
export class EsqlResponseError extends Error {
public readonly type?: string;
public readonly reason?: string;
public readonly rootCause?: ErrorCause[];
constructor(errorCause: ErrorCause) {
super(formatErrorCause(errorCause));
this.name = 'EsqlResponseError';
this.type = errorCause.type;
this.reason = errorCause.reason ?? undefined;
this.rootCause = errorCause.root_cause;
}
}
// execute_esql_query.ts
const fetchEsqlResponseOrThrow = async (
params: Parameters<typeof getESQLResults>[0]
): Promise<ESQLSearchResponse> => {
const { response } = await getESQLResults(params);
const errorCause = extractEsqlResponseErrorCause(response);
if (errorCause) {
throw new EsqlResponseError(errorCause);
}
return response;
};
// execute_esql_query.ts (usage in executeEsqlQuery)
const response = await fetchEsqlResponseOrThrow({
esqlQuery, search, signal, filter, timeRange, variables,
...getMetricsExecutionContext(
MetricsExecutionContextAction.FETCH,
MetricsExecutionContextName.METRICS_INFO
),
});
return esqlResultToPlainObjects<TDocument>(response);Let me know what you think!
There was a problem hiding this comment.
Thanks for your comment!
When it comes to your suggestions:
- Sounds great and I already have a small change in place
- If we're talking a simple, small class, it could make sense even if we're most likely just going to use a general message
- Sounds good
- Yes, some are already in place but with these changes I'll add a few more most likely
- That makes sense, we could change the return type to force the users to handle the response properly but, it could also be a bit more costly. Shall we create a small follow-up for this specific point?
There was a problem hiding this comment.
That makes sense, we could change the return type to force the users to handle the response properly but, it could also be a bit more costly. Shall we create a small follow-up for this specific point?
It depends, what happens when we throw an error? We should now how it behaves now before taking a decision
…-200-but-metrics_info-responds-with-error' of https://github.com/jorgeoliveira117/kibana into 259815-metricsdiscover-internalsearchesql_async-returns-200-but-metrics_info-responds-with-error
…ync-returns-200-but-metrics_info-responds-with-error
justinkambic
left a comment
There was a problem hiding this comment.
LGTM from my side, I'll let @lucaslopezf also follow-up with his feedback from before and approve for the both of us.
...ified-chart-section-viewer/src/components/observability/metrics/utils/esql_response_error.ts
Show resolved
Hide resolved
lucaslopezf
left a comment
There was a problem hiding this comment.
Great Jorge, much better, a few comments.
Also, thinking about it, as second iteration we should manage the errors for this in a core way, same for Discover, profiles, etc, so I'd like to create a follow-up issue to review the error handling architecture and be consistent with Discover, like:
- How errors are captured
- Use the same error type
- Handle it homogeneously cross-profiles
and so on. Could you create it and add a comment with the TODO and the issue related?
...ified-chart-section-viewer/src/components/observability/metrics/utils/esql_response_error.ts
Show resolved
Hide resolved
...ified-chart-section-viewer/src/components/observability/metrics/utils/esql_response_error.ts
Show resolved
Hide resolved
...ified-chart-section-viewer/src/components/observability/metrics/utils/esql_response_error.ts
Show resolved
Hide resolved
…-200-but-metrics_info-responds-with-error' of https://github.com/jorgeoliveira117/kibana into 259815-metricsdiscover-internalsearchesql_async-returns-200-but-metrics_info-responds-with-error
lucaslopezf
left a comment
There was a problem hiding this comment.
LGTM! Just two minor things before merge it!
...ified-chart-section-viewer/src/components/observability/metrics/utils/esql_response_error.ts
Show resolved
Hide resolved
...ified-chart-section-viewer/src/components/observability/metrics/utils/esql_response_error.ts
Show resolved
Hide resolved
💚 Build Succeeded
Metrics [docs]Module Count
Async chunks
History
|
| response: object | ||
| ): EsqlResponseErrorCause | undefined => extractEsqlEmbeddedError(response)?.cause; | ||
|
|
||
| export class EsqlResponseError extends Error { |
There was a problem hiding this comment.
This is probably fine for now, but we should not implement custom error messages. If such handlers are needed, they should be handled at the ESQL level rather than on our side.
There was a problem hiding this comment.
Totally agree, we created this issue to handle things like this and more
| : undefined; | ||
|
|
||
| const { response } = await getESQLResults({ | ||
| const response = await fetchEsqlResponseOrThrow({ |
There was a problem hiding this comment.
A try/catch handler would likely handle the same
…S_INFO responds with error (elastic#260237) Closes elastic#259815 ## Summary For the async ES|QL query we do when we're in the Metrics Experience, there's a chance that `METRICS_INFO` ES|QL request returns a 200 answer but it actually fails. To make things more clear, after `getESQLResults` resolves for the background `METRICS_INFO` ES|QL request, we now detect if there's an `error` from ES, throwing an error if needed. **This won't change anything in the UI yet** but will enable future work to provide a better UX. Which will be a follow-up on elastic#258493 ### Changes - Add `getErrorMessageFromEsqlResponse` to build an error message from `error.type` / `error.reason`, or `error.root_cause[0]`, with a generic fallback. - If a message is present, an error is thrown so `executeEsqlQuery` returns rejected promise. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
…S_INFO responds with error (elastic#260746) ## Summary This PR covers small code changes missed in elastic#260237 --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
…S_INFO responds with error (elastic#260237) Closes elastic#259815 ## Summary For the async ES|QL query we do when we're in the Metrics Experience, there's a chance that `METRICS_INFO` ES|QL request returns a 200 answer but it actually fails. To make things more clear, after `getESQLResults` resolves for the background `METRICS_INFO` ES|QL request, we now detect if there's an `error` from ES, throwing an error if needed. **This won't change anything in the UI yet** but will enable future work to provide a better UX. Which will be a follow-up on elastic#258493 ### Changes - Add `getErrorMessageFromEsqlResponse` to build an error message from `error.type` / `error.reason`, or `error.root_cause[0]`, with a generic fallback. - If a message is present, an error is thrown so `executeEsqlQuery` returns rejected promise. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
…S_INFO responds with error (elastic#260746) ## Summary This PR covers small code changes missed in elastic#260237 --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Closes #259815
Summary
For the async ES|QL query we do when we're in the Metrics Experience, there's a chance that
METRICS_INFOES|QL request returns a 200 answer but it actually fails.To make things more clear, after
getESQLResultsresolves for the backgroundMETRICS_INFOES|QL request, we now detect if there's anerrorfrom ES, throwing an error if needed.This won't change anything in the UI yet but will enable future work to provide a better UX. Which will be a follow-up on #258493
Changes
getErrorMessageFromEsqlResponseto build an error message fromerror.type/error.reason, orerror.root_cause[0], with a generic fallback.executeEsqlQueryreturns rejected promise.