Skip to content

Commit

Permalink
Move error extraction logic outside component
Browse files Browse the repository at this point in the history
  • Loading branch information
demjened committed May 2, 2023
1 parent 9da87d1 commit bb605db
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import { docLinks } from '../../../../../shared/doc_links';
import { KibanaLogic } from '../../../../../shared/kibana';

import { useTextExpansionCallOutData } from './text_expansion_callout_data';
import { TextExpansionCalloutLogic } from './text_expansion_callout_logic';
import { getTextExpansionError, TextExpansionCalloutLogic } from './text_expansion_callout_logic';
import { TextExpansionErrors } from './text_expansion_errors';

export interface TextExpansionCallOutState {
Expand Down Expand Up @@ -339,19 +339,12 @@ export const TextExpansionCallOut: React.FC<TextExpansionCallOutProps> = (props)
} = useValues(TextExpansionCalloutLogic);

// In case of an error, show the error callout only
if (
createTextExpansionModelError !== undefined ||
fetchTextExpansionModelError !== undefined ||
startTextExpansionModelError !== undefined
) {
return (
<TextExpansionErrors
createError={createTextExpansionModelError}
fetchError={fetchTextExpansionModelError}
startError={startTextExpansionModelError}
/>
);
}
const error = getTextExpansionError(
createTextExpansionModelError,
fetchTextExpansionModelError,
startTextExpansionModelError
);
if (error) return <TextExpansionErrors error={error} />;

if (!show) return null;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ import { LogicMounter } from '../../../../../__mocks__/kea_logic';

import { HttpResponse } from '@kbn/core/public';

import { ErrorResponse, Status } from '../../../../../../../common/types/api';
import { ErrorResponse, HttpError, Status } from '../../../../../../../common/types/api';
import { MlModelDeploymentState } from '../../../../../../../common/types/ml';
import { CreateTextExpansionModelApiLogic } from '../../../../api/ml_models/text_expansion/create_text_expansion_model_api_logic';
import { FetchTextExpansionModelApiLogic } from '../../../../api/ml_models/text_expansion/fetch_text_expansion_model_api_logic';

import {
getTextExpansionError,
TextExpansionCalloutLogic,
TextExpansionCalloutValues,
} from './text_expansion_callout_logic';
Expand Down Expand Up @@ -58,6 +59,37 @@ describe('TextExpansionCalloutLogic', () => {
expect(TextExpansionCalloutLogic.values).toEqual(DEFAULT_VALUES);
});

describe('getTextExpansionError', () => {
const error = {
body: {
error: 'some-error',
message: 'some-error-message',
statusCode: 500,
},
} as HttpError;
it('returns null if there is no error', () => {
expect(getTextExpansionError(undefined, undefined, undefined)).toBe(null);
});
it('uses the correct title and message from a create error', () => {
expect(getTextExpansionError(error, undefined, undefined)).toEqual({
title: 'Error with ELSER deployment',
message: error.body?.message,
});
});
it('uses the correct title and message from a create error', () => {
expect(getTextExpansionError(undefined, error, undefined)).toEqual({
title: 'Error fetching ELSER model',
message: error.body?.message,
});
});
it('uses the correct title and message from a create error', () => {
expect(getTextExpansionError(undefined, undefined, error)).toEqual({
title: 'Error starting ELSER deployment',
message: error.body?.message,
});
});
});

describe('listeners', () => {
describe('createTextExpansionModelPollingTimeout', () => {
const duration = 5000;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* 2.0.
*/

import { i18n } from '@kbn/i18n';
import { kea, MakeLogicType } from 'kea';

import { HttpError, Status } from '../../../../../../../common/types/api';
Expand All @@ -23,6 +24,7 @@ import {
StartTextExpansionModelApiLogic,
StartTextExpansionModelApiLogicActions,
} from '../../../../api/ml_models/text_expansion/start_text_expansion_model_api_logic';
import { getErrorsFromHttpResponse } from '../../../../../shared/flash_messages/handle_api_errors';

const FETCH_TEXT_EXPANSION_MODEL_POLLING_DURATION = 5000; // 5 seconds
const FETCH_TEXT_EXPANSION_MODEL_POLLING_DURATION_ON_FAILURE = 30000; // 30 seconds
Expand Down Expand Up @@ -62,6 +64,51 @@ export interface TextExpansionCalloutValues {
textExpansionModelPollTimeoutId: null | ReturnType<typeof setTimeout>;
}

/**
* Extracts the topmost error in precedence order (create > start > fetch).
* @param createError
* @param fetchError
* @param startError
* @returns the extracted error or null if there is no error
*/
export const getTextExpansionError = (
createError: HttpError | undefined,
fetchError: HttpError | undefined,
startError: HttpError | undefined
) => {
return createError !== undefined
? {
title: i18n.translate(
'xpack.enterpriseSearch.content.indices.pipelines.textExpansionCreateError.title',
{
defaultMessage: 'Error with ELSER deployment',
}
),
message: getErrorsFromHttpResponse(createError)[0],
}
: startError !== undefined
? {
title: i18n.translate(
'xpack.enterpriseSearch.content.indices.pipelines.textExpansionStartError.title',
{
defaultMessage: 'Error starting ELSER deployment',
}
),
message: getErrorsFromHttpResponse(startError)[0],
}
: fetchError !== undefined
? {
title: i18n.translate(
'xpack.enterpriseSearch.content.indices.pipelines.textExpansionFetchError.title',
{
defaultMessage: 'Error fetching ELSER model',
}
),
message: getErrorsFromHttpResponse(fetchError)[0],
}
: null;
};

export const TextExpansionCalloutLogic = kea<
MakeLogicType<TextExpansionCalloutValues, TextExpansionCalloutActions>
>({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ import { shallow } from 'enzyme';

import { EuiCallOut } from '@elastic/eui';

import { HttpError } from '../../../../../../../common/types/api';

import { TextExpansionErrors } from './text_expansion_errors';

describe('TextExpansionErrors', () => {
Expand All @@ -23,38 +21,14 @@ describe('TextExpansionErrors', () => {
setMockValues({});
});
const error = {
body: {
error: 'some-error',
message: 'some-error-message',
statusCode: 500,
},
} as HttpError;
it('renders error panel if ELSER deployment fails', () => {
const wrapper = shallow(
<TextExpansionErrors createError={error} fetchError={undefined} startError={undefined} />
);
expect(wrapper.find(EuiCallOut).length).toBe(1);
expect(wrapper.find(EuiCallOut).prop('title')).toEqual('Error with ELSER deployment');
});
it('renders error panel if ELSER fetching fails', () => {
const wrapper = shallow(
<TextExpansionErrors createError={undefined} fetchError={error} startError={undefined} />
);
title: 'some-error-title',
message: 'some-error-message',
};
it('extracts error panel with the given title and message', () => {
const wrapper = shallow(<TextExpansionErrors error={error} />);
expect(wrapper.find(EuiCallOut).length).toBe(1);
expect(wrapper.find(EuiCallOut).prop('title')).toEqual('Error fetching ELSER model');
});
it('renders error panel if ELSER starting fails', () => {
const wrapper = shallow(
<TextExpansionErrors createError={undefined} fetchError={undefined} startError={error} />
);
expect(wrapper.find(EuiCallOut).length).toBe(1);
expect(wrapper.find(EuiCallOut).prop('title')).toEqual('Error starting ELSER deployment');
});
it('extracts and renders the error message', () => {
const wrapper = shallow(
<TextExpansionErrors createError={error} fetchError={undefined} startError={undefined} />
);
expect(wrapper.find(EuiCallOut).prop('title')).toEqual(error.title);
expect(wrapper.find(EuiCallOut).find('p').length).toBe(1);
expect(wrapper.find(EuiCallOut).find('p').text()).toEqual(error?.body?.message);
expect(wrapper.find(EuiCallOut).find('p').text()).toEqual(error.message);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -13,59 +13,17 @@ import { EuiCallOut, EuiLink } from '@elastic/eui';

import { i18n } from '@kbn/i18n';

import { HttpError } from '../../../../../../../common/types/api';
import { getErrorsFromHttpResponse } from '../../../../../shared/flash_messages/handle_api_errors';
import { HttpLogic } from '../../../../../shared/http';

import { ML_NOTIFICATIONS_PATH } from '../../../../routes';

export const TextExpansionErrors = ({
createError,
fetchError,
startError,
}: {
createError: HttpError | undefined;
fetchError: HttpError | undefined;
startError: HttpError | undefined;
}) => {
export const TextExpansionErrors = ({ error }: { error: { title: string; message: string } }) => {
const { http } = useValues(HttpLogic);

// Extract the topmost error in precedence order
const error: HttpError | undefined = createError ?? startError ?? fetchError;
if (error === undefined) {
return null;
}
const topError = getErrorsFromHttpResponse(error)[0];

return (
<>
<EuiCallOut
color="danger"
iconType="error"
title={
createError !== undefined
? i18n.translate(
'xpack.enterpriseSearch.content.indices.pipelines.textExpansionCreateError.title',
{
defaultMessage: 'Error with ELSER deployment',
}
)
: startError !== undefined
? i18n.translate(
'xpack.enterpriseSearch.content.indices.pipelines.textExpansionStartError.title',
{
defaultMessage: 'Error starting ELSER deployment',
}
)
: i18n.translate(
'xpack.enterpriseSearch.content.indices.pipelines.textExpansionFetchError.title',
{
defaultMessage: 'Error fetching ELSER model',
}
)
}
>
<p>{topError}</p>
<EuiCallOut color="danger" iconType="error" title={error.title}>
<p>{error.message}</p>
<EuiLink href={http.basePath.prepend(ML_NOTIFICATIONS_PATH)} target="_blank">
{i18n.translate(
'xpack.enterpriseSearch.content.indices.pipelines.textExpansionCreateError.mlNotificationsLink',
Expand Down

0 comments on commit bb605db

Please sign in to comment.