[Enterprise Search] Final KibanaPageTemplate cleanup (#103355)

* [AS] Delete AppSearchNav and EngineNav

* [WS] Delete WorkplaceSearchNav

* [Shared] Delete custom Layout & SideNav components
This commit is contained in:
Constance 2021-06-24 16:02:48 -07:00 committed by GitHub
parent bd2215f587
commit 803d0fa57b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 18 additions and 1233 deletions

View file

@ -10,7 +10,6 @@ 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 })),
}));
@ -20,9 +19,7 @@ import { shallow } from 'enzyme';
import { EuiBadge, EuiIcon } from '@elastic/eui';
import { rerender } from '../../../test_helpers';
import { useEngineNav, EngineNav } from './engine_nav';
import { useEngineNav } from './engine_nav';
describe('useEngineNav', () => {
const values = { ...mockEngineValues, myRole: {}, dataLoading: false };
@ -321,182 +318,3 @@ describe('useEngineNav', () => {
});
});
});
describe('EngineNav', () => {
const values = { ...mockEngineValues, myRole: {}, dataLoading: false };
beforeEach(() => {
setMockValues(values);
});
it('does not render if async data is still loading', () => {
setMockValues({ ...values, dataLoading: true });
const wrapper = shallow(<EngineNav />);
expect(wrapper.isEmptyRender()).toBe(true);
});
it('does not render without an engine name', () => {
setMockValues({ ...values, engineName: '' });
const wrapper = shallow(<EngineNav />);
expect(wrapper.isEmptyRender()).toBe(true);
});
it('renders an engine label and badges', () => {
setMockValues({ ...values, isSampleEngine: false, isMetaEngine: false });
const wrapper = shallow(<EngineNav />);
const label = wrapper.find('[data-test-subj="EngineLabel"]').find('.eui-textTruncate');
expect(label.text()).toEqual('SOME-ENGINE');
expect(wrapper.find(EuiBadge)).toHaveLength(0);
setMockValues({ ...values, isSampleEngine: true });
rerender(wrapper);
expect(wrapper.find(EuiBadge).prop('children')).toEqual('SAMPLE ENGINE');
setMockValues({ ...values, isMetaEngine: true });
rerender(wrapper);
expect(wrapper.find(EuiBadge).prop('children')).toEqual('META ENGINE');
});
it('renders a default engine overview link', () => {
const wrapper = shallow(<EngineNav />);
expect(wrapper.find('[data-test-subj="EngineOverviewLink"]')).toHaveLength(1);
});
it('renders an analytics link', () => {
setMockValues({ ...values, myRole: { canViewEngineAnalytics: true } });
const wrapper = shallow(<EngineNav />);
expect(wrapper.find('[data-test-subj="EngineAnalyticsLink"]')).toHaveLength(1);
});
it('renders a documents link', () => {
setMockValues({ ...values, myRole: { canViewEngineDocuments: true } });
const wrapper = shallow(<EngineNav />);
expect(wrapper.find('[data-test-subj="EngineDocumentsLink"]')).toHaveLength(1);
});
it('renders a schema link', () => {
setMockValues({ ...values, myRole: { canViewEngineSchema: true } });
const wrapper = shallow(<EngineNav />);
expect(wrapper.find('[data-test-subj="EngineSchemaLink"]')).toHaveLength(1);
});
describe('schema nav icons', () => {
const myRole = { canViewEngineSchema: true };
it('renders schema errors alert icon', () => {
setMockValues({ ...values, myRole, hasSchemaErrors: true });
const wrapper = shallow(<EngineNav />);
expect(wrapper.find('[data-test-subj="EngineNavSchemaErrors"]')).toHaveLength(1);
});
it('renders unconfirmed schema fields info icon', () => {
setMockValues({ ...values, myRole, hasUnconfirmedSchemaFields: true });
const wrapper = shallow(<EngineNav />);
expect(wrapper.find('[data-test-subj="EngineNavSchemaUnconfirmedFields"]')).toHaveLength(1);
});
it('renders schema conflicts alert icon', () => {
setMockValues({ ...values, myRole, hasSchemaConflicts: true });
const wrapper = shallow(<EngineNav />);
expect(wrapper.find('[data-test-subj="EngineNavSchemaConflicts"]')).toHaveLength(1);
});
});
describe('crawler link', () => {
const myRole = { canViewEngineCrawler: true };
it('renders', () => {
setMockValues({ ...values, myRole });
const wrapper = shallow(<EngineNav />);
expect(wrapper.find('[data-test-subj="EngineCrawlerLink"]')).toHaveLength(1);
});
it('does not render for meta engines', () => {
setMockValues({ ...values, myRole, isMetaEngine: true });
const wrapper = shallow(<EngineNav />);
expect(wrapper.find('[data-test-subj="EngineCrawlerLink"]')).toHaveLength(0);
});
});
describe('meta engine source engines link', () => {
const myRole = { canViewMetaEngineSourceEngines: true };
it('renders', () => {
setMockValues({ ...values, myRole, isMetaEngine: true });
const wrapper = shallow(<EngineNav />);
expect(wrapper.find('[data-test-subj="MetaEngineEnginesLink"]')).toHaveLength(1);
});
it('does not render for non meta engines', () => {
setMockValues({ ...values, myRole, isMetaEngine: false });
const wrapper = shallow(<EngineNav />);
expect(wrapper.find('[data-test-subj="MetaEngineEnginesLink"]')).toHaveLength(0);
});
});
it('renders a relevance tuning link', () => {
setMockValues({ ...values, myRole: { canManageEngineRelevanceTuning: true } });
const wrapper = shallow(<EngineNav />);
expect(wrapper.find('[data-test-subj="EngineRelevanceTuningLink"]')).toHaveLength(1);
});
describe('relevance tuning nav icons', () => {
const myRole = { canManageEngineRelevanceTuning: true };
it('renders unconfirmed schema fields info icon', () => {
const engine = { unsearchedUnconfirmedFields: true };
setMockValues({ ...values, myRole, engine });
const wrapper = shallow(<EngineNav />);
expect(
wrapper.find('[data-test-subj="EngineNavRelevanceTuningUnsearchedFields"]')
).toHaveLength(1);
});
it('renders schema conflicts alert icon', () => {
const engine = { invalidBoosts: true };
setMockValues({ ...values, myRole, engine });
const wrapper = shallow(<EngineNav />);
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 = shallow(<EngineNav />);
expect(wrapper.find(EuiIcon)).toHaveLength(2);
});
});
it('renders a synonyms link', () => {
setMockValues({ ...values, myRole: { canManageEngineSynonyms: true } });
const wrapper = shallow(<EngineNav />);
expect(wrapper.find('[data-test-subj="EngineSynonymsLink"]')).toHaveLength(1);
});
it('renders a curations link', () => {
setMockValues({ ...values, myRole: { canManageEngineCurations: true } });
const wrapper = shallow(<EngineNav />);
expect(wrapper.find('[data-test-subj="EngineCurationsLink"]')).toHaveLength(1);
});
it('renders a results settings link', () => {
setMockValues({ ...values, myRole: { canManageEngineResultSettings: true } });
const wrapper = shallow(<EngineNav />);
expect(wrapper.find('[data-test-subj="EngineResultSettingsLink"]')).toHaveLength(1);
});
it('renders a Search UI link', () => {
setMockValues({ ...values, myRole: { canManageEngineSearchUi: true } });
const wrapper = shallow(<EngineNav />);
expect(wrapper.find('[data-test-subj="EngineSearchUILink"]')).toHaveLength(1);
});
it('renders an API logs link', () => {
setMockValues({ ...values, myRole: { canViewEngineApiLogs: true } });
const wrapper = shallow(<EngineNav />);
expect(wrapper.find('[data-test-subj="EngineAPILogsLink"]')).toHaveLength(1);
});
});

View file

@ -10,17 +10,10 @@ import { useRouteMatch } from 'react-router-dom';
import { useValues } from 'kea';
import {
EuiSideNavItemType,
EuiText,
EuiBadge,
EuiIcon,
EuiFlexGroup,
EuiFlexItem,
} from '@elastic/eui';
import { EuiSideNavItemType, EuiText, EuiBadge, EuiIcon } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { generateNavLink, SideNavLink, SideNavItem } from '../../../shared/layout';
import { generateNavLink } from '../../../shared/layout';
import { AppLogic } from '../../app_logic';
import {
ENGINE_PATH,
@ -49,8 +42,6 @@ import { SCHEMA_TITLE } from '../schema';
import { SEARCH_UI_TITLE } from '../search_ui';
import { SYNONYMS_TITLE } from '../synonyms';
import { EngineDetails } from './types';
import { EngineLogic, generateEnginePath } from './';
import './engine_nav.scss';
@ -198,7 +189,10 @@ export const useEngineNav = () => {
navItems.push({
id: 'crawler',
name: CRAWLER_TITLE,
...generateNavLink({ to: generateEnginePath(ENGINE_CRAWLER_PATH) }),
...generateNavLink({
to: generateEnginePath(ENGINE_CRAWLER_PATH),
shouldShowActiveForSubroutes: true,
}),
'data-test-subj': 'EngineCrawlerLink',
});
}
@ -301,220 +295,3 @@ export const useEngineNav = () => {
return navItems;
};
// TODO: Delete the below once page template migration is complete
export const EngineNav: React.FC = () => {
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 (dataLoading) return null;
if (!engineName) return null;
const { invalidBoosts, unsearchedUnconfirmedFields } = engine as Required<EngineDetails>;
return (
<>
<SideNavItem className="appSearchNavEngineLabel" data-test-subj="EngineLabel">
<EuiText color="subdued" size="s">
<div className="eui-textTruncate">{engineName.toUpperCase()}</div>
{isSampleEngine && (
<EuiBadge>
{i18n.translate('xpack.enterpriseSearch.appSearch.engine.sampleEngineBadge', {
defaultMessage: 'SAMPLE ENGINE',
})}
</EuiBadge>
)}
{isMetaEngine && (
<EuiBadge>
{i18n.translate('xpack.enterpriseSearch.appSearch.engine.metaEngineBadge', {
defaultMessage: 'META ENGINE',
})}
</EuiBadge>
)}
</EuiText>
</SideNavItem>
<SideNavLink to={generateEnginePath(ENGINE_PATH)} data-test-subj="EngineOverviewLink">
{OVERVIEW_TITLE}
</SideNavLink>
{canViewEngineAnalytics && (
<SideNavLink
to={generateEnginePath(ENGINE_ANALYTICS_PATH)}
shouldShowActiveForSubroutes
data-test-subj="EngineAnalyticsLink"
>
{ANALYTICS_TITLE}
</SideNavLink>
)}
{canViewEngineDocuments && (
<SideNavLink
to={generateEnginePath(ENGINE_DOCUMENTS_PATH)}
shouldShowActiveForSubroutes
data-test-subj="EngineDocumentsLink"
>
{DOCUMENTS_TITLE}
</SideNavLink>
)}
{canViewEngineSchema && (
<SideNavLink
to={generateEnginePath(ENGINE_SCHEMA_PATH)}
shouldShowActiveForSubroutes
data-test-subj="EngineSchemaLink"
>
<EuiFlexGroup justifyContent="spaceBetween" gutterSize="none" responsive={false}>
<EuiFlexItem>{SCHEMA_TITLE}</EuiFlexItem>
<EuiFlexItem className="appSearchNavIcons">
{hasSchemaErrors && (
<EuiIcon
type="alert"
color="danger"
title={i18n.translate('xpack.enterpriseSearch.appSearch.engine.schema.errors', {
defaultMessage: 'Schema change errors',
})}
data-test-subj="EngineNavSchemaErrors"
/>
)}
{hasUnconfirmedSchemaFields && (
<EuiIcon
type="iInCircle"
color="primary"
title={i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.schema.unconfirmedFields',
{ defaultMessage: 'New unconfirmed fields' }
)}
data-test-subj="EngineNavSchemaUnconfirmedFields"
/>
)}
{hasSchemaConflicts && (
<EuiIcon
type="alert"
color="warning"
title={i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.schema.conflicts',
{ defaultMessage: 'Schema conflicts' }
)}
data-test-subj="EngineNavSchemaConflicts"
/>
)}
</EuiFlexItem>
</EuiFlexGroup>
</SideNavLink>
)}
{canViewEngineCrawler && !isMetaEngine && (
<SideNavLink
to={generateEnginePath(ENGINE_CRAWLER_PATH)}
data-test-subj="EngineCrawlerLink"
>
{CRAWLER_TITLE}
</SideNavLink>
)}
{canViewMetaEngineSourceEngines && isMetaEngine && (
<SideNavLink
to={generateEnginePath(META_ENGINE_SOURCE_ENGINES_PATH)}
data-test-subj="MetaEngineEnginesLink"
>
{ENGINES_TITLE}
</SideNavLink>
)}
{canManageEngineRelevanceTuning && (
<SideNavLink
to={generateEnginePath(ENGINE_RELEVANCE_TUNING_PATH)}
data-test-subj="EngineRelevanceTuningLink"
>
<EuiFlexGroup justifyContent="spaceBetween" gutterSize="none" responsive={false}>
<EuiFlexItem>{RELEVANCE_TUNING_TITLE}</EuiFlexItem>
<EuiFlexItem className="appSearchNavIcons">
{invalidBoosts && (
<EuiIcon
type="alert"
color="warning"
title={i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.invalidBoosts',
{ defaultMessage: 'Invalid boosts' }
)}
data-test-subj="EngineNavRelevanceTuningInvalidBoosts"
/>
)}
{unsearchedUnconfirmedFields && (
<EuiIcon
type="alert"
color="warning"
title={i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.unsearchedFields',
{ defaultMessage: 'Unsearched fields' }
)}
data-test-subj="EngineNavRelevanceTuningUnsearchedFields"
/>
)}
</EuiFlexItem>
</EuiFlexGroup>
</SideNavLink>
)}
{canManageEngineSynonyms && (
<SideNavLink
to={generateEnginePath(ENGINE_SYNONYMS_PATH)}
data-test-subj="EngineSynonymsLink"
>
{SYNONYMS_TITLE}
</SideNavLink>
)}
{canManageEngineCurations && (
<SideNavLink
to={generateEnginePath(ENGINE_CURATIONS_PATH)}
shouldShowActiveForSubroutes
data-test-subj="EngineCurationsLink"
>
{CURATIONS_TITLE}
</SideNavLink>
)}
{canManageEngineResultSettings && (
<SideNavLink
to={generateEnginePath(ENGINE_RESULT_SETTINGS_PATH)}
data-test-subj="EngineResultSettingsLink"
>
{RESULT_SETTINGS_TITLE}
</SideNavLink>
)}
{canManageEngineSearchUi && (
<SideNavLink
to={generateEnginePath(ENGINE_SEARCH_UI_PATH)}
data-test-subj="EngineSearchUILink"
>
{SEARCH_UI_TITLE}
</SideNavLink>
)}
{canViewEngineApiLogs && (
<SideNavLink
to={generateEnginePath(ENGINE_API_LOGS_PATH)}
data-test-subj="EngineAPILogsLink"
>
{API_LOGS_TITLE}
</SideNavLink>
)}
</>
);
};

View file

@ -6,6 +6,5 @@
*/
export { EngineRouter } from './engine_router';
export { EngineNav } from './engine_nav';
export { EngineLogic } from './engine_logic';
export { generateEnginePath, getEngineBreadcrumbs } from './utils';

View file

@ -7,7 +7,6 @@
import { DEFAULT_INITIAL_APP_DATA } from '../../../common/__mocks__';
import { setMockValues } from '../__mocks__/kea_logic';
import { mockUseRouteMatch } from '../__mocks__/react_router';
import '../__mocks__/shallow_useeffect.mock';
import '../__mocks__/enterprise_search_url.mock';
@ -17,15 +16,13 @@ import { Redirect } from 'react-router-dom';
import { shallow, ShallowWrapper } from 'enzyme';
import { SideNav, SideNavLink } from '../shared/layout';
import { rerender } from '../test_helpers';
jest.mock('./app_logic', () => ({ AppLogic: jest.fn() }));
import { AppLogic } from './app_logic';
import { Credentials } from './components/credentials';
import { EngineRouter, EngineNav } from './components/engine';
import { EngineRouter } from './components/engine';
import { EngineCreation } from './components/engine_creation';
import { EnginesOverview } from './components/engines';
import { ErrorConnecting } from './components/error_connecting';
@ -35,7 +32,7 @@ import { RoleMappings } from './components/role_mappings';
import { Settings } from './components/settings';
import { SetupGuide } from './components/setup_guide';
import { AppSearch, AppSearchUnconfigured, AppSearchConfigured, AppSearchNav } from './';
import { AppSearch, AppSearchUnconfigured, AppSearchConfigured } from './';
describe('AppSearch', () => {
it('always renders the Setup Guide', () => {
@ -142,51 +139,3 @@ describe('AppSearchConfigured', () => {
});
});
});
describe('AppSearchNav', () => {
it('renders with the Engines link', () => {
const wrapper = shallow(<AppSearchNav />);
expect(wrapper.find(SideNav)).toHaveLength(1);
expect(wrapper.find(SideNavLink).prop('to')).toEqual('/engines');
});
describe('engine subnavigation', () => {
const getEnginesLink = (wrapper: ShallowWrapper) => wrapper.find(SideNavLink).dive();
it('does not render the engine subnav on top-level routes', () => {
mockUseRouteMatch.mockReturnValueOnce(false);
const wrapper = shallow(<AppSearchNav />);
expect(getEnginesLink(wrapper).find(EngineNav)).toHaveLength(0);
});
it('renders the engine subnav if currently on an engine route', () => {
mockUseRouteMatch.mockReturnValueOnce(true);
const wrapper = shallow(<AppSearchNav />);
expect(getEnginesLink(wrapper).find(EngineNav)).toHaveLength(1);
});
});
it('renders the Settings link', () => {
setMockValues({ myRole: { canViewSettings: true } });
const wrapper = shallow(<AppSearchNav />);
expect(wrapper.find(SideNavLink).last().prop('to')).toEqual('/settings');
});
it('renders the Credentials link', () => {
setMockValues({ myRole: { canViewAccountCredentials: true } });
const wrapper = shallow(<AppSearchNav />);
expect(wrapper.find(SideNavLink).last().prop('to')).toEqual('/credentials');
});
it('renders the Role Mappings link', () => {
setMockValues({ myRole: { canViewRoleMappings: true } });
const wrapper = shallow(<AppSearchNav />);
expect(wrapper.find(SideNavLink).last().prop('to')).toEqual('/users_and_roles');
});
});

View file

@ -6,30 +6,26 @@
*/
import React, { useEffect } from 'react';
import { Route, Redirect, Switch, useRouteMatch } from 'react-router-dom';
import { Route, Redirect, Switch } from 'react-router-dom';
import { useValues } from 'kea';
import { APP_SEARCH_PLUGIN } from '../../../common/constants';
import { InitialAppData } from '../../../common/types';
import { HttpLogic } from '../shared/http';
import { KibanaLogic } from '../shared/kibana';
import { SideNav, SideNavLink } from '../shared/layout';
import { ROLE_MAPPINGS_TITLE } from '../shared/role_mapping/constants';
import { AppLogic } from './app_logic';
import { Credentials, CREDENTIALS_TITLE } from './components/credentials';
import { EngineNav, EngineRouter } from './components/engine';
import { Credentials } from './components/credentials';
import { EngineRouter } from './components/engine';
import { EngineCreation } from './components/engine_creation';
import { EnginesOverview, ENGINES_TITLE } from './components/engines';
import { EnginesOverview } from './components/engines';
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 { Settings } from './components/settings';
import { SetupGuide } from './components/setup_guide';
import {
ENGINE_CREATION_PATH,
@ -137,28 +133,3 @@ export const AppSearchConfigured: React.FC<Required<InitialAppData>> = (props) =
</Switch>
);
};
export const AppSearchNav: React.FC = () => {
const {
myRole: { canViewSettings, canViewAccountCredentials, canViewRoleMappings },
} = useValues(AppLogic);
const isEngineRoute = !!useRouteMatch(ENGINE_PATH);
return (
<SideNav product={APP_SEARCH_PLUGIN}>
<SideNavLink to={ENGINES_PATH} subNav={isEngineRoute ? <EngineNav /> : null} isRoot>
{ENGINES_TITLE}
</SideNavLink>
{canViewSettings && <SideNavLink to={SETTINGS_PATH}>{SETTINGS_TITLE}</SideNavLink>}
{canViewAccountCredentials && (
<SideNavLink to={CREDENTIALS_PATH}>{CREDENTIALS_TITLE}</SideNavLink>
)}
{canViewRoleMappings && (
<SideNavLink shouldShowActiveForSubroutes to={USERS_AND_ROLES_PATH}>
{ROLE_MAPPINGS_TITLE}
</SideNavLink>
)}
</SideNav>
);
};

View file

@ -7,7 +7,3 @@
export { EnterpriseSearchPageTemplate, PageTemplateProps } from './page_template';
export { generateNavLink } from './nav_link_helpers';
// TODO: Delete these once KibanaPageTemplate migration is done
export { Layout } from './layout';
export { SideNav, SideNavLink, SideNavItem } from './side_nav';

View file

@ -1,96 +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.
*/
.enterpriseSearchLayout {
$sideBarWidth: $euiSize * 15;
$sideBarMobileHeight: $euiSize * 4.75;
$consoleHeaderHeight: 98px; // NOTE: Keep an eye on this for changes
$pageHeight: calc(100vh - #{$consoleHeaderHeight});
display: block;
background-color: $euiColorEmptyShade;
min-height: $pageHeight;
position: relative;
left: $sideBarWidth;
width: calc(100% - #{$sideBarWidth});
padding: 0;
@include euiBreakpoint('xs', 's', 'm') {
left: auto;
width: 100%;
}
&__sideBarToggle {
display: none;
@include euiBreakpoint('xs', 's', 'm') {
display: block;
position: absolute;
right: $euiSize;
top: $sideBarMobileHeight / 2;
transform: translateY(-50%) scale(.9);
.euiButton {
min-width: 0;
}
}
}
&__sideBar {
z-index: $euiZNavigation;
position: fixed;
margin-left: -1 * $sideBarWidth;
margin-right: 0;
overflow-y: auto;
overflow-x: hidden;
height: $pageHeight;
width: $sideBarWidth;
background-color: $euiColorLightestShade;
box-shadow: inset (-1 * $euiSizeXS) 0 $euiSizeS (-1 * $euiSizeXS) rgba($euiShadowColor, .25);
@include euiBreakpoint('xs', 's', 'm') {
position: relative;
width: 100%;
height: $sideBarMobileHeight;
margin-left: 0;
overflow-y: hidden;
border-bottom: $euiBorderThin;
box-shadow: none;
&--isOpen {
height: auto;
overflow-y: auto;
}
}
}
&__body {
padding: $euiSizeXXL;
@include euiBreakpoint('m') {
padding: $euiSizeL;
}
@include euiBreakpoint('xs', 's') {
padding: $euiSize;
}
}
&__readOnlyMode {
margin: -$euiSizeM 0 $euiSizeL;
@include euiBreakpoint('m') {
margin: 0 0 $euiSizeL;
}
@include euiBreakpoint('xs', 's') {
margin: 0;
}
}
}

View file

@ -1,77 +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 { shallow } from 'enzyme';
import { EuiPageSideBar, EuiButton, EuiPageBody, EuiCallOut } from '@elastic/eui';
import { Layout, INavContext } from './layout';
describe('Layout', () => {
it('renders', () => {
const wrapper = shallow(<Layout navigation={null} />);
expect(wrapper.find('.enterpriseSearchLayout')).toHaveLength(1);
expect(wrapper.find(EuiPageBody).prop('restrictWidth')).toBeFalsy();
});
it('passes the restrictWidth prop', () => {
const wrapper = shallow(<Layout navigation={null} restrictWidth />);
expect(wrapper.find(EuiPageBody).prop('restrictWidth')).toEqual(true);
});
it('renders navigation', () => {
const wrapper = shallow(<Layout navigation={<nav className="nav-test">Hello World</nav>} />);
expect(wrapper.find('.enterpriseSearchLayout__sideBar')).toHaveLength(1);
expect(wrapper.find('.nav-test')).toHaveLength(1);
});
it('renders navigation toggle state', () => {
const wrapper = shallow(<Layout navigation={<nav className="nav-test">Hello World</nav>} />);
expect(wrapper.find(EuiPageSideBar).prop('className')).not.toContain('--isOpen');
expect(wrapper.find(EuiButton).prop('iconType')).toEqual('arrowRight');
const toggle = wrapper.find('[data-test-subj="enterpriseSearchNavToggle"]');
toggle.simulate('click');
expect(wrapper.find(EuiPageSideBar).prop('className')).toContain('--isOpen');
expect(wrapper.find(EuiButton).prop('iconType')).toEqual('arrowDown');
});
it('passes down NavContext to navigation links', () => {
const wrapper = shallow(<Layout navigation={<nav />} />);
const toggle = wrapper.find('[data-test-subj="enterpriseSearchNavToggle"]');
toggle.simulate('click');
expect(wrapper.find(EuiPageSideBar).prop('className')).toContain('--isOpen');
const context = (wrapper.find('ContextProvider').prop('value') as unknown) as INavContext;
context.closeNavigation();
expect(wrapper.find(EuiPageSideBar).prop('className')).not.toContain('--isOpen');
});
it('renders a read-only mode callout', () => {
const wrapper = shallow(<Layout navigation={null} readOnlyMode />);
expect(wrapper.find(EuiCallOut)).toHaveLength(1);
});
it('renders children', () => {
const wrapper = shallow(
<Layout navigation={null}>
<div className="testing">Test</div>
</Layout>
);
expect(wrapper.find('.enterpriseSearchLayout__body')).toHaveLength(1);
expect(wrapper.find('.testing')).toHaveLength(1);
});
});

View file

@ -1,82 +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, { useState } from 'react';
import classNames from 'classnames';
import { EuiPage, EuiPageSideBar, EuiPageBody, EuiButton, EuiCallOut } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import './layout.scss';
interface LayoutProps {
navigation: React.ReactNode;
restrictWidth?: boolean;
readOnlyMode?: boolean;
}
export interface INavContext {
closeNavigation(): void;
}
export const NavContext = React.createContext({});
export const Layout: React.FC<LayoutProps> = ({
children,
navigation,
restrictWidth,
readOnlyMode,
}) => {
const [isNavOpen, setIsNavOpen] = useState(false);
const toggleNavigation = () => setIsNavOpen(!isNavOpen);
const closeNavigation = () => setIsNavOpen(false);
const navClasses = classNames('enterpriseSearchLayout__sideBar', {
'enterpriseSearchLayout__sideBar--isOpen': isNavOpen, // eslint-disable-line @typescript-eslint/naming-convention
});
return (
<EuiPage className="enterpriseSearchLayout">
<EuiPageSideBar className={navClasses}>
<div className="enterpriseSearchLayout__sideBarToggle">
<EuiButton
size="s"
iconType={isNavOpen ? 'arrowDown' : 'arrowRight'}
iconSide="right"
aria-label={i18n.translate('xpack.enterpriseSearch.nav.toggleMenu', {
defaultMessage: 'Toggle secondary navigation',
})}
aria-expanded={isNavOpen}
aria-pressed={isNavOpen}
aria-controls="enterpriseSearchNav"
onClick={toggleNavigation}
data-test-subj="enterpriseSearchNavToggle"
>
{i18n.translate('xpack.enterpriseSearch.nav.menu', {
defaultMessage: 'Menu',
})}
</EuiButton>
</div>
<NavContext.Provider value={{ closeNavigation }}>{navigation}</NavContext.Provider>
</EuiPageSideBar>
<EuiPageBody className="enterpriseSearchLayout__body" restrictWidth={restrictWidth}>
{readOnlyMode && (
<EuiCallOut
className="enterpriseSearchLayout__readOnlyMode"
color="warning"
iconType="lock"
title={i18n.translate('xpack.enterpriseSearch.readOnlyMode.warning', {
defaultMessage:
'Enterprise Search is in read-only mode. You will be unable to make changes such as creating, editing, or deleting.',
})}
/>
)}
{children}
</EuiPageBody>
</EuiPage>
);
};

View file

@ -1,87 +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.
*/
$euiSizeML: $euiSize * 1.25; // 20px - between medium and large ¯\_()_/¯
.enterpriseSearchProduct {
display: flex;
align-items: center;
padding: $euiSizeML;
background-image: url('./side_nav_bg.svg');
background-repeat: no-repeat;
@include euiBreakpoint('xs', 's', 'm') {
padding: $euiSize $euiSizeML;
}
&__icon {
display: flex;
align-items: center;
justify-content: center;
width: $euiSizeXXL;
height: $euiSizeXXL;
margin-right: $euiSizeS;
background-color: $euiColorEmptyShade;
border-radius: 50%;
@include euiSlightShadow();
.euiIcon {
width: $euiSizeML;
height: $euiSizeML;
}
}
&__title {
.euiText {
font-weight: $euiFontWeightMedium;
}
}
}
.enterpriseSearchNavLinks {
&__item {
display: block;
padding: $euiSizeM $euiSizeML;
font-size: $euiFontSizeS;
font-weight: $euiFontWeightMedium;
line-height: $euiFontSizeM;
$activeBgColor: rgba($euiColorFullShade, .05);
&--isActive {
background-color: $activeBgColor;
}
&.euiLink {
color: $euiTextColor;
font-weight: $euiFontWeightMedium;
&:hover {
color: $euiTextColor;
}
&:focus {
outline: solid 0 $activeBgColor;
background-color: $activeBgColor;
}
}
}
&__subNav {
padding-left: $euiSizeML;
// Extends the click area of links more to the left, so that second tiers
// of subnavigation links still have the same hitbox as first tier links
.enterpriseSearchNavLinks__item {
margin-left: -$euiSizeML;
padding-left: $euiSizeXXL;
}
}
}

View file

@ -1,166 +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 { mockLocation } from '../../__mocks__/react_router';
import React from 'react';
import { shallow } from 'enzyme';
import { EuiLink } from '@elastic/eui';
import { ENTERPRISE_SEARCH_PLUGIN, APP_SEARCH_PLUGIN } from '../../../../common/constants';
import { EuiLinkTo } from '../react_router_helpers';
import { SideNav, SideNavLink, SideNavItem } from './';
describe('SideNav', () => {
it('renders link children', () => {
const wrapper = shallow(
<SideNav product={ENTERPRISE_SEARCH_PLUGIN}>
<div className="testing">Hello World</div>
</SideNav>
);
expect(wrapper.type()).toEqual('nav');
expect(wrapper.find('.enterpriseSearchNavLinks')).toHaveLength(1);
expect(wrapper.find('.testing')).toHaveLength(1);
});
it('renders a custom product', () => {
const wrapper = shallow(<SideNav product={APP_SEARCH_PLUGIN} />);
expect(wrapper.find('h3').text()).toEqual('App Search');
expect(wrapper.find('.enterpriseSearchProduct--appSearch')).toHaveLength(1);
});
});
describe('SideNavLink', () => {
it('renders', () => {
const wrapper = shallow(<SideNavLink to="/">Link</SideNavLink>);
expect(wrapper.type()).toEqual('li');
expect(wrapper.find(EuiLinkTo)).toHaveLength(1);
expect(wrapper.find('.enterpriseSearchNavLinks__item')).toHaveLength(1);
});
it('renders an external link if isExternal is true', () => {
const wrapper = shallow(
<SideNavLink to="http://website.com" isExternal>
Link
</SideNavLink>
);
const externalLink = wrapper.find(EuiLink);
expect(externalLink).toHaveLength(1);
expect(externalLink.prop('href')).toEqual('http://website.com');
expect(externalLink.prop('target')).toEqual('_blank');
});
it('sets an active class if the current path matches the nav link', () => {
mockLocation.pathname = '/test/';
const wrapper = shallow(<SideNavLink to="/test">Link</SideNavLink>);
expect(wrapper.find('.enterpriseSearchNavLinks__item--isActive')).toHaveLength(1);
});
it('sets an active class if the current path is / and the link isRoot', () => {
mockLocation.pathname = '/';
const wrapper = shallow(
<SideNavLink to="/test" isRoot>
Link
</SideNavLink>
);
expect(wrapper.find('.enterpriseSearchNavLinks__item--isActive')).toHaveLength(1);
});
it('passes down custom classes and props', () => {
const wrapper = shallow(
<SideNavLink to="/" className="testing" data-test-subj="testing">
Link
</SideNavLink>
);
expect(wrapper.find('.testing')).toHaveLength(1);
expect(wrapper.find('[data-test-subj="testing"]')).toHaveLength(1);
});
it('renders nested subnavigation', () => {
const subNav = (
<SideNavLink to="/elsewhere" data-test-subj="subNav">
Another link!
</SideNavLink>
);
const wrapper = shallow(
<SideNavLink to="/" subNav={subNav}>
Link
</SideNavLink>
);
expect(wrapper.find('.enterpriseSearchNavLinks__subNav')).toHaveLength(1);
expect(wrapper.find('[data-test-subj="subNav"]')).toHaveLength(1);
});
describe('shouldShowActiveForSubroutes', () => {
it("won't set an active class when route is a subroute of 'to'", () => {
mockLocation.pathname = '/documents/1234';
const wrapper = shallow(
<SideNavLink to="/documents" isRoot>
Link
</SideNavLink>
);
expect(wrapper.find('.enterpriseSearchNavLinks__item--isActive')).toHaveLength(0);
});
it('sets an active class if the current path is a subRoute of "to", and shouldShowActiveForSubroutes is true', () => {
mockLocation.pathname = '/documents/1234';
const wrapper = shallow(
<SideNavLink to="/documents" isRoot shouldShowActiveForSubroutes>
Link
</SideNavLink>
);
expect(wrapper.find('.enterpriseSearchNavLinks__item--isActive')).toHaveLength(1);
});
});
});
describe('SideNavItem', () => {
it('renders', () => {
const wrapper = shallow(<SideNavItem>Test</SideNavItem>);
expect(wrapper.type()).toEqual('li');
expect(wrapper.find('.enterpriseSearchNavLinks__item')).toHaveLength(1);
});
it('renders children', () => {
const wrapper = shallow(
<SideNavItem>
<span data-test-subj="hello">World</span>
</SideNavItem>
);
expect(wrapper.find('[data-test-subj="hello"]').text()).toEqual('World');
});
it('passes down custom classes and props', () => {
const wrapper = shallow(
<SideNavItem className="testing" data-test-subj="testing">
Test
</SideNavItem>
);
expect(wrapper.find('.testing')).toHaveLength(1);
expect(wrapper.find('[data-test-subj="testing"]')).toHaveLength(1);
});
});

View file

@ -1,130 +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, { useContext } from 'react';
import { useLocation } from 'react-router-dom';
import classNames from 'classnames';
import { EuiIcon, EuiTitle, EuiText, EuiLink } from '@elastic/eui'; // TODO: Remove EuiLink after full Kibana transition
import { i18n } from '@kbn/i18n';
import { ENTERPRISE_SEARCH_PLUGIN } from '../../../../common/constants';
import { stripTrailingSlash } from '../../../../common/strip_slashes';
import { EuiLinkTo } from '../react_router_helpers';
import { NavContext, INavContext } from './layout';
import './side_nav.scss';
/**
* Side navigation - product & icon + links wrapper
*/
interface SideNavProps {
// Expects product plugin constants (@see common/constants.ts)
product: {
NAME: string;
ID: string;
};
}
export const SideNav: React.FC<SideNavProps> = ({ product, children }) => {
return (
<nav
id="enterpriseSearchNav"
aria-label={i18n.translate('xpack.enterpriseSearch.nav.hierarchy', {
defaultMessage: 'Secondary', // The main Kibana nav is primary
})}
>
<div className={`enterpriseSearchProduct enterpriseSearchProduct--${product.ID}`}>
<div className="enterpriseSearchProduct__icon">
<EuiIcon type="logoEnterpriseSearch" />
</div>
<div className="enterpriseSearchProduct__title">
<EuiText size="xs" color="subdued">
{ENTERPRISE_SEARCH_PLUGIN.NAME}
</EuiText>
<EuiTitle size="xs">
<h3>{product.NAME}</h3>
</EuiTitle>
</div>
</div>
<ul className="enterpriseSearchNavLinks">{children}</ul>
</nav>
);
};
/**
* Side navigation link item
*/
interface SideNavLinkProps {
to: string;
shouldShowActiveForSubroutes?: boolean;
isExternal?: boolean;
className?: string;
isRoot?: boolean;
subNav?: React.ReactNode;
}
export const SideNavLink: React.FC<SideNavLinkProps> = ({
to,
shouldShowActiveForSubroutes = false,
isExternal,
children,
className,
isRoot,
subNav,
...rest
}) => {
const { closeNavigation } = useContext(NavContext) as INavContext;
const { pathname } = useLocation();
const currentPath = stripTrailingSlash(pathname);
const isActive =
currentPath === to ||
(shouldShowActiveForSubroutes && currentPath.startsWith(to)) ||
(isRoot && currentPath === '');
const classes = classNames('enterpriseSearchNavLinks__item', className, {
'enterpriseSearchNavLinks__item--isActive': !isExternal && isActive, // eslint-disable-line @typescript-eslint/naming-convention
});
return (
<li>
{isExternal ? (
// eslint-disable-next-line @elastic/eui/href-or-on-click
<EuiLink {...rest} className={classes} href={to} target="_blank" onClick={closeNavigation}>
{children}
</EuiLink>
) : (
<EuiLinkTo {...rest} className={classes} to={to} onClick={closeNavigation}>
{children}
</EuiLinkTo>
)}
{subNav && <ul className="enterpriseSearchNavLinks__subNav">{subNav}</ul>}
</li>
);
};
/**
* Side navigation non-link item
*/
interface SideNavItemProps {
className?: string;
}
export const SideNavItem: React.FC<SideNavItemProps> = ({ children, className, ...rest }) => {
const classes = classNames('enterpriseSearchNavLinks__item', className);
return (
<li {...rest} className={classes}>
{children}
</li>
);
};

View file

@ -1,25 +0,0 @@
<svg width="240" height="185" viewBox="0 0 240 185" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0" mask-type="alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="240" height="185">
<rect width="240" height="185" fill="#F7F8F9"/>
</mask>
<g mask="url(#mask0)">
<circle opacity="0.06" cx="-8" cy="3" r="149" fill="url(#paint0_linear)"/>
<path d="M57 3C57 38.3462 27.8985 67 -8 67C-43.8985 67 -73 38.3462 -73 3C-73 -32.3462 -43.8985 -61 -8 -61C27.8985 -61 57 -32.3462 57 3Z" fill="url(#paint1_linear)"/>
<circle opacity="0.33" cx="-8" cy="3" r="32" fill="black"/>
<circle cx="-8" cy="3" r="24" fill="url(#paint2_linear)"/>
</g>
<defs>
<linearGradient id="paint0_linear" x1="45.5" y1="-59.5" x2="104.5" y2="75" gradientUnits="userSpaceOnUse">
<stop/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint1_linear" x1="22" y1="-6" x2="-8" y2="67" gradientUnits="userSpaceOnUse">
<stop stop-color="#009187"/>
<stop offset="1" stop-color="#01BEB2"/>
</linearGradient>
<linearGradient id="paint2_linear" x1="-8" y1="5.5" x2="-8" y2="27" gradientUnits="userSpaceOnUse">
<stop stop-color="#D5A100"/>
<stop offset="1" stop-color="#FEC514"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -6,7 +6,7 @@
*/
export { WorkplaceSearchPageTemplate } from './page_template';
export { useWorkplaceSearchNav, WorkplaceSearchNav } from './nav';
export { useWorkplaceSearchNav } from './nav';
export { WorkplaceSearchHeaderActions } from './kibana_header_actions';
export { AccountHeader } from './account_header';
export { PersonalDashboardLayout } from './personal_dashboard_layout';

View file

@ -6,7 +6,6 @@
*/
jest.mock('../../../shared/layout', () => ({
...jest.requireActual('../../../shared/layout'),
generateNavLink: jest.fn(({ to, items }) => ({ href: to, items })),
}));
jest.mock('../../views/content_sources/components/source_sub_nav', () => ({
@ -19,13 +18,7 @@ jest.mock('../../views/settings/components/settings_sub_nav', () => ({
useSettingsSubNav: () => [],
}));
import React from 'react';
import { shallow } from 'enzyme';
import { SideNav, SideNavLink } from '../../../shared/layout';
import { useWorkplaceSearchNav, WorkplaceSearchNav } from './';
import { useWorkplaceSearchNav } from './';
describe('useWorkplaceSearchNav', () => {
it('returns an array of top-level Workplace Search nav items', () => {
@ -72,15 +65,3 @@ describe('useWorkplaceSearchNav', () => {
]);
});
});
// TODO: Delete below once fully migrated to KibanaPageTemplate
describe('WorkplaceSearchNav', () => {
it('renders', () => {
const wrapper = shallow(<WorkplaceSearchNav />);
expect(wrapper.find(SideNav)).toHaveLength(1);
expect(wrapper.find(SideNavLink).first().prop('to')).toEqual('/');
expect(wrapper.find(SideNavLink)).toHaveLength(6);
});
});

View file

@ -5,12 +5,9 @@
* 2.0.
*/
import React from 'react';
import { EuiSideNavItemType } from '@elastic/eui';
import { EuiSideNavItemType, EuiSpacer } from '@elastic/eui';
import { WORKPLACE_SEARCH_PLUGIN } from '../../../../../common/constants';
import { generateNavLink, SideNav, SideNavLink } from '../../../shared/layout';
import { generateNavLink } from '../../../shared/layout';
import { NAV } from '../../constants';
import {
SOURCES_PATH,
@ -68,37 +65,3 @@ export const useWorkplaceSearchNav = () => {
// to cause all our navItems to properly render as nav links.
return [{ id: '', name: '', items: navItems }];
};
// TODO: Delete below once fully migrated to KibanaPageTemplate
interface Props {
sourcesSubNav?: React.ReactNode;
groupsSubNav?: React.ReactNode;
settingsSubNav?: React.ReactNode;
}
export const WorkplaceSearchNav: React.FC<Props> = ({
sourcesSubNav,
groupsSubNav,
settingsSubNav,
}) => (
<SideNav product={WORKPLACE_SEARCH_PLUGIN}>
<SideNavLink to="/" isRoot>
{NAV.OVERVIEW}
</SideNavLink>
<SideNavLink to={SOURCES_PATH} subNav={sourcesSubNav}>
{NAV.SOURCES}
</SideNavLink>
<SideNavLink to={GROUPS_PATH} subNav={groupsSubNav}>
{NAV.GROUPS}
</SideNavLink>
<SideNavLink shouldShowActiveForSubroutes to={USERS_AND_ROLES_PATH}>
{NAV.ROLE_MAPPINGS}
</SideNavLink>
<SideNavLink to={SECURITY_PATH}>{NAV.SECURITY}</SideNavLink>
<SideNavLink subNav={settingsSubNav} to={ORG_SETTINGS_PATH}>
{NAV.SETTINGS}
</SideNavLink>
<EuiSpacer />
</SideNav>
);

View file

@ -7884,9 +7884,6 @@
"xpack.enterpriseSearch.featureCatalogueDescription2": "ユーザーを関連するデータにつなげます。",
"xpack.enterpriseSearch.featureCatalogueDescription3": "チームの内容を統合します。",
"xpack.enterpriseSearch.hiddenText": "非表示のテキスト",
"xpack.enterpriseSearch.nav.hierarchy": "セカンダリ",
"xpack.enterpriseSearch.nav.menu": "メニュー",
"xpack.enterpriseSearch.nav.toggleMenu": "セカンダリナビゲーションを切り替える",
"xpack.enterpriseSearch.navTitle": "概要",
"xpack.enterpriseSearch.notFound.action1": "ダッシュボードに戻す",
"xpack.enterpriseSearch.notFound.action2": "サポートに問い合わせる",

View file

@ -7952,9 +7952,6 @@
"xpack.enterpriseSearch.featureCatalogueDescription2": "将您的用户连接到相关数据。",
"xpack.enterpriseSearch.featureCatalogueDescription3": "统一您的团队内容。",
"xpack.enterpriseSearch.hiddenText": "隐藏文本",
"xpack.enterpriseSearch.nav.hierarchy": "次级",
"xpack.enterpriseSearch.nav.menu": "菜单",
"xpack.enterpriseSearch.nav.toggleMenu": "切换次级导航",
"xpack.enterpriseSearch.navTitle": "概览",
"xpack.enterpriseSearch.notFound.action1": "返回到您的仪表板",
"xpack.enterpriseSearch.notFound.action2": "联系支持人员",