[Enterprise Search] New overview page (#136586)

This commit is contained in:
Davey Holler 2022-07-22 11:15:05 -07:00 committed by GitHub
parent 1cbf53e355
commit 4b52441a53
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 534 additions and 271 deletions

View file

@ -47,10 +47,6 @@ export const ELASTICSEARCH_PLUGIN = {
DESCRIPTION: i18n.translate('xpack.enterpriseSearch.elasticsearch.productDescription', {
defaultMessage: 'Low-level tools for creating performant and relevant search experiences.',
}),
CARD_DESCRIPTION: i18n.translate('xpack.enterpriseSearch.elasticsearch.productCardDescription', {
defaultMessage:
'Design and build performant, relevant search-powered applications or large-scale search implementations directly in Elasticsearch.',
}),
URL: '/app/enterprise_search/elasticsearch',
SUPPORT_URL: 'https://discuss.elastic.co/c/elastic-stack/elasticsearch/',
};
@ -64,10 +60,6 @@ export const APP_SEARCH_PLUGIN = {
defaultMessage:
'Leverage dashboards, analytics, and APIs for advanced application search made simple.',
}),
CARD_DESCRIPTION: i18n.translate('xpack.enterpriseSearch.appSearch.productCardDescription', {
defaultMessage:
'Design, deploy, and manage powerful search experiences for your websites and web/mobile apps.',
}),
URL: '/app/enterprise_search/app_search',
SUPPORT_URL: 'https://discuss.elastic.co/c/enterprise-search/app-search/',
};
@ -81,13 +73,6 @@ export const WORKPLACE_SEARCH_PLUGIN = {
defaultMessage:
'Search all documents, files, and sources available across your virtual workplace.',
}),
CARD_DESCRIPTION: i18n.translate(
'xpack.enterpriseSearch.workplaceSearch.productCardDescription',
{
defaultMessage:
'Unify your content in one place, with instant connectivity to popular productivity and collaboration tools.',
}
),
URL: '/app/enterprise_search/workplace_search',
NON_ADMIN_URL: '/app/enterprise_search/workplace_search/p',
SUPPORT_URL: 'https://discuss.elastic.co/c/enterprise-search/workplace-search/',

View file

@ -9,5 +9,5 @@ import { i18n } from '@kbn/i18n';
export const LICENSE_CALLOUT_BODY = i18n.translate('xpack.enterpriseSearch.licenseCalloutBody', {
defaultMessage:
'Enterprise authentication via SAML, document-level permission and authorization support, custom search experiences and more are available with a valid Platinum license.',
'Enterprise authentication via SAML, document-level permission and authorization support, custom search experiences and more are available with a valid Premium license.',
});

View file

@ -11,23 +11,22 @@ import React from 'react';
import { shallow } from 'enzyme';
import { EuiPanel, EuiText } from '@elastic/eui';
import { ManageLicenseButton } from '../../../shared/licensing';
import { EuiPanel } from '@elastic/eui';
import { LicenseCallout } from '.';
// TODO: Remove this license callout code completely (eventually)
// for now, the test is merely updated to reflect that it shouldn't
// render at all
describe('LicenseCallout', () => {
it('renders when non-platinum or on trial', () => {
it('never renders a license callout', () => {
setMockValues({
hasPlatinumLicense: false,
isTrial: true,
});
const wrapper = shallow(<LicenseCallout />);
expect(wrapper.find(EuiPanel)).toHaveLength(1);
expect(wrapper.find(EuiText)).toHaveLength(2);
expect(wrapper.find(ManageLicenseButton)).toHaveLength(1);
expect(wrapper.find(EuiPanel)).toHaveLength(0);
});
it('does not render for platinum', () => {

View file

@ -9,7 +9,7 @@ import React from 'react';
import { useValues } from 'kea';
import { EuiPanel, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui';
import { LicensingLogic, ManageLicenseButton } from '../../../shared/licensing';
@ -23,19 +23,18 @@ export const LicenseCallout: React.FC = () => {
if (hasPlatinumLicense && !isTrial) return null;
return (
<EuiPanel hasBorder color="transparent" paddingSize="l">
<EuiFlexGroup gutterSize="s" alignItems="center" justifyContent="spaceBetween">
<EuiFlexItem grow={7}>
<EuiText>
<h3>{PRODUCT_SELECTOR_CALLOUT_HEADING}</h3>
</EuiText>
<EuiText size="s">{LICENSE_CALLOUT_BODY}</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={1} />
<EuiFlexItem grow={false}>
<ManageLicenseButton />
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
<EuiFlexGroup gutterSize="s" direction="column">
<EuiFlexItem>
<EuiTitle size="xs">
<h3>{PRODUCT_SELECTOR_CALLOUT_HEADING}</h3>
</EuiTitle>
<EuiSpacer size="s" />
<EuiText size="s">{LICENSE_CALLOUT_BODY}</EuiText>
</EuiFlexItem>
<EuiFlexItem />
<EuiFlexItem>
<ManageLicenseButton />
</EuiFlexItem>
</EuiFlexGroup>
);
};

View file

@ -6,10 +6,7 @@
*/
.productCard {
&__imageContainer {
@include euiBreakpoint('xs') {
max-height: 115px;
overflow: hidden;
}
& &-features {
padding-top: 1.5rem;
}
}

View file

@ -5,67 +5,83 @@
* 2.0.
*/
import { setMockValues, mockTelemetryActions } from '../../../__mocks__/kea_logic';
import { mockTelemetryActions } from '../../../__mocks__/kea_logic';
import React from 'react';
import { shallow } from 'enzyme';
import { EuiCard } from '@elastic/eui';
import { snakeCase } from 'lodash';
import { APP_SEARCH_PLUGIN, WORKPLACE_SEARCH_PLUGIN } from '../../../../../common/constants';
import { EuiButtonTo } from '../../../shared/react_router_helpers';
import { EuiListGroup, EuiPanel } from '@elastic/eui';
import { ProductCard } from '.';
import { EuiButtonTo, EuiButtonEmptyTo } from '../../../shared/react_router_helpers';
import { ProductCard, ProductCardProps } from './product_card';
const MOCK_VALUES: ProductCardProps = {
cta: 'Click me',
description: 'Mock description',
features: ['first feature', 'second feature'],
icon: 'logoElasticsearch',
name: 'Mock product',
productId: 'mockProduct',
resourceLinks: [
{
label: 'Link one',
to: 'https://www.elastic.co/guide/one',
},
{
label: 'Link twwo',
to: 'https://www.elastic.co/guide/two',
},
],
url: '/app/mock_app',
};
describe('ProductCard', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('renders an App Search card', () => {
const wrapper = shallow(<ProductCard product={APP_SEARCH_PLUGIN} image="as.jpg" />);
const card = wrapper.find(EuiCard).dive().shallow();
it('renders a product card', () => {
const wrapper = shallow(<ProductCard {...MOCK_VALUES} />);
const card = wrapper.find(EuiPanel);
expect(card.find('h2').text()).toEqual('Elastic App Search');
expect(card.find('.productCard__image').prop('src')).toEqual('as.jpg');
expect(card.find('h3').text()).toEqual(MOCK_VALUES.name);
expect(card.find(EuiListGroup).children()).toHaveLength(MOCK_VALUES.features.length);
expect(card.find('[data-test-subj="productCard-resources"]').text()).toEqual('Resources');
expect(card.find('[data-test-subj="productCard-resourceLinks"]').children()).toHaveLength(
MOCK_VALUES.resourceLinks.length
);
const button = card.find(EuiButtonEmptyTo);
expect(button).toHaveLength(1);
expect(button.prop('to')).toEqual(MOCK_VALUES.url);
expect(card.find(EuiButtonTo)).toHaveLength(0);
button.simulate('click');
expect(mockTelemetryActions.sendEnterpriseSearchTelemetry).toHaveBeenCalledWith({
action: 'clicked',
metric: snakeCase(MOCK_VALUES.productId),
});
});
it('renders an empty cta', () => {
const wrapper = shallow(<ProductCard {...MOCK_VALUES} emptyCta />);
const card = wrapper.find(EuiPanel);
const button = card.find(EuiButtonTo);
expect(button.prop('to')).toEqual('/app/enterprise_search/app_search');
expect(button.prop('children')).toEqual('Open App Search');
expect(button).toHaveLength(1);
expect(button.prop('to')).toEqual(MOCK_VALUES.url);
expect(card.find(EuiButtonEmptyTo)).toHaveLength(0);
button.simulate('click');
expect(mockTelemetryActions.sendEnterpriseSearchTelemetry).toHaveBeenCalledWith({
action: 'clicked',
metric: 'app_search',
metric: snakeCase(MOCK_VALUES.productId),
});
});
it('renders a Workplace Search card', () => {
const wrapper = shallow(<ProductCard product={WORKPLACE_SEARCH_PLUGIN} image="ws.jpg" />);
const card = wrapper.find(EuiCard).dive().shallow();
expect(card.find('h2').text()).toEqual('Elastic Workplace Search');
expect(card.find('.productCard__image').prop('src')).toEqual('ws.jpg');
const button = card.find(EuiButtonTo);
expect(button.prop('to')).toEqual('/app/enterprise_search/workplace_search');
expect(button.prop('children')).toEqual('Open Workplace Search');
button.simulate('click');
expect(mockTelemetryActions.sendEnterpriseSearchTelemetry).toHaveBeenCalledWith({
action: 'clicked',
metric: 'workplace_search',
});
});
it('renders correct button text when host not present', () => {
setMockValues({ config: { host: '' } });
const wrapper = shallow(<ProductCard product={WORKPLACE_SEARCH_PLUGIN} image="ws.jpg" />);
const card = wrapper.find(EuiCard).dive().shallow();
const button = card.find(EuiButtonTo);
expect(button.prop('children')).toEqual('Set up Workplace Search');
});
});

View file

@ -7,81 +7,147 @@
import React from 'react';
import { useValues, useActions } from 'kea';
import { useActions } from 'kea';
import { snakeCase } from 'lodash';
import { EuiCard, EuiTextColor } from '@elastic/eui';
import {
EuiFlexGroup,
EuiFlexItem,
EuiIcon,
EuiLink,
EuiListGroup,
EuiListGroupItem,
EuiPanel,
EuiSpacer,
EuiText,
EuiTitle,
IconType,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { KibanaLogic } from '../../../shared/kibana';
import { EuiButtonTo } from '../../../shared/react_router_helpers';
import { EuiButtonTo, EuiButtonEmptyTo } from '../../../shared/react_router_helpers';
import { TelemetryLogic } from '../../../shared/telemetry';
import './product_card.scss';
interface ProductCardProps {
// Expects product plugin constants (@see common/constants.ts)
product: {
ID: string;
NAME: string;
CARD_DESCRIPTION: string;
URL: string;
};
image: string;
url?: string;
interface ProductResourceLink {
label: string;
to: string;
}
export const ProductCard: React.FC<ProductCardProps> = ({ product, image, url }) => {
export interface ProductCardProps {
cta: string;
description: string;
emptyCta?: boolean;
features: string[];
icon: IconType;
name: string;
productId: string;
resourceLinks: ProductResourceLink[];
url: string;
}
export const ProductCard: React.FC<ProductCardProps> = ({
cta,
description,
emptyCta = false,
features,
icon,
productId,
name,
resourceLinks,
url,
}) => {
const { sendEnterpriseSearchTelemetry } = useActions(TelemetryLogic);
const { config } = useValues(KibanaLogic);
const LAUNCH_BUTTON_TEXT = i18n.translate(
'xpack.enterpriseSearch.overview.productCard.launchButton',
{
defaultMessage: 'Open {productName}',
values: { productName: product.NAME },
}
);
const SETUP_BUTTON_TEXT = i18n.translate(
'xpack.enterpriseSearch.overview.productCard.setupButton',
{
defaultMessage: 'Set up {productName}',
values: { productName: product.NAME },
}
);
return (
<EuiCard
className="productCard"
titleElement="h2"
title={i18n.translate('xpack.enterpriseSearch.overview.productCard.heading', {
defaultMessage: 'Elastic {productName}',
values: { productName: product.NAME },
})}
image={
<div className="productCard__imageContainer">
<img src={image} className="productCard__image" alt="" role="presentation" />
</div>
}
<EuiPanel
hasBorder
paddingSize="l"
description={<EuiTextColor color="subdued">{product.CARD_DESCRIPTION}</EuiTextColor>}
footer={
<EuiButtonTo
fill
to={url || product.URL}
shouldNotCreateHref
onClick={() =>
sendEnterpriseSearchTelemetry({
action: 'clicked',
metric: snakeCase(product.ID),
})
}
>
{config.host ? LAUNCH_BUTTON_TEXT : SETUP_BUTTON_TEXT}
</EuiButtonTo>
}
data-test-subj={`${product.ID}ProductCard`}
/>
data-test-subj={`${productId}ProductCard`}
className="productCard"
>
<EuiFlexGroup>
<EuiFlexItem grow={false} data-test-subj="productCard-icon">
<EuiIcon size="xl" type={icon} />
</EuiFlexItem>
<EuiFlexItem data-test-subj="productCard-details">
<EuiTitle size="s">
<h3>{name}</h3>
</EuiTitle>
<EuiSpacer size="s" />
<EuiText color="subdued" size="s">
{description}
</EuiText>
<EuiSpacer />
<div>
{emptyCta ? (
<EuiButtonTo
to={url}
shouldNotCreateHref
onClick={() =>
sendEnterpriseSearchTelemetry({
action: 'clicked',
metric: snakeCase(productId),
})
}
>
{cta}
</EuiButtonTo>
) : (
<EuiButtonEmptyTo
flush="both"
to={url}
shouldNotCreateHref
onClick={() =>
sendEnterpriseSearchTelemetry({
action: 'clicked',
metric: snakeCase(productId),
})
}
>
{cta}
</EuiButtonEmptyTo>
)}
</div>
</EuiFlexItem>
<EuiFlexItem data-test-subj="productCard-features">
<EuiListGroup flush className="productCard-features">
{features.map((item: string, index: number) => (
<EuiListGroupItem
key={index}
size="s"
label={item}
icon={<EuiIcon color="success" type="checkInCircleFilled" />}
/>
))}
</EuiListGroup>
</EuiFlexItem>
<EuiFlexItem>
<EuiTitle size="xs">
<h4 data-test-subj="productCard-resources">
{i18n.translate('xpack.enterpriseSearch.productCard.resourcesTitle', {
defaultMessage: 'Resources',
})}
</h4>
</EuiTitle>
<EuiSpacer size="s" />
<EuiFlexGroup
direction="column"
gutterSize="m"
data-test-subj="productCard-resourceLinks"
>
{resourceLinks.map((resource, index) => (
<EuiFlexItem key={index} grow={false}>
<EuiLink href={resource.to} external>
{resource.label}
</EuiLink>
</EuiFlexItem>
))}
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
);
};

View file

@ -15,8 +15,6 @@ import { EuiEmptyPrompt } from '@elastic/eui';
import { WORKPLACE_SEARCH_PLUGIN } from '../../../../../common/constants';
import { ElasticsearchCard } from '../elasticsearch_card';
import { LicenseCallout } from '../license_callout';
import { ProductCard } from '../product_card';
import { SetupGuideCta } from '../setup_guide';
import { TrialCallout } from '../trial_callout';
@ -33,10 +31,8 @@ describe('ProductSelector', () => {
setMockValues({ config: { host: '' } });
const wrapper = shallow(<ProductSelector {...props} />);
expect(wrapper.find(ProductCard)).toHaveLength(2);
expect(wrapper.find(ProductCard)).toHaveLength(3);
expect(wrapper.find(SetupGuideCta)).toHaveLength(1);
expect(wrapper.find(LicenseCallout)).toHaveLength(0);
expect(wrapper.find(ElasticsearchCard)).toHaveLength(1);
});
it('renders the trial callout', () => {
@ -60,15 +56,6 @@ describe('ProductSelector', () => {
setMockValues({ config: { host: 'localhost' } });
});
it('renders the license callout when user has access to a product', () => {
setMockValues({ config: { host: 'localhost' } });
const wrapper = shallow(
<ProductSelector {...props} access={{ hasWorkplaceSearchAccess: true }} />
);
expect(wrapper.find(LicenseCallout)).toHaveLength(1);
});
it('does not render the App Search card if the user does not have access to AS', () => {
const wrapper = shallow(
<ProductSelector
@ -77,8 +64,9 @@ describe('ProductSelector', () => {
/>
);
expect(wrapper.find(ProductCard)).toHaveLength(1);
expect(wrapper.find(ProductCard).prop('product').ID).toEqual('workplaceSearch');
expect(wrapper.find(ProductCard)).toHaveLength(2);
expect(wrapper.find('[data-test-subj="productCard-workplaceSearch"]')).toHaveLength(1);
expect(wrapper.find('[data-test-subj="productCard-elasticsearch"]')).toHaveLength(1);
});
it('does not render the Workplace Search card if the user does not have access to WS', () => {
@ -89,8 +77,9 @@ describe('ProductSelector', () => {
/>
);
expect(wrapper.find(ProductCard)).toHaveLength(1);
expect(wrapper.find(ProductCard).prop('product').ID).toEqual('appSearch');
expect(wrapper.find(ProductCard)).toHaveLength(2);
expect(wrapper.find('[data-test-subj="productCard-appSearch"]')).toHaveLength(1);
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', () => {
@ -98,8 +87,6 @@ describe('ProductSelector', () => {
expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(1);
expect(wrapper.find(ProductCard)).toHaveLength(0);
expect(wrapper.find(LicenseCallout)).toHaveLength(0);
expect(wrapper.find(ElasticsearchCard)).toHaveLength(0);
});
});
});

View file

@ -17,28 +17,23 @@ import {
EuiImage,
EuiLink,
EuiSpacer,
EuiText,
EuiTitle,
} from '@elastic/eui';
import { Chat } from '@kbn/cloud-plugin/public';
import { i18n } from '@kbn/i18n';
import {
KibanaPageTemplateSolutionNavAvatar,
NO_DATA_PAGE_TEMPLATE_PROPS,
} from '@kbn/kibana-react-plugin/public';
import { APP_SEARCH_PLUGIN, WORKPLACE_SEARCH_PLUGIN } from '../../../../../common/constants';
APP_SEARCH_PLUGIN,
ELASTICSEARCH_PLUGIN,
WORKPLACE_SEARCH_PLUGIN,
} from '../../../../../common/constants';
import { AddContentEmptyPrompt } from '../../../shared/add_content_empty_prompt';
import { docLinks } from '../../../shared/doc_links';
import { KibanaLogic } from '../../../shared/kibana';
import { SetEnterpriseSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome';
import { SendEnterpriseSearchTelemetry as SendTelemetry } from '../../../shared/telemetry';
import AppSearchImage from '../../assets/app_search.png';
import WorkplaceSearchImage from '../../assets/workplace_search.png';
import { ElasticsearchCard } from '../elasticsearch_card';
import { EnterpriseSearchOverviewPageTemplate } from '../layout';
import { LicenseCallout } from '../license_callout';
import { ProductCard } from '../product_card';
import { SetupGuideCta } from '../setup_guide';
import { TrialCallout } from '../trial_callout';
@ -74,30 +69,236 @@ export const ProductSelector: React.FC<ProductSelectorProps> = ({
const productCards = (
<>
<ElasticsearchCard />
<AddContentEmptyPrompt
title={i18n.translate('xpack.enterpriseSearch.overview.emptyPromptTitle', {
defaultMessage: 'A new start for search',
})}
buttonLabel={i18n.translate('xpack.enterpriseSearch.overview.emptyPromptButtonLabel', {
defaultMessage: 'Create an Elasticsearch index',
})}
/>
<EuiSpacer size="xxl" />
<EuiFlexGroup justifyContent="center" gutterSize="xl">
<EuiSpacer size="xxl" />
<EuiTitle>
<h3>
{i18n.translate('xpack.enterpriseSearch.overview.productSelector.title', {
defaultMessage: 'Search experiences for every use case',
})}
</h3>
</EuiTitle>
<EuiSpacer size="xl" />
<EuiFlexGroup direction="column" gutterSize="m">
<EuiFlexItem>
<ProductCard
data-test-subj="productCard-elasticsearch"
cta={i18n.translate('xpack.enterpriseSearch.elasticsearch.productCardCTA', {
defaultMessage: 'View the setup guide',
})}
description={i18n.translate(
'xpack.enterpriseSearch.elasticsearch.productCardDescription',
{
defaultMessage:
'Ideal for bespoke applications, Elasticsearch helps you build highly customizable search and offers many different ingestion methods.',
}
)}
features={[
i18n.translate('xpack.enterpriseSearch.elasticsearch.features.integrate', {
defaultMessage: 'Integrate with databases, websites, and more',
}),
i18n.translate('xpack.enterpriseSearch.elasticsearch.features.buildTooling', {
defaultMessage: 'Build custom tooling',
}),
i18n.translate(
'xpack.enterpriseSearch.elasticsearch.features.buildSearchExperiences',
{
defaultMessage: 'Build custom search experiences',
}
),
]}
emptyCta
icon="logoElasticsearch"
name={ELASTICSEARCH_PLUGIN.NAME}
productId={ELASTICSEARCH_PLUGIN.ID}
resourceLinks={[
{
label: i18n.translate(
'xpack.enterpriseSearch.elasticsearch.resources.gettingStartedLabel',
{
defaultMessage: 'Getting started with Elasticsearch',
}
),
to: '', // TODO Update docLink
},
{
label: i18n.translate(
'xpack.enterpriseSearch.elasticsearch.resources.createNewIndexLabel',
{
defaultMessage: 'Create a new index',
}
),
to: '', // TODO Update docLink
},
{
label: i18n.translate(
'xpack.enterpriseSearch.elasticsearch.resources.languageClientLabel',
{
defaultMessage: 'Set up a language client',
}
),
to: '', // TODO Update docLink
},
{
label: i18n.translate(
'xpack.enterpriseSearch.elasticsearch.resources.searchUILabel',
{
defaultMessage: 'Search UI for Elasticsearch',
}
),
to: '', // TODO Update docLink
},
]}
url={ELASTICSEARCH_PLUGIN.URL}
/>
</EuiFlexItem>
{shouldShowAppSearchCard && (
<EuiFlexItem grow={false}>
<ProductCard product={APP_SEARCH_PLUGIN} image={AppSearchImage} />
<EuiFlexItem>
<ProductCard
data-test-subj="productCard-appSearch"
cta={i18n.translate('xpack.enterpriseSearch.appSearch.productCardCTA', {
defaultMessage: 'Open App Search',
})}
description={i18n.translate(
'xpack.enterpriseSearch.appSearch.productCardDescription',
{
defaultMessage:
'Ideal for apps and websites, App Search helps you design, deploy, and manage powerful search experiences.',
}
)}
features={[
i18n.translate('xpack.enterpriseSearch.appSearch.features.ingest', {
defaultMessage: 'Ingest with a web crawler, API, or Elasticsearch',
}),
i18n.translate('xpack.enterpriseSearch.appSearch.features.managementDashboards', {
defaultMessage: 'Search management dashboards',
}),
i18n.translate('xpack.enterpriseSearch.appSearch.features.searchApis', {
defaultMessage: 'Search-optimized APIs',
}),
]}
icon="logoAppSearch"
name={APP_SEARCH_PLUGIN.NAME}
productId={APP_SEARCH_PLUGIN.ID}
resourceLinks={[
{
label: i18n.translate(
'xpack.enterpriseSearch.appSearch.resources.gettingStartedLabel',
{
defaultMessage: 'Getting started with App Search',
}
),
to: '', // TODO Update docLink
},
{
label: i18n.translate(
'xpack.enterpriseSearch.appSearch.resources.searchUILabel',
{
defaultMessage: 'Search UI for App Search',
}
),
to: '', // TODO Update docLink
},
{
label: i18n.translate(
'xpack.enterpriseSearch.appSearch.resources.searchRelevanceLabel',
{
defaultMessage: 'Tune your search relevance',
}
),
to: '', // TODO Update docLink
},
{
label: i18n.translate(
'xpack.enterpriseSearch.appSearch.resources.adaptiveRelevanceLabel',
{
defaultMessage: 'Automate with Adaptive Relevance',
}
),
to: '', // TODO Update docLink
},
]}
url={APP_SEARCH_PLUGIN.URL}
/>
</EuiFlexItem>
)}
{shouldShowWorkplaceSearchCard && (
<EuiFlexItem grow={false}>
<EuiFlexItem>
<ProductCard
product={WORKPLACE_SEARCH_PLUGIN}
data-test-subj="productCard-workplaceSearch"
cta={i18n.translate('xpack.enterpriseSearch.workplaceSearch.productCardCTA', {
defaultMessage: 'Open Workplace Search',
})}
description={i18n.translate(
'xpack.enterpriseSearch.workplaceSearch.productCardDescription',
{
defaultMessage:
'Ideal for internal teams, Workplace Search helps unify your content in one place with instant connectivity to popular productivity tools.',
}
)}
features={[
i18n.translate('xpack.enterpriseSearch.workplaceSearch.features.ingest', {
defaultMessage: 'Ingest from third-party sources',
}),
i18n.translate(
'xpack.enterpriseSearch.workplaceSearch.features.managementDashboards',
{
defaultMessage: 'Search management dashboards',
}
),
i18n.translate(
'xpack.enterpriseSearch.workplaceSearch.features.searchExperiences',
{
defaultMessage: 'Search experiences for authenticated users',
}
),
]}
icon="logoWorkplaceSearch"
name={WORKPLACE_SEARCH_PLUGIN.NAME}
productId={WORKPLACE_SEARCH_PLUGIN.ID}
resourceLinks={[
{
label: i18n.translate(
'xpack.enterpriseSearch.workplaceSearch.resources.gettingStartedLabel',
{
defaultMessage: 'Getting started with Workplace Search',
}
),
to: '', // TODO Update docLink
},
{
label: i18n.translate(
'xpack.enterpriseSearch.workplaceSearch.resources.setUpConnectorsLabel',
{
defaultMessage: 'Set up your connectors',
}
),
to: '', // TODO Update docLink
},
{
label: i18n.translate(
'xpack.enterpriseSearch.workplaceSearch.resources.managePermissionsLabel',
{
defaultMessage: 'Manage permissions',
}
),
to: '', // TODO Update docLink
},
]}
url={WORKPLACE_SEARCH_URL}
image={WorkplaceSearchImage}
/>
</EuiFlexItem>
)}
</EuiFlexGroup>
<EuiSpacer size="xxl" />
{config.host ? <LicenseCallout /> : <SetupGuideCta />}
{!config.host && <SetupGuideCta />}
</>
);
@ -152,38 +353,17 @@ export const ProductSelector: React.FC<ProductSelectorProps> = ({
/>
);
return (
<EnterpriseSearchOverviewPageTemplate {...NO_DATA_PAGE_TEMPLATE_PROPS}>
<EnterpriseSearchOverviewPageTemplate
restrictWidth
pageHeader={{
pageTitle: i18n.translate('xpack.enterpriseSearch.overview.pageTitle', {
defaultMessage: 'Welcome to Enterprise Search',
}),
}}
>
<SetPageChrome />
<SendTelemetry action="viewed" metric="overview" />
<TrialCallout />
<EuiFlexGroup responsive={false} alignItems="center">
<EuiFlexItem grow={false}>
<KibanaPageTemplateSolutionNavAvatar
name="Enterprise Search"
iconType="logoEnterpriseSearch"
size="xxl"
/>
</EuiFlexItem>
<EuiFlexItem>
<EuiText>
<h1>
{i18n.translate('xpack.enterpriseSearch.overview.heading', {
defaultMessage: 'Welcome to Elastic Enterprise Search',
})}
</h1>
<p>
{config.host
? i18n.translate('xpack.enterpriseSearch.overview.subheading', {
defaultMessage: 'Add search to your app or organization.',
})
: i18n.translate('xpack.enterpriseSearch.overview.setupHeading', {
defaultMessage: 'Choose a product to set up and get started.',
})}
</p>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="xxl" />
{shouldShowEnterpriseSearchCards ? productCards : insufficientAccessMessage}
<Chat />
</EnterpriseSearchOverviewPageTemplate>

View file

@ -10,6 +10,6 @@ import { i18n } from '@kbn/i18n';
export const PRODUCT_SELECTOR_CALLOUT_HEADING = i18n.translate(
'xpack.enterpriseSearch.productSelectorCalloutTitle',
{
defaultMessage: 'Enterprise-grade features for teams big and small',
defaultMessage: 'Upgrade to get enterprise-level features for your team',
}
);

View file

@ -2,6 +2,4 @@
@include euiBreakpoint('xs','s') {
flex-direction: column-reverse;
}
border-bottom: $euiBorderThin;
}

View file

@ -19,29 +19,39 @@ import {
EuiTitle,
EuiText,
EuiSpacer,
useEuiTheme,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { ENTERPRISE_SEARCH_CONTENT_PLUGIN } from '../../../../common/constants';
import welcomeGraphicDark from '../../../assets/images/welcome_dark.svg';
import welcomeGraphicLight from '../../../assets/images/welcome_light.svg';
import { NEW_INDEX_PATH } from '../../enterprise_search_content/routes';
import { EuiLinkTo } from '../react_router_helpers';
import searchIndicesIllustration from './search_indices.svg';
import './add_content_empty_prompt.scss';
export const AddContentEmptyPrompt: React.FC = () => {
interface EmptyPromptProps {
title?: string;
buttonLabel?: string;
}
export const AddContentEmptyPrompt: React.FC<EmptyPromptProps> = ({ title, buttonLabel }) => {
const { colorMode } = useEuiTheme();
return (
<EuiPanel color="transparent" paddingSize="l">
<EuiPanel color="transparent" paddingSize="none">
<EuiFlexGroup className="addContentEmptyPrompt" justifyContent="spaceBetween" direction="row">
<EuiFlexItem grow>
<EuiFlexGroup direction="column" responsive={false}>
<EuiFlexItem grow>
<EuiTitle>
<h2>
{i18n.translate('xpack.enterpriseSearch.overview.emptyState.heading', {
defaultMessage: 'Add content to Enterprise Search',
})}
{title ||
i18n.translate('xpack.enterpriseSearch.overview.emptyState.heading', {
defaultMessage: 'Add content to Enterprise Search',
})}
</h2>
</EuiTitle>
<EuiSpacer size="l" />
@ -49,7 +59,13 @@ export const AddContentEmptyPrompt: React.FC = () => {
<p>
{i18n.translate('xpack.enterpriseSearch.emptyState.description', {
defaultMessage:
"Data you add in Enterprise Search is called a Search index and it's searchable in both App and Workplace Search. Now you can use your connectors in App Search and your web crawlers in Workplace Search.",
'You can now easily create and add content to Elasticsearch indices with Enterprise Search - including website content with the Elastic Web Crawler or third-party data sources with Custom Connectors.',
})}
</p>
<p>
{i18n.translate('xpack.enterpriseSearch.emptyState.description.line2', {
defaultMessage:
"Whether you're building a search experience with App Search or Elasticsearch, you can now get started here.",
})}
</p>
</EuiText>
@ -62,9 +78,10 @@ export const AddContentEmptyPrompt: React.FC = () => {
shouldNotCreateHref
>
<EuiButton color="primary" fill>
{i18n.translate('xpack.enterpriseSearch.overview.emptyState.buttonTitle', {
defaultMessage: 'Add content to Enterprise Search',
})}
{buttonLabel ||
i18n.translate('xpack.enterpriseSearch.overview.emptyState.buttonTitle', {
defaultMessage: 'Add content to Enterprise Search',
})}
</EuiButton>
</EuiLinkTo>
</EuiFlexItem>
@ -82,9 +99,9 @@ export const AddContentEmptyPrompt: React.FC = () => {
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiImage
size="l"
size="xl"
float="right"
src={searchIndicesIllustration}
src={colorMode === 'LIGHT' ? welcomeGraphicLight : welcomeGraphicDark}
alt={i18n.translate('xpack.enterpriseSearch.overview.searchIndices.image.altText', {
defaultMessage: 'Search indices illustration',
})}

View file

@ -7,13 +7,13 @@
import React from 'react';
import { EuiSpacer, EuiPanel, EuiTitle, EuiLink } from '@elastic/eui';
import { EuiSpacer, EuiTitle, EuiLink } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { docLinks } from '../doc_links';
export const ElasticsearchResources: React.FC = () => (
<EuiPanel hasShadow grow={false} color="subdued">
<>
<EuiTitle size="xs">
<h4>
{i18n.translate('xpack.enterpriseSearch.overview.elasticsearchResources.title', {
@ -53,5 +53,5 @@ export const ElasticsearchResources: React.FC = () => (
defaultMessage: 'Search UI for Elasticsearch',
})}
</EuiLink>
</EuiPanel>
</>
);

View file

@ -44,15 +44,15 @@ describe('GettingStartedSteps', () => {
...rest,
}));
expect(steps[0].title).toEqual('Add your documents and data to Enterprise Search');
expect(steps[0].title).toEqual('Select or create an Elasticsearch index');
expect(steps[0].status).toEqual('current');
expect(steps[0].children.find(IconRow).length).toEqual(1);
expect(steps[1].title).toEqual('Build a search experience');
expect(steps[1].title).toEqual('Assign your index to an App Search engine');
expect(steps[1].status).toEqual('incomplete');
expect(steps[1].children.find(EuiLinkTo).prop('to')).toEqual(ELASTICSEARCH_PLUGIN.URL);
expect(steps[2].title).toEqual('Tune your search relevance');
expect(steps[2].title).toEqual('Build a client-side search experience with Search UI');
expect(steps[2].status).toEqual('incomplete');
});
});

View file

@ -29,17 +29,17 @@ export const GettingStartedSteps: React.FC<GettingStartedStepsProps> = ({ step =
{
title: i18n.translate(
'xpack.enterpriseSearch.overview.gettingStartedSteps.addData.title',
{ defaultMessage: 'Add your documents and data to Enterprise Search' }
{ defaultMessage: 'Select or create an Elasticsearch index' }
),
children: (
<>
<EuiText>
<EuiText color="subdued">
<p>
{i18n.translate(
'xpack.enterpriseSearch.overview.gettingStartedSteps.addData.message',
{
defaultMessage:
'Get started by adding your data to Enterprise Search. You can use the Elastic Web Crawler, API endpoints, existing Elasticsearch indices or third party connectors like Google Drive, Microsoft Sharepoint and more.',
'Get started by adding your data to Enterprise Search. You can use the Elastic Web Crawler, API endpoints, existing Elasticsearch indices , or third-party connectors like Google Drive, Microsoft Sharepoint and more.',
}
)}
</p>
@ -53,17 +53,17 @@ export const GettingStartedSteps: React.FC<GettingStartedStepsProps> = ({ step =
{
title: i18n.translate(
'xpack.enterpriseSearch.overview.gettingStartedSteps.buildSearchExperience.title',
{ defaultMessage: 'Build a search experience' }
{ defaultMessage: 'Assign your index to an App Search engine' }
),
children: (
<>
<EuiText>
<EuiText color="subdued">
<p>
{i18n.translate(
'xpack.enterpriseSearch.overview.gettingStartedSteps.buildSearchExperience.message',
{
defaultMessage:
'You can use Search Engines to build customized search experiences for your customers or internal teams with App Search or Workplace Search. Or you can use Search UI to connect directly to an Elasticsearch index to build client-side search experinces for your users.',
'You can use search engines to build customizable search experiences for your customers with App Search.',
}
)}
</p>
@ -88,16 +88,16 @@ export const GettingStartedSteps: React.FC<GettingStartedStepsProps> = ({ step =
{
title: i18n.translate(
'xpack.enterpriseSearch.overview.gettingStartedSteps.tuneSearchExperience.title',
{ defaultMessage: 'Tune your search relevance' }
{ defaultMessage: 'Build a client-side search experience with Search UI' }
),
children: (
<EuiText>
<EuiText color="subdued">
<p>
{i18n.translate(
'xpack.enterpriseSearch.overview.gettingStartedSteps.tuneSearchExperience.message',
{
defaultMessage:
"Dive into analytics and tune the result settings to help your users find exactly what they're looking for",
'Take full control over your client-side search experience by building with the Search UI library. Connect directly to App Search or Elasticsearch.',
}
)}
</p>

View file

@ -21,7 +21,12 @@ export const ManageLicenseButton: React.FC<EuiButtonProps> = (props) => {
const { canManageLicense } = useValues(LicensingLogic);
return canManageLicense ? (
<EuiButtonTo {...props} to="/app/management/stack/license_management" shouldNotCreateHref>
<EuiButtonTo
color="success"
{...props}
to="/app/management/stack/license_management"
shouldNotCreateHref
>
{i18n.translate('xpack.enterpriseSearch.licenseManagementLink', {
defaultMessage: 'Manage your license',
})}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 116 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 122 KiB

View file

@ -11856,7 +11856,6 @@
"xpack.enterpriseSearch.overview.gettingStartedSteps.searchWithElasticsearchLink": "Rechercher avec l'API Elasticsearch",
"xpack.enterpriseSearch.overview.gettingStartedSteps.tuneSearchExperience.message": "Plonger dans les analyses et ajuster les paramètres de recherche pour aider vos utilisateurs à trouver exactement ce qu'ils cherchent",
"xpack.enterpriseSearch.overview.gettingStartedSteps.tuneSearchExperience.title": "Ajuster votre pertinence de recherche",
"xpack.enterpriseSearch.overview.heading": "Bienvenue dans Elastic Enterprise Search",
"xpack.enterpriseSearch.overview.iconRow.api.title": "API",
"xpack.enterpriseSearch.overview.iconRow.api.tooltip": "Publier des documents sur un point de terminaison d'API à partir de vos propres applications",
"xpack.enterpriseSearch.overview.iconRow.confluence.title": "Confluence",
@ -11877,14 +11876,9 @@
"xpack.enterpriseSearch.overview.insufficientPermissionsFooterLinkLabel": "Lire la documentation",
"xpack.enterpriseSearch.overview.insufficientPermissionsTitle": "Permissions insuffisantes",
"xpack.enterpriseSearch.overview.navTitle": "Aperçu",
"xpack.enterpriseSearch.overview.productCard.heading": "Elastic {productName}",
"xpack.enterpriseSearch.overview.productCard.launchButton": "Ouvrir {productName}",
"xpack.enterpriseSearch.overview.productCard.setupButton": "Configurer {productName}",
"xpack.enterpriseSearch.overview.productName": "Enterprise Search",
"xpack.enterpriseSearch.overview.productName": "Enterprise Search",
"xpack.enterpriseSearch.overview.searchIndices.image.altText": "Illustration d'index de recherche",
"xpack.enterpriseSearch.overview.setupCta.description": "Ajoutez des fonctions de recherche à votre application ou à votre organisation interne avec Elastic App Search et Workplace Search. Regardez la vidéo pour savoir ce qu'il est possible de faire lorsque la recherche est facilitée.",
"xpack.enterpriseSearch.overview.setupHeading": "Choisissez un produit à configurer et lancez-vous.",
"xpack.enterpriseSearch.overview.subheading": "Ajoutez une fonction de recherche à votre application ou à votre organisation.",
"xpack.enterpriseSearch.overview.setupCta.description": "Ajoutez des fonctions de recherche à votre application ou à votre organisation interne avec Elastic App Search et Workplace Search. Regardez la vidéo pour savoir ce qu'il est possible de faire lorsque la recherche est facilitée.",
"xpack.enterpriseSearch.productSelectorCalloutTitle": "Des fonctionnalités de niveau entreprise pour les grandes et petites équipes",
"xpack.enterpriseSearch.readOnlyMode.warning": "Enterprise Search est en mode de lecture seule. Vous ne pourrez pas effectuer de changements tels que création, modification ou suppression.",
"xpack.enterpriseSearch.roleMapping.addRoleMappingButtonLabel": "Ajouter un mapping",

View file

@ -11848,7 +11848,6 @@
"xpack.enterpriseSearch.overview.gettingStartedSteps.searchWithElasticsearchLink": "Elasticsearch APIで検索",
"xpack.enterpriseSearch.overview.gettingStartedSteps.tuneSearchExperience.message": "分析を行い、結果設定を調整して、ユーザーが検索している内容を正確に見つけられるようにします。",
"xpack.enterpriseSearch.overview.gettingStartedSteps.tuneSearchExperience.title": "検索関連性の調整",
"xpack.enterpriseSearch.overview.heading": "Elasticエンタープライズサーチへようこそ",
"xpack.enterpriseSearch.overview.iconRow.api.title": "API",
"xpack.enterpriseSearch.overview.iconRow.api.tooltip": "独自のアプリケーションからAPIエンドポイントにドキュメントをPOST",
"xpack.enterpriseSearch.overview.iconRow.confluence.title": "Confluence",
@ -11869,14 +11868,9 @@
"xpack.enterpriseSearch.overview.insufficientPermissionsFooterLinkLabel": "ドキュメンテーションを表示",
"xpack.enterpriseSearch.overview.insufficientPermissionsTitle": "パーミッションがありません",
"xpack.enterpriseSearch.overview.navTitle": "概要",
"xpack.enterpriseSearch.overview.productCard.heading": "Elastic {productName}",
"xpack.enterpriseSearch.overview.productCard.launchButton": "{productName}を開く",
"xpack.enterpriseSearch.overview.productCard.setupButton": "{productName}をセットアップ",
"xpack.enterpriseSearch.overview.productName": "Enterprise Search",
"xpack.enterpriseSearch.overview.searchIndices.image.altText": "検索インデックスの例",
"xpack.enterpriseSearch.overview.setupCta.description": "Elastic App Search および Workplace Search を使用して、アプリまたは社内組織に検索を追加できます。検索が簡単になるとどのような利点があるのかについては、動画をご覧ください。",
"xpack.enterpriseSearch.overview.setupHeading": "セットアップする製品を選択し、開始してください。",
"xpack.enterpriseSearch.overview.subheading": "アプリまたは組織に検索機能を追加できます。",
"xpack.enterpriseSearch.productSelectorCalloutTitle": "あらゆる規模のチームに対応するエンタープライズ級の機能",
"xpack.enterpriseSearch.readOnlyMode.warning": "エンタープライズ サーチは読み取り専用モードです。作成、編集、削除などの変更を実行できません。",
"xpack.enterpriseSearch.roleMapping.addRoleMappingButtonLabel": "マッピングを追加",

View file

@ -11864,7 +11864,6 @@
"xpack.enterpriseSearch.overview.gettingStartedSteps.searchWithElasticsearchLink": "使用 Elasticsearch API 进行搜索",
"xpack.enterpriseSearch.overview.gettingStartedSteps.tuneSearchExperience.message": "深入进行分析并调整结果设置,以帮助用户准确找到他们寻找的内容",
"xpack.enterpriseSearch.overview.gettingStartedSteps.tuneSearchExperience.title": "调整搜索相关性",
"xpack.enterpriseSearch.overview.heading": "欢迎使用 Elastic 企业搜索",
"xpack.enterpriseSearch.overview.iconRow.api.title": "API",
"xpack.enterpriseSearch.overview.iconRow.api.tooltip": "从您自己的应用程序将文档 POST 到 API 终端",
"xpack.enterpriseSearch.overview.iconRow.confluence.title": "Confluence",
@ -11885,14 +11884,9 @@
"xpack.enterpriseSearch.overview.insufficientPermissionsFooterLinkLabel": "阅读文档",
"xpack.enterpriseSearch.overview.insufficientPermissionsTitle": "权限不足",
"xpack.enterpriseSearch.overview.navTitle": "概览",
"xpack.enterpriseSearch.overview.productCard.heading": "Elastic {productName}",
"xpack.enterpriseSearch.overview.productCard.launchButton": "打开 {productName}",
"xpack.enterpriseSearch.overview.productCard.setupButton": "设置 {productName}",
"xpack.enterpriseSearch.overview.productName": "Enterprise Search",
"xpack.enterpriseSearch.overview.searchIndices.image.altText": "搜索索引图示",
"xpack.enterpriseSearch.overview.setupCta.description": "通过 Elastic App Search 和 Workplace Search将搜索添加到您的应用或内部组织中。观看视频了解方便易用的搜索功能可以帮您做些什么。",
"xpack.enterpriseSearch.overview.setupHeading": "选择产品进行设置并开始使用。",
"xpack.enterpriseSearch.overview.subheading": "将搜索功能添加到您的应用或组织。",
"xpack.enterpriseSearch.productSelectorCalloutTitle": "适用于大型和小型团队的企业级功能",
"xpack.enterpriseSearch.readOnlyMode.warning": "企业搜索处于只读模式。您将无法执行更改,例如创建、编辑或删除。",
"xpack.enterpriseSearch.roleMapping.addRoleMappingButtonLabel": "添加映射",

View file

@ -33,11 +33,15 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
it('loads a landing page with product cards', async function () {
await retry.waitFor(
'AS product card visible',
'Elasticsearch product card visible',
async () => await testSubjects.exists('elasticsearchProductCard')
);
await retry.waitFor(
'App Search product card visible',
async () => await testSubjects.exists('appSearchProductCard')
);
await retry.waitFor(
'WS product card visible',
'Workplace Search product card visible',
async () => await testSubjects.exists('workplaceSearchProductCard')
);
await a11y.testAppSnapshot();