mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Enterprise Search] Show success toast on index creation (#137284)
This commit is contained in:
parent
0fe8d3f468
commit
8bf7c32c8c
10 changed files with 177 additions and 38 deletions
|
@ -92,3 +92,7 @@ export const ENTERPRISE_SEARCH_KIBANA_COOKIE = '_enterprise_search';
|
|||
|
||||
export const ENTERPRISE_SEARCH_RELEVANCE_LOGS_SOURCE_ID = 'ent-search-logs';
|
||||
export const ENTERPRISE_SEARCH_AUDIT_LOGS_SOURCE_ID = 'ent-search-audit-logs';
|
||||
|
||||
export const APP_SEARCH_URL = '/app/enterprise_search/app_search';
|
||||
export const ENTERPRISE_SEARCH_ELASTICSEARCH_URL = '/app/enterprise_search/elasticsearch';
|
||||
export const WORKPLACE_SEARCH_URL = '/app/enterprise_search/workplace_search';
|
||||
|
|
|
@ -12,6 +12,7 @@ import { useValues } from 'kea';
|
|||
import { EuiButton, EuiEmptyPrompt, EuiImage, EuiSpacer } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { EuiButtonTo } from '../../../shared/react_router_helpers';
|
||||
import { DOCS_URL } from '../../routes';
|
||||
import { DocumentCreationButtons, DocumentCreationFlyout } from '../document_creation';
|
||||
import illustration from '../document_creation/illustration.svg';
|
||||
|
@ -58,14 +59,18 @@ export const EmptyEngineOverview: React.FC = () => {
|
|||
})}
|
||||
</p>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiButton fill href="/app/management/data/index_management/indices">
|
||||
<EuiButtonTo
|
||||
fill
|
||||
to={'/app/management/data/index_management/indices'}
|
||||
shouldNotCreateHref
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.elasticsearchEngine.emptyStateButton',
|
||||
{
|
||||
defaultMessage: 'Manage indices',
|
||||
}
|
||||
)}
|
||||
</EuiButton>
|
||||
</EuiButtonTo>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
|
|
|
@ -25,7 +25,7 @@ const DEFAULT_VALUES: AddConnectorValues = {
|
|||
|
||||
describe('AddConnectorPackageLogic', () => {
|
||||
const { mount } = new LogicMounter(AddConnectorPackageLogic);
|
||||
const { flashAPIErrors, flashSuccessToast } = mockFlashMessageHelpers;
|
||||
const { flashAPIErrors } = mockFlashMessageHelpers;
|
||||
|
||||
it('has expected default values', () => {
|
||||
mount();
|
||||
|
@ -56,7 +56,6 @@ describe('AddConnectorPackageLogic', () => {
|
|||
jest.useFakeTimers();
|
||||
AddConnectorPackageApiLogic.actions.apiSuccess({ indexName: 'success' } as any);
|
||||
await nextTick();
|
||||
expect(flashSuccessToast).toHaveBeenCalled();
|
||||
jest.advanceTimersByTime(1001);
|
||||
await nextTick();
|
||||
expect(KibanaLogic.values.navigateToUrl).toHaveBeenCalledWith(
|
||||
|
|
|
@ -7,14 +7,12 @@
|
|||
|
||||
import { kea, MakeLogicType } from 'kea';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { ErrorCode } from '../../../../../../common/types/error_codes';
|
||||
|
||||
import { generateEncodedPath } from '../../../../app_search/utils/encode_path_params';
|
||||
|
||||
import { Actions } from '../../../../shared/api_logic/create_api_logic';
|
||||
import { flashAPIErrors, flashSuccessToast } from '../../../../shared/flash_messages';
|
||||
import { flashAPIErrors } from '../../../../shared/flash_messages';
|
||||
import { KibanaLogic } from '../../../../shared/kibana';
|
||||
import {
|
||||
AddConnectorPackageApiLogic,
|
||||
|
@ -46,23 +44,7 @@ export const AddConnectorPackageLogic = kea<MakeLogicType<AddConnectorValues, Ad
|
|||
listeners: {
|
||||
apiError: (error) => flashAPIErrors(error),
|
||||
apiSuccess: async ({ indexName }, breakpoint) => {
|
||||
flashSuccessToast(
|
||||
i18n.translate(
|
||||
'xpack.enterpriseSearch.content.newIndex.steps.buildConnector.successToast.label',
|
||||
{ defaultMessage: 'Index created successfully' }
|
||||
),
|
||||
{
|
||||
text: i18n.translate(
|
||||
'xpack.enterpriseSearch.content.newIndex.steps.buildConnector.successToast.description',
|
||||
{
|
||||
defaultMessage:
|
||||
'You can use App Search engines to build a search experience for your new Elasticsearch index.',
|
||||
}
|
||||
),
|
||||
}
|
||||
);
|
||||
// Flash the success toast so people can read it
|
||||
// But also give Elasticsearch the chance to propagate the index so we don't end up in an error state after navigating
|
||||
// Give Elasticsearch the chance to propagate the index so we don't end up in an error state after navigating
|
||||
await breakpoint(1000);
|
||||
KibanaLogic.values.navigateToUrl(
|
||||
generateEncodedPath(SEARCH_INDEX_TAB_PATH, {
|
||||
|
|
|
@ -21,9 +21,15 @@ import { i18n } from '@kbn/i18n';
|
|||
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import {
|
||||
APP_SEARCH_URL,
|
||||
ENTERPRISE_SEARCH_ELASTICSEARCH_URL,
|
||||
} from '../../../../../../common/constants';
|
||||
|
||||
import { HttpError, Status } from '../../../../../../common/types/api';
|
||||
import { ErrorCode } from '../../../../../../common/types/error_codes';
|
||||
import { docLinks } from '../../../../shared/doc_links';
|
||||
import { EuiLinkTo } from '../../../../shared/react_router_helpers';
|
||||
import { AddConnectorPackageApiLogic } from '../../../api/connector_package/add_connector_package_api_logic';
|
||||
|
||||
import { NewSearchIndexLogic } from '../new_search_index_logic';
|
||||
|
@ -198,13 +204,36 @@ export const MethodConnector: React.FC = () => {
|
|||
children: (
|
||||
<EuiText size="s">
|
||||
<p>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.newIndex.connector.steps.buildSearchExperience.content',
|
||||
{
|
||||
defaultMessage:
|
||||
'After building your connector, your content is ready. Build your first search experience with Elasticsearch, or explore the search experience tools provided by App Search. We recommend that you create a search engine for the best balance of flexible power and turnkey simplicity.',
|
||||
}
|
||||
)}
|
||||
<FormattedMessage
|
||||
id="xpack.enterpriseSearch.content.newIndex.connector.steps.buildSearchExperience.content"
|
||||
defaultMessage="After building your connector, your content is ready. Build your first search experience with {elasticsearchLink}, or explore the search experience tools provided by {appSearchLink}. We recommend that you create a {searchEngineLink} for the best balance of flexible power and turnkey simplicity."
|
||||
values={{
|
||||
appSearchLink: (
|
||||
<EuiLinkTo to={APP_SEARCH_URL} shouldNotCreateHref>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.newIndex.methodConnector.steps.buildConnector.appSearchLink',
|
||||
{ defaultMessage: 'App Search' }
|
||||
)}
|
||||
</EuiLinkTo>
|
||||
),
|
||||
elasticsearchLink: (
|
||||
<EuiLinkTo to={ENTERPRISE_SEARCH_ELASTICSEARCH_URL} shouldNotCreateHref>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.newIndex.methodConnector.steps.buildConnector.elasticsearchLink',
|
||||
{ defaultMessage: 'Elasticsearch' }
|
||||
)}
|
||||
</EuiLinkTo>
|
||||
),
|
||||
searchEngineLink: (
|
||||
<EuiLinkTo to={`${APP_SEARCH_URL}/engines/new`} shouldNotCreateHref>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.newIndex.methodConnector.steps.buildConnector.searchEngineLink',
|
||||
{ defaultMessage: 'search engine' }
|
||||
)}
|
||||
</EuiLinkTo>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
),
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText } from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { APP_SEARCH_URL } from '../../../../../common/constants';
|
||||
|
||||
import { flashSuccessToast } from '../../../shared/flash_messages';
|
||||
import { EuiButtonTo } from '../../../shared/react_router_helpers';
|
||||
|
||||
const SuccessToast = (
|
||||
<>
|
||||
<EuiText size="s">
|
||||
{i18n.translate('xpack.enterpriseSearch.content.new_index.successToast.description', {
|
||||
defaultMessage:
|
||||
'You can use App Search engines to build a search experience for your new Elasticsearch index.',
|
||||
})}
|
||||
</EuiText>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFlexGroup justifyContent="flexEnd" gutterSize="xs">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonTo to={`${APP_SEARCH_URL}/engines`} shouldNotCreateHref color="success">
|
||||
{i18n.translate('xpack.enterpriseSearch.content.new_index.successToast.button.label', {
|
||||
defaultMessage: 'Create an engine',
|
||||
})}
|
||||
</EuiButtonTo>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
);
|
||||
|
||||
export function flashIndexCreatedToast(): void {
|
||||
flashSuccessToast(
|
||||
i18n.translate('xpack.enterpriseSearch.content.new_index.successToast.title', {
|
||||
defaultMessage: 'Index created successfully',
|
||||
}),
|
||||
{
|
||||
iconType: 'cheer',
|
||||
text: SuccessToast,
|
||||
}
|
||||
);
|
||||
}
|
|
@ -12,8 +12,11 @@ import { nextTick } from '@kbn/test-jest-helpers';
|
|||
import { IndexExistsApiLogic } from '../../api/index/index_exists_api_logic';
|
||||
|
||||
import { UNIVERSAL_LANGUAGE_VALUE } from './constants';
|
||||
import { flashIndexCreatedToast } from './new_index_created_toast';
|
||||
import { NewSearchIndexLogic, NewSearchIndexValues } from './new_search_index_logic';
|
||||
|
||||
jest.mock('./new_index_created_toast', () => ({ flashIndexCreatedToast: jest.fn() }));
|
||||
|
||||
const DEFAULT_VALUES: NewSearchIndexValues = {
|
||||
data: undefined as any,
|
||||
fullIndexName: 'search-',
|
||||
|
@ -108,5 +111,26 @@ describe('NewSearchIndexLogic', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
describe('apiIndexCreated', () => {
|
||||
it('calls flash index created toast', () => {
|
||||
NewSearchIndexLogic.actions.apiIndexCreated({ indexName: 'indexName' });
|
||||
expect(flashIndexCreatedToast).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
describe('connectorIndexCreated', () => {
|
||||
it('calls flash index created toast', () => {
|
||||
NewSearchIndexLogic.actions.connectorIndexCreated({
|
||||
id: 'connectorId',
|
||||
indexName: 'indexName',
|
||||
});
|
||||
expect(flashIndexCreatedToast).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
describe('crawlerIndexCreated', () => {
|
||||
it('calls flash index created toast', () => {
|
||||
NewSearchIndexLogic.actions.crawlerIndexCreated({ created: 'indexName' });
|
||||
expect(flashIndexCreatedToast).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,6 +8,21 @@
|
|||
import { kea, MakeLogicType } from 'kea';
|
||||
|
||||
import { Actions } from '../../../shared/api_logic/create_api_logic';
|
||||
import {
|
||||
AddConnectorPackageApiLogic,
|
||||
AddConnectorPackageApiLogicArgs,
|
||||
AddConnectorPackageApiLogicResponse,
|
||||
} from '../../api/connector_package/add_connector_package_api_logic';
|
||||
import {
|
||||
CreateCrawlerIndexApiLogic,
|
||||
CreateCrawlerIndexArgs,
|
||||
CreateCrawlerIndexResponse,
|
||||
} from '../../api/crawler/create_crawler_index_api_logic';
|
||||
import {
|
||||
CreateApiIndexApiLogic,
|
||||
CreateApiIndexApiLogicArgs,
|
||||
CreateApiIndexApiLogicResponse,
|
||||
} from '../../api/index/create_api_index_api_logic';
|
||||
|
||||
import {
|
||||
IndexExistsApiLogic,
|
||||
|
@ -18,6 +33,7 @@ import {
|
|||
import { isValidIndexName } from '../../utils/validate_index_name';
|
||||
|
||||
import { UNIVERSAL_LANGUAGE_VALUE } from './constants';
|
||||
import { flashIndexCreatedToast } from './new_index_created_toast';
|
||||
import { LanguageForOptimization } from './types';
|
||||
import { getLanguageForOptimization } from './utils';
|
||||
|
||||
|
@ -31,12 +47,22 @@ export interface NewSearchIndexValues {
|
|||
rawName: string;
|
||||
}
|
||||
|
||||
export type NewSearchIndexActions = Pick<
|
||||
type NewSearchIndexActions = Pick<
|
||||
Actions<IndexExistsApiParams, IndexExistsApiResponse>,
|
||||
'makeRequest'
|
||||
> & {
|
||||
apiIndexCreated: Actions<
|
||||
CreateApiIndexApiLogicArgs,
|
||||
CreateApiIndexApiLogicResponse
|
||||
>['apiSuccess'];
|
||||
connectorIndexCreated: Actions<
|
||||
AddConnectorPackageApiLogicArgs,
|
||||
AddConnectorPackageApiLogicResponse
|
||||
>['apiSuccess'];
|
||||
crawlerIndexCreated: Actions<CreateCrawlerIndexArgs, CreateCrawlerIndexResponse>['apiSuccess'];
|
||||
setLanguageSelectValue(language: string): { language: string };
|
||||
setRawName(rawName: string): { rawName: string };
|
||||
showIndexCreatedCallout: () => void;
|
||||
};
|
||||
|
||||
export const NewSearchIndexLogic = kea<MakeLogicType<NewSearchIndexValues, NewSearchIndexActions>>({
|
||||
|
@ -45,10 +71,28 @@ export const NewSearchIndexLogic = kea<MakeLogicType<NewSearchIndexValues, NewSe
|
|||
setRawName: (rawName) => ({ rawName }),
|
||||
},
|
||||
connect: {
|
||||
actions: [IndexExistsApiLogic, ['makeRequest']],
|
||||
actions: [
|
||||
AddConnectorPackageApiLogic,
|
||||
['apiSuccess as connectorIndexCreated'],
|
||||
CreateApiIndexApiLogic,
|
||||
['apiSuccess as apiIndexCreated'],
|
||||
CreateCrawlerIndexApiLogic,
|
||||
['apiSuccess as crawlerIndexCreated'],
|
||||
IndexExistsApiLogic,
|
||||
['makeRequest'],
|
||||
],
|
||||
values: [IndexExistsApiLogic, ['data']],
|
||||
},
|
||||
listeners: ({ actions, values }) => ({
|
||||
apiIndexCreated: () => {
|
||||
flashIndexCreatedToast();
|
||||
},
|
||||
connectorIndexCreated: () => {
|
||||
flashIndexCreatedToast();
|
||||
},
|
||||
crawlerIndexCreated: () => {
|
||||
flashIndexCreatedToast();
|
||||
},
|
||||
setRawName: async (_, breakpoint) => {
|
||||
await breakpoint(150);
|
||||
actions.makeRequest({ indexName: values.fullIndexName });
|
||||
|
|
|
@ -19,11 +19,11 @@ export const FlashMessages: React.FC = ({ children }) => {
|
|||
|
||||
return (
|
||||
<div aria-live="polite" data-test-subj="FlashMessages">
|
||||
{messages.map(({ type, message, description }, index) => (
|
||||
{messages.map(({ type, message, description, iconType }, index) => (
|
||||
<Fragment key={index}>
|
||||
<EuiCallOut
|
||||
color={FLASH_MESSAGE_TYPES[type].color}
|
||||
iconType={FLASH_MESSAGE_TYPES[type].iconType}
|
||||
iconType={iconType ?? FLASH_MESSAGE_TYPES[type].iconType}
|
||||
title={message}
|
||||
>
|
||||
{description}
|
||||
|
|
|
@ -11,14 +11,16 @@ export type FlashMessageTypes = 'success' | 'info' | 'warning' | 'error';
|
|||
export type FlashMessageColors = 'success' | 'primary' | 'warning' | 'danger';
|
||||
|
||||
export interface IFlashMessage {
|
||||
type: FlashMessageTypes;
|
||||
message: ReactNode;
|
||||
description?: ReactNode;
|
||||
iconType?: string;
|
||||
message: ReactNode;
|
||||
type: FlashMessageTypes;
|
||||
}
|
||||
|
||||
// @see EuiGlobalToastListToast for more props
|
||||
export interface ToastOptions {
|
||||
iconType?: string;
|
||||
id?: string;
|
||||
text?: ReactChild; // Additional text below the message/title, same as IFlashMessage['description']
|
||||
toastLifeTimeMs?: number; // Allows customing per-toast timeout
|
||||
id?: string;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue