Skip to content

Commit

Permalink
[Enterprise Search] Create HttpLogic Kea store, add http interceptors…
Browse files Browse the repository at this point in the history
…, and manage error connecting at top app-level (#75790)

* [Setup] Change error connecting status code to 502

- For clearer error handling

* Set up new HttpProvider/Logic Kea store & listeners

- This allows us to:
  - connect() directly to HttpLogic in other Kea logic files that need to make http calls, instead of passing in http manually via args
  - Set http interceptors & remove them interceptors on unmount within Kea
  - Share state derived from http (e.g. errorConnecting, readOnlyMode) between both AS & WS (but allow each app to handle that state differently if needed)

+ Refactors necessary for these changes:
  - Kea types - add events key, clarify that mount returns an unmount function, fix reducer state type
  - ReactDOM unmount - remove resetContext({}), was preventing logic from unmounting properly

* Update AS & WS to show error connecting component at app level

* [WS] Remove errorConnecting logic & http arg from Overview

- Since main app is now handling errorConnecting
- http can now be connected directly from HttpLogic Kea store, so no need to pass it
+ minor cleanup in logic_overview.test.ts - remove unneeded unmount(), act(), switch to HttpLogic mock

* [AS] Add top-level ErrorConnecting component & remove error logic from EngineOverview

* [AS] Clean up/move EngineOverview child components into subfolder

- delete old ErrorState component
- move LoadingState, EmptyState, and EngineOverviewHeader into subfolders in engine_overview

* PR feedback: Update test assertions 404 copy
  • Loading branch information
Constance committed Aug 24, 2020
1 parent 3256992 commit 90bd654
Show file tree
Hide file tree
Showing 34 changed files with 460 additions and 207 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,19 @@
* you may not use this file except in compliance with the Elastic License.
*/

import '../../../__mocks__/shallow_usecontext.mock';
import '../../../../__mocks__/shallow_usecontext.mock';

import React from 'react';
import { shallow } from 'enzyme';
import { EuiEmptyPrompt, EuiButton, EuiLoadingContent } from '@elastic/eui';
import { ErrorStatePrompt } from '../../../shared/error_state';
import { EuiEmptyPrompt, EuiButton } from '@elastic/eui';

jest.mock('../../../shared/telemetry', () => ({
jest.mock('../../../../shared/telemetry', () => ({
sendTelemetry: jest.fn(),
SendAppSearchTelemetry: jest.fn(),
}));
import { sendTelemetry } from '../../../shared/telemetry';
import { sendTelemetry } from '../../../../shared/telemetry';

import { ErrorState, EmptyState, LoadingState } from './';

describe('ErrorState', () => {
it('renders', () => {
const wrapper = shallow(<ErrorState />);

expect(wrapper.find(ErrorStatePrompt)).toHaveLength(1);
});
});
import { EmptyState } from './';

describe('EmptyState', () => {
it('renders', () => {
Expand All @@ -44,11 +35,3 @@ describe('EmptyState', () => {
(sendTelemetry as jest.Mock).mockClear();
});
});

describe('LoadingState', () => {
it('renders', () => {
const wrapper = shallow(<LoadingState />);

expect(wrapper.find(EuiLoadingContent)).toHaveLength(2);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import React, { useContext } from 'react';
import { EuiPageContent, EuiEmptyPrompt, EuiButton } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';

import { sendTelemetry } from '../../../shared/telemetry';
import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome';
import { KibanaContext, IKibanaContext } from '../../../index';
import { CREATE_ENGINES_PATH } from '../../routes';
import { sendTelemetry } from '../../../../shared/telemetry';
import { SetAppSearchChrome as SetPageChrome } from '../../../../shared/kibana_chrome';
import { KibanaContext, IKibanaContext } from '../../../../index';
import { CREATE_ENGINES_PATH } from '../../../routes';

import { EngineOverviewHeader } from '../engine_overview_header';
import { EngineOverviewHeader } from './header';

import './empty_states.scss';
import './empty_state.scss';

export const EmptyState: React.FC = () => {
const {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@
* you may not use this file except in compliance with the Elastic License.
*/

import '../../../__mocks__/shallow_usecontext.mock';
import '../../../../__mocks__/shallow_usecontext.mock';

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

jest.mock('../../../shared/telemetry', () => ({ sendTelemetry: jest.fn() }));
import { sendTelemetry } from '../../../shared/telemetry';
jest.mock('../../../../shared/telemetry', () => ({ sendTelemetry: jest.fn() }));
import { sendTelemetry } from '../../../../shared/telemetry';

import { EngineOverviewHeader } from '../engine_overview_header';
import { EngineOverviewHeader } from './';

describe('EngineOverviewHeader', () => {
it('renders', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import {
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';

import { sendTelemetry } from '../../../shared/telemetry';
import { KibanaContext, IKibanaContext } from '../../../index';
import { sendTelemetry } from '../../../../shared/telemetry';
import { KibanaContext, IKibanaContext } from '../../../../index';

export const EngineOverviewHeader: React.FC = () => {
const {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/

export { EngineOverviewHeader } from './header';
export { LoadingState } from './loading_state';
export { EmptyState } from './empty_state';
export { ErrorState } from './error_state';
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.
*/

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

import { LoadingState } from './';

describe('LoadingState', () => {
it('renders', () => {
const wrapper = shallow(<LoadingState />);

expect(wrapper.find(EuiLoadingContent)).toHaveLength(2);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,15 @@
import React from 'react';
import { EuiPageContent, EuiSpacer, EuiLoadingContent } from '@elastic/eui';

import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome';
import { EngineOverviewHeader } from '../engine_overview_header';

import './empty_states.scss';
import { SetAppSearchChrome as SetPageChrome } from '../../../../shared/kibana_chrome';
import { EngineOverviewHeader } from './header';

export const LoadingState: React.FC = () => {
return (
<>
<SetPageChrome isRoot />
<EngineOverviewHeader />
<EuiPageContent className="emptyState">
<EuiPageContent paddingSize="l">
<EuiLoadingContent lines={5} />
<EuiSpacer size="xxl" />
<EuiLoadingContent lines={4} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { shallow, ReactWrapper } from 'enzyme';

import { mountWithAsyncContext, mockKibanaContext } from '../../../__mocks__';

import { LoadingState, EmptyState, ErrorState } from '../empty_states';
import { LoadingState, EmptyState } from './components';
import { EngineTable } from './engine_table';

import { EngineOverview } from './';
Expand Down Expand Up @@ -40,16 +40,6 @@ describe('EngineOverview', () => {

expect(wrapper.find(EmptyState)).toHaveLength(1);
});

it('hasErrorConnecting', async () => {
const wrapper = await mountWithAsyncContext(<EngineOverview />, {
http: {
...mockHttp,
get: () => ({ invalidPayload: true }),
},
});
expect(wrapper.find(ErrorState)).toHaveLength(1);
});
});

describe('happy-path states', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ import { KibanaContext, IKibanaContext } from '../../../index';
import EnginesIcon from '../../assets/engine.svg';
import MetaEnginesIcon from '../../assets/meta_engine.svg';

import { LoadingState, EmptyState, ErrorState } from '../empty_states';
import { EngineOverviewHeader } from '../engine_overview_header';
import { EngineOverviewHeader, LoadingState, EmptyState } from './components';
import { EngineTable } from './engine_table';

import './engine_overview.scss';
Expand All @@ -42,8 +41,6 @@ export const EngineOverview: React.FC = () => {
const { license } = useContext(LicenseContext) as ILicenseContext;

const [isLoading, setIsLoading] = useState(true);
const [hasErrorConnecting, setHasErrorConnecting] = useState(false);

const [engines, setEngines] = useState([]);
const [enginesPage, setEnginesPage] = useState(1);
const [enginesTotal, setEnginesTotal] = useState(0);
Expand All @@ -57,16 +54,12 @@ export const EngineOverview: React.FC = () => {
});
};
const setEnginesData = async (params: IGetEnginesParams, callbacks: ISetEnginesCallbacks) => {
try {
const response = await getEnginesData(params);
const response = await getEnginesData(params);

callbacks.setResults(response.results);
callbacks.setResultsTotal(response.meta.page.total_results);
callbacks.setResults(response.results);
callbacks.setResultsTotal(response.meta.page.total_results);

setIsLoading(false);
} catch (error) {
setHasErrorConnecting(true);
}
setIsLoading(false);
};

useEffect(() => {
Expand All @@ -85,7 +78,6 @@ export const EngineOverview: React.FC = () => {
}
}, [license, metaEnginesPage]);

if (hasErrorConnecting) return <ErrorState />;
if (isLoading) return <LoadingState />;
if (!engines.length) return <EmptyState />;

Expand Down
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.
*/

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

import { ErrorStatePrompt } from '../../../shared/error_state';
import { ErrorConnecting } from './';

describe('ErrorConnecting', () => {
it('renders', () => {
const wrapper = shallow(<ErrorConnecting />);

expect(wrapper.find(ErrorStatePrompt)).toHaveLength(1);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,13 @@ import { EuiPageContent } from '@elastic/eui';
import { ErrorStatePrompt } from '../../../shared/error_state';
import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome';
import { SendAppSearchTelemetry as SendTelemetry } from '../../../shared/telemetry';
import { EngineOverviewHeader } from '../engine_overview_header';

import './empty_states.scss';

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

<EngineOverviewHeader />
<EuiPageContent>
<ErrorStatePrompt />
</EuiPageContent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/

export { EngineOverviewHeader } from './engine_overview_header';
export { ErrorConnecting } from './error_connecting';
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import { Redirect } from 'react-router-dom';
import { shallow } from 'enzyme';
import { useValues, useActions } from 'kea';

import { SetupGuide } from './components/setup_guide';
import { Layout, SideNav, SideNavLink } from '../shared/layout';
import { SetupGuide } from './components/setup_guide';
import { ErrorConnecting } from './components/error_connecting';
import { EngineOverview } from './components/engine_overview';
import { AppSearch, AppSearchUnconfigured, AppSearchConfigured, AppSearchNav } from './';

describe('AppSearch', () => {
Expand Down Expand Up @@ -42,12 +44,17 @@ describe('AppSearchUnconfigured', () => {
});

describe('AppSearchConfigured', () => {
it('renders with layout', () => {
beforeEach(() => {
// Mock resets
(useValues as jest.Mock).mockImplementation(() => ({}));
(useActions as jest.Mock).mockImplementation(() => ({ initializeAppData: () => {} }));
});

it('renders with layout', () => {
const wrapper = shallow(<AppSearchConfigured />);

expect(wrapper.find(Layout)).toHaveLength(1);
expect(wrapper.find(EngineOverview)).toHaveLength(1);
});

it('initializes app data with passed props', () => {
Expand All @@ -62,12 +69,20 @@ describe('AppSearchConfigured', () => {
it('does not re-initialize app data', () => {
const initializeAppData = jest.fn();
(useActions as jest.Mock).mockImplementation(() => ({ initializeAppData }));
(useValues as jest.Mock).mockImplementationOnce(() => ({ hasInitialized: true }));
(useValues as jest.Mock).mockImplementation(() => ({ hasInitialized: true }));

shallow(<AppSearchConfigured />);

expect(initializeAppData).not.toHaveBeenCalled();
});

it('renders ErrorConnecting', () => {
(useValues as jest.Mock).mockImplementation(() => ({ errorConnecting: true }));

const wrapper = shallow(<AppSearchConfigured />);

expect(wrapper.find(ErrorConnecting)).toHaveLength(1);
});
});

describe('AppSearchNav', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { useActions, useValues } from 'kea';
import { i18n } from '@kbn/i18n';

import { KibanaContext, IKibanaContext } from '../index';
import { HttpLogic, IHttpLogicValues } from '../shared/http';
import { AppLogic, IAppLogicActions, IAppLogicValues } from './app_logic';
import { IInitialAppData } from '../../../common/types';

Expand All @@ -27,6 +28,7 @@ import {
} from './routes';

import { SetupGuide } from './components/setup_guide';
import { ErrorConnecting } from './components/error_connecting';
import { EngineOverview } from './components/engine_overview';

export const AppSearch: React.FC<IInitialAppData> = (props) => {
Expand All @@ -48,6 +50,7 @@ export const AppSearchUnconfigured: React.FC = () => (
export const AppSearchConfigured: React.FC<IInitialAppData> = (props) => {
const { hasInitialized } = useValues(AppLogic) as IAppLogicValues;
const { initializeAppData } = useActions(AppLogic) as IAppLogicActions;
const { errorConnecting } = useValues(HttpLogic) as IHttpLogicValues;

useEffect(() => {
if (!hasInitialized) initializeAppData(props);
Expand All @@ -60,14 +63,18 @@ export const AppSearchConfigured: React.FC<IInitialAppData> = (props) => {
</Route>
<Route>
<Layout navigation={<AppSearchNav />}>
<Switch>
<Route exact path={ROOT_PATH}>
<Redirect to={ENGINES_PATH} />
</Route>
<Route exact path={ENGINES_PATH}>
<EngineOverview />
</Route>
</Switch>
{errorConnecting ? (
<ErrorConnecting />
) : (
<Switch>
<Route exact path={ROOT_PATH}>
<Redirect to={ENGINES_PATH} />
</Route>
<Route exact path={ENGINES_PATH}>
<EngineOverview />
</Route>
</Switch>
)}
</Layout>
</Route>
</Switch>
Expand Down
Loading

0 comments on commit 90bd654

Please sign in to comment.