Skip to content

Commit

Permalink
[Serverless Search] Add consistent toast messages for all APIs (#173989)
Browse files Browse the repository at this point in the history
## Summary

This adds default error toast messages to all API calls in the
Serverless Elasticsearch plugin, and provides some ways to skip or
modify the toasts.

---------

Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
sphilipse and kibanamachine authored Jan 4, 2024
1 parent ee3c5c6 commit 07948b9
Show file tree
Hide file tree
Showing 14 changed files with 73 additions and 87 deletions.
2 changes: 1 addition & 1 deletion docs/developer/plugin-list.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -783,7 +783,7 @@ It leverages universal configuration and other APIs in the serverless plugin to
|{kib-repo}blob/{branch}/x-pack/plugins/serverless_search/README.mdx[serverlessSearch]
|This plugin contains configuration and code used to create a Serverless Search project. It leverages universal configuration and other APIs in the serverless plugin to configure Kibana.
|This plugin contains configuration and code used to create a Serverless Search project. It leverages universal configuration and other APIs in the serverless plugin to configure Kibana.
|{kib-repo}blob/{branch}/x-pack/plugins/session_view/README.md[sessionView]
Expand Down
9 changes: 8 additions & 1 deletion x-pack/plugins/serverless_search/README.mdx
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
# Serverless Search project plugin

This plugin contains configuration and code used to create a Serverless Search project. It leverages universal configuration and other APIs in the [`serverless`](../serverless/README.mdx) plugin to configure Kibana.
This plugin contains configuration and code used to create a Serverless Search project. It leverages universal configuration and other APIs in the [`serverless`](../serverless/README.mdx) plugin to configure Kibana.

## Code guidance

We use Tanstack React Query to handle all API calls in the browser. To create consistent error behavior, all APIs will flash an error toast if they encounter an error, except for 404 errors on GET calls.
To skip the error message, add a boolean property `skipToast` to your error and set it to `true`.
To change the toast message, change the `name` property on your error to change the title and the `body.message` property to change the message contents.
You can do this by catching the error in your fetch function and modifying the error in your catch before rethrowing.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import { useConnector } from '../../../hooks/api/use_connector';
import { useKibanaServices } from '../../../hooks/use_kibana';
import { ApiKeyPanel } from './api_key_panel';
import { ConnectorIndexNameForm } from './connector_index_name_form';
import { useShowErrorToast } from '../../../hooks/use_error_toast';
import { SyncScheduledCallOut } from './sync_scheduled_callout';

interface ConnectorIndexNameProps {
Expand All @@ -27,7 +26,6 @@ export const ConnectorIndexName: React.FC<ConnectorIndexNameProps> = ({ connecto
const { http } = useKibanaServices();
const queryClient = useQueryClient();
const { queryKey } = useConnector(connector.id);
const showErrorToast = useShowErrorToast();
const { data, isLoading, isSuccess, mutate } = useMutation({
mutationFn: async ({ inputName, sync }: { inputName: string | null; sync?: boolean }) => {
if (inputName && inputName !== connector.index_name) {
Expand All @@ -41,13 +39,6 @@ export const ConnectorIndexName: React.FC<ConnectorIndexNameProps> = ({ connecto
}
return inputName;
},
onError: (error) =>
showErrorToast(
error,
i18n.translate('xpack.serverlessSearch.connectors.config.connectorIndexNameError', {
defaultMessage: 'Error updating index name',
})
),
onSuccess: () => {
queryClient.setQueryData(queryKey, { connector: { ...connector, index_name: data } });
queryClient.invalidateQueries(queryKey);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
*/

import { EuiFlexGroup, EuiFlexItem, EuiButton, EuiSpacer } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { Connector } from '@kbn/search-connectors';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import React, { useEffect, useState } from 'react';
Expand All @@ -21,8 +20,8 @@ interface ConnectorIndexNamePanelProps {
}

export const ConnectorIndexnamePanel: React.FC<ConnectorIndexNamePanelProps> = ({ connector }) => {
const { http, notifications } = useKibanaServices();
const { data, error, isLoading, isSuccess, mutate, reset } = useMutation({
const { http } = useKibanaServices();
const { data, isLoading, isSuccess, mutate, reset } = useMutation({
mutationFn: async (inputName: string) => {
if (inputName && inputName !== connector.index_name) {
const body = { index_name: inputName };
Expand All @@ -44,16 +43,6 @@ export const ConnectorIndexnamePanel: React.FC<ConnectorIndexNamePanelProps> = (
}
}, [data, isSuccess, connector, queryClient, queryKey, reset]);

useEffect(() => {
if (error) {
notifications.toasts.addError(error as Error, {
title: i18n.translate('xpack.serverlessSearch.connectors.config.connectorIndexNameError', {
defaultMessage: 'Error updating index name',
}),
});
}
}, [error, notifications]);

const [newIndexName, setNewIndexName] = useState(connector.index_name || '');

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import { useQueryClient, useMutation } from '@tanstack/react-query';
import React, { useState } from 'react';
import { useConnector } from '../../../hooks/api/use_connector';
import { useSyncJobs } from '../../../hooks/api/use_sync_jobs';
import { useShowErrorToast } from '../../../hooks/use_error_toast';
import { useKibanaServices } from '../../../hooks/use_kibana';
import { SyncScheduledCallOut } from './sync_scheduled_callout';

Expand All @@ -29,18 +28,10 @@ export const ConnectorOverview: React.FC<ConnectorOverviewProps> = ({ connector
const { http } = useKibanaServices();
const queryClient = useQueryClient();
const { queryKey } = useConnector(connector.id);
const showErrorToast = useShowErrorToast();
const { data, isLoading, isSuccess, mutate } = useMutation({
mutationFn: async () => {
await http.post(`/internal/serverless_search/connectors/${connector.id}/sync`);
},
onError: (error) =>
showErrorToast(
error,
i18n.translate('xpack.serverlessSearch.connectors.config.connectorSyncError', {
defaultMessage: 'Error scheduling sync',
})
),
onSuccess: () => {
queryClient.setQueryData(queryKey, { connector: { ...connector, index_name: data } });
queryClient.invalidateQueries(queryKey);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export const EditConnector: React.FC = () => {
title={
<h1>
{i18n.translate('xpack.serverlessSearch.connectors.notFound', {
defaultMessage: 'Could not find a connector with id {id}',
defaultMessage: 'Could not find connector {id}',
values: { id },
})}
</h1>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import { Connector } from '@kbn/search-connectors';
import { CANCEL_LABEL, EDIT_LABEL, SAVE_LABEL } from '../../../../common/i18n_string';
import { useKibanaServices } from '../../hooks/use_kibana';
import { useConnector } from '../../hooks/api/use_connector';
import { useShowErrorToast } from '../../hooks/use_error_toast';

interface EditDescriptionProps {
connector: Connector;
Expand All @@ -34,7 +33,6 @@ export const EditDescription: React.FC<EditDescriptionProps> = ({ connector }) =
const [isEditing, setIsEditing] = useState(false);
const [newDescription, setNewDescription] = useState(connector.description || '');
const { http } = useKibanaServices();
const showErrorToast = useShowErrorToast();
const queryClient = useQueryClient();
const { queryKey } = useConnector(connector.id);

Expand All @@ -48,13 +46,6 @@ export const EditDescription: React.FC<EditDescriptionProps> = ({ connector }) =
});
return inputDescription;
},
onError: (error) =>
showErrorToast(
error,
i18n.translate('xpack.serverlessSearch.connectors.config.connectorDescription', {
defaultMessage: 'Error updating description',
})
),
onSuccess: (successData) => {
queryClient.setQueryData(queryKey, {
connector: { ...connector, description: successData },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import { Connector } from '@kbn/search-connectors';
import { CANCEL_LABEL, CONNECTOR_LABEL, SAVE_LABEL } from '../../../../common/i18n_string';
import { useKibanaServices } from '../../hooks/use_kibana';
import { useConnector } from '../../hooks/api/use_connector';
import { useShowErrorToast } from '../../hooks/use_error_toast';

interface EditNameProps {
connector: Connector;
Expand All @@ -34,7 +33,6 @@ export const EditName: React.FC<EditNameProps> = ({ connector }) => {
const [isEditing, setIsEditing] = useState(false);
const [newName, setNewName] = useState(connector.name || CONNECTOR_LABEL);
const { http } = useKibanaServices();
const showErrorToast = useShowErrorToast();
const queryClient = useQueryClient();
const { queryKey } = useConnector(connector.id);

Expand All @@ -48,13 +46,6 @@ export const EditName: React.FC<EditNameProps> = ({ connector }) => {
});
return inputName;
},
onError: (error) =>
showErrorToast(
error,
i18n.translate('xpack.serverlessSearch.connectors.config.connectorNameError', {
defaultMessage: 'Error updating name',
})
),
onSuccess: (successData) => {
queryClient.setQueryData(queryKey, {
connector: { ...connector, service_type: successData },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
import { Connector } from '@kbn/search-connectors';
import { useKibanaServices } from '../../hooks/use_kibana';
import { useConnectorTypes } from '../../hooks/api/use_connector_types';
import { useShowErrorToast } from '../../hooks/use_error_toast';
import { useConnector } from '../../hooks/api/use_connector';

interface EditServiceTypeProps {
Expand All @@ -29,7 +28,6 @@ interface EditServiceTypeProps {
export const EditServiceType: React.FC<EditServiceTypeProps> = ({ connector }) => {
const { http } = useKibanaServices();
const { data: connectorTypes } = useConnectorTypes();
const showErrorToast = useShowErrorToast();
const queryClient = useQueryClient();
const { queryKey } = useConnector(connector.id);

Expand Down Expand Up @@ -62,13 +60,6 @@ export const EditServiceType: React.FC<EditServiceTypeProps> = ({ connector }) =
});
return inputServiceType;
},
onError: (error) =>
showErrorToast(
error,
i18n.translate('xpack.serverlessSearch.connectors.config.connectorServiceTypeError', {
defaultMessage: 'Error updating service type',
})
),
onSuccess: (successData) => {
queryClient.setQueryData(queryKey, {
connector: { ...connector, service_type: successData },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@ import { ServerlessSearchContext } from './hooks/use_kibana';
export async function renderApp(
element: HTMLElement,
core: CoreStart,
services: ServerlessSearchContext
services: ServerlessSearchContext,
queryClient: QueryClient
) {
const { ConnectorsRouter } = await import('./components/connectors_router');
const queryClient = new QueryClient();

ReactDOM.render(
<KibanaThemeProvider theme={core.theme}>
<KibanaContextProvider services={{ ...core, ...services }}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ import { ServerlessSearchContext } from './hooks/use_kibana';
export async function renderApp(
element: HTMLElement,
core: CoreStart,
services: ServerlessSearchContext
services: ServerlessSearchContext,
queryClient: QueryClient
) {
const { ElasticsearchOverview } = await import('./components/overview');
const queryClient = new QueryClient();
ReactDOM.render(
<KibanaThemeProvider theme={core.theme}>
<KibanaContextProvider services={{ ...core, ...services }}>
Expand Down

This file was deleted.

32 changes: 30 additions & 2 deletions x-pack/plugins/serverless_search/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
import { i18n } from '@kbn/i18n';
import { appIds } from '@kbn/management-cards-navigation';
import { AuthenticatedUser } from '@kbn/security-plugin/common';
import { QueryClient, MutationCache, QueryCache } from '@tanstack/react-query';
import { createIndexMappingsDocsLinkContent as createIndexMappingsContent } from './application/components/index_management/index_mappings_docs_link';
import { createIndexOverviewContent } from './application/components/index_management/index_overview_content';
import { createServerlessSearchSideNavComponent as createComponent } from './layout/nav';
Expand All @@ -26,6 +27,7 @@ import {
ServerlessSearchPluginStartDependencies,
} from './types';
import { createIndexDocumentsContent } from './application/components/index_documents/documents_tab';
import { getErrorCode, getErrorMessage, isKibanaServerError } from './utils/get_error_message';

export class ServerlessSearchPlugin
implements
Expand All @@ -40,6 +42,32 @@ export class ServerlessSearchPlugin
core: CoreSetup<ServerlessSearchPluginStartDependencies, ServerlessSearchPluginStart>,
_setupDeps: ServerlessSearchPluginSetupDependencies
): ServerlessSearchPluginSetup {
const queryClient = new QueryClient({
mutationCache: new MutationCache({
onError: (error) => {
core.notifications.toasts.addError(error as Error, {
title: (error as Error).name,
toastMessage: getErrorMessage(error),
toastLifeTimeMs: 1000,
});
},
}),
queryCache: new QueryCache({
onError: (error) => {
// 404s are often functionally okay and shouldn't show toasts by default
if (getErrorCode(error) === 404) {
return;
}
if (isKibanaServerError(error) && !error.skipToast) {
core.notifications.toasts.addError(error, {
title: error.name,
toastMessage: getErrorMessage(error),
toastLifeTimeMs: 1000,
});
}
},
}),
});
core.application.register({
id: 'serverlessElasticsearch',
title: i18n.translate('xpack.serverlessSearch.app.elasticsearch.title', {
Expand All @@ -61,7 +89,7 @@ export class ServerlessSearchPlugin
user = undefined;
}

return await renderApp(element, coreStart, { history, user, ...services });
return await renderApp(element, coreStart, { history, user, ...services }, queryClient);
},
});

Expand All @@ -79,7 +107,7 @@ export class ServerlessSearchPlugin
const [coreStart, services] = await core.getStartServices();

docLinks.setDocLinks(coreStart.docLinks.links);
return await renderApp(element, coreStart, { history, ...services });
return await renderApp(element, coreStart, { history, ...services }, queryClient);
},
});
return {};
Expand Down
28 changes: 26 additions & 2 deletions x-pack/plugins/serverless_search/public/utils/get_error_message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,33 @@ export function getErrorMessage(error: unknown): string {
if (typeof error === 'string') {
return error;
}
if (typeof error === 'object') {
return (error as { body: KibanaServerError })?.body?.message || '';
if (isKibanaServerError(error)) {
return error.body.message;
}

if (typeof error === 'object' && (error as { name: string }).name) {
return (error as { name: string }).name;
}

return '';
}

export function getErrorCode(error: unknown): number | undefined {
if (isKibanaServerError(error)) {
return error.body.statusCode;
}
return undefined;
}

export function isKibanaServerError(
input: unknown
): input is Error & { body: KibanaServerError; name: string; skipToast?: boolean } {
if (
typeof input === 'object' &&
(input as { body: KibanaServerError }).body &&
typeof (input as { body: KibanaServerError }).body.message === 'string'
) {
return true;
}
return false;
}

0 comments on commit 07948b9

Please sign in to comment.