Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[App Search] Convert Search UI view to new page template + minor UI polish #102813

Merged
merged 6 commits into from
Jun 22, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ export const EngineRouter: React.FC = () => {
<Route exact path={ENGINE_PATH}>
<EngineOverview />
</Route>
{canManageEngineSearchUi && (
<Route path={ENGINE_SEARCH_UI_PATH}>
<SearchUI />
</Route>
)}
{/* TODO: Remove layout once page template migration is over */}
<Layout navigation={<AppSearchNav />}>
{canViewEngineAnalytics && (
Expand Down Expand Up @@ -141,11 +146,6 @@ export const EngineRouter: React.FC = () => {
<ApiLogs />
</Route>
)}
{canManageEngineSearchUi && (
<Route path={ENGINE_SEARCH_UI_PATH}>
<SearchUI />
</Route>
)}
{canViewMetaEngineSourceEngines && (
<Route path={META_ENGINE_SOURCE_ENGINES_PATH}>
<SourceEngines />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* 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 { EuiEmptyPrompt, EuiButton } from '@elastic/eui';

import { EmptyState } from './empty_state';

describe('EmptyState', () => {
it('renders', () => {
const wrapper = shallow(<EmptyState />)
.find(EuiEmptyPrompt)
.dive();

expect(wrapper.find('h2').text()).toEqual('Add documents to generate a Search UI');
expect(wrapper.find(EuiButton).prop('href')).toEqual(
expect.stringContaining('/reference-ui-guide.html')
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* 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 { EuiButton, EuiEmptyPrompt } from '@elastic/eui';
import { i18n } from '@kbn/i18n';

import { DOCS_PREFIX } from '../../../routes';

export const EmptyState: React.FC = () => (
<EuiEmptyPrompt
iconType="search"
title={
<h2>
{i18n.translate('xpack.enterpriseSearch.appSearch.engine.searchUI.empty.title', {
defaultMessage: 'Add documents to generate a Search UI',
})}
</h2>
}
body={
<p>
{i18n.translate('xpack.enterpriseSearch.appSearch.engine.searchUI.empty.description', {
defaultMessage:
'A schema will be automatically created for you after you index some documents.',
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This copy is the exact same as the empty states for relevance tuning & result settings, since all of these pages depend on/use engine schema

})}
</p>
}
actions={
<EuiButton
size="s"
target="_blank"
iconType="popout"
href={`${DOCS_PREFIX}/reference-ui-guide.html`}
>
{i18n.translate('xpack.enterpriseSearch.appSearch.engine.searchUI.empty.buttonLabel', {
defaultMessage: 'Read the Search UI guide',
})}
</EuiButton>
}
/>
);
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,17 @@
*/

import '../../../__mocks__/shallow_useeffect.mock';
import '../../__mocks__/engine_logic.mock';

import { setMockActions } from '../../../__mocks__/kea_logic';
import { setMockActions, setMockValues } from '../../../__mocks__/kea_logic';
import { mockEngineValues } from '../../__mocks__/engine_logic.mock';

import React from 'react';

import { shallow } from 'enzyme';

import { SearchUIForm } from './components/search_ui_form';
import { SearchUIGraphic } from './components/search_ui_graphic';

import { SearchUI } from './';

describe('SearchUI', () => {
Expand All @@ -24,11 +27,13 @@ describe('SearchUI', () => {
beforeEach(() => {
jest.clearAllMocks();
setMockActions(actions);
setMockValues(mockEngineValues);
});

it('renders', () => {
shallow(<SearchUI />);
// TODO: Check for form
const wrapper = shallow(<SearchUI />);
expect(wrapper.find(SearchUIForm).exists()).toBe(true);
expect(wrapper.find(SearchUIGraphic).exists()).toBe(true);
});

it('initializes data on mount', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,87 +7,79 @@

import React, { useEffect } from 'react';

import { useActions } from 'kea';
import { useActions, useValues } from 'kea';

import {
EuiPageHeader,
EuiPageContentBody,
EuiText,
EuiFlexItem,
EuiFlexGroup,
EuiSpacer,
EuiLink,
} from '@elastic/eui';
import { EuiText, EuiFlexItem, EuiFlexGroup, EuiSpacer, EuiLink } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';

import { FlashMessages } from '../../../shared/flash_messages';
import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome';

import { DOCS_PREFIX } from '../../routes';
import { getEngineBreadcrumbs } from '../engine';
import { EngineLogic, getEngineBreadcrumbs } from '../engine';
import { AppSearchPageTemplate } from '../layout';

import { EmptyState } from './components/empty_state';
import { SearchUIForm } from './components/search_ui_form';
import { SearchUIGraphic } from './components/search_ui_graphic';
import { SEARCH_UI_TITLE } from './i18n';
import { SearchUILogic } from './search_ui_logic';

export const SearchUI: React.FC = () => {
const { loadFieldData } = useActions(SearchUILogic);
const { engine } = useValues(EngineLogic);

useEffect(() => {
loadFieldData();
}, []);

return (
<>
<SetPageChrome trail={getEngineBreadcrumbs([SEARCH_UI_TITLE])} />
<EuiPageHeader pageTitle={SEARCH_UI_TITLE} />
<FlashMessages />
<EuiPageContentBody>
<EuiFlexGroup alignItems="flexStart">
<EuiFlexItem>
<EuiText size="s" color="subdued">
<p>
<FormattedMessage
id="xpack.enterpriseSearch.appSearch.engine.searchUI.bodyDescription"
defaultMessage="Search UI is a free and open library for building search experiences with React. {link}."
values={{
link: (
<EuiLink target="_blank" href="https://github.com/elastic/search-ui">
<FormattedMessage
id="xpack.enterpriseSearch.appSearch.engine.searchUI.repositoryLinkText"
defaultMessage="Learn more"
/>
</EuiLink>
),
}}
/>
</p>
<p>
<FormattedMessage
id="xpack.enterpriseSearch.appSearch.engine.searchUI.lowerBodyDescription"
defaultMessage="Use the fields below to generate a sample search experience built with Search UI. Use the sample to preview search results, or build upon it to create your own custom search experience. {link}."
values={{
link: (
<EuiLink target="_blank" href={`${DOCS_PREFIX}/reference-ui-guide.html`}>
<FormattedMessage
id="xpack.enterpriseSearch.appSearch.engine.searchUI.guideLinkText"
defaultMessage="Learn more"
/>
</EuiLink>
),
}}
/>
</p>
</EuiText>
<EuiSpacer />
<SearchUIForm />
</EuiFlexItem>
<EuiFlexItem>
<SearchUIGraphic />
</EuiFlexItem>
</EuiFlexGroup>
</EuiPageContentBody>
</>
<AppSearchPageTemplate
pageChrome={getEngineBreadcrumbs([SEARCH_UI_TITLE])}
pageHeader={{ pageTitle: SEARCH_UI_TITLE }}
isEmptyState={Object.keys(engine.schema || {}).length === 0}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heads up, I fully intend on refactoring this Object.keys check to an EngineLogic selector - but that's going to come in a follow-up polish PR once all the views land. Essentially I want to tackle the concept of empty engine polling more holistically and at the engine logic level, and ensure all pages update dynamically when an engine is empty and receives new documents/schema.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I did check w/ Davey on this and he confirmed that this view should check for empty schema and not just for empty documents. So someone could index a document and create a schema but then delete the document, and this would no longer show the empty state (which matches how Relevance Tuning & Result Settings behave)

emptyState={<EmptyState />}
>
<EuiFlexGroup alignItems="flexStart">
<EuiFlexItem>
<EuiText size="s" color="subdued">
<p>
<FormattedMessage
id="xpack.enterpriseSearch.appSearch.engine.searchUI.bodyDescription"
defaultMessage="Search UI is a free and open library for building search experiences with React. {link}."
values={{
link: (
<EuiLink target="_blank" href="https://github.com/elastic/search-ui">
<FormattedMessage
id="xpack.enterpriseSearch.appSearch.engine.searchUI.repositoryLinkText"
defaultMessage="Learn more"
/>
</EuiLink>
),
}}
/>
</p>
<p>
<FormattedMessage
id="xpack.enterpriseSearch.appSearch.engine.searchUI.lowerBodyDescription"
defaultMessage="Use the fields below to generate a sample search experience built with Search UI. Use the sample to preview search results, or build upon it to create your own custom search experience. {link}."
values={{
link: (
<EuiLink target="_blank" href={`${DOCS_PREFIX}/reference-ui-guide.html`}>
<FormattedMessage
id="xpack.enterpriseSearch.appSearch.engine.searchUI.guideLinkText"
defaultMessage="Learn more"
/>
</EuiLink>
),
}}
/>
</p>
</EuiText>
<EuiSpacer />
<SearchUIForm />
</EuiFlexItem>
<EuiFlexItem>
<SearchUIGraphic />
</EuiFlexItem>
</EuiFlexGroup>
</AppSearchPageTemplate>
);
};