mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[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:
parent
bd2215f587
commit
803d0fa57b
18 changed files with 18 additions and 1233 deletions
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -6,6 +6,5 @@
|
|||
*/
|
||||
|
||||
export { EngineRouter } from './engine_router';
|
||||
export { EngineNav } from './engine_nav';
|
||||
export { EngineLogic } from './engine_logic';
|
||||
export { generateEnginePath, getEngineBreadcrumbs } from './utils';
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -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 |
|
@ -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';
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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": "サポートに問い合わせる",
|
||||
|
|
|
@ -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": "联系支持人员",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue