Skip to content

Commit

Permalink
[Tines connector] Improve error handling and fallback (#191263)
Browse files Browse the repository at this point in the history
## Summary

From: #188115

Improvements of the Tines connector:

- Original Axios error passed back from `getResponseErrorMessage` to the
sub-actions framework, instead of returning `Unknown API error` string.
The error appears in the error toast so the user has more information
about the problem:

Before:
<img width="975" alt="before"
src="https://github.com/user-attachments/assets/0fdfd085-a4d6-4ebe-b1ae-0f62332b4f5c">

After:
<img width="975" alt="after"
src="https://github.com/user-attachments/assets/1019c8ce-c97c-4564-8ec0-eca303706cfa">


- Fallback input (direct webhook URL) now appears when there's some
error. Before this change, the fallback input only appeared when the
Tines API response was incomplete. Proper callout message added:

<img width="975" alt="Error fallback"
src="https://github.com/user-attachments/assets/b8f46df5-5dfe-42cb-88cc-f10e12a07e25">

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
  • Loading branch information
semd and elasticmachine committed Aug 27, 2024
1 parent 1e428ad commit ffd4076
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,50 @@ describe('TinesParamsFields renders', () => {
webhookUrl
);
});

it('should render webhook url fallback when stories request has error', () => {
const errorMessage = 'something broke';
mockUseSubActionStories.mockReturnValueOnce({
isLoading: false,
response: { stories: [story] },
error: new Error(errorMessage),
});

const wrapper = mountWithIntl(
<TinesParamsFields
actionParams={{}}
errors={emptyErrors}
editAction={mockEditAction}
index={index}
executionMode={ActionConnectorMode.ActionForm}
/>
);

expect(wrapper.find('[data-test-subj="tines-fallbackCallout"]').exists()).toBe(true);
expect(wrapper.find('[data-test-subj="tines-webhookUrlInput"]').exists()).toBe(true);
});

it('should render webhook url fallback when webhooks request has error', () => {
const errorMessage = 'something broke';
mockUseSubActionWebhooks.mockReturnValueOnce({
isLoading: false,
response: { webhooks: [webhook] },
error: new Error(errorMessage),
});

const wrapper = mountWithIntl(
<TinesParamsFields
actionParams={{}}
errors={emptyErrors}
editAction={mockEditAction}
index={index}
executionMode={ActionConnectorMode.ActionForm}
/>
);

expect(wrapper.find('[data-test-subj="tines-fallbackCallout"]').exists()).toBe(true);
expect(wrapper.find('[data-test-subj="tines-webhookUrlInput"]').exists()).toBe(true);
});
});

describe('subActions error', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,10 @@ const TinesParamsFields: React.FunctionComponent<ActionParamsProps<TinesExecuteA
}
}, [toasts, storiesError, webhooksError]);

const showFallbackFrom = useMemo<'Story' | 'Webhook' | 'any' | null>(() => {
const showFallbackFrom = useMemo<'Story' | 'Webhook' | 'any' | 'error' | null>(() => {
if (storiesError || webhooksError) {
return 'error';
}
if (incompleteStories && !selectedStoryOption) {
return 'Story';
}
Expand All @@ -150,7 +153,9 @@ const TinesParamsFields: React.FunctionComponent<ActionParamsProps<TinesExecuteA
return null;
}, [
webhookUrl,
storiesError,
incompleteStories,
webhooksError,
incompleteWebhooks,
selectedStoryOption,
selectedWebhookOption,
Expand Down Expand Up @@ -276,7 +281,19 @@ const TinesParamsFields: React.FunctionComponent<ActionParamsProps<TinesExecuteA

{showFallbackFrom != null && (
<EuiFlexItem>
{showFallbackFrom !== 'any' && (
{showFallbackFrom === 'error' && (
<>
<EuiCallOut
title={i18n.WEBHOOK_URL_ERROR_FALLBACK_TITLE}
color="primary"
data-test-subj="tines-fallbackCallout"
>
{i18n.WEBHOOK_URL_ERROR_FALLBACK}
</EuiCallOut>
<EuiSpacer size="s" />
</>
)}
{(showFallbackFrom === 'Story' || showFallbackFrom === 'Webhook') && (
<>
<EuiCallOut
title={i18n.WEBHOOK_URL_FALLBACK_TITLE}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,20 @@ export const WEBHOOK_URL_FALLBACK_TEXT = (entity: 'Story' | 'Webhook') =>
values: { entity, limit: API_MAX_RESULTS },
defaultMessage: `Not possible to retrieve more than {limit} results from the Tines {entity} API. If your {entity} does not appear in the list, please fill the Webhook URL below`,
});

export const WEBHOOK_URL_ERROR_FALLBACK_TITLE = i18n.translate(
'xpack.stackConnectors.security.tines.params.webhookUrlErrorFallbackTitle',
{
defaultMessage: 'Error using Tines API',
}
);
export const WEBHOOK_URL_ERROR_FALLBACK = i18n.translate(
'xpack.stackConnectors.security.tines.params.webhookUrlErrorFallback',
{
defaultMessage:
'There seems to be an issue retrieving Stories or Webhooks using the Tines API. Alternatively, you can set the Tines Webhook URL in the input below',
}
);
export const WEBHOOK_URL_HELP = i18n.translate(
'xpack.stackConnectors.security.tines.params.webhookUrlHelp',
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import axios, { AxiosInstance } from 'axios';
import axios, { AxiosError, AxiosInstance, AxiosResponse } from 'axios';
import { actionsConfigMock } from '@kbn/actions-plugin/server/actions_config.mock';
import { actionsMock } from '@kbn/actions-plugin/server/mocks';
import { loggingSystemMock } from '@kbn/core-logging-server-mocks';
Expand Down Expand Up @@ -150,6 +150,28 @@ describe('TinesConnector', () => {
});
});

describe('Error handling', () => {
let error: AxiosError;

beforeEach(() => {
error = new AxiosError();
});

it('should return status text api error', () => {
error.response = { status: 401, statusText: 'Unauthorized' } as AxiosResponse;
// @ts-expect-error protected method
const errorMessage = connector.getResponseErrorMessage(error);
expect(errorMessage).toEqual('API Error: Unauthorized');
});

it('should return original error', () => {
error.toString = () => 'Network Error';
// @ts-expect-error protected method
const errorMessage = connector.getResponseErrorMessage(error);
expect(errorMessage).toEqual('Network Error');
});
});

describe('getWebhooks', () => {
beforeAll(() => {
mockRequest.mockReturnValue({ data: { agents: [webhookAgent], meta: { pages: 1 } } });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,13 +126,10 @@ export class TinesConnector extends SubActionConnector<TinesConfig, TinesSecrets
}

protected getResponseErrorMessage(error: AxiosError): string {
if (!error.response?.status) {
return 'Unknown API Error';
if (error.response?.statusText) {
return `API Error: ${error.response?.statusText}`;
}
if (error.response.status === 401) {
return 'Unauthorized API Error';
}
return `API Error: ${error.response?.statusText}`;
return error.toString();
}

public async getStories(
Expand Down

0 comments on commit ffd4076

Please sign in to comment.