From 1f2de03941e31bd25610f44d9782f03fd0739706 Mon Sep 17 00:00:00 2001 From: Constance Date: Mon, 21 Jun 2021 11:59:52 -0700 Subject: [PATCH] [App Search] Convert Engine subnav and Engine Overview pages to new page template (#102679) * Update routers - To facilitate this being broken up into multiple PRs, non-migrated views still get a wrapping layout * Set up Engine subnav in EuiSideNav format + tweak CSS for new label - heads up that this will break the old nav, but will disappear as the pages get converted * Convert Engine Overview pages to new page template * [PR feedback] Code comment explaining side nav icon reorder Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../components/engine/engine_nav.scss | 16 +- .../components/engine/engine_nav.test.tsx | 306 +++++++++++++++++- .../components/engine/engine_nav.tsx | 261 ++++++++++++++- .../components/engine/engine_router.test.tsx | 9 +- .../components/engine/engine_router.tsx | 136 ++++---- .../engine_overview/engine_overview.test.tsx | 6 +- .../engine_overview/engine_overview.tsx | 6 +- .../engine_overview_empty.test.tsx | 12 +- .../engine_overview/engine_overview_empty.tsx | 31 +- .../engine_overview_metrics.test.tsx | 6 +- .../engine_overview_metrics.tsx | 21 +- .../app_search/components/layout/nav.test.tsx | 3 + .../app_search/components/layout/nav.tsx | 3 +- .../public/applications/app_search/index.tsx | 6 +- 14 files changed, 695 insertions(+), 127 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.scss index c750f63dab248a..486abeb3dce4c9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.scss +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.scss @@ -6,21 +6,17 @@ */ .appSearchNavEngineLabel { - padding-top: $euiSizeS; + margin-left: $euiSizeS; + padding-top: $euiSizeXS; padding-bottom: $euiSizeS; - .euiText { - font-weight: $euiFontWeightMedium; - } .euiBadge { margin-top: $euiSizeXS; } } -.appSearchNavIcons { - // EUI override - &.euiFlexItem { - flex-grow: 0; - flex-direction: row; - } +.appSearchNavIcon { + // EuiSideNav renders icons to the left of the nav link by default, but we use icons + // as warning or error indicators & prefer to render them on the right side of the nav + order: 1; } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.test.tsx index c2b0a6a50fd068..015fb997c29ed4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.test.tsx @@ -6,8 +6,14 @@ */ import { setMockValues } from '../../../__mocks__/kea_logic'; +import { mockUseRouteMatch } from '../../../__mocks__/react_router'; import { mockEngineValues } from '../../__mocks__'; +jest.mock('../../../shared/layout', () => ({ + ...jest.requireActual('../../../shared/layout'), // TODO: Remove once side nav components are gone + generateNavLink: jest.fn(({ to }) => ({ href: to })), +})); + import React from 'react'; import { shallow } from 'enzyme'; @@ -16,7 +22,305 @@ import { EuiBadge, EuiIcon } from '@elastic/eui'; import { rerender } from '../../../test_helpers'; -import { EngineNav } from './engine_nav'; +import { useEngineNav, EngineNav } from './engine_nav'; + +describe('useEngineNav', () => { + const values = { ...mockEngineValues, myRole: {}, dataLoading: false }; + + beforeEach(() => { + setMockValues(values); + mockUseRouteMatch.mockReturnValue(true); + }); + + describe('returns empty', () => { + it('does not return engine nav items if not on an engine route', () => { + mockUseRouteMatch.mockReturnValueOnce(false); + expect(useEngineNav()).toBeUndefined(); + }); + + it('does not return engine nav items if data is still loading', () => { + setMockValues({ ...values, dataLoading: true }); + expect(useEngineNav()).toBeUndefined(); + }); + + it('does not return engine nav items if engine data is missing', () => { + setMockValues({ ...values, engineName: '' }); + expect(useEngineNav()).toBeUndefined(); + }); + }); + + describe('returns an array of EUI side nav items', () => { + const BASE_NAV = [ + { + id: 'engineName', + name: 'some-engine', + renderItem: expect.any(Function), + 'data-test-subj': 'EngineLabel', + }, + { + id: 'overview', + name: 'Overview', + href: '/engines/some-engine', + 'data-test-subj': 'EngineOverviewLink', + }, + ]; + + it('always returns an engine label and overview link', () => { + expect(useEngineNav()).toEqual(BASE_NAV); + }); + + describe('engine label', () => { + const renderEngineLabel = (engineNav: any) => { + return shallow(engineNav[0].renderItem() as any); + }; + + it('renders the capitalized engine name', () => { + const wrapper = renderEngineLabel(useEngineNav()); + const name = wrapper.find('.eui-textTruncate'); + + expect(name.text()).toEqual('SOME-ENGINE'); + expect(wrapper.find(EuiBadge)).toHaveLength(0); + }); + + it('renders a sample engine badge for the sample engine', () => { + setMockValues({ ...values, isSampleEngine: true }); + const wrapper = renderEngineLabel(useEngineNav()); + + expect(wrapper.find(EuiBadge).prop('children')).toEqual('SAMPLE ENGINE'); + }); + + it('renders a meta engine badge for meta engines', () => { + setMockValues({ ...values, isMetaEngine: true }); + const wrapper = renderEngineLabel(useEngineNav()); + + expect(wrapper.find(EuiBadge).prop('children')).toEqual('META ENGINE'); + }); + }); + + it('returns an analytics nav item', () => { + setMockValues({ ...values, myRole: { canViewEngineAnalytics: true } }); + expect(useEngineNav()).toEqual([ + ...BASE_NAV, + { + id: 'analytics', + name: 'Analytics', + href: '/engines/some-engine/analytics', + 'data-test-subj': 'EngineAnalyticsLink', + }, + ]); + }); + + it('returns a documents nav item', () => { + setMockValues({ ...values, myRole: { canViewEngineDocuments: true } }); + expect(useEngineNav()).toEqual([ + ...BASE_NAV, + { + id: 'documents', + name: 'Documents', + href: '/engines/some-engine/documents', + 'data-test-subj': 'EngineDocumentsLink', + }, + ]); + }); + + it('returns a schema nav item', () => { + setMockValues({ ...values, myRole: { canViewEngineSchema: true } }); + expect(useEngineNav()).toEqual([ + ...BASE_NAV, + { + id: 'schema', + name: 'Schema', + href: '/engines/some-engine/schema', + 'data-test-subj': 'EngineSchemaLink', + icon: expect.anything(), + }, + ]); + }); + + describe('schema nav icons', () => { + const myRole = { canViewEngineSchema: true }; + + const renderIcons = (engineNav: any) => { + return shallow(
{engineNav[2].icon}
); + }; + + it('renders schema errors alert icon', () => { + setMockValues({ ...values, myRole, hasSchemaErrors: true }); + const wrapper = renderIcons(useEngineNav()); + + expect(wrapper.find('[data-test-subj="EngineNavSchemaErrors"]')).toHaveLength(1); + }); + + it('renders unconfirmed schema fields info icon', () => { + setMockValues({ ...values, myRole, hasUnconfirmedSchemaFields: true }); + const wrapper = renderIcons(useEngineNav()); + + expect(wrapper.find('[data-test-subj="EngineNavSchemaUnconfirmedFields"]')).toHaveLength(1); + }); + + it('renders schema conflicts alert icon', () => { + setMockValues({ ...values, myRole, hasSchemaConflicts: true }); + const wrapper = renderIcons(useEngineNav()); + + expect(wrapper.find('[data-test-subj="EngineNavSchemaConflicts"]')).toHaveLength(1); + }); + }); + + describe('crawler', () => { + const myRole = { canViewEngineCrawler: true }; + + it('returns a crawler nav item', () => { + setMockValues({ ...values, myRole }); + expect(useEngineNav()).toEqual([ + ...BASE_NAV, + { + id: 'crawler', + name: 'Web Crawler', + href: '/engines/some-engine/crawler', + 'data-test-subj': 'EngineCrawlerLink', + }, + ]); + }); + + it('does not return a crawler nav item for meta engines', () => { + setMockValues({ ...values, myRole, isMetaEngine: true }); + expect(useEngineNav()).toEqual(BASE_NAV); + }); + }); + + describe('meta engine source engines', () => { + const myRole = { canViewMetaEngineSourceEngines: true }; + + it('returns a source engines nav item', () => { + setMockValues({ ...values, myRole, isMetaEngine: true }); + expect(useEngineNav()).toEqual([ + ...BASE_NAV, + { + id: 'sourceEngines', + name: 'Engines', + href: '/engines/some-engine/engines', + 'data-test-subj': 'MetaEngineEnginesLink', + }, + ]); + }); + + it('does not return a source engines nav item for non-meta engines', () => { + setMockValues({ ...values, myRole, isMetaEngine: false }); + expect(useEngineNav()).toEqual(BASE_NAV); + }); + }); + + it('returns a relevance tuning nav item', () => { + setMockValues({ ...values, myRole: { canManageEngineRelevanceTuning: true } }); + expect(useEngineNav()).toEqual([ + ...BASE_NAV, + { + id: 'relevanceTuning', + name: 'Relevance Tuning', + href: '/engines/some-engine/relevance_tuning', + 'data-test-subj': 'EngineRelevanceTuningLink', + icon: expect.anything(), + }, + ]); + }); + + describe('relevance tuning nav icons', () => { + const myRole = { canManageEngineRelevanceTuning: true }; + + const renderIcons = (engineNav: any) => { + return shallow(
{engineNav[2].icon}
); + }; + + it('renders unconfirmed schema fields info icon', () => { + setMockValues({ ...values, myRole, engine: { unsearchedUnconfirmedFields: true } }); + const wrapper = renderIcons(useEngineNav()); + expect( + wrapper.find('[data-test-subj="EngineNavRelevanceTuningUnsearchedFields"]') + ).toHaveLength(1); + }); + + it('renders schema conflicts alert icon', () => { + setMockValues({ ...values, myRole, engine: { invalidBoosts: true } }); + const wrapper = renderIcons(useEngineNav()); + expect( + wrapper.find('[data-test-subj="EngineNavRelevanceTuningInvalidBoosts"]') + ).toHaveLength(1); + }); + + it('can render multiple icons', () => { + const engine = { invalidBoosts: true, unsearchedUnconfirmedFields: true }; + setMockValues({ ...values, myRole, engine }); + const wrapper = renderIcons(useEngineNav()); + expect(wrapper.find(EuiIcon)).toHaveLength(2); + }); + }); + + it('returns a synonyms nav item', () => { + setMockValues({ ...values, myRole: { canManageEngineSynonyms: true } }); + expect(useEngineNav()).toEqual([ + ...BASE_NAV, + { + id: 'synonyms', + name: 'Synonyms', + href: '/engines/some-engine/synonyms', + 'data-test-subj': 'EngineSynonymsLink', + }, + ]); + }); + + it('returns a curations nav item', () => { + setMockValues({ ...values, myRole: { canManageEngineCurations: true } }); + expect(useEngineNav()).toEqual([ + ...BASE_NAV, + { + id: 'curations', + name: 'Curations', + href: '/engines/some-engine/curations', + 'data-test-subj': 'EngineCurationsLink', + }, + ]); + }); + + it('returns a results settings nav item', () => { + setMockValues({ ...values, myRole: { canManageEngineResultSettings: true } }); + expect(useEngineNav()).toEqual([ + ...BASE_NAV, + { + id: 'resultSettings', + name: 'Result Settings', + href: '/engines/some-engine/result_settings', + 'data-test-subj': 'EngineResultSettingsLink', + }, + ]); + }); + + it('returns a Search UI nav item', () => { + setMockValues({ ...values, myRole: { canManageEngineSearchUi: true } }); + expect(useEngineNav()).toEqual([ + ...BASE_NAV, + { + id: 'searchUI', + name: 'Search UI', + href: '/engines/some-engine/search_ui', + 'data-test-subj': 'EngineSearchUILink', + }, + ]); + }); + + it('returns an API logs nav item', () => { + setMockValues({ ...values, myRole: { canViewEngineApiLogs: true } }); + expect(useEngineNav()).toEqual([ + ...BASE_NAV, + { + id: 'apiLogs', + name: 'API Logs', + href: '/engines/some-engine/api_logs', + 'data-test-subj': 'EngineAPILogsLink', + }, + ]); + }); + }); +}); describe('EngineNav', () => { const values = { ...mockEngineValues, myRole: {}, dataLoading: false }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx index 0edf01bada9381..76e751cf4da5f1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx @@ -6,13 +6,21 @@ */ import React from 'react'; +import { useRouteMatch } from 'react-router-dom'; import { useValues } from 'kea'; -import { EuiText, EuiBadge, EuiIcon, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { + EuiSideNavItemType, + EuiText, + EuiBadge, + EuiIcon, + EuiFlexGroup, + EuiFlexItem, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { SideNavLink, SideNavItem } from '../../../shared/layout'; +import { generateNavLink, SideNavLink, SideNavItem } from '../../../shared/layout'; import { AppLogic } from '../../app_logic'; import { ENGINE_PATH, @@ -47,6 +55,255 @@ import { EngineLogic, generateEnginePath } from './'; import './engine_nav.scss'; +export const useEngineNav = () => { + const isEngineRoute = !!useRouteMatch(ENGINE_PATH); + const { + myRole: { + canViewEngineAnalytics, + canViewEngineDocuments, + canViewEngineSchema, + canViewEngineCrawler, + canViewMetaEngineSourceEngines, + canManageEngineSynonyms, + canManageEngineCurations, + canManageEngineRelevanceTuning, + canManageEngineResultSettings, + canManageEngineSearchUi, + canViewEngineApiLogs, + }, + } = useValues(AppLogic); + const { + engineName, + dataLoading, + isSampleEngine, + isMetaEngine, + hasSchemaErrors, + hasSchemaConflicts, + hasUnconfirmedSchemaFields, + engine, + } = useValues(EngineLogic); + + if (!isEngineRoute) return undefined; + if (dataLoading) return undefined; + if (!engineName) return undefined; + + const navItems: Array> = [ + { + id: 'engineName', + name: engineName, + renderItem: () => ( + +
{engineName.toUpperCase()}
+ {isSampleEngine && ( + + {i18n.translate('xpack.enterpriseSearch.appSearch.engine.sampleEngineBadge', { + defaultMessage: 'SAMPLE ENGINE', + })} + + )} + {isMetaEngine && ( + + {i18n.translate('xpack.enterpriseSearch.appSearch.engine.metaEngineBadge', { + defaultMessage: 'META ENGINE', + })} + + )} +
+ ), + 'data-test-subj': 'EngineLabel', + }, + { + id: 'overview', + name: OVERVIEW_TITLE, + ...generateNavLink({ to: generateEnginePath(ENGINE_PATH) }), + 'data-test-subj': 'EngineOverviewLink', + }, + ]; + + if (canViewEngineAnalytics) { + navItems.push({ + id: 'analytics', + name: ANALYTICS_TITLE, + ...generateNavLink({ + to: generateEnginePath(ENGINE_ANALYTICS_PATH), + shouldShowActiveForSubroutes: true, + }), + 'data-test-subj': 'EngineAnalyticsLink', + }); + } + + if (canViewEngineDocuments) { + navItems.push({ + id: 'documents', + name: DOCUMENTS_TITLE, + ...generateNavLink({ + to: generateEnginePath(ENGINE_DOCUMENTS_PATH), + shouldShowActiveForSubroutes: true, + }), + 'data-test-subj': 'EngineDocumentsLink', + }); + } + + if (canViewEngineSchema) { + navItems.push({ + id: 'schema', + name: SCHEMA_TITLE, + ...generateNavLink({ + to: generateEnginePath(ENGINE_SCHEMA_PATH), + shouldShowActiveForSubroutes: true, + }), + 'data-test-subj': 'EngineSchemaLink', + icon: ( + <> + {hasSchemaErrors && ( + + )} + {hasUnconfirmedSchemaFields && ( + + )} + {hasSchemaConflicts && ( + + )} + + ), + }); + } + + if (canViewEngineCrawler && !isMetaEngine) { + navItems.push({ + id: 'crawler', + name: CRAWLER_TITLE, + ...generateNavLink({ to: generateEnginePath(ENGINE_CRAWLER_PATH) }), + 'data-test-subj': 'EngineCrawlerLink', + }); + } + + if (canViewMetaEngineSourceEngines && isMetaEngine) { + navItems.push({ + id: 'sourceEngines', + name: ENGINES_TITLE, + ...generateNavLink({ to: generateEnginePath(META_ENGINE_SOURCE_ENGINES_PATH) }), + 'data-test-subj': 'MetaEngineEnginesLink', + }); + } + + if (canManageEngineRelevanceTuning) { + const { invalidBoosts, unsearchedUnconfirmedFields } = engine; + + navItems.push({ + id: 'relevanceTuning', + name: RELEVANCE_TUNING_TITLE, + ...generateNavLink({ to: generateEnginePath(ENGINE_RELEVANCE_TUNING_PATH) }), + 'data-test-subj': 'EngineRelevanceTuningLink', + icon: ( + <> + {invalidBoosts && ( + + )} + {unsearchedUnconfirmedFields && ( + + )} + + ), + }); + } + + if (canManageEngineSynonyms) { + navItems.push({ + id: 'synonyms', + name: SYNONYMS_TITLE, + ...generateNavLink({ to: generateEnginePath(ENGINE_SYNONYMS_PATH) }), + 'data-test-subj': 'EngineSynonymsLink', + }); + } + + if (canManageEngineCurations) { + navItems.push({ + id: 'curations', + name: CURATIONS_TITLE, + ...generateNavLink({ + to: generateEnginePath(ENGINE_CURATIONS_PATH), + shouldShowActiveForSubroutes: true, + }), + 'data-test-subj': 'EngineCurationsLink', + }); + } + + if (canManageEngineResultSettings) { + navItems.push({ + id: 'resultSettings', + name: RESULT_SETTINGS_TITLE, + ...generateNavLink({ to: generateEnginePath(ENGINE_RESULT_SETTINGS_PATH) }), + 'data-test-subj': 'EngineResultSettingsLink', + }); + } + + if (canManageEngineSearchUi) { + navItems.push({ + id: 'searchUI', + name: SEARCH_UI_TITLE, + ...generateNavLink({ to: generateEnginePath(ENGINE_SEARCH_UI_PATH) }), + 'data-test-subj': 'EngineSearchUILink', + }); + } + + if (canViewEngineApiLogs) { + navItems.push({ + id: 'apiLogs', + name: API_LOGS_TITLE, + ...generateNavLink({ to: generateEnginePath(ENGINE_API_LOGS_PATH) }), + 'data-test-subj': 'EngineAPILogsLink', + }); + } + + return navItems; +}; + +// TODO: Delete the below once page template migration is complete + export const EngineNav: React.FC = () => { const { myRole: { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.test.tsx index b74c31adca4386..ee1c0578debfc0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.test.tsx @@ -19,7 +19,6 @@ import { Switch, Redirect } from 'react-router-dom'; import { shallow } from 'enzyme'; -import { Loading } from '../../../shared/loading'; import { AnalyticsRouter } from '../analytics'; import { ApiLogs } from '../api_logs'; import { CrawlerRouter } from '../crawler'; @@ -80,20 +79,20 @@ describe('EngineRouter', () => { ); }); - it('renders a loading component if async data is still loading', () => { + it('renders a loading page template if async data is still loading', () => { setMockValues({ ...values, dataLoading: true }); const wrapper = shallow(); - expect(wrapper.find(Loading)).toHaveLength(1); + expect(wrapper.prop('isLoading')).toEqual(true); }); // This would happen if a user jumps around from one engine route to another. If the engine name // on the path has changed, but we still have an engine stored in state, we do not want to load // any route views as they would be rendering with the wrong data. - it('renders a loading component if the engine stored in state is stale', () => { + it('renders a loading page template if the engine stored in state is stale', () => { setMockValues({ ...values, engineName: 'some-engine' }); mockUseParams.mockReturnValue({ engineName: 'some-new-engine' }); const wrapper = shallow(); - expect(wrapper.find(Loading)).toHaveLength(1); + expect(wrapper.prop('isLoading')).toEqual(true); }); it('renders a default engine overview', () => { 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 40cc2ef0368c05..6510e99a000fc5 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 @@ -13,11 +13,12 @@ import { useValues, useActions } from 'kea'; import { i18n } from '@kbn/i18n'; import { setQueuedErrorMessage } from '../../../shared/flash_messages'; -import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; -import { Loading } from '../../../shared/loading'; +import { Layout } from '../../../shared/layout'; import { AppLogic } from '../../app_logic'; +import { AppSearchNav } from '../../index'; import { + ENGINE_PATH, ENGINES_PATH, ENGINE_ANALYTICS_PATH, ENGINE_DOCUMENTS_PATH, @@ -38,6 +39,7 @@ import { CrawlerRouter } from '../crawler'; import { CurationsRouter } from '../curations'; import { DocumentDetail, Documents } from '../documents'; import { EngineOverview } from '../engine_overview'; +import { AppSearchPageTemplate } from '../layout'; import { RelevanceTuning } from '../relevance_tuning'; import { ResultSettings } from '../result_settings'; import { SchemaRouter } from '../schema'; @@ -45,7 +47,7 @@ import { SearchUI } from '../search_ui'; import { SourceEngines } from '../source_engines'; import { Synonyms } from '../synonyms'; -import { EngineLogic, getEngineBreadcrumbs } from './'; +import { EngineLogic } from './'; export const EngineRouter: React.FC = () => { const { @@ -85,74 +87,76 @@ export const EngineRouter: React.FC = () => { } const isLoadingNewEngine = engineName !== engineNameFromUrl; - if (isLoadingNewEngine || dataLoading) return ; + if (isLoadingNewEngine || dataLoading) return ; return ( - {canViewEngineAnalytics && ( - - - - )} - {canViewEngineDocuments && ( - - - - )} - {canViewEngineDocuments && ( - - - - )} - {canViewEngineSchema && ( - - - - )} - {canManageEngineCurations && ( - - - - )} - {canManageEngineRelevanceTuning && ( - - - - )} - {canManageEngineSynonyms && ( - - - - )} - {canManageEngineResultSettings && ( - - - - )} - {canViewEngineApiLogs && ( - - - - )} - {canManageEngineSearchUi && ( - - - - )} - {canViewMetaEngineSourceEngines && ( - - - - )} - {canViewEngineCrawler && ( - - - - )} - - + + {/* TODO: Remove layout once page template migration is over */} + }> + {canViewEngineAnalytics && ( + + + + )} + {canViewEngineDocuments && ( + + + + )} + {canViewEngineDocuments && ( + + + + )} + {canViewEngineSchema && ( + + + + )} + {canManageEngineCurations && ( + + + + )} + {canManageEngineRelevanceTuning && ( + + + + )} + {canManageEngineSynonyms && ( + + + + )} + {canManageEngineResultSettings && ( + + + + )} + {canViewEngineApiLogs && ( + + + + )} + {canManageEngineSearchUi && ( + + + + )} + {canViewMetaEngineSourceEngines && ( + + + + )} + {canViewEngineCrawler && ( + + + + )} + ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.test.tsx index a3b2f4cfd8b9f5..edacd74e046a28 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.test.tsx @@ -12,8 +12,6 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { Loading } from '../../../shared/loading'; - import { EmptyEngineOverview } from './engine_overview_empty'; import { EngineOverviewMetrics } from './engine_overview_metrics'; @@ -46,10 +44,10 @@ describe('EngineOverview', () => { expect(actions.pollForOverviewMetrics).toHaveBeenCalledTimes(1); }); - it('renders a loading component if async data is still loading', () => { + it('renders a loading page template if async data is still loading', () => { setMockValues({ ...values, dataLoading: true }); const wrapper = shallow(); - expect(wrapper.find(Loading)).toHaveLength(1); + expect(wrapper.prop('isLoading')).toEqual(true); }); describe('EmptyEngineOverview', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx index 77552b36af2391..4c15ffd8b7f947 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx @@ -9,9 +9,9 @@ import React, { useEffect } from 'react'; import { useActions, useValues } from 'kea'; -import { Loading } from '../../../shared/loading'; import { AppLogic } from '../../app_logic'; import { EngineLogic } from '../engine'; +import { AppSearchPageTemplate } from '../layout'; import { EmptyEngineOverview } from './engine_overview_empty'; @@ -32,9 +32,7 @@ export const EngineOverview: React.FC = () => { pollForOverviewMetrics(); }, []); - if (dataLoading) { - return ; - } + if (dataLoading) return ; const engineHasDocuments = documentCount > 0; const canAddDocuments = canManageEngineDocuments && canViewEngineCredentials; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.test.tsx index ea47dc8956ddd9..6750ebf1140e03 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.test.tsx @@ -5,13 +5,16 @@ * 2.0. */ +import '../../__mocks__/engine_logic.mock'; + import React from 'react'; import { shallow, ShallowWrapper } from 'enzyme'; -import { EuiPageHeader, EuiButton } from '@elastic/eui'; +import { EuiButton } from '@elastic/eui'; import { docLinks } from '../../../shared/doc_links'; +import { getPageTitle, getPageHeaderActions } from '../../../test_helpers'; import { DocumentCreationButtons, DocumentCreationFlyout } from '../document_creation'; @@ -25,12 +28,13 @@ describe('EmptyEngineOverview', () => { }); it('renders', () => { - expect(wrapper.find(EuiPageHeader).prop('pageTitle')).toEqual('Engine setup'); + expect(getPageTitle(wrapper)).toEqual('Engine setup'); }); it('renders a documentation link', () => { - const header = wrapper.find(EuiPageHeader).dive().children().dive(); - expect(header.find(EuiButton).prop('href')).toEqual(`${docLinks.appSearchBase}/index.html`); + expect(getPageHeaderActions(wrapper).find(EuiButton).prop('href')).toEqual( + `${docLinks.appSearchBase}/index.html` + ); }); it('renders document creation components', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.tsx index 959d544a673243..27d9c3723f1268 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.tsx @@ -7,35 +7,36 @@ import React from 'react'; -import { EuiPageHeader, EuiPageContentBody, EuiButton } from '@elastic/eui'; +import { EuiButton } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { FlashMessages } from '../../../shared/flash_messages'; import { DOCS_PREFIX } from '../../routes'; import { DocumentCreationButtons, DocumentCreationFlyout } from '../document_creation'; +import { getEngineBreadcrumbs } from '../engine'; +import { AppSearchPageTemplate } from '../layout'; + export const EmptyEngineOverview: React.FC = () => { return ( - <> - {i18n.translate( 'xpack.enterpriseSearch.appSearch.engine.overview.empty.headingAction', { defaultMessage: 'View documentation' } )} , - ]} - /> - - - - - - + ], + }} + > + + + ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_metrics.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_metrics.test.tsx index 00ac2af219bff5..620d913c5f9a7d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_metrics.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_metrics.test.tsx @@ -5,11 +5,13 @@ * 2.0. */ +import '../../__mocks__/engine_logic.mock'; + import React from 'react'; import { shallow } from 'enzyme'; -import { EuiPageHeader } from '@elastic/eui'; +import { getPageTitle } from '../../../test_helpers'; import { TotalStats, TotalCharts, RecentApiLogs } from './components'; import { EngineOverviewMetrics } from './engine_overview_metrics'; @@ -18,7 +20,7 @@ describe('EngineOverviewMetrics', () => { it('renders', () => { const wrapper = shallow(); - expect(wrapper.find(EuiPageHeader).prop('pageTitle')).toEqual('Engine overview'); + expect(getPageTitle(wrapper)).toEqual('Engine overview'); expect(wrapper.find(TotalStats)).toHaveLength(1); expect(wrapper.find(TotalCharts)).toHaveLength(1); expect(wrapper.find(RecentApiLogs)).toHaveLength(1); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_metrics.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_metrics.tsx index 2b01cfae49a201..b47ae21104ae96 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_metrics.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_metrics.tsx @@ -7,23 +7,24 @@ import React from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiPageHeader, EuiSpacer } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { FlashMessages } from '../../../shared/flash_messages'; +import { getEngineBreadcrumbs } from '../engine'; +import { AppSearchPageTemplate } from '../layout'; import { TotalStats, TotalCharts, RecentApiLogs } from './components'; export const EngineOverviewMetrics: React.FC = () => { return ( - <> - - - + }), + }} + > @@ -34,6 +35,6 @@ export const EngineOverviewMetrics: React.FC = () => { - + ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/layout/nav.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/layout/nav.test.tsx index 8b06f4b26835d4..80230394ce2a2f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/layout/nav.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/layout/nav.test.tsx @@ -10,6 +10,9 @@ import { setMockValues } from '../../../__mocks__/kea_logic'; jest.mock('../../../shared/layout', () => ({ generateNavLink: jest.fn(({ to }) => ({ href: to })), })); +jest.mock('../engine/engine_nav', () => ({ + useEngineNav: () => [], +})); import { useAppSearchNav } from './nav'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/layout/nav.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/layout/nav.tsx index 57fa740caebec2..4737fbcf07e23c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/layout/nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/layout/nav.tsx @@ -15,6 +15,7 @@ import { ROLE_MAPPINGS_TITLE } from '../../../shared/role_mapping/constants'; import { AppLogic } from '../../app_logic'; import { ENGINES_PATH, SETTINGS_PATH, CREDENTIALS_PATH, ROLE_MAPPINGS_PATH } from '../../routes'; import { CREDENTIALS_TITLE } from '../credentials'; +import { useEngineNav } from '../engine/engine_nav'; import { ENGINES_TITLE } from '../engines'; import { SETTINGS_TITLE } from '../settings'; @@ -28,7 +29,7 @@ export const useAppSearchNav = () => { id: 'engines', name: ENGINES_TITLE, ...generateNavLink({ to: ENGINES_PATH, isRoot: true }), - items: [], // TODO: Engine nav + items: useEngineNav(), }, ]; 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 b2cd3d7b54a1a9..d724371cf1dc6e 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 @@ -98,6 +98,9 @@ export const AppSearchConfigured: React.FC> = (props) = + + + {canManageEngines && ( @@ -116,9 +119,6 @@ export const AppSearchConfigured: React.FC> = (props) = } readOnlyMode={readOnlyMode}> - - -