Skip to content

Commit

Permalink
[App Search] Added the Documents View (#83947)
Browse files Browse the repository at this point in the history
  • Loading branch information
JasonStoltz committed Dec 4, 2020
1 parent f176e8b commit 40e206e
Show file tree
Hide file tree
Showing 29 changed files with 1,464 additions and 19 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,10 @@
"@elastic/good": "^9.0.1-kibana3",
"@elastic/node-crypto": "1.2.1",
"@elastic/numeral": "^2.5.0",
"@elastic/react-search-ui": "^1.5.0",
"@elastic/request-crypto": "1.1.4",
"@elastic/safer-lodash-set": "link:packages/elastic-safer-lodash-set",
"@elastic/search-ui-app-search-connector": "^1.5.0",
"@hapi/boom": "^7.4.11",
"@hapi/cookie": "^10.1.2",
"@hapi/good-squeeze": "5.2.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import React from 'react';
import { shallow } from 'enzyme';
import { EuiButton } from '@elastic/eui';

import { DocumentCreationButton } from './document_creation_button';

describe('DocumentCreationButton', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('should render', () => {
const wrapper = shallow(<DocumentCreationButton />);
expect(wrapper.find(EuiButton).length).toEqual(1);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import React from 'react';
import { i18n } from '@kbn/i18n';

import { EuiButton } from '@elastic/eui';

export const DocumentCreationButton: React.FC = () => {
return (
<EuiButton fill={true} color="primary" data-test-subj="IndexDocumentsButton">
{i18n.translate('xpack.enterpriseSearch.appSearch.documents.indexDocuments', {
defaultMessage: 'Index documents',
})}
</EuiButton>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { setMockValues } from '../../../__mocks__/kea.mock';

import React from 'react';
import { shallow } from 'enzyme';

import { DocumentCreationButton } from './document_creation_button';
import { SearchExperience } from './search_experience';
import { Documents } from '.';

describe('Documents', () => {
const values = {
isMetaEngine: false,
myRole: { canManageEngineDocuments: true },
};

beforeEach(() => {
jest.clearAllMocks();
setMockValues(values);
});

it('renders', () => {
const wrapper = shallow(<Documents engineBreadcrumb={['test']} />);
expect(wrapper.find(SearchExperience).exists()).toBe(true);
});

it('renders a DocumentCreationButton if the user can manage engine documents', () => {
setMockValues({
...values,
myRole: { canManageEngineDocuments: true },
});

const wrapper = shallow(<Documents engineBreadcrumb={['test']} />);
expect(wrapper.find(DocumentCreationButton).exists()).toBe(true);
});

describe('Meta Engines', () => {
it('renders a Meta Engines message if this is a meta engine', () => {
setMockValues({
...values,
isMetaEngine: true,
});

const wrapper = shallow(<Documents engineBreadcrumb={['test']} />);
expect(wrapper.find('[data-test-subj="MetaEnginesCallout"]').exists()).toBe(true);
});

it('does not render a Meta Engines message if this is not a meta engine', () => {
setMockValues({
...values,
isMetaEngine: false,
});

const wrapper = shallow(<Documents engineBreadcrumb={['test']} />);
expect(wrapper.find('[data-test-subj="MetaEnginesCallout"]').exists()).toBe(false);
});

it('does not render a DocumentCreationButton even if the user can manage engine documents', () => {
setMockValues({
...values,
myRole: { canManageEngineDocuments: true },
isMetaEngine: true,
});

const wrapper = shallow(<Documents engineBreadcrumb={['test']} />);
expect(wrapper.find(DocumentCreationButton).exists()).toBe(false);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,26 @@

import React from 'react';

import {
EuiPageHeader,
EuiPageHeaderSection,
EuiTitle,
EuiPageContent,
EuiPageContentBody,
} from '@elastic/eui';
import { EuiPageHeader, EuiPageHeaderSection, EuiTitle, EuiCallOut, EuiSpacer } from '@elastic/eui';
import { useValues } from 'kea';
import { i18n } from '@kbn/i18n';

import { DocumentCreationButton } from './document_creation_button';
import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome';
import { FlashMessages } from '../../../shared/flash_messages';
import { DOCUMENTS_TITLE } from './constants';
import { EngineLogic } from '../engine';
import { AppLogic } from '../../app_logic';
import { SearchExperience } from './search_experience';

interface Props {
engineBreadcrumb: string[];
}

export const Documents: React.FC<Props> = ({ engineBreadcrumb }) => {
const { isMetaEngine } = useValues(EngineLogic);
const { myRole } = useValues(AppLogic);

return (
<>
<SetPageChrome trail={[...engineBreadcrumb, DOCUMENTS_TITLE]} />
Expand All @@ -32,12 +35,36 @@ export const Documents: React.FC<Props> = ({ engineBreadcrumb }) => {
<h1>{DOCUMENTS_TITLE}</h1>
</EuiTitle>
</EuiPageHeaderSection>
{myRole.canManageEngineDocuments && !isMetaEngine && (
<EuiPageHeaderSection>
<DocumentCreationButton />
</EuiPageHeaderSection>
)}
</EuiPageHeader>
<EuiPageContent>
<EuiPageContentBody>
<FlashMessages />
</EuiPageContentBody>
</EuiPageContent>
<FlashMessages />
{isMetaEngine && (
<>
<EuiCallOut
data-test-subj="MetaEnginesCallout"
iconType="iInCircle"
title={i18n.translate(
'xpack.enterpriseSearch.appSearch.documents.metaEngineCallout.title',
{
defaultMessage: 'You are within a Meta Engine.',
}
)}
>
<p>
{i18n.translate('xpack.enterpriseSearch.appSearch.documents.metaEngineCallout', {
defaultMessage:
'Meta Engines have many Source Engines. Visit your Source Engines to alter their documents.',
})}
</p>
</EuiCallOut>
<EuiSpacer />
</>
)}
<SearchExperience />
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

jest.mock('../hooks', () => ({
useSearchContextActions: jest.fn(() => ({})),
useSearchContextState: jest.fn(() => ({})),
}));

import { useSearchContextState, useSearchContextActions } from '../hooks';

export const setMockSearchContextState = (values: object) => {
(useSearchContextState as jest.Mock).mockImplementation(() => values);
};
export const setMockSearchContextActions = (actions: object) => {
(useSearchContextActions as jest.Mock).mockImplementation(() => actions);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

const mockAction = jest.fn();

let mockSubcription: (state: object) => void;
const mockDriver = {
state: { foo: 'foo' },
actions: { bar: mockAction },
subscribeToStateChanges: jest.fn().mockImplementation((fn) => {
mockSubcription = fn;
}),
unsubscribeToStateChanges: jest.fn(),
};

jest.mock('react', () => ({
...(jest.requireActual('react') as object),
useContext: jest.fn(() => ({
driver: mockDriver,
})),
}));

import React from 'react';
import { act } from 'react-dom/test-utils';
import { mount, ReactWrapper } from 'enzyme';

import { useSearchContextState, useSearchContextActions } from './hooks';

describe('hooks', () => {
describe('useSearchContextState', () => {
const TestComponent = () => {
const { foo } = useSearchContextState();
return <div>{foo}</div>;
};

let wrapper: ReactWrapper;
beforeAll(() => {
wrapper = mount(<TestComponent />);
});

it('exposes search state', () => {
expect(wrapper.text()).toEqual('foo');
});

it('subscribes to state changes', () => {
act(() => {
mockSubcription({ foo: 'bar' });
});

expect(wrapper.text()).toEqual('bar');
});

it('unsubscribes to state changes when unmounted', () => {
wrapper.unmount();

expect(mockDriver.unsubscribeToStateChanges).toHaveBeenCalled();
});
});

describe('useSearchContextActions', () => {
it('exposes actions', () => {
const TestComponent = () => {
const { bar } = useSearchContextActions();
bar();
return null;
};

mount(<TestComponent />);
expect(mockAction).toHaveBeenCalled();
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { useContext, useEffect, useState } from 'react';

// @ts-expect-error types are not available for this package yet
import { SearchContext } from '@elastic/react-search-ui';

export const useSearchContextState = () => {
const { driver } = useContext(SearchContext);
const [state, setState] = useState(driver.state);

useEffect(() => {
driver.subscribeToStateChanges((newState: object) => {
setState(newState);
});
return () => {
driver.unsubscribeToStateChanges();
};
}, [state]);

return state;
};

export const useSearchContextActions = () => {
const { driver } = useContext(SearchContext);
return driver.actions;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

export { SearchExperience } from './search_experience';
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import React from 'react';

import { shallow } from 'enzyme';
// @ts-expect-error types are not available for this package yet
import { Paging, ResultsPerPage } from '@elastic/react-search-ui';

import { Pagination } from './pagination';

describe('Pagination', () => {
it('renders', () => {
const wrapper = shallow(<Pagination aria-label="foo" />);
expect(wrapper.find(Paging).exists()).toBe(true);
expect(wrapper.find(ResultsPerPage).exists()).toBe(true);
});

it('passes aria-label through to Paging', () => {
const wrapper = shallow(<Pagination aria-label="foo" />);
expect(wrapper.find(Paging).prop('aria-label')).toEqual('foo');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';

import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
// @ts-expect-error types are not available for this package yet
import { Paging, ResultsPerPage } from '@elastic/react-search-ui';
import { PagingView, ResultsPerPageView } from './views';

export const Pagination: React.FC<{ 'aria-label': string }> = ({ 'aria-label': ariaLabel }) => (
<EuiFlexGroup alignItems="center" className="documentsSearchExperience__pagingInfo">
<EuiFlexItem>
<Paging view={PagingView} aria-label={ariaLabel} />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<ResultsPerPage view={ResultsPerPageView} />
</EuiFlexItem>
</EuiFlexGroup>
);
Loading

0 comments on commit 40e206e

Please sign in to comment.