[Enterprise Search] De-couple Overview from ent-search (#161995)

## Summary

Updated the Overview page to render an connection error callout instead
of the full ErrorConnecting page. This allows us to render the
Elasticsearch product card even without ent-search available.

This required removing the "Insufficient permissions" view since we also
always want to show the elasticsearch product card even if the user
doesn't have access to app search and workplace search.

### Screenshots
<img width="1530" alt="image"
src="2ad5b905-3f42-435b-81e5-a7d71ce8039e">
This commit is contained in:
Rodney Norris 2023-07-18 09:43:33 -05:00 committed by GitHub
parent fde21b15de
commit b5965a303f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 128 additions and 171 deletions

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 74 KiB

View file

@ -11,9 +11,9 @@ import React from 'react';
import { shallow } from 'enzyme';
import { EuiEmptyPrompt } from '@elastic/eui';
import { WORKPLACE_SEARCH_PLUGIN } from '../../../../../common/constants';
import { AddContentEmptyPrompt } from '../../../shared/add_content_empty_prompt';
import { ErrorStateCallout } from '../../../shared/error_state';
import { ProductCard } from '../product_card';
import { SetupGuideCta } from '../setup_guide';
@ -51,6 +51,40 @@ describe('ProductSelector', () => {
);
});
it('does not render connection error callout without an error', () => {
setMockValues({ config: { canDeployEntSearch: true, host: 'localhost' } });
const wrapper = shallow(<ProductSelector {...props} />);
expect(wrapper.find(ErrorStateCallout)).toHaveLength(0);
});
it('does render connection error callout with an error', () => {
setMockValues({
config: { canDeployEntSearch: true, host: 'localhost' },
errorConnectingMessage: '502 Bad Gateway',
});
const wrapper = shallow(<ProductSelector {...props} />);
expect(wrapper.find(ErrorStateCallout)).toHaveLength(1);
});
it('renders add content', () => {
setMockValues({ config: { canDeployEntSearch: true, host: 'localhost' } });
const wrapper = shallow(<ProductSelector {...props} />);
expect(wrapper.find(AddContentEmptyPrompt)).toHaveLength(1);
});
it('does not render add content when theres a connection error', () => {
setMockValues({
config: { canDeployEntSearch: true, host: 'localhost' },
errorConnectingMessage: '502 Bad Gateway',
});
const wrapper = shallow(<ProductSelector {...props} />);
expect(wrapper.find(AddContentEmptyPrompt)).toHaveLength(0);
});
describe('access checks when host is set', () => {
beforeEach(() => {
setMockValues({ config: { canDeployEntSearch: true, host: 'localhost' } });
@ -82,11 +116,11 @@ describe('ProductSelector', () => {
expect(wrapper.find('[data-test-subj="productCard-elasticsearch"]')).toHaveLength(1);
});
it('renders empty prompt and no cards or license callout if the user does not have access', () => {
it('renders elasticsearch card if the user does not have access app search & workplace search', () => {
const wrapper = shallow(<ProductSelector {...props} />);
expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(1);
expect(wrapper.find(ProductCard)).toHaveLength(0);
expect(wrapper.find(ProductCard)).toHaveLength(1);
expect(wrapper.find('[data-test-subj="productCard-elasticsearch"]')).toHaveLength(1);
});
});
});

View file

@ -9,16 +9,7 @@ import React from 'react';
import { useValues } from 'kea';
import {
EuiButton,
EuiEmptyPrompt,
EuiFlexGroup,
EuiFlexItem,
EuiImage,
EuiLink,
EuiSpacer,
EuiTitle,
} from '@elastic/eui';
import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle } from '@elastic/eui';
import { Chat } from '@kbn/cloud-chat-plugin/public';
import { i18n } from '@kbn/i18n';
@ -29,6 +20,8 @@ import {
} from '../../../../../common/constants';
import { AddContentEmptyPrompt } from '../../../shared/add_content_empty_prompt';
import { docLinks } from '../../../shared/doc_links';
import { ErrorStateCallout } from '../../../shared/error_state';
import { HttpLogic } from '../../../shared/http';
import { KibanaLogic } from '../../../shared/kibana';
import { SetSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome';
import { SendEnterpriseSearchTelemetry as SendTelemetry } from '../../../shared/telemetry';
@ -38,8 +31,6 @@ import { ProductCard } from '../product_card';
import { SetupGuideCta } from '../setup_guide';
import { TrialCallout } from '../trial_callout';
import illustration from './lock_light.svg';
interface ProductSelectorProps {
access: {
hasAppSearchAccess?: boolean;
@ -54,32 +45,53 @@ export const ProductSelector: React.FC<ProductSelectorProps> = ({
}) => {
const { hasAppSearchAccess, hasWorkplaceSearchAccess } = access;
const { config } = useValues(KibanaLogic);
const { errorConnectingMessage } = useValues(HttpLogic);
const showErrorConnecting = !!(config.host && errorConnectingMessage);
// The create index flow does not work without ent-search, when content is updated
// to no longer rely on ent-search we can always show the Add Content component
const showAddContent = config.host && !errorConnectingMessage;
// If Enterprise Search hasn't been set up yet, show all products. Otherwise, only show products the user has access to
const shouldShowAppSearchCard = (!config.host && config.canDeployEntSearch) || hasAppSearchAccess;
const shouldShowWorkplaceSearchCard =
(!config.host && config.canDeployEntSearch) || hasWorkplaceSearchAccess;
// If Enterprise Search has been set up and the user does not have access to either product, show a message saying they
// need to contact an administrator to get access to one of the products.
const shouldShowEnterpriseSearchCards =
shouldShowAppSearchCard || shouldShowWorkplaceSearchCard || !config.canDeployEntSearch;
const WORKPLACE_SEARCH_URL = isWorkplaceSearchAdmin
? WORKPLACE_SEARCH_PLUGIN.URL
: WORKPLACE_SEARCH_PLUGIN.NON_ADMIN_URL;
const productCards = (
<>
<AddContentEmptyPrompt
title={i18n.translate('xpack.enterpriseSearch.overview.emptyPromptTitle', {
defaultMessage: 'Add data and start searching',
})}
buttonLabel={i18n.translate('xpack.enterpriseSearch.overview.emptyPromptButtonLabel', {
defaultMessage: 'Create an Elasticsearch index',
})}
/>
<EuiSpacer size="xxl" />
return (
<EnterpriseSearchOverviewPageTemplate
restrictWidth
pageHeader={{
pageTitle: i18n.translate('xpack.enterpriseSearch.overview.pageTitle', {
defaultMessage: 'Welcome to Search',
}),
}}
>
<SetPageChrome />
<SendTelemetry action="viewed" metric="overview" />
<TrialCallout />
{showAddContent && (
<>
<AddContentEmptyPrompt
title={i18n.translate('xpack.enterpriseSearch.overview.emptyPromptTitle', {
defaultMessage: 'Add data and start searching',
})}
buttonLabel={i18n.translate('xpack.enterpriseSearch.overview.emptyPromptButtonLabel', {
defaultMessage: 'Create an Elasticsearch index',
})}
/>
<EuiSpacer size="xxl" />
</>
)}
{showErrorConnecting && (
<>
<SendTelemetry action="error" metric="cannot_connect" />
<ErrorStateCallout />
</>
)}
<EuiSpacer size="xxl" />
<EuiTitle>
<h3>
@ -305,72 +317,6 @@ export const ProductSelector: React.FC<ProductSelectorProps> = ({
</EuiFlexItem>
)}
</EuiFlexGroup>
</>
);
const insufficientAccessMessage = (
<EuiEmptyPrompt
icon={<EuiImage size="fullWidth" src={illustration} alt="" />}
title={
<h2>
{i18n.translate('xpack.enterpriseSearch.overview.insufficientPermissionsTitle', {
defaultMessage: 'Insufficient permissions',
})}
</h2>
}
layout="horizontal"
color="plain"
body={
<>
<p>
{i18n.translate('xpack.enterpriseSearch.overview.insufficientPermissionsBody', {
defaultMessage:
'You dont have access to view this page. If you feel this may be an error, please contact your administrator.',
})}
</p>
</>
}
actions={
<EuiButton color="primary" fill href="/">
{i18n.translate('xpack.enterpriseSearch.overview.insufficientPermissionsButtonLabel', {
defaultMessage: 'Go to the Kibana dashboard',
})}
</EuiButton>
}
footer={
<>
<EuiTitle size="xxs">
<span>
{i18n.translate('xpack.enterpriseSearch.overview.insufficientPermissionsFooterBody', {
defaultMessage: 'Go to the Kibana dashboard',
})}
</span>
</EuiTitle>{' '}
<EuiLink href={docLinks.kibanaSecurity} target="_blank">
{i18n.translate(
'xpack.enterpriseSearch.overview.insufficientPermissionsFooterLinkLabel',
{
defaultMessage: 'Read documentation',
}
)}
</EuiLink>
</>
}
/>
);
return (
<EnterpriseSearchOverviewPageTemplate
restrictWidth
pageHeader={{
pageTitle: i18n.translate('xpack.enterpriseSearch.overview.pageTitle', {
defaultMessage: 'Welcome to Enterprise Search',
}),
}}
>
<SetPageChrome />
<SendTelemetry action="viewed" metric="overview" />
<TrialCallout />
{shouldShowEnterpriseSearchCards ? productCards : insufficientAccessMessage}
<Chat />
</EnterpriseSearchOverviewPageTemplate>
);

View file

@ -12,9 +12,7 @@ import React from 'react';
import { shallow } from 'enzyme';
import { VersionMismatchPage } from '../shared/version_mismatch';
import { rerender } from '../test_helpers';
import { ErrorConnecting } from './components/error_connecting';
import { ProductSelector } from './components/product_selector';
import { SetupGuide } from './components/setup_guide';
@ -32,29 +30,6 @@ describe('EnterpriseSearchOverview', () => {
expect(wrapper.find(ProductSelector)).toHaveLength(1);
});
it('renders the error connecting prompt only if host is configured', () => {
setMockValues({
errorConnectingMessage: '502 Bad Gateway',
config: { host: 'localhost' },
});
const wrapper = shallow(<EnterpriseSearchOverview />);
expect(wrapper.find(VersionMismatchPage)).toHaveLength(0);
const errorConnecting = wrapper.find(ErrorConnecting);
expect(errorConnecting).toHaveLength(1);
expect(wrapper.find(ProductSelector)).toHaveLength(0);
setMockValues({
errorConnectingMessage: '502 Bad Gateway',
config: { host: '' },
});
rerender(wrapper);
expect(wrapper.find(VersionMismatchPage)).toHaveLength(0);
expect(wrapper.find(ErrorConnecting)).toHaveLength(0);
expect(wrapper.find(ProductSelector)).toHaveLength(1);
});
it('renders the version error message if versions mismatch and the host is configured', () => {
setMockValues({
errorConnectingMessage: '',
@ -65,7 +40,6 @@ describe('EnterpriseSearchOverview', () => {
);
expect(wrapper.find(VersionMismatchPage)).toHaveLength(1);
expect(wrapper.find(ErrorConnecting)).toHaveLength(0);
expect(wrapper.find(ProductSelector)).toHaveLength(0);
});
});

View file

@ -13,11 +13,9 @@ import { Routes, Route } from '@kbn/shared-ux-router';
import { isVersionMismatch } from '../../../common/is_version_mismatch';
import { InitialAppData } from '../../../common/types';
import { HttpLogic } from '../shared/http';
import { KibanaLogic } from '../shared/kibana';
import { VersionMismatchPage } from '../shared/version_mismatch';
import { ErrorConnecting } from './components/error_connecting';
import { ProductSelector } from './components/product_selector';
import { SetupGuide } from './components/setup_guide';
import { ROOT_PATH, SETUP_GUIDE_PATH } from './routes';
@ -28,10 +26,8 @@ export const EnterpriseSearchOverview: React.FC<InitialAppData> = ({
enterpriseSearchVersion,
kibanaVersion,
}) => {
const { errorConnectingMessage } = useValues(HttpLogic);
const { config } = useValues(KibanaLogic);
const showErrorConnecting = !!(config.host && errorConnectingMessage);
const incompatibleVersions = !!(
config.host && isVersionMismatch(enterpriseSearchVersion, kibanaVersion)
);
@ -45,8 +41,6 @@ export const EnterpriseSearchOverview: React.FC<InitialAppData> = ({
kibanaVersion={kibanaVersion}
/>
);
} else if (showErrorConnecting) {
return <ErrorConnecting />;
}
return <ProductSelector isWorkplaceSearchAdmin={isWorkplaceSearchAdmin} access={access} />;

View file

@ -9,7 +9,7 @@ import React, { useEffect } from 'react';
import { useValues } from 'kea';
import { EuiEmptyPrompt, EuiCode, EuiLink, EuiCodeBlock } from '@elastic/eui';
import { EuiEmptyPrompt, EuiCode, EuiLink, EuiCodeBlock, EuiCallOut } from '@elastic/eui';
import { CloudSetup } from '@kbn/cloud-plugin/public';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
@ -27,9 +27,7 @@ import './error_state_prompt.scss';
const WORKPLACE_SEARCH_PERSONAL_DASHBOARD_PATH = '/p/';
export const ErrorStatePrompt: React.FC = () => {
const { errorConnectingMessage } = useValues(HttpLogic);
const { config, cloud, setChromeIsVisible, history } = useValues(KibanaLogic);
const isCloudEnabled = cloud.isCloudEnabled;
const { setChromeIsVisible, history } = useValues(KibanaLogic);
const isWorkplaceSearchPersonalDashboardRoute = history.location.pathname.includes(
WORKPLACE_SEARCH_PERSONAL_DASHBOARD_PATH
);
@ -55,25 +53,7 @@ export const ErrorStatePrompt: React.FC = () => {
</h2>
}
titleSize="l"
body={
<>
<p>
<FormattedMessage
id="xpack.enterpriseSearch.errorConnectingState.description1"
defaultMessage="We cant establish a connection to Enterprise Search at the host URL {enterpriseSearchUrl} due to the following error:"
values={{
enterpriseSearchUrl: (
<EuiLink target="_blank" href={config.host} css={{ overflowWrap: 'break-word' }}>
{config.host}
</EuiLink>
),
}}
/>
</p>
<EuiCodeBlock css={{ textAlign: 'left' }}>{errorConnectingMessage}</EuiCodeBlock>
{isCloudEnabled ? cloudError(cloud) : nonCloudError()}
</>
}
body={<ErrorBody />}
actions={[
<EuiButtonTo iconType="help" fill to="/setup_guide">
<FormattedMessage
@ -86,6 +66,51 @@ export const ErrorStatePrompt: React.FC = () => {
);
};
export const ErrorStateCallout: React.FC = () => {
return (
<EuiCallOut
title={i18n.translate('xpack.enterpriseSearch.errorConnectingCallout.title', {
defaultMessage: 'Unable to connect to Enterprise Search',
})}
iconType="warning"
color="warning"
>
<ErrorBody />
<EuiButtonTo iconType="help" fill to="/setup_guide" color="warning">
<FormattedMessage
id="xpack.enterpriseSearch.errorConnectingCallout.setupGuideCta"
defaultMessage="Review setup guide"
/>
</EuiButtonTo>
</EuiCallOut>
);
};
const ErrorBody: React.FC = () => {
const { errorConnectingMessage } = useValues(HttpLogic);
const { config, cloud } = useValues(KibanaLogic);
const isCloudEnabled = cloud.isCloudEnabled;
return (
<>
<p>
<FormattedMessage
id="xpack.enterpriseSearch.errorConnectingState.description1"
defaultMessage="We cant establish a connection to Enterprise Search at the host URL {enterpriseSearchUrl} due to the following error:"
values={{
enterpriseSearchUrl: (
<EuiLink target="_blank" href={config.host} css={{ overflowWrap: 'break-word' }}>
{config.host}
</EuiLink>
),
}}
/>
</p>
<EuiCodeBlock css={{ textAlign: 'left' }}>{errorConnectingMessage}</EuiCodeBlock>
{isCloudEnabled ? cloudError(cloud) : nonCloudError()}
</>
);
};
const cloudError = (cloud: Partial<CloudSetup>) => {
const deploymentUrl = cloud?.deploymentUrl;
return (

View file

@ -5,4 +5,4 @@
* 2.0.
*/
export { ErrorStatePrompt } from './error_state_prompt';
export { ErrorStatePrompt, ErrorStateCallout } from './error_state_prompt';

View file

@ -13902,11 +13902,6 @@
"xpack.enterpriseSearch.overview.iconRow.manyMoreBadge": "Et bien plus",
"xpack.enterpriseSearch.overview.iconRow.sharePoint.title": "Microsoft SharePoint",
"xpack.enterpriseSearch.overview.iconRow.sharePoint.tooltip": "Indexer des contenus depuis Microsoft SharePoint",
"xpack.enterpriseSearch.overview.insufficientPermissionsBody": "Vous n'avez pas l'autorisation de consulter cette page. Si vous pensez qu'il s'agit d'une erreur, veuillez contacter votre administrateur.",
"xpack.enterpriseSearch.overview.insufficientPermissionsButtonLabel": "Rendez-vous sur le tableau de bord Kibana",
"xpack.enterpriseSearch.overview.insufficientPermissionsFooterBody": "Rendez-vous sur le tableau de bord Kibana",
"xpack.enterpriseSearch.overview.insufficientPermissionsFooterLinkLabel": "Lire la documentation",
"xpack.enterpriseSearch.overview.insufficientPermissionsTitle": "Permissions insuffisantes",
"xpack.enterpriseSearch.overview.navTitle": "Aperçu",
"xpack.enterpriseSearch.overview.pageTitle": "Bienvenue dans Enterprise Search",
"xpack.enterpriseSearch.overview.productSelector.title": "Des expériences de recherche pour chaque cas d'utilisation",

View file

@ -13901,11 +13901,6 @@
"xpack.enterpriseSearch.overview.iconRow.manyMoreBadge": "他多数",
"xpack.enterpriseSearch.overview.iconRow.sharePoint.title": "Microsoft SharePoint",
"xpack.enterpriseSearch.overview.iconRow.sharePoint.tooltip": "Microsoft SharePointのコンテンツにインデックスを作成",
"xpack.enterpriseSearch.overview.insufficientPermissionsBody": "このページを表示するためのアクセス権がありません。エラーだと思われる場合は、管理者に問い合わせてください。",
"xpack.enterpriseSearch.overview.insufficientPermissionsButtonLabel": "Kibanaダッシュボードに移動",
"xpack.enterpriseSearch.overview.insufficientPermissionsFooterBody": "Kibanaダッシュボードに移動",
"xpack.enterpriseSearch.overview.insufficientPermissionsFooterLinkLabel": "ドキュメンテーションを表示",
"xpack.enterpriseSearch.overview.insufficientPermissionsTitle": "パーミッションがありません",
"xpack.enterpriseSearch.overview.navTitle": "概要",
"xpack.enterpriseSearch.overview.pageTitle": "エンタープライズ サーチへようこそ",
"xpack.enterpriseSearch.overview.productSelector.title": "すべてのユースケースの検索エクスペリエンス",

View file

@ -13901,11 +13901,6 @@
"xpack.enterpriseSearch.overview.iconRow.manyMoreBadge": "以及更多其他内容",
"xpack.enterpriseSearch.overview.iconRow.sharePoint.title": "Microsoft SharePoint",
"xpack.enterpriseSearch.overview.iconRow.sharePoint.tooltip": "索引来自 Microsoft SharePoint 的内容",
"xpack.enterpriseSearch.overview.insufficientPermissionsBody": "您无权查看此页面。如果您认为这可能是个错误,请联系您的管理员。",
"xpack.enterpriseSearch.overview.insufficientPermissionsButtonLabel": "前往 Kibana 仪表板",
"xpack.enterpriseSearch.overview.insufficientPermissionsFooterBody": "前往 Kibana 仪表板",
"xpack.enterpriseSearch.overview.insufficientPermissionsFooterLinkLabel": "阅读文档",
"xpack.enterpriseSearch.overview.insufficientPermissionsTitle": "权限不足",
"xpack.enterpriseSearch.overview.navTitle": "概览",
"xpack.enterpriseSearch.overview.pageTitle": "欢迎使用 Enterprise Search",
"xpack.enterpriseSearch.overview.productSelector.title": "每个用例的搜索体验",