Skip to content

Commit

Permalink
[Enterprise Search] Add solution-level side navigation (#74705)
Browse files Browse the repository at this point in the history
* Add basic layout/sidebar blocking

- note: we should *not* set left: 0 / top: 0 etc., as this can interfere with Kibana's existing UI (e.g. docked navigation, telemetry callout)

* Add sidebar styles + static links

* Refactor SideNav to be a reusable component

- So that Workplace Search can reuse the same layout but pass in their own custom nav
+ Refactor AppSearch to use Layout in router

* Refactor all views to account for in-router Layout

* Fix root redirects not working as expected

- If enterpriseSearchUrl hasn't been set, all pages should redirect to SetupGuide, not just root
- The engines redirect simply wasn't working at all - it would always show a blank page when '/' was clicked in the Kibana breadcrumbs. Not sure if this is a Kibana issue - had to change to a component load to fix
+ Simplify index.test.tsx (probably unreasonable and not super helpful to add assertions for each new route)

* Implement active styling for links

* Fix failing location tests

- By adding a new useLocation mock
+ add SideNavLink active class test

TODO: I should probably rename react_router_history.mock to just react_router.mock

* Add responsive toggle + styling

* Add navigation accessibility attributes/controls

* [Feedback] Update mobile UX to close menu on link click/navigation

- This requires updating our EUI/React Router components to accept and run custom onClick events
- Also requires adding a new ReactContext to pass down closeNavigation, but that's not too onerous thanks to useContext
  • Loading branch information
Constance committed Aug 12, 2020
1 parent 8f54198 commit 6ee2460
Show file tree
Hide file tree
Showing 19 changed files with 741 additions and 173 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

export { mockHistory } from './react_router_history.mock';
export { mockHistory, mockLocation } from './react_router_history.mock';
export { mockKibanaContext } from './kibana_context.mock';
export { mockLicenseContext } from './license_context.mock';
export {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

/**
* NOTE: This variable name MUST start with 'mock*' in order for
* NOTE: These variable names MUST start with 'mock*' in order for
* Jest to accept its use within a jest.mock()
*/
export const mockHistory = {
Expand All @@ -15,9 +15,17 @@ export const mockHistory = {
pathname: '/current-path',
},
};
export const mockLocation = {
key: 'someKey',
pathname: '/current-path',
search: '?query=something',
hash: '#hash',
state: {},
};

jest.mock('react-router-dom', () => ({
useHistory: jest.fn(() => mockHistory),
useLocation: jest.fn(() => mockLocation),
}));

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

import React, { useContext } from 'react';
import { EuiPage, EuiPageBody, EuiPageContent, EuiEmptyPrompt, EuiButton } from '@elastic/eui';
import { EuiPageContent, EuiEmptyPrompt, EuiButton } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';

import { sendTelemetry } from '../../../shared/telemetry';
Expand All @@ -32,43 +32,40 @@ export const EmptyState: React.FC = () => {
};

return (
<EuiPage restrictWidth>
<>
<SetPageChrome isRoot />

<EuiPageBody>
<EngineOverviewHeader />
<EuiPageContent className="emptyState">
<EuiEmptyPrompt
className="emptyState__prompt"
iconType="eyeClosed"
title={
<h2>
<FormattedMessage
id="xpack.enterpriseSearch.appSearch.emptyState.title"
defaultMessage="Create your first engine"
/>
</h2>
}
titleSize="l"
body={
<p>
<FormattedMessage
id="xpack.enterpriseSearch.appSearch.emptyState.description1"
defaultMessage="An App Search engine stores the documents for your search experience."
/>
</p>
}
actions={
<EuiButton iconType="popout" fill {...buttonProps}>
<FormattedMessage
id="xpack.enterpriseSearch.appSearch.emptyState.createFirstEngineCta"
defaultMessage="Create an engine"
/>
</EuiButton>
}
/>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
<EngineOverviewHeader />
<EuiPageContent className="emptyState">
<EuiEmptyPrompt
className="emptyState__prompt"
iconType="eyeClosed"
title={
<h2>
<FormattedMessage
id="xpack.enterpriseSearch.appSearch.emptyState.title"
defaultMessage="Create your first engine"
/>
</h2>
}
titleSize="l"
body={
<p>
<FormattedMessage
id="xpack.enterpriseSearch.appSearch.emptyState.description1"
defaultMessage="An App Search engine stores the documents for your search experience."
/>
</p>
}
actions={
<EuiButton iconType="popout" fill {...buttonProps}>
<FormattedMessage
id="xpack.enterpriseSearch.appSearch.emptyState.createFirstEngineCta"
defaultMessage="Create an engine"
/>
</EuiButton>
}
/>
</EuiPageContent>
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

import React from 'react';
import { EuiPage, EuiPageBody, EuiPageContent } from '@elastic/eui';
import { EuiPageContent } from '@elastic/eui';

import { ErrorStatePrompt } from '../../../shared/error_state';
import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome';
Expand All @@ -16,16 +16,14 @@ import './empty_states.scss';

export const ErrorState: React.FC = () => {
return (
<EuiPage restrictWidth>
<>
<SetPageChrome isRoot />
<SendTelemetry action="error" metric="cannot_connect" />

<EuiPageBody>
<EngineOverviewHeader />
<EuiPageContent>
<ErrorStatePrompt />
</EuiPageContent>
</EuiPageBody>
</EuiPage>
<EngineOverviewHeader />
<EuiPageContent>
<ErrorStatePrompt />
</EuiPageContent>
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

import React from 'react';
import { EuiPage, EuiPageBody, EuiPageContent, EuiSpacer, EuiLoadingContent } from '@elastic/eui';
import { EuiPageContent, EuiSpacer, EuiLoadingContent } from '@elastic/eui';

import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome';
import { EngineOverviewHeader } from '../engine_overview_header';
Expand All @@ -14,17 +14,14 @@ import './empty_states.scss';

export const LoadingState: React.FC = () => {
return (
<EuiPage restrictWidth>
<>
<SetPageChrome isRoot />

<EuiPageBody>
<EngineOverviewHeader />
<EuiPageContent className="emptyState">
<EuiLoadingContent lines={5} />
<EuiSpacer size="xxl" />
<EuiLoadingContent lines={4} />
</EuiPageContent>
</EuiPageBody>
</EuiPage>
<EngineOverviewHeader />
<EuiPageContent className="emptyState">
<EuiLoadingContent lines={5} />
<EuiSpacer size="xxl" />
<EuiLoadingContent lines={4} />
</EuiPageContent>
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,11 @@
* you may not use this file except in compliance with the Elastic License.
*/

/**
* Engine Overview
*/
.engineOverview {
width: 100%;

&__body {
padding: $euiSize;
padding: $euiSize;

@include euiBreakpoint('m', 'l', 'xl') {
padding: $euiSizeXL;
}
@include euiBreakpoint('m', 'l', 'xl') {
padding: $euiSizeXL;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@

import React, { useContext, useEffect, useState } from 'react';
import {
EuiPage,
EuiPageBody,
EuiPageContent,
EuiPageContentHeader,
EuiPageContentBody,
Expand Down Expand Up @@ -92,64 +90,61 @@ export const EngineOverview: React.FC = () => {
if (!engines.length) return <EmptyState />;

return (
<EuiPage restrictWidth className="engineOverview">
<>
<SetPageChrome isRoot />
<SendTelemetry action="viewed" metric="engines_overview" />

<EuiPageBody>
<EngineOverviewHeader />

<EuiPageContent panelPaddingSize="s" className="engineOverview__body">
<EuiPageContentHeader>
<EuiTitle size="s">
<h2>
<img src={EnginesIcon} alt="" className="engineIcon" />
<FormattedMessage
id="xpack.enterpriseSearch.appSearch.enginesOverview.engines"
defaultMessage="Engines"
/>
</h2>
</EuiTitle>
</EuiPageContentHeader>
<EuiPageContentBody data-test-subj="appSearchEngines">
<EngineTable
data={engines}
pagination={{
totalEngines: enginesTotal,
pageIndex: enginesPage - 1,
onPaginate: setEnginesPage,
}}
/>
</EuiPageContentBody>

{metaEngines.length > 0 && (
<>
<EuiSpacer size="xl" />
<EuiPageContentHeader>
<EuiTitle size="s">
<h2>
<img src={MetaEnginesIcon} alt="" className="engineIcon" />
<FormattedMessage
id="xpack.enterpriseSearch.appSearch.enginesOverview.metaEngines"
defaultMessage="Meta Engines"
/>
</h2>
</EuiTitle>
</EuiPageContentHeader>
<EuiPageContentBody data-test-subj="appSearchMetaEngines">
<EngineTable
data={metaEngines}
pagination={{
totalEngines: metaEnginesTotal,
pageIndex: metaEnginesPage - 1,
onPaginate: setMetaEnginesPage,
}}
/>
</EuiPageContentBody>
</>
)}
</EuiPageContent>
</EuiPageBody>
</EuiPage>
<EngineOverviewHeader />
<EuiPageContent panelPaddingSize="s" className="engineOverview">
<EuiPageContentHeader>
<EuiTitle size="s">
<h2>
<img src={EnginesIcon} alt="" className="engineIcon" />
<FormattedMessage
id="xpack.enterpriseSearch.appSearch.enginesOverview.engines"
defaultMessage="Engines"
/>
</h2>
</EuiTitle>
</EuiPageContentHeader>
<EuiPageContentBody data-test-subj="appSearchEngines">
<EngineTable
data={engines}
pagination={{
totalEngines: enginesTotal,
pageIndex: enginesPage - 1,
onPaginate: setEnginesPage,
}}
/>
</EuiPageContentBody>

{metaEngines.length > 0 && (
<>
<EuiSpacer size="xl" />
<EuiPageContentHeader>
<EuiTitle size="s">
<h2>
<img src={MetaEnginesIcon} alt="" className="engineIcon" />
<FormattedMessage
id="xpack.enterpriseSearch.appSearch.enginesOverview.metaEngines"
defaultMessage="Meta Engines"
/>
</h2>
</EuiTitle>
</EuiPageContentHeader>
<EuiPageContentBody data-test-subj="appSearchMetaEngines">
<EngineTable
data={metaEngines}
pagination={{
totalEngines: metaEnginesTotal,
pageIndex: metaEnginesPage - 1,
onPaginate: setMetaEnginesPage,
}}
/>
</EuiPageContentBody>
</>
)}
</EuiPageContent>
</>
);
};
Loading

0 comments on commit 6ee2460

Please sign in to comment.