diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_card/product_card.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_card/product_card.test.tsx index f651511e61b44..35301af44b413 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_card/product_card.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_card/product_card.test.tsx @@ -5,8 +5,9 @@ */ import '../../../__mocks__/kea.mock'; +import '../../../__mocks__/shallow_usecontext.mock'; -import React from 'react'; +import React, { useContext } from 'react'; import { shallow } from 'enzyme'; import { EuiCard } from '@elastic/eui'; @@ -26,6 +27,7 @@ describe('ProductCard', () => { }); it('renders an App Search card', () => { + (useContext as jest.Mock).mockImplementationOnce(() => ({ config: { host: 'localhost' } })); const wrapper = shallow(); const card = wrapper.find(EuiCard).dive().shallow(); @@ -34,13 +36,14 @@ describe('ProductCard', () => { const button = card.find(EuiButton); expect(button.prop('to')).toEqual('/app/enterprise_search/app_search'); - expect(button.prop('data-test-subj')).toEqual('LaunchAppSearchButton'); + expect(button.prop('children')).toEqual('Launch App Search'); button.simulate('click'); expect(sendTelemetry).toHaveBeenCalledWith(expect.objectContaining({ metric: 'app_search' })); }); it('renders a Workplace Search card', () => { + (useContext as jest.Mock).mockImplementationOnce(() => ({ config: { host: 'localhost' } })); const wrapper = shallow(); const card = wrapper.find(EuiCard).dive().shallow(); @@ -49,11 +52,21 @@ describe('ProductCard', () => { const button = card.find(EuiButton); expect(button.prop('to')).toEqual('/app/enterprise_search/workplace_search'); - expect(button.prop('data-test-subj')).toEqual('LaunchWorkplaceSearchButton'); + expect(button.prop('children')).toEqual('Launch Workplace Search'); button.simulate('click'); expect(sendTelemetry).toHaveBeenCalledWith( expect.objectContaining({ metric: 'workplace_search' }) ); }); + + it('renders correct button text when host not present', () => { + (useContext as jest.Mock).mockImplementation(() => ({ config: { host: '' } })); + + const wrapper = shallow(); + const card = wrapper.find(EuiCard).dive().shallow(); + const button = card.find(EuiButton); + + expect(button.prop('children')).toEqual('Setup Workplace Search'); + }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_card/product_card.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_card/product_card.tsx index 833a782a32f00..482d68736af01 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_card/product_card.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_card/product_card.tsx @@ -4,13 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useContext } from 'react'; import { useValues } from 'kea'; -import upperFirst from 'lodash/upperFirst'; -import snakeCase from 'lodash/snakeCase'; +import { snakeCase } from 'lodash'; import { i18n } from '@kbn/i18n'; import { EuiCard, EuiTextColor } from '@elastic/eui'; +import { KibanaContext, IKibanaContext } from '../../../index'; + import { EuiButton } from '../../../shared/react_router_helpers'; import { sendTelemetry } from '../../../shared/telemetry'; import { HttpLogic } from '../../../shared/http'; @@ -30,6 +31,25 @@ interface IProductCard { export const ProductCard: React.FC = ({ product, image }) => { const { http } = useValues(HttpLogic); + const { + config: { host }, + } = useContext(KibanaContext) as IKibanaContext; + + const LAUNCH_BUTTON_TEXT = i18n.translate( + 'xpack.enterpriseSearch.overview.productCard.launchButton', + { + defaultMessage: 'Launch {productName}', + values: { productName: product.NAME }, + } + ); + + const SETUP_BUTTON_TEXT = i18n.translate( + 'xpack.enterpriseSearch.overview.productCard.setupButton', + { + defaultMessage: 'Setup {productName}', + values: { productName: product.NAME }, + } + ); return ( = ({ product, image }) => { metric: snakeCase(product.ID), }) } - data-test-subj={`Launch${upperFirst(product.ID)}Button`} > - {i18n.translate('xpack.enterpriseSearch.overview.productCard.button', { - defaultMessage: `Launch {productName}`, - values: { productName: product.NAME }, - })} + {host ? LAUNCH_BUTTON_TEXT : SETUP_BUTTON_TEXT} } /> diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/index.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/index.ts new file mode 100644 index 0000000000000..b67d130cd68f0 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { ProductSelector } from './product_selector'; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/product_selector.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/product_selector.test.tsx new file mode 100644 index 0000000000000..44efa57db897f --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/product_selector.test.tsx @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import '../../../__mocks__/shallow_usecontext.mock'; + +import React, { useContext } from 'react'; +import { shallow } from 'enzyme'; +import { EuiPage } from '@elastic/eui'; + +import { ProductSelector } from './'; +import { ProductCard } from '../product_card'; + +describe('ProductSelector', () => { + it('renders the overview page and product cards with no host set', () => { + (useContext as jest.Mock).mockImplementationOnce(() => ({ config: { host: '' } })); + const wrapper = shallow(); + + expect(wrapper.find(EuiPage).hasClass('enterpriseSearchOverview')).toBe(true); + expect(wrapper.find(ProductCard)).toHaveLength(2); + }); + + describe('access checks when host is set', () => { + beforeEach(() => { + (useContext as jest.Mock).mockImplementationOnce(() => ({ config: { host: 'localhost' } })); + }); + + it('does not render the App Search card if the user does not have access to AS', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find(ProductCard)).toHaveLength(1); + expect(wrapper.find(ProductCard).prop('product').ID).toEqual('workplaceSearch'); + }); + + it('does not render the Workplace Search card if the user does not have access to WS', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find(ProductCard)).toHaveLength(1); + expect(wrapper.find(ProductCard).prop('product').ID).toEqual('appSearch'); + }); + + it('does not render any cards if the user does not have access', () => { + const wrapper = shallow(); + + expect(wrapper.find(ProductCard)).toHaveLength(0); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/product_selector.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/product_selector.tsx new file mode 100644 index 0000000000000..07b8d4b9926d7 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/product_selector.tsx @@ -0,0 +1,97 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useContext } from 'react'; + +import { + EuiPage, + EuiPageBody, + EuiPageHeader, + EuiPageHeaderSection, + EuiPageContentBody, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiTitle, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { KibanaContext, IKibanaContext } from '../../../index'; + +import { APP_SEARCH_PLUGIN, WORKPLACE_SEARCH_PLUGIN } from '../../../../../common/constants'; + +import { SetEnterpriseSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; +import { SendEnterpriseSearchTelemetry as SendTelemetry } from '../../../shared/telemetry'; + +import { ProductCard } from '../product_card'; + +import AppSearchImage from '../../assets/app_search.png'; +import WorkplaceSearchImage from '../../assets/workplace_search.png'; + +interface IProductSelectorProps { + access: { + hasAppSearchAccess?: boolean; + hasWorkplaceSearchAccess?: boolean; + }; +} + +export const ProductSelector: React.FC = ({ access }) => { + const { hasAppSearchAccess, hasWorkplaceSearchAccess } = access; + const { + config: { host }, + } = useContext(KibanaContext) as IKibanaContext; + + const shouldShowAppSearchCard = !host || hasAppSearchAccess; + const shouldShowWorkplaceSearchCard = !host || hasWorkplaceSearchAccess; + + return ( + + + + + + + + +

+ {i18n.translate('xpack.enterpriseSearch.overview.heading', { + defaultMessage: 'Welcome to Elastic Enterprise Search', + })} +

+
+ +

+ {i18n.translate('xpack.enterpriseSearch.overview.subheading', { + defaultMessage: 'Select a product to get started', + })} +

+
+
+
+ + + {shouldShowAppSearchCard && ( + + + + )} + {shouldShowWorkplaceSearchCard && ( + + + + )} + + + +
+
+ ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/assets/getting_started.png b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/assets/getting_started.png new file mode 100644 index 0000000000000..f0fcb432c29e1 Binary files /dev/null and b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/assets/getting_started.png differ diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/index.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/index.ts new file mode 100644 index 0000000000000..c367424d375f9 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { SetupGuide } from './setup_guide'; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/setup_guide.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/setup_guide.test.tsx new file mode 100644 index 0000000000000..63b0cc5a56cd1 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/setup_guide.test.tsx @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { SetEnterpriseSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; +import { SetupGuide as SetupGuideLayout } from '../../../shared/setup_guide'; +import { SetupGuide } from './'; + +describe('SetupGuide', () => { + it('renders', () => { + const wrapper = shallow(); + + expect(wrapper.find(SetupGuideLayout)).toHaveLength(1); + expect(wrapper.find(SetPageChrome)).toHaveLength(1); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/setup_guide.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/setup_guide.tsx new file mode 100644 index 0000000000000..fcb3b399c75b0 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/setup_guide.tsx @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { EuiSpacer, EuiTitle, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; + +import { ENTERPRISE_SEARCH_PLUGIN } from '../../../../../common/constants'; +import { SetupGuide as SetupGuideLayout } from '../../../shared/setup_guide'; +import { SetEnterpriseSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; +import { SendEnterpriseSearchTelemetry as SendTelemetry } from '../../../shared/telemetry'; +import GettingStarted from './assets/getting_started.png'; + +export const SetupGuide: React.FC = () => ( + + + + + + {i18n.translate('xpack.enterpriseSearch.enterpriseSearch.setupGuide.videoAlt', + + + +

+ +

+
+ + +

+ +

+
+
+); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.test.tsx index b2918dac086f6..2c0902163e3d6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.test.tsx @@ -4,7 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import '../__mocks__/shallow_usecontext.mock'; + +import React, { useContext } from 'react'; import { shallow } from 'enzyme'; import { EuiPage } from '@elastic/eui'; @@ -12,54 +14,31 @@ import '../__mocks__/kea.mock'; import { useValues } from 'kea'; import { EnterpriseSearch } from './'; +import { SetupGuide } from './components/setup_guide'; import { ErrorConnecting } from './components/error_connecting'; -import { ProductCard } from './components/product_card'; +import { ProductSelector } from './components/product_selector'; describe('EnterpriseSearch', () => { beforeEach(() => { (useValues as jest.Mock).mockReturnValue({ errorConnecting: false }); + (useContext as jest.Mock).mockImplementationOnce(() => ({ config: { host: 'localhost' } })); }); - it('renders the overview page and product cards', () => { - const wrapper = shallow( - - ); + it('renders the Setup Guide and Product Selector', () => { + const wrapper = shallow(); - expect(wrapper.find(EuiPage).hasClass('enterpriseSearchOverview')).toBe(true); - expect(wrapper.find(ProductCard)).toHaveLength(2); + expect(wrapper.find(SetupGuide)).toHaveLength(1); + expect(wrapper.find(ProductSelector)).toHaveLength(1); }); - it('renders the error connecting prompt', () => { + it('renders the error connecting prompt when host is not configured', () => { (useValues as jest.Mock).mockReturnValueOnce({ errorConnecting: true }); + (useContext as jest.Mock).mockImplementationOnce(() => ({ config: { host: '' } })); + const wrapper = shallow(); expect(wrapper.find(ErrorConnecting)).toHaveLength(1); expect(wrapper.find(EuiPage)).toHaveLength(0); - }); - - describe('access checks', () => { - it('does not render the App Search card if the user does not have access to AS', () => { - const wrapper = shallow( - - ); - - expect(wrapper.find(ProductCard)).toHaveLength(1); - expect(wrapper.find(ProductCard).prop('product').ID).toEqual('workplaceSearch'); - }); - - it('does not render the Workplace Search card if the user does not have access to WS', () => { - const wrapper = shallow( - - ); - - expect(wrapper.find(ProductCard)).toHaveLength(1); - expect(wrapper.find(ProductCard).prop('product').ID).toEqual('appSearch'); - }); - - it('does not render any cards if the user does not have access', () => { - const wrapper = shallow(); - - expect(wrapper.find(ProductCard)).toHaveLength(0); - }); + expect(wrapper.find(ProductSelector)).toHaveLength(0); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.tsx index 3a3ba02e07058..e2c05434dd0bb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.tsx @@ -4,81 +4,37 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useContext } from 'react'; +import { Route, Switch } from 'react-router-dom'; import { useValues } from 'kea'; -import { - EuiPage, - EuiPageBody, - EuiPageHeader, - EuiPageHeaderSection, - EuiPageContentBody, - EuiFlexGroup, - EuiFlexItem, - EuiSpacer, - EuiTitle, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; +import { KibanaContext, IKibanaContext } from '../index'; import { IInitialAppData } from '../../../common/types'; -import { APP_SEARCH_PLUGIN, WORKPLACE_SEARCH_PLUGIN } from '../../../common/constants'; import { HttpLogic } from '../shared/http'; -import { SetEnterpriseSearchChrome as SetPageChrome } from '../shared/kibana_chrome'; -import { SendEnterpriseSearchTelemetry as SendTelemetry } from '../shared/telemetry'; + +import { ROOT_PATH, SETUP_GUIDE_PATH } from './routes'; import { ErrorConnecting } from './components/error_connecting'; -import { ProductCard } from './components/product_card'; +import { ProductSelector } from './components/product_selector'; +import { SetupGuide } from './components/setup_guide'; -import AppSearchImage from './assets/app_search.png'; -import WorkplaceSearchImage from './assets/workplace_search.png'; import './index.scss'; export const EnterpriseSearch: React.FC = ({ access = {} }) => { const { errorConnecting } = useValues(HttpLogic); - const { hasAppSearchAccess, hasWorkplaceSearchAccess } = access; - - return errorConnecting ? ( - - ) : ( - - - - - - - - -

- {i18n.translate('xpack.enterpriseSearch.overview.heading', { - defaultMessage: 'Welcome to Elastic Enterprise Search', - })} -

-
- -

- {i18n.translate('xpack.enterpriseSearch.overview.subheading', { - defaultMessage: 'Select a product to get started', - })} -

-
-
-
- - - {hasAppSearchAccess && ( - - - - )} - {hasWorkplaceSearchAccess && ( - - - - )} - - - -
-
+ const { config } = useContext(KibanaContext) as IKibanaContext; + + const showErrorConnecting = config.host && errorConnecting; + + return ( + + + + + + {showErrorConnecting ? : } + + ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/routes.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/routes.ts new file mode 100644 index 0000000000000..1f9c06e9683ab --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/routes.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const ROOT_PATH = '/'; +export const SETUP_GUIDE_PATH = '/setup_guide';