[Enterprise Search] Model state change error handling (#172409)

## Summary

This PR adds error handling to model actions (deploy, start) in the ML
model selection list. If either of these API calls fail, an error is
displayed. The error stays on the screen until the flyout is reopened or
another model action succeeds.

Unfortunately there's no straightforward way to surface the root cause,
so we display a generic error message to check the Kibana logs (which do
contain the cause).

<img width="1443" alt="Screenshot 2023-12-01 at 17 50 52"
src="d9fe2633-33ee-47ef-a47a-3bd401216853">

### Checklist

- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [ ] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed
- [x] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
- [x] Any UI touched in this PR does not create any new axe failures
(run axe in browser:
[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),
[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))
- [x] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
This commit is contained in:
Adam Demjen 2023-12-01 19:16:08 -05:00 committed by GitHub
parent c88d4a7e49
commit 7ad94e9c4b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 49 additions and 1 deletions

View file

@ -10,6 +10,7 @@ import React from 'react';
import { useValues, useActions } from 'kea';
import {
EuiCallOut,
EuiFieldText,
EuiForm,
EuiFormRow,
@ -28,6 +29,7 @@ import { IndexViewLogic } from '../../index_view_logic';
import { EMPTY_PIPELINE_CONFIGURATION, MLInferenceLogic } from './ml_inference_logic';
import { ModelSelect } from './model_select';
import { ModelSelectLogic } from './model_select_logic';
import { PipelineSelectOption } from './pipeline_select_option';
const PIPELINE_SELECT_PLACEHOLDER_VALUE = 'pipeline_placeholder$$';
@ -56,6 +58,7 @@ export const ConfigurePipeline: React.FC = () => {
const { selectExistingPipeline, setInferencePipelineConfiguration } =
useActions(MLInferenceLogic);
const { ingestionMethod } = useValues(IndexViewLogic);
const { modelStateChangeError } = useValues(ModelSelectLogic);
const { pipelineName } = configuration;
const nameError = formErrors.pipelineName !== undefined && pipelineName.length > 0;
@ -133,6 +136,22 @@ export const ConfigurePipeline: React.FC = () => {
}
/>
</EuiFormRow>
{modelStateChangeError && (
<>
<EuiSpacer />
<EuiCallOut
title={i18n.translate(
'xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.modelStateChangeError.title',
{ defaultMessage: 'Error changing model state' }
)}
color="danger"
iconType="error"
>
{modelStateChangeError}
</EuiCallOut>
<EuiSpacer />
</>
)}
<EuiFormRow
fullWidth
label={i18n.translate(

View file

@ -7,6 +7,7 @@
import { LogicMounter } from '../../../../../__mocks__/kea_logic';
import { HttpError } from '../../../../../../../common/types/api';
import { MlModel, MlModelDeploymentState } from '../../../../../../../common/types/ml';
import { CachedFetchModelsApiLogic } from '../../../../api/ml_models/cached_fetch_models_api_logic';
import {
@ -124,6 +125,22 @@ describe('ModelSelectLogic', () => {
});
});
describe('modelStateChangeError', () => {
it('gets error from API error response', () => {
const error = {
body: {
error: 'some-error',
message: 'some-error-message',
statusCode: 500,
},
} as HttpError;
StartModelApiLogic.actions.apiError(error);
expect(ModelSelectLogic.values.modelStateChangeError).toEqual('some-error-message');
});
});
describe('selectableModels', () => {
it('gets models data from API response', () => {
CachedFetchModelsApiLogic.actions.apiSuccess(FETCH_MODELS_API_DATA_RESPONSE);

View file

@ -9,6 +9,7 @@ import { kea, MakeLogicType } from 'kea';
import { HttpError, Status } from '../../../../../../../common/types/api';
import { MlModel } from '../../../../../../../common/types/ml';
import { getErrorsFromHttpResponse } from '../../../../../shared/flash_messages/handle_api_errors';
import {
CachedFetchModelsApiLogic,
CachedFetchModlesApiLogicActions,
@ -25,16 +26,18 @@ import {
export interface ModelSelectActions {
createModel: (modelId: string) => { modelId: string };
createModelError: CreateModelApiLogicActions['apiError'];
createModelMakeRequest: CreateModelApiLogicActions['makeRequest'];
createModelSuccess: CreateModelApiLogicActions['apiSuccess'];
fetchModels: () => void;
fetchModelsMakeRequest: CachedFetchModlesApiLogicActions['makeRequest'];
fetchModelsError: CachedFetchModlesApiLogicActions['apiError'];
fetchModelsMakeRequest: CachedFetchModlesApiLogicActions['makeRequest'];
fetchModelsSuccess: CachedFetchModlesApiLogicActions['apiSuccess'];
startPollingModels: CachedFetchModlesApiLogicActions['startPolling'];
startModel: (modelId: string) => { modelId: string };
startModelError: CreateModelApiLogicActions['apiError'];
startModelMakeRequest: StartModelApiLogicActions['makeRequest'];
startModelSuccess: StartModelApiLogicActions['apiSuccess'];
}
@ -45,6 +48,7 @@ export interface ModelSelectValues {
createModelStatus: Status;
isLoading: boolean;
isInitialLoading: boolean;
modelStateChangeError: string | undefined;
modelsData: FetchModelsApiResponse | undefined;
modelsStatus: Status;
selectableModels: MlModel[];
@ -118,6 +122,14 @@ export const ModelSelectLogic = kea<MakeLogicType<ModelSelectValues, ModelSelect
(createModelStatus: Status, startModelStatus: Status) =>
createModelStatus === Status.LOADING || startModelStatus === Status.LOADING,
],
modelStateChangeError: [
() => [selectors.createModelError, selectors.startModelError],
(createModelError?: HttpError, startModelError?: HttpError) => {
if (!createModelError && !startModelError) return undefined;
return getErrorsFromHttpResponse(createModelError ?? startModelError!)[0];
},
],
selectableModels: [
() => [selectors.modelsData],
(response: FetchModelsApiResponse) => response ?? [],