diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_router.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_router.tsx index d56fe949431c3..2ed06d68301c9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_router.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_router.tsx @@ -8,8 +8,6 @@ import React from 'react'; import { Route, Switch, Redirect } from 'react-router-dom'; -import { APP_SEARCH_PLUGIN } from '../../../../../common/constants'; -import { NotFound } from '../../../shared/not_found'; import { ENGINE_ANALYTICS_PATH, ENGINE_ANALYTICS_TOP_QUERIES_PATH, @@ -21,6 +19,7 @@ import { ENGINE_ANALYTICS_QUERY_DETAIL_PATH, } from '../../routes'; import { generateEnginePath, getEngineBreadcrumbs } from '../engine'; +import { NotFound } from '../not_found'; import { ANALYTICS_TITLE } from './constants'; import { @@ -61,10 +60,7 @@ export const AnalyticsRouter: React.FC = () => { - + ); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx index da8dd8467bb61..2d1bd32a0fff5 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx @@ -38,6 +38,7 @@ import { CurationsRouter } from '../curations'; import { DocumentDetail, Documents } from '../documents'; import { EngineOverview } from '../engine_overview'; import { AppSearchPageTemplate } from '../layout'; +import { NotFound } from '../not_found'; import { RelevanceTuning } from '../relevance_tuning'; import { ResultSettings } from '../result_settings'; import { SchemaRouter } from '../schema'; @@ -45,7 +46,7 @@ import { SearchUI } from '../search_ui'; import { SourceEngines } from '../source_engines'; import { Synonyms } from '../synonyms'; -import { EngineLogic } from './'; +import { EngineLogic, getEngineBreadcrumbs } from './'; export const EngineRouter: React.FC = () => { const { @@ -159,6 +160,9 @@ export const EngineRouter: React.FC = () => { )} + + + ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/assets/logo.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/not_found/index.ts similarity index 53% rename from x-pack/plugins/enterprise_search/public/applications/shared/not_found/assets/logo.scss rename to x-pack/plugins/enterprise_search/public/applications/app_search/components/not_found/index.ts index b157f55cbba68..482c1a58faa9c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/assets/logo.scss +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/not_found/index.ts @@ -5,18 +5,4 @@ * 2.0. */ -.logo404 { - width: $euiSize * 8; - height: $euiSize * 8; - - fill: $euiColorEmptyShade; - stroke: $euiColorLightShade; - - &__light { - fill: $euiColorLightShade; - } - - &__dark { - fill: $euiColorMediumShade; - } -} +export { NotFound } from './not_found'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/not_found/not_found.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/not_found/not_found.test.tsx new file mode 100644 index 0000000000000..6fed726eb5e0b --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/not_found/not_found.test.tsx @@ -0,0 +1,38 @@ +/* + * 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 { shallow } from 'enzyme'; + +import { NotFoundPrompt } from '../../../shared/not_found'; +import { SendAppSearchTelemetry } from '../../../shared/telemetry'; +import { AppSearchPageTemplate } from '../layout'; + +import { NotFound } from './'; + +describe('NotFound', () => { + const wrapper = shallow(); + + it('renders the shared not found prompt', () => { + expect(wrapper.find(NotFoundPrompt)).toHaveLength(1); + }); + + it('renders a telemetry error event', () => { + expect(wrapper.find(SendAppSearchTelemetry).prop('action')).toEqual('error'); + }); + + it('passes optional preceding page chrome', () => { + wrapper.setProps({ pageChrome: ['Engines', 'some-engine'] }); + + expect(wrapper.find(AppSearchPageTemplate).prop('pageChrome')).toEqual([ + 'Engines', + 'some-engine', + '404', + ]); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/not_found/not_found.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/not_found/not_found.tsx new file mode 100644 index 0000000000000..f6165fa192d57 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/not_found/not_found.tsx @@ -0,0 +1,23 @@ +/* + * 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 { APP_SEARCH_PLUGIN } from '../../../../../common/constants'; +import { PageTemplateProps } from '../../../shared/layout'; +import { NotFoundPrompt } from '../../../shared/not_found'; +import { SendAppSearchTelemetry } from '../../../shared/telemetry'; +import { AppSearchPageTemplate } from '../layout'; + +export const NotFound: React.FC = ({ pageChrome = [] }) => { + return ( + + + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx index 00acea945177a..46596cc5d6765 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx @@ -17,7 +17,7 @@ import { Redirect } from 'react-router-dom'; import { shallow, ShallowWrapper } from 'enzyme'; -import { Layout, SideNav, SideNavLink } from '../shared/layout'; +import { SideNav, SideNavLink } from '../shared/layout'; import { rerender } from '../test_helpers'; @@ -83,13 +83,6 @@ describe('AppSearchConfigured', () => { wrapper = shallow(); }); - it('renders with layout', () => { - expect(wrapper.find(Layout)).toHaveLength(1); - expect(wrapper.find(Layout).prop('readOnlyMode')).toBeFalsy(); - expect(wrapper.find(EnginesOverview)).toHaveLength(1); - expect(wrapper.find(EngineRouter)).toHaveLength(1); - }); - it('renders header actions', () => { expect(renderHeaderActions).toHaveBeenCalled(); }); @@ -98,11 +91,9 @@ describe('AppSearchConfigured', () => { expect(AppLogic).toHaveBeenCalledWith(DEFAULT_INITIAL_APP_DATA); }); - it('passes readOnlyMode state', () => { - setMockValues({ myRole: {}, readOnlyMode: true }); - rerender(wrapper); - - expect(wrapper.find(Layout).first().prop('readOnlyMode')).toEqual(true); + it('renders engine routes', () => { + expect(wrapper.find(EnginesOverview)).toHaveLength(1); + expect(wrapper.find(EngineRouter)).toHaveLength(1); }); describe('routes with ability checks', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx index d7ddad5683f38..6d049b2015487 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx @@ -14,8 +14,7 @@ import { APP_SEARCH_PLUGIN } from '../../../common/constants'; import { InitialAppData } from '../../../common/types'; import { HttpLogic } from '../shared/http'; import { KibanaLogic } from '../shared/kibana'; -import { Layout, SideNav, SideNavLink } from '../shared/layout'; -import { NotFound } from '../shared/not_found'; +import { SideNav, SideNavLink } from '../shared/layout'; import { ROLE_MAPPINGS_TITLE } from '../shared/role_mapping/constants'; @@ -28,6 +27,7 @@ import { ErrorConnecting } from './components/error_connecting'; import { KibanaHeaderActions } from './components/layout'; import { Library } from './components/library'; import { MetaEngineCreation } from './components/meta_engine_creation'; +import { NotFound } from './components/not_found'; import { RoleMappings } from './components/role_mappings'; import { Settings, SETTINGS_TITLE } from './components/settings'; import { SetupGuide } from './components/setup_guide'; @@ -85,7 +85,6 @@ export const AppSearchConfigured: React.FC> = (props) = }, } = useValues(AppLogic(props)); const { renderHeaderActions } = useValues(KibanaLogic); - const { readOnlyMode } = useValues(HttpLogic); useEffect(() => { renderHeaderActions(KibanaHeaderActions); @@ -133,13 +132,7 @@ export const AppSearchConfigured: React.FC> = (props) = )} - } readOnlyMode={readOnlyMode}> - - - - - - + ); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/assets/app_search_logo.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/assets/app_search_logo.tsx deleted file mode 100644 index 8eb2059afd3ed..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/assets/app_search_logo.tsx +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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'; - -export const AppSearchLogo: React.FC = () => ( - -); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/assets/workplace_search_logo.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/assets/workplace_search_logo.tsx deleted file mode 100644 index df5b1a1118c41..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/assets/workplace_search_logo.tsx +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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'; - -export const WorkplaceSearchLogo: React.FC = () => ( - -); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/index.ts index 482c1a58faa9c..8be374d549952 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { NotFound } from './not_found'; +export { NotFoundPrompt } from './not_found_prompt'; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.test.tsx deleted file mode 100644 index 1561224a26e42..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.test.tsx +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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 { setMockValues } from '../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiButton as EuiButtonExternal, EuiEmptyPrompt } from '@elastic/eui'; - -import { APP_SEARCH_PLUGIN, WORKPLACE_SEARCH_PLUGIN } from '../../../../common/constants'; -import { SetAppSearchChrome } from '../kibana_chrome'; - -import { AppSearchLogo } from './assets/app_search_logo'; -import { WorkplaceSearchLogo } from './assets/workplace_search_logo'; - -import { NotFound } from './'; - -describe('NotFound', () => { - it('renders an App Search 404 view', () => { - const wrapper = shallow(); - const prompt = wrapper.find(EuiEmptyPrompt).dive().shallow(); - - expect(prompt.find('h2').text()).toEqual('404 error'); - expect(prompt.find(EuiButtonExternal).prop('href')).toEqual(APP_SEARCH_PLUGIN.SUPPORT_URL); - - const logo = prompt.find(AppSearchLogo).dive().shallow(); - expect(logo.type()).toEqual('svg'); - }); - - it('renders a Workplace Search 404 view', () => { - const wrapper = shallow(); - const prompt = wrapper.find(EuiEmptyPrompt).dive().shallow(); - - expect(prompt.find('h2').text()).toEqual('404 error'); - expect(prompt.find(EuiButtonExternal).prop('href')).toEqual( - WORKPLACE_SEARCH_PLUGIN.SUPPORT_URL - ); - - const logo = prompt.find(WorkplaceSearchLogo).dive().shallow(); - expect(logo.type()).toEqual('svg'); - }); - - it('changes the support URL if the user has a gold+ license', () => { - setMockValues({ hasGoldLicense: true }); - const wrapper = shallow(); - const prompt = wrapper.find(EuiEmptyPrompt).dive().shallow(); - - expect(prompt.find(EuiButtonExternal).prop('href')).toEqual('https://support.elastic.co'); - }); - - it('passes down optional custom breadcrumbs', () => { - const wrapper = shallow( - - ); - - expect(wrapper.find(SetAppSearchChrome).prop('trail')).toEqual(['Hello', 'World']); - }); - - it('does not render anything without a valid product', () => { - const wrapper = shallow(); - - expect(wrapper.isEmptyRender()).toBe(true); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.tsx deleted file mode 100644 index f288961b72de4..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.tsx +++ /dev/null @@ -1,117 +0,0 @@ -/* - * 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 { useValues } from 'kea'; - -import { - EuiPageContent, - EuiEmptyPrompt, - EuiTitle, - EuiFlexGroup, - EuiFlexItem, - EuiButton, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { - APP_SEARCH_PLUGIN, - WORKPLACE_SEARCH_PLUGIN, - LICENSED_SUPPORT_URL, -} from '../../../../common/constants'; - -import { SetAppSearchChrome, SetWorkplaceSearchChrome } from '../kibana_chrome'; -import { BreadcrumbTrail } from '../kibana_chrome/generate_breadcrumbs'; -import { LicensingLogic } from '../licensing'; -import { EuiButtonTo } from '../react_router_helpers'; -import { SendAppSearchTelemetry, SendWorkplaceSearchTelemetry } from '../telemetry'; - -import { AppSearchLogo } from './assets/app_search_logo'; -import { WorkplaceSearchLogo } from './assets/workplace_search_logo'; -import './assets/logo.scss'; - -interface NotFoundProps { - // Expects product plugin constants (@see common/constants.ts) - product: { - ID: string; - SUPPORT_URL: string; - }; - // Optional breadcrumbs - breadcrumbs?: BreadcrumbTrail; -} - -export const NotFound: React.FC = ({ product = {}, breadcrumbs }) => { - const { hasGoldLicense } = useValues(LicensingLogic); - const supportUrl = hasGoldLicense ? LICENSED_SUPPORT_URL : product.SUPPORT_URL; - - let Logo; - let SetPageChrome; - let SendTelemetry; - - switch (product.ID) { - case APP_SEARCH_PLUGIN.ID: - Logo = AppSearchLogo; - SetPageChrome = SetAppSearchChrome; - SendTelemetry = SendAppSearchTelemetry; - break; - case WORKPLACE_SEARCH_PLUGIN.ID: - Logo = WorkplaceSearchLogo; - SetPageChrome = SetWorkplaceSearchChrome; - SendTelemetry = SendWorkplaceSearchTelemetry; - break; - default: - return null; - } - - return ( - <> - - - - - } - body={ - <> - -

- {i18n.translate('xpack.enterpriseSearch.notFound.title', { - defaultMessage: '404 error', - })} -

-
-

- {i18n.translate('xpack.enterpriseSearch.notFound.description', { - defaultMessage: 'The page you’re looking for was not found.', - })} -

- - } - actions={ - - - - {i18n.translate('xpack.enterpriseSearch.notFound.action1', { - defaultMessage: 'Back to your dashboard', - })} - - - - - {i18n.translate('xpack.enterpriseSearch.notFound.action2', { - defaultMessage: 'Contact support', - })} - - - - } - /> -
- - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found_prompt.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found_prompt.test.tsx new file mode 100644 index 0000000000000..c21aeff2780b6 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found_prompt.test.tsx @@ -0,0 +1,51 @@ +/* + * 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 { setMockValues } from '../../__mocks__/kea_logic'; + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { EuiEmptyPrompt, EuiButton } from '@elastic/eui'; + +import { EuiButtonTo } from '../react_router_helpers'; + +import { NotFoundPrompt } from './'; + +describe('NotFoundPrompt', () => { + const subject = (props?: object) => + shallow() + .find(EuiEmptyPrompt) + .dive(); + + it('renders', () => { + const wrapper = subject({ + productSupportUrl: 'https://discuss.elastic.co/c/enterprise-search/app-search/', + }); + + expect(wrapper.find('h1').text()).toEqual('404 error'); + expect(wrapper.find(EuiButtonTo).prop('to')).toEqual('/'); + expect(wrapper.find(EuiButton).prop('href')).toContain('https://discuss.elastic.co'); + }); + + it('renders with a custom "Back to dashboard" link if passed', () => { + const wrapper = subject({ + productSupportUrl: 'https://discuss.elastic.co/c/enterprise-search/workplace-search/', + backToLink: '/workplace_search/p/sources', + }); + + expect(wrapper.find(EuiButtonTo).prop('to')).toEqual('/workplace_search/p/sources'); + }); + + it('renders with a link to our licensed support URL for gold+ licenses', () => { + setMockValues({ hasGoldLicense: true }); + const wrapper = subject(); + + expect(wrapper.find(EuiButton).prop('href')).toEqual('https://support.elastic.co'); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found_prompt.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found_prompt.tsx new file mode 100644 index 0000000000000..97debd21ec16c --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found_prompt.tsx @@ -0,0 +1,65 @@ +/* + * 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 { useValues } from 'kea'; + +import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, EuiButton } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { LICENSED_SUPPORT_URL } from '../../../../common/constants'; +import { LicensingLogic } from '../licensing'; +import { EuiButtonTo } from '../react_router_helpers'; + +interface Props { + productSupportUrl: string; + backToLink?: string; +} + +export const NotFoundPrompt: React.FC = ({ productSupportUrl, backToLink = '/' }) => { + const { hasGoldLicense } = useValues(LicensingLogic); + const supportUrl = hasGoldLicense ? LICENSED_SUPPORT_URL : productSupportUrl; + + return ( + + {i18n.translate('xpack.enterpriseSearch.notFound.title', { + defaultMessage: '404 error', + })} + + } + body={ +

+ {i18n.translate('xpack.enterpriseSearch.notFound.description', { + defaultMessage: 'The page you’re looking for was not found.', + })} +

+ } + actions={ + + + + {i18n.translate('xpack.enterpriseSearch.notFound.action1', { + defaultMessage: 'Back to your dashboard', + })} + + + + + {i18n.translate('xpack.enterpriseSearch.notFound.action2', { + defaultMessage: 'Contact support', + })} + + + + } + /> + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.test.tsx index 28169afd4bdeb..2743dfc794ec6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.test.tsx @@ -5,17 +5,15 @@ * 2.0. */ -import '../__mocks__/react_router'; import '../__mocks__/shallow_useeffect.mock'; import { setMockValues, setMockActions, mockKibanaValues } from '../__mocks__/kea_logic'; +import { mockUseRouteMatch } from '../__mocks__/react_router'; import React from 'react'; import { Redirect } from 'react-router-dom'; import { shallow } from 'enzyme'; -import { Layout } from '../shared/layout'; - import { WorkplaceSearchHeaderActions } from './components/layout'; import { SourceAdded } from './views/content_sources/components/source_added'; import { ErrorState } from './views/error_state'; @@ -38,6 +36,14 @@ describe('WorkplaceSearch', () => { expect(wrapper.find(WorkplaceSearchConfigured)).toHaveLength(1); }); + + it('renders ErrorState', () => { + setMockValues({ errorConnecting: true }); + + const wrapper = shallow(); + + expect(wrapper.find(ErrorState)).toHaveLength(1); + }); }); describe('WorkplaceSearchUnconfigured', () => { @@ -56,12 +62,12 @@ describe('WorkplaceSearchConfigured', () => { beforeEach(() => { jest.clearAllMocks(); setMockActions({ initializeAppData, setContext }); + mockUseRouteMatch.mockReturnValue(false); }); - it('renders layout, chrome, and header actions', () => { + it('renders chrome and header actions', () => { const wrapper = shallow(); - expect(wrapper.find(Layout).first().prop('readOnlyMode')).toBeFalsy(); expect(wrapper.find(Overview)).toHaveLength(1); expect(mockKibanaValues.setChromeIsVisible).toHaveBeenCalledWith(true); @@ -83,22 +89,6 @@ describe('WorkplaceSearchConfigured', () => { expect(mockKibanaValues.renderHeaderActions).not.toHaveBeenCalled(); }); - it('renders ErrorState', () => { - setMockValues({ errorConnecting: true }); - - const wrapper = shallow(); - - expect(wrapper.find(ErrorState)).toHaveLength(1); - }); - - it('passes readOnlyMode state', () => { - setMockValues({ readOnlyMode: true }); - - const wrapper = shallow(); - - expect(wrapper.find(Layout).first().prop('readOnlyMode')).toEqual(true); - }); - it('renders SourceAdded', () => { const wrapper = shallow(); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx index 05018be2934b4..2daf677962163 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx @@ -6,19 +6,16 @@ */ import React, { useEffect } from 'react'; -import { Route, Redirect, Switch, useLocation } from 'react-router-dom'; +import { Route, Redirect, Switch, useRouteMatch } from 'react-router-dom'; import { useActions, useValues } from 'kea'; -import { WORKPLACE_SEARCH_PLUGIN } from '../../../common/constants'; import { InitialAppData } from '../../../common/types'; import { HttpLogic } from '../shared/http'; import { KibanaLogic } from '../shared/kibana'; -import { Layout } from '../shared/layout'; -import { NotFound } from '../shared/not_found'; import { AppLogic } from './app_logic'; -import { WorkplaceSearchNav, WorkplaceSearchHeaderActions } from './components/layout'; +import { WorkplaceSearchHeaderActions } from './components/layout'; import { GROUPS_PATH, SETUP_GUIDE_PATH, @@ -36,6 +33,7 @@ import { SourcesRouter } from './views/content_sources'; import { SourceAdded } from './views/content_sources/components/source_added'; import { ErrorState } from './views/error_state'; import { GroupsRouter } from './views/groups'; +import { NotFound } from './views/not_found'; import { Overview } from './views/overview'; import { RoleMappings } from './views/role_mappings'; import { Security } from './views/security'; @@ -44,30 +42,33 @@ import { SetupGuide } from './views/setup_guide'; export const WorkplaceSearch: React.FC = (props) => { const { config } = useValues(KibanaLogic); - return !config.host ? : ; + const { errorConnecting } = useValues(HttpLogic); + return !config.host ? ( + + ) : errorConnecting ? ( + + ) : ( + + ); }; export const WorkplaceSearchConfigured: React.FC = (props) => { const { hasInitialized } = useValues(AppLogic); const { initializeAppData, setContext } = useActions(AppLogic); const { renderHeaderActions, setChromeIsVisible } = useValues(KibanaLogic); - const { errorConnecting, readOnlyMode } = useValues(HttpLogic); - - const { pathname } = useLocation(); /** * Personal dashboard urls begin with /p/ * EX: http://localhost:5601/app/enterprise_search/workplace_search/p/sources */ - const personalSourceUrlRegex = /^\/p\//g; // matches '/p/*' - const isOrganization = !pathname.match(personalSourceUrlRegex); // TODO: Once auth is figured out, we need to have a check for the equivilent of `isAdmin`. + const isOrganization = !useRouteMatch(PERSONAL_PATH); // TODO: Once auth is figured out, we need to have a check for the equivalent of `isAdmin`. setContext(isOrganization); useEffect(() => { setChromeIsVisible(isOrganization); - }, [pathname]); + }, [isOrganization]); useEffect(() => { if (!hasInitialized) { @@ -95,6 +96,9 @@ export const WorkplaceSearchConfigured: React.FC = (props) => { + + + @@ -113,15 +117,7 @@ export const WorkplaceSearchConfigured: React.FC = (props) => { - } restrictWidth readOnlyMode={readOnlyMode}> - {errorConnecting ? ( - - ) : ( - - - - )} - + ); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.test.ts index 03f46830fafc3..2aed64af53f16 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.test.ts @@ -22,8 +22,6 @@ jest.mock('../../app_logic', () => ({ })); import { AppLogic } from '../../app_logic'; -import { NOT_FOUND_PATH } from '../../routes'; - import { SourceLogic } from './source_logic'; describe('SourceLogic', () => { @@ -176,47 +174,55 @@ describe('SourceLogic', () => { expect(initializeFederatedSummarySpy).toHaveBeenCalledWith(contentSource.id); }); - it('handles error', async () => { - const error = { - response: { - error: 'this is an error', - status: 400, - }, - }; - const promise = Promise.reject(error); - http.get.mockReturnValue(promise); - SourceLogic.actions.initializeSource(contentSource.id); - await expectedAsyncError(promise); + describe('errors', () => { + it('handles generic errors', async () => { + const mockError = Promise.reject('error'); + http.get.mockReturnValue(mockError); - expect(flashAPIErrors).toHaveBeenCalledWith(error); - }); + SourceLogic.actions.initializeSource(contentSource.id); + await expectedAsyncError(mockError); - it('handles not found state', async () => { - const error = { - response: { - error: 'this is an error', - status: 404, - }, - }; - const promise = Promise.reject(error); - http.get.mockReturnValue(promise); - SourceLogic.actions.initializeSource(contentSource.id); - await expectedAsyncError(promise); + expect(flashAPIErrors).toHaveBeenCalledWith('error'); + }); - expect(navigateToUrl).toHaveBeenCalledWith(NOT_FOUND_PATH); - }); + describe('404s', () => { + const mock404 = Promise.reject({ response: { status: 404 } }); - it('renders error messages passed in success response from server', async () => { - const errors = ['ERROR']; - const promise = Promise.resolve({ - ...contentSource, - errors, + it('redirects to the organization sources page on organization views', async () => { + AppLogic.values.isOrganization = true; + http.get.mockReturnValue(mock404); + + SourceLogic.actions.initializeSource('404ing_org_source'); + await expectedAsyncError(mock404); + + expect(navigateToUrl).toHaveBeenCalledWith('/sources'); + expect(setErrorMessage).toHaveBeenCalledWith('Source not found.'); + }); + + it('redirects to the personal dashboard sources page on personal views', async () => { + AppLogic.values.isOrganization = false; + http.get.mockReturnValue(mock404); + + SourceLogic.actions.initializeSource('404ing_personal_source'); + await expectedAsyncError(mock404); + + expect(navigateToUrl).toHaveBeenCalledWith('/p/sources'); + expect(setErrorMessage).toHaveBeenCalledWith('Source not found.'); + }); }); - http.get.mockReturnValue(promise); - SourceLogic.actions.initializeSource(contentSource.id); - await promise; - expect(setErrorMessage).toHaveBeenCalledWith(errors); + it('renders error messages passed in success response from server', async () => { + const errors = ['ERROR']; + const promise = Promise.resolve({ + ...contentSource, + errors, + }); + http.get.mockReturnValue(promise); + SourceLogic.actions.initializeSource(contentSource.id); + await promise; + + expect(setErrorMessage).toHaveBeenCalledWith(errors); + }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts index 2e6a3c65597ea..0fd44e01ae495 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts @@ -20,7 +20,7 @@ import { import { HttpLogic } from '../../../shared/http'; import { KibanaLogic } from '../../../shared/kibana'; import { AppLogic } from '../../app_logic'; -import { NOT_FOUND_PATH, SOURCES_PATH, getSourcesPath } from '../../routes'; +import { PERSONAL_SOURCES_PATH, SOURCES_PATH, getSourcesPath } from '../../routes'; import { ContentSourceFullData, Meta, DocumentSummaryItem, SourceContentItem } from '../../types'; export interface SourceActions { @@ -155,8 +155,14 @@ export const SourceLogic = kea>({ clearFlashMessages(); } } catch (e) { - if (e.response.status === 404) { - KibanaLogic.values.navigateToUrl(NOT_FOUND_PATH); + if (e?.response?.status === 404) { + const redirect = isOrganization ? SOURCES_PATH : PERSONAL_SOURCES_PATH; + KibanaLogic.values.navigateToUrl(redirect); + setErrorMessage( + i18n.translate('xpack.enterpriseSearch.workplaceSearch.sources.notFoundErrorMessage', { + defaultMessage: 'Source not found.', + }) + ); } else { flashAPIErrors(e); } diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.test.tsx index afe0d1f89faea..fbc8eb159a7a8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.test.tsx @@ -90,7 +90,7 @@ describe('SourceRouter', () => { expect(wrapper.find(Overview)).toHaveLength(1); expect(wrapper.find(SourceSettings)).toHaveLength(1); expect(wrapper.find(SourceContent)).toHaveLength(1); - expect(wrapper.find(Route)).toHaveLength(3); + expect(wrapper.find(Route)).toHaveLength(4); }); it('renders source routes (custom)', () => { @@ -100,6 +100,6 @@ describe('SourceRouter', () => { expect(wrapper.find(DisplaySettingsRouter)).toHaveLength(1); expect(wrapper.find(Schema)).toHaveLength(1); expect(wrapper.find(SchemaChangeErrors)).toHaveLength(1); - expect(wrapper.find(Route)).toHaveLength(6); + expect(wrapper.find(Route)).toHaveLength(7); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.tsx index bf68a60757c0d..9f793fcd34fbe 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.tsx @@ -13,7 +13,7 @@ import { useActions, useValues } from 'kea'; import { AppLogic } from '../../app_logic'; import { WorkplaceSearchPageTemplate, PersonalDashboardLayout } from '../../components/layout'; -import { CUSTOM_SERVICE_TYPE } from '../../constants'; +import { NAV, CUSTOM_SERVICE_TYPE } from '../../constants'; import { REINDEX_JOB_PATH, SOURCE_DETAILS_PATH, @@ -24,6 +24,7 @@ import { getContentSourcePath as sourcePath, getSourcesPath, } from '../../routes'; +import { NotFound } from '../../views/not_found'; import { DisplaySettingsRouter } from './components/display_settings'; import { Overview } from './components/overview'; @@ -85,6 +86,9 @@ export const SourceRouter: React.FC = () => { + + + ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/not_found/index.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/not_found/index.ts new file mode 100644 index 0000000000000..482c1a58faa9c --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/not_found/index.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { NotFound } from './not_found'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/not_found/not_found.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/not_found/not_found.test.tsx new file mode 100644 index 0000000000000..0e388a73f0e18 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/not_found/not_found.test.tsx @@ -0,0 +1,52 @@ +/* + * 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 { shallow } from 'enzyme'; + +import { NotFoundPrompt } from '../../../shared/not_found'; +import { SendWorkplaceSearchTelemetry } from '../../../shared/telemetry'; +import { WorkplaceSearchPageTemplate, PersonalDashboardLayout } from '../../components/layout'; + +import { NotFound } from './'; + +describe('NotFound', () => { + it('renders the shared not found prompt', () => { + const wrapper = shallow(); + expect(wrapper.find(NotFoundPrompt)).toHaveLength(1); + }); + + it('renders a telemetry error event', () => { + const wrapper = shallow(); + expect(wrapper.find(SendWorkplaceSearchTelemetry).prop('action')).toEqual('error'); + }); + + it('passes optional preceding page chrome', () => { + const wrapper = shallow(); + expect(wrapper.prop('pageChrome')).toEqual(['Sources', '404']); + }); + + describe('organization views', () => { + it('renders the WorkplaceSearchPageTemplate', () => { + const wrapper = shallow(); + expect(wrapper.type()).toEqual(WorkplaceSearchPageTemplate); + }); + }); + + describe('personal views', () => { + it('renders the PersonalDashboardLayout', () => { + const wrapper = shallow(); + expect(wrapper.type()).toEqual(PersonalDashboardLayout); + }); + + it('sets the "Back to dashboard" link to /p/sources', () => { + const wrapper = shallow(); + expect(wrapper.find(NotFoundPrompt).prop('backToLink')).toEqual('/p/sources'); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/not_found/not_found.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/not_found/not_found.tsx new file mode 100644 index 0000000000000..ef55668775513 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/not_found/not_found.tsx @@ -0,0 +1,33 @@ +/* + * 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 { WORKPLACE_SEARCH_PLUGIN } from '../../../../../common/constants'; +import { PageTemplateProps } from '../../../shared/layout'; +import { NotFoundPrompt } from '../../../shared/not_found'; +import { SendWorkplaceSearchTelemetry } from '../../../shared/telemetry'; +import { WorkplaceSearchPageTemplate, PersonalDashboardLayout } from '../../components/layout'; +import { PERSONAL_SOURCES_PATH } from '../../routes'; + +interface Props { + isOrganization?: boolean; + pageChrome?: PageTemplateProps['pageChrome']; +} +export const NotFound: React.FC = ({ isOrganization = true, pageChrome = [] }) => { + const Layout = isOrganization ? WorkplaceSearchPageTemplate : PersonalDashboardLayout; + + return ( + + + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/settings_router.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/settings_router.test.tsx index 74092f17eadcf..123167f0ad1d0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/settings_router.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/settings_router.test.tsx @@ -25,8 +25,8 @@ import { SettingsRouter } from './settings_router'; describe('SettingsRouter', () => { const initializeSettings = jest.fn(); const NUM_SOURCES = staticSourceData.length; - // Should be 3 routes other than the sources listed Connectors, Customize, & OauthApplication - const NUM_ROUTES = NUM_SOURCES + 3; + // Should be 4 routes other than the sources listed: Connectors, Customize, & OauthApplication, & a redirect + const NUM_ROUTES = NUM_SOURCES + 4; beforeEach(() => { setMockActions({ initializeSettings }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/settings_router.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/settings_router.tsx index f8c8050e20153..d9aeba361d240 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/settings_router.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/settings_router.tsx @@ -11,7 +11,6 @@ import { Redirect, Route, Switch } from 'react-router-dom'; import { useActions } from 'kea'; import { - ORG_SETTINGS_PATH, ORG_SETTINGS_CUSTOMIZE_PATH, ORG_SETTINGS_CONNECTORS_PATH, ORG_SETTINGS_OAUTH_APPLICATION_PATH, @@ -33,7 +32,6 @@ export const SettingsRouter: React.FC = () => { return ( - @@ -48,6 +46,9 @@ export const SettingsRouter: React.FC = () => { ))} + + + ); };