mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Enterprise Search] Move http out of React Context and to Kea Logic; prefer directly mounting (#78167)
* Remove HttpProvider in favor of mounting HttpLogic directly w/ props - removes need for initializeHttp call - ensures http value is loaded into HttpLogic as soon as possible / should never load in as null, reducing # of rerenders/checks see: https://kea.js.org/docs/guide/advanced#mounting-and-unmounting * Update simplest components using http for sendTelemetry * Update simplest tests for components using HttpLogic + default Kea mocks - Kea mock import should now contain mock default values which can be overridden * Update moderately complex tests using HttpLogic send_telemetry: - refactor to use shallow (w/ useEffect mocked) vs mount - check mockHttpValues directly engine_table: - refactor to use mount w/ an I18nProvider rather than mountWithContext helper (which we'll likely need to overhaul in the future) - assert mockHttpValues directly * Update EngineOverview to HttpLogic + refactors EngineOverview: - Change use of FormattedMessage to i18n.translate (simpler, no provider required) Tests: - Create mock values/actions for FlashMessages, since EngineOverview calls it - Create combined mockAllValues obj for easier overriding - Create setMockValues helper for easier test overriding (credit to @scottybollinger for the idea!) - Update engine_overview tests to setMockValues instead of passing context to mountWithAsyncContext - Fix mountWithAsyncContext to accept an undefined obj * Remove http from KibanaContext - it should now only live in HttpLogic 🔥 * Remove FlashMessagesProvider in favor of mounting logic directly w/ props - send history as prop - refactor out now-unnecessary listenToHistory (we can just do it directly in afterMount without worrying about duplicate react rerenders) - add mount helper Tests: - refactor history.listen mock to mockHistory (so that set_message_helpers can use it as well) - use mountFlashMessagesLogic + create an even shorter mount() helper (credit to @JasonStoltz for the idea!) - refactor out DEFAULT_VALUES since we're not really using it anywhere else in the file, and it's not super applicable to this store - update history listener tests to account for logic occurring immediately on mount
This commit is contained in:
parent
426df45c6f
commit
42026cbbf5
37 changed files with 269 additions and 333 deletions
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* 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 const mockFlashMessagesValues = {
|
||||
messages: [],
|
||||
queuedMessages: [],
|
||||
};
|
||||
|
||||
export const mockFlashMessagesActions = {
|
||||
setFlashMessages: jest.fn(),
|
||||
clearFlashMessages: jest.fn(),
|
||||
setQueuedMessages: jest.fn(),
|
||||
clearQueuedMessages: jest.fn(),
|
||||
};
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* 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 { httpServiceMock } from 'src/core/public/mocks';
|
||||
|
||||
export const mockHttpValues = {
|
||||
http: httpServiceMock.createSetupContract(),
|
||||
errorConnecting: false,
|
||||
readOnlyMode: false,
|
||||
};
|
|
@ -7,6 +7,10 @@
|
|||
export { mockHistory, mockLocation } from './react_router_history.mock';
|
||||
export { mockKibanaContext } from './kibana_context.mock';
|
||||
export { mockLicenseContext } from './license_context.mock';
|
||||
export { mockHttpValues } from './http_logic.mock';
|
||||
export { mockFlashMessagesValues, mockFlashMessagesActions } from './flash_messages_logic.mock';
|
||||
export { mockAllValues, mockAllActions, setMockValues } from './kea.mock';
|
||||
|
||||
export {
|
||||
mountWithContext,
|
||||
mountWithKibanaContext,
|
||||
|
|
|
@ -4,21 +4,46 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* NOTE: These variable names MUST start with 'mock*' in order for
|
||||
* Jest to accept its use within a jest.mock()
|
||||
*/
|
||||
import { mockHttpValues } from './http_logic.mock';
|
||||
import { mockFlashMessagesValues, mockFlashMessagesActions } from './flash_messages_logic.mock';
|
||||
|
||||
export const mockAllValues = {
|
||||
...mockHttpValues,
|
||||
...mockFlashMessagesValues,
|
||||
};
|
||||
export const mockAllActions = {
|
||||
...mockFlashMessagesActions,
|
||||
};
|
||||
|
||||
/**
|
||||
* Import this file directly to mock useValues with a set of default values for all shared logic files.
|
||||
* Example usage:
|
||||
*
|
||||
* import '../../../__mocks__/kea'; // Must come before kea's import, adjust relative path as needed
|
||||
*/
|
||||
jest.mock('kea', () => ({
|
||||
...(jest.requireActual('kea') as object),
|
||||
useValues: jest.fn(() => ({})),
|
||||
useActions: jest.fn(() => ({})),
|
||||
useValues: jest.fn(() => ({ ...mockAllValues })),
|
||||
useActions: jest.fn(() => ({ ...mockAllActions })),
|
||||
}));
|
||||
|
||||
/**
|
||||
* Call this function to override a specific set of Kea values while retaining all other defaults
|
||||
* Example usage within a component test:
|
||||
*
|
||||
* import '../../../__mocks__/kea'; // Must come before kea's import, adjust relative path as needed
|
||||
*
|
||||
* import { useActions, useValues } from 'kea';
|
||||
* import '../../../__mocks__/kea';
|
||||
* import { setMockValues } from ''../../../__mocks__';
|
||||
*
|
||||
* it('some test', () => {
|
||||
* (useValues as jest.Mock).mockImplementationOnce(() => ({ someValue: 'hello' }));
|
||||
* (useActions as jest.Mock).mockImplementationOnce(() => ({ someAction: () => 'world' }));
|
||||
* setMockValues({ someValue: 'hello' });
|
||||
* });
|
||||
*/
|
||||
import { useValues } from 'kea';
|
||||
|
||||
export const setMockValues = (values: object) => {
|
||||
(useValues as jest.Mock).mockImplementation(() => ({ ...mockAllValues, ...values }));
|
||||
};
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { httpServiceMock } from 'src/core/public/mocks';
|
||||
import { ExternalUrl } from '../shared/enterprise_search_url';
|
||||
|
||||
/**
|
||||
|
@ -12,7 +11,6 @@ import { ExternalUrl } from '../shared/enterprise_search_url';
|
|||
* @see enterprise_search/public/index.tsx for the KibanaContext definition/import
|
||||
*/
|
||||
export const mockKibanaContext = {
|
||||
http: httpServiceMock.createSetupContract(),
|
||||
navigateToUrl: jest.fn(),
|
||||
setBreadcrumbs: jest.fn(),
|
||||
setDocTitle: jest.fn(),
|
||||
|
|
|
@ -67,7 +67,7 @@ export const mountWithKibanaContext = (children: React.ReactNode, context?: obje
|
|||
*/
|
||||
export const mountWithAsyncContext = async (
|
||||
children: React.ReactNode,
|
||||
context: object
|
||||
context?: object
|
||||
): Promise<ReactWrapper> => {
|
||||
let wrapper: ReactWrapper | undefined;
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ export const mockHistory = {
|
|||
location: {
|
||||
pathname: '/current-path',
|
||||
},
|
||||
listen: jest.fn(() => jest.fn()),
|
||||
};
|
||||
export const mockLocation = {
|
||||
key: 'someKey',
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import '../../../../__mocks__/kea.mock';
|
||||
import '../../../../__mocks__/shallow_usecontext.mock';
|
||||
|
||||
import React from 'react';
|
||||
|
|
|
@ -5,10 +5,12 @@
|
|||
*/
|
||||
|
||||
import React, { useContext } from 'react';
|
||||
import { useValues } from 'kea';
|
||||
import { EuiPageContent, EuiEmptyPrompt, EuiButton } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import { sendTelemetry } from '../../../../shared/telemetry';
|
||||
import { HttpLogic } from '../../../../shared/http';
|
||||
import { SetAppSearchChrome as SetPageChrome } from '../../../../shared/kibana_chrome';
|
||||
import { KibanaContext, IKibanaContext } from '../../../../index';
|
||||
import { CREATE_ENGINES_PATH } from '../../../routes';
|
||||
|
@ -18,9 +20,9 @@ import { EngineOverviewHeader } from './header';
|
|||
import './empty_state.scss';
|
||||
|
||||
export const EmptyState: React.FC = () => {
|
||||
const { http } = useValues(HttpLogic);
|
||||
const {
|
||||
externalUrl: { getAppSearchUrl },
|
||||
http,
|
||||
} = useContext(KibanaContext) as IKibanaContext;
|
||||
|
||||
const buttonProps = {
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import '../../../../__mocks__/kea.mock';
|
||||
import '../../../../__mocks__/shallow_usecontext.mock';
|
||||
|
||||
import React from 'react';
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import React, { useContext } from 'react';
|
||||
import { useValues } from 'kea';
|
||||
import {
|
||||
EuiPageHeader,
|
||||
EuiPageHeaderSection,
|
||||
|
@ -16,12 +17,13 @@ import {
|
|||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import { sendTelemetry } from '../../../../shared/telemetry';
|
||||
import { HttpLogic } from '../../../../shared/http';
|
||||
import { KibanaContext, IKibanaContext } from '../../../../index';
|
||||
|
||||
export const EngineOverviewHeader: React.FC = () => {
|
||||
const { http } = useValues(HttpLogic);
|
||||
const {
|
||||
externalUrl: { getAppSearchUrl },
|
||||
http,
|
||||
} = useContext(KibanaContext) as IKibanaContext;
|
||||
|
||||
const buttonProps = {
|
||||
|
|
|
@ -4,13 +4,14 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import '../../../__mocks__/kea.mock';
|
||||
import '../../../__mocks__/react_router_history.mock';
|
||||
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { shallow, ReactWrapper } from 'enzyme';
|
||||
|
||||
import { mountWithAsyncContext, mockKibanaContext } from '../../../__mocks__';
|
||||
import { mountWithAsyncContext, mockHttpValues, setMockValues } from '../../../__mocks__';
|
||||
|
||||
import { LoadingState, EmptyState } from './components';
|
||||
import { EngineTable } from './engine_table';
|
||||
|
@ -18,8 +19,6 @@ import { EngineTable } from './engine_table';
|
|||
import { EngineOverview } from './';
|
||||
|
||||
describe('EngineOverview', () => {
|
||||
const mockHttp = mockKibanaContext.http;
|
||||
|
||||
describe('non-happy-path states', () => {
|
||||
it('isLoading', () => {
|
||||
const wrapper = shallow(<EngineOverview />);
|
||||
|
@ -28,15 +27,16 @@ describe('EngineOverview', () => {
|
|||
});
|
||||
|
||||
it('isEmpty', async () => {
|
||||
const wrapper = await mountWithAsyncContext(<EngineOverview />, {
|
||||
setMockValues({
|
||||
http: {
|
||||
...mockHttp,
|
||||
...mockHttpValues.http,
|
||||
get: () => ({
|
||||
results: [],
|
||||
meta: { page: { total_results: 0 } },
|
||||
}),
|
||||
},
|
||||
});
|
||||
const wrapper = await mountWithAsyncContext(<EngineOverview />);
|
||||
|
||||
expect(wrapper.find(EmptyState)).toHaveLength(1);
|
||||
});
|
||||
|
@ -65,12 +65,11 @@ describe('EngineOverview', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
setMockValues({ http: { ...mockHttpValues.http, get: mockApi } });
|
||||
});
|
||||
|
||||
it('renders and calls the engines API', async () => {
|
||||
const wrapper = await mountWithAsyncContext(<EngineOverview />, {
|
||||
http: { ...mockHttp, get: mockApi },
|
||||
});
|
||||
const wrapper = await mountWithAsyncContext(<EngineOverview />);
|
||||
|
||||
expect(wrapper.find(EngineTable)).toHaveLength(1);
|
||||
expect(mockApi).toHaveBeenNthCalledWith(1, '/api/app_search/engines', {
|
||||
|
@ -84,7 +83,6 @@ describe('EngineOverview', () => {
|
|||
describe('when on a platinum license', () => {
|
||||
it('renders a 2nd meta engines table & makes a 2nd meta engines API call', async () => {
|
||||
const wrapper = await mountWithAsyncContext(<EngineOverview />, {
|
||||
http: { ...mockHttp, get: mockApi },
|
||||
license: { type: 'platinum', isActive: true },
|
||||
});
|
||||
|
||||
|
@ -103,9 +101,7 @@ describe('EngineOverview', () => {
|
|||
wrapper.find(EngineTable).prop('pagination');
|
||||
|
||||
it('passes down page data from the API', async () => {
|
||||
const wrapper = await mountWithAsyncContext(<EngineOverview />, {
|
||||
http: { ...mockHttp, get: mockApi },
|
||||
});
|
||||
const wrapper = await mountWithAsyncContext(<EngineOverview />);
|
||||
const pagination = getTablePagination(wrapper);
|
||||
|
||||
expect(pagination.totalEngines).toEqual(100);
|
||||
|
@ -113,9 +109,7 @@ describe('EngineOverview', () => {
|
|||
});
|
||||
|
||||
it('re-polls the API on page change', async () => {
|
||||
const wrapper = await mountWithAsyncContext(<EngineOverview />, {
|
||||
http: { ...mockHttp, get: mockApi },
|
||||
});
|
||||
const wrapper = await mountWithAsyncContext(<EngineOverview />);
|
||||
await act(async () => getTablePagination(wrapper).onPaginate(5));
|
||||
wrapper.update();
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { useValues } from 'kea';
|
||||
import {
|
||||
EuiPageContent,
|
||||
EuiPageContentHeader,
|
||||
|
@ -12,13 +13,13 @@ import {
|
|||
EuiTitle,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome';
|
||||
import { SendAppSearchTelemetry as SendTelemetry } from '../../../shared/telemetry';
|
||||
import { FlashMessages } from '../../../shared/flash_messages';
|
||||
import { HttpLogic } from '../../../shared/http';
|
||||
import { LicenseContext, ILicenseContext, hasPlatinumLicense } from '../../../shared/licensing';
|
||||
import { KibanaContext, IKibanaContext } from '../../../index';
|
||||
|
||||
import { EngineIcon } from './assets/engine_icon';
|
||||
import { MetaEngineIcon } from './assets/meta_engine_icon';
|
||||
|
@ -38,7 +39,7 @@ interface ISetEnginesCallbacks {
|
|||
}
|
||||
|
||||
export const EngineOverview: React.FC = () => {
|
||||
const { http } = useContext(KibanaContext) as IKibanaContext;
|
||||
const { http } = useValues(HttpLogic);
|
||||
const { license } = useContext(LicenseContext) as ILicenseContext;
|
||||
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
@ -94,10 +95,9 @@ export const EngineOverview: React.FC = () => {
|
|||
<EuiTitle size="s">
|
||||
<h2>
|
||||
<EngineIcon />
|
||||
<FormattedMessage
|
||||
id="xpack.enterpriseSearch.appSearch.enginesOverview.engines"
|
||||
defaultMessage="Engines"
|
||||
/>
|
||||
{i18n.translate('xpack.enterpriseSearch.appSearch.enginesOverview.engines', {
|
||||
defaultMessage: 'Engines',
|
||||
})}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiPageContentHeader>
|
||||
|
@ -119,10 +119,9 @@ export const EngineOverview: React.FC = () => {
|
|||
<EuiTitle size="s">
|
||||
<h2>
|
||||
<MetaEngineIcon />
|
||||
<FormattedMessage
|
||||
id="xpack.enterpriseSearch.appSearch.enginesOverview.metaEngines"
|
||||
defaultMessage="Meta Engines"
|
||||
/>
|
||||
{i18n.translate('xpack.enterpriseSearch.appSearch.enginesOverview.metaEngines', {
|
||||
defaultMessage: 'Meta Engines',
|
||||
})}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiPageContentHeader>
|
||||
|
|
|
@ -4,10 +4,15 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import '../../../__mocks__/kea.mock';
|
||||
import '../../../__mocks__/shallow_usecontext.mock';
|
||||
import { mockHttpValues } from '../../../__mocks__/';
|
||||
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import { I18nProvider } from '@kbn/i18n/react';
|
||||
import { EuiBasicTable, EuiPagination, EuiButtonEmpty, EuiLink } from '@elastic/eui';
|
||||
|
||||
import { mountWithContext } from '../../../__mocks__';
|
||||
jest.mock('../../../shared/telemetry', () => ({ sendTelemetry: jest.fn() }));
|
||||
import { sendTelemetry } from '../../../shared/telemetry';
|
||||
|
||||
|
@ -16,22 +21,24 @@ import { EngineTable } from './engine_table';
|
|||
describe('EngineTable', () => {
|
||||
const onPaginate = jest.fn(); // onPaginate updates the engines API call upstream
|
||||
|
||||
const wrapper = mountWithContext(
|
||||
<EngineTable
|
||||
data={[
|
||||
{
|
||||
name: 'test-engine',
|
||||
created_at: 'Fri, 1 Jan 1970 12:00:00 +0000',
|
||||
document_count: 99999,
|
||||
field_count: 10,
|
||||
},
|
||||
]}
|
||||
pagination={{
|
||||
totalEngines: 50,
|
||||
pageIndex: 0,
|
||||
onPaginate,
|
||||
}}
|
||||
/>
|
||||
const wrapper = mount(
|
||||
<I18nProvider>
|
||||
<EngineTable
|
||||
data={[
|
||||
{
|
||||
name: 'test-engine',
|
||||
created_at: 'Fri, 1 Jan 1970 12:00:00 +0000',
|
||||
document_count: 99999,
|
||||
field_count: 10,
|
||||
},
|
||||
]}
|
||||
pagination={{
|
||||
totalEngines: 50,
|
||||
pageIndex: 0,
|
||||
onPaginate,
|
||||
}}
|
||||
/>
|
||||
</I18nProvider>
|
||||
);
|
||||
const table = wrapper.find(EuiBasicTable);
|
||||
|
||||
|
@ -56,7 +63,7 @@ describe('EngineTable', () => {
|
|||
link.simulate('click');
|
||||
|
||||
expect(sendTelemetry).toHaveBeenCalledWith({
|
||||
http: expect.any(Object),
|
||||
http: mockHttpValues.http,
|
||||
product: 'app_search',
|
||||
action: 'clicked',
|
||||
metric: 'engine_table_link',
|
||||
|
@ -71,10 +78,16 @@ describe('EngineTable', () => {
|
|||
});
|
||||
|
||||
it('handles empty data', () => {
|
||||
const emptyWrapper = mountWithContext(
|
||||
<EngineTable data={[]} pagination={{ totalEngines: 0, pageIndex: 0, onPaginate: () => {} }} />
|
||||
const emptyWrapper = mount(
|
||||
<I18nProvider>
|
||||
<EngineTable
|
||||
data={[]}
|
||||
pagination={{ totalEngines: 0, pageIndex: 0, onPaginate: () => {} }}
|
||||
/>
|
||||
</I18nProvider>
|
||||
);
|
||||
const emptyTable = emptyWrapper.find(EuiBasicTable);
|
||||
|
||||
expect(emptyTable.prop('pagination').pageIndex).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,11 +5,13 @@
|
|||
*/
|
||||
|
||||
import React, { useContext } from 'react';
|
||||
import { useValues } from 'kea';
|
||||
import { EuiBasicTable, EuiBasicTableColumn, EuiLink } from '@elastic/eui';
|
||||
import { FormattedMessage, FormattedDate, FormattedNumber } from '@kbn/i18n/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { sendTelemetry } from '../../../shared/telemetry';
|
||||
import { HttpLogic } from '../../../shared/http';
|
||||
import { KibanaContext, IKibanaContext } from '../../../index';
|
||||
import { getEngineRoute } from '../../routes';
|
||||
|
||||
|
@ -40,9 +42,9 @@ export const EngineTable: React.FC<IEngineTableProps> = ({
|
|||
data,
|
||||
pagination: { totalEngines, pageIndex, onPaginate },
|
||||
}) => {
|
||||
const { http } = useValues(HttpLogic);
|
||||
const {
|
||||
externalUrl: { getAppSearchUrl },
|
||||
http,
|
||||
} = useContext(KibanaContext) as IKibanaContext;
|
||||
|
||||
const engineLinkProps = (name: string) => ({
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import '../../../__mocks__/kea.mock';
|
||||
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { useContext } from 'react';
|
||||
import React from 'react';
|
||||
import { useValues } from 'kea';
|
||||
import upperFirst from 'lodash/upperFirst';
|
||||
import snakeCase from 'lodash/snakeCase';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
@ -12,7 +13,7 @@ import { EuiCard, EuiTextColor } from '@elastic/eui';
|
|||
|
||||
import { EuiButton } from '../../../shared/react_router_helpers';
|
||||
import { sendTelemetry } from '../../../shared/telemetry';
|
||||
import { KibanaContext, IKibanaContext } from '../../../index';
|
||||
import { HttpLogic } from '../../../shared/http';
|
||||
|
||||
import './product_card.scss';
|
||||
|
||||
|
@ -28,7 +29,7 @@ interface IProductCard {
|
|||
}
|
||||
|
||||
export const ProductCard: React.FC<IProductCard> = ({ product, image }) => {
|
||||
const { http } = useContext(KibanaContext) as IKibanaContext;
|
||||
const { http } = useValues(HttpLogic);
|
||||
|
||||
return (
|
||||
<EuiCard
|
||||
|
|
|
@ -13,24 +13,17 @@ import { Store } from 'redux';
|
|||
import { getContext, resetContext } from 'kea';
|
||||
|
||||
import { I18nProvider } from '@kbn/i18n/react';
|
||||
import {
|
||||
AppMountParameters,
|
||||
CoreStart,
|
||||
ApplicationStart,
|
||||
HttpSetup,
|
||||
ChromeBreadcrumb,
|
||||
} from 'src/core/public';
|
||||
import { AppMountParameters, CoreStart, ApplicationStart, ChromeBreadcrumb } from 'src/core/public';
|
||||
import { ClientConfigType, ClientData, PluginsSetup } from '../plugin';
|
||||
import { LicenseProvider } from './shared/licensing';
|
||||
import { FlashMessagesProvider } from './shared/flash_messages';
|
||||
import { HttpProvider } from './shared/http';
|
||||
import { mountHttpLogic } from './shared/http';
|
||||
import { mountFlashMessagesLogic } from './shared/flash_messages';
|
||||
import { IExternalUrl } from './shared/enterprise_search_url';
|
||||
import { IInitialAppData } from '../../common/types';
|
||||
|
||||
export interface IKibanaContext {
|
||||
config: { host?: string };
|
||||
externalUrl: IExternalUrl;
|
||||
http: HttpSetup;
|
||||
navigateToUrl: ApplicationStart['navigateToUrl'];
|
||||
setBreadcrumbs(crumbs: ChromeBreadcrumb[]): void;
|
||||
setDocTitle(title: string): void;
|
||||
|
@ -55,13 +48,20 @@ export const renderApp = (
|
|||
resetContext({ createStore: true });
|
||||
const store = getContext().store as Store;
|
||||
|
||||
const unmountHttpLogic = mountHttpLogic({
|
||||
http: core.http,
|
||||
errorConnecting,
|
||||
readOnlyMode: initialData.readOnlyMode,
|
||||
});
|
||||
|
||||
const unmountFlashMessagesLogic = mountFlashMessagesLogic({ history: params.history });
|
||||
|
||||
ReactDOM.render(
|
||||
<I18nProvider>
|
||||
<KibanaContext.Provider
|
||||
value={{
|
||||
config,
|
||||
externalUrl,
|
||||
http: core.http,
|
||||
navigateToUrl: core.application.navigateToUrl,
|
||||
setBreadcrumbs: core.chrome.setBreadcrumbs,
|
||||
setDocTitle: core.chrome.docTitle.change,
|
||||
|
@ -69,12 +69,6 @@ export const renderApp = (
|
|||
>
|
||||
<LicenseProvider license$={plugins.licensing.license$}>
|
||||
<Provider store={store}>
|
||||
<HttpProvider
|
||||
http={core.http}
|
||||
errorConnecting={errorConnecting}
|
||||
readOnlyMode={initialData.readOnlyMode}
|
||||
/>
|
||||
<FlashMessagesProvider history={params.history} />
|
||||
<Router history={params.history}>
|
||||
<App {...initialData} />
|
||||
</Router>
|
||||
|
@ -86,6 +80,8 @@ export const renderApp = (
|
|||
);
|
||||
return () => {
|
||||
ReactDOM.unmountComponentAtNode(params.element);
|
||||
unmountHttpLogic();
|
||||
unmountFlashMessagesLogic();
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -6,23 +6,25 @@
|
|||
|
||||
import { resetContext } from 'kea';
|
||||
|
||||
import { FlashMessagesLogic, IFlashMessage } from './flash_messages_logic';
|
||||
import { mockHistory } from '../../__mocks__';
|
||||
|
||||
import { FlashMessagesLogic, mountFlashMessagesLogic, IFlashMessage } from './';
|
||||
|
||||
describe('FlashMessagesLogic', () => {
|
||||
const DEFAULT_VALUES = {
|
||||
messages: [],
|
||||
queuedMessages: [],
|
||||
historyListener: null,
|
||||
};
|
||||
const mount = () => mountFlashMessagesLogic({ history: mockHistory as any });
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
resetContext({});
|
||||
});
|
||||
|
||||
it('has expected default values', () => {
|
||||
FlashMessagesLogic.mount();
|
||||
expect(FlashMessagesLogic.values).toEqual(DEFAULT_VALUES);
|
||||
it('has default values', () => {
|
||||
mount();
|
||||
expect(FlashMessagesLogic.values).toEqual({
|
||||
messages: [],
|
||||
queuedMessages: [],
|
||||
historyListener: expect.any(Function),
|
||||
});
|
||||
});
|
||||
|
||||
describe('setFlashMessages()', () => {
|
||||
|
@ -33,7 +35,7 @@ describe('FlashMessagesLogic', () => {
|
|||
{ type: 'info', message: 'Everything is fine, nothing is ruined' },
|
||||
];
|
||||
|
||||
FlashMessagesLogic.mount();
|
||||
mount();
|
||||
FlashMessagesLogic.actions.setFlashMessages(messages);
|
||||
|
||||
expect(FlashMessagesLogic.values.messages).toEqual(messages);
|
||||
|
@ -42,7 +44,7 @@ describe('FlashMessagesLogic', () => {
|
|||
it('automatically converts to an array if a single message obj is passed in', () => {
|
||||
const message = { type: 'success', message: 'I turn into an array!' } as IFlashMessage;
|
||||
|
||||
FlashMessagesLogic.mount();
|
||||
mount();
|
||||
FlashMessagesLogic.actions.setFlashMessages(message);
|
||||
|
||||
expect(FlashMessagesLogic.values.messages).toEqual([message]);
|
||||
|
@ -51,7 +53,7 @@ describe('FlashMessagesLogic', () => {
|
|||
|
||||
describe('clearFlashMessages()', () => {
|
||||
it('sets messages back to an empty array', () => {
|
||||
FlashMessagesLogic.mount();
|
||||
mount();
|
||||
FlashMessagesLogic.actions.setFlashMessages('test' as any);
|
||||
FlashMessagesLogic.actions.clearFlashMessages();
|
||||
|
||||
|
@ -63,7 +65,7 @@ describe('FlashMessagesLogic', () => {
|
|||
it('sets an array of messages', () => {
|
||||
const queuedMessage: IFlashMessage = { type: 'error', message: 'You deleted a thing' };
|
||||
|
||||
FlashMessagesLogic.mount();
|
||||
mount();
|
||||
FlashMessagesLogic.actions.setQueuedMessages(queuedMessage);
|
||||
|
||||
expect(FlashMessagesLogic.values.queuedMessages).toEqual([queuedMessage]);
|
||||
|
@ -72,7 +74,7 @@ describe('FlashMessagesLogic', () => {
|
|||
|
||||
describe('clearQueuedMessages()', () => {
|
||||
it('sets queued messages back to an empty array', () => {
|
||||
FlashMessagesLogic.mount();
|
||||
mount();
|
||||
FlashMessagesLogic.actions.setQueuedMessages('test' as any);
|
||||
FlashMessagesLogic.actions.clearQueuedMessages();
|
||||
|
||||
|
@ -83,30 +85,25 @@ describe('FlashMessagesLogic', () => {
|
|||
describe('history listener logic', () => {
|
||||
describe('setHistoryListener()', () => {
|
||||
it('sets the historyListener value', () => {
|
||||
FlashMessagesLogic.mount();
|
||||
mount();
|
||||
FlashMessagesLogic.actions.setHistoryListener('test' as any);
|
||||
|
||||
expect(FlashMessagesLogic.values.historyListener).toEqual('test');
|
||||
});
|
||||
});
|
||||
|
||||
describe('listenToHistory()', () => {
|
||||
describe('on mount', () => {
|
||||
it('listens for history changes and clears messages on change', () => {
|
||||
FlashMessagesLogic.mount();
|
||||
mount();
|
||||
expect(mockHistory.listen).toHaveBeenCalled();
|
||||
|
||||
FlashMessagesLogic.actions.setQueuedMessages(['queuedMessages'] as any);
|
||||
jest.spyOn(FlashMessagesLogic.actions, 'clearFlashMessages');
|
||||
jest.spyOn(FlashMessagesLogic.actions, 'setFlashMessages');
|
||||
jest.spyOn(FlashMessagesLogic.actions, 'clearQueuedMessages');
|
||||
jest.spyOn(FlashMessagesLogic.actions, 'setHistoryListener');
|
||||
|
||||
const mockListener = jest.fn(() => jest.fn());
|
||||
const history = { listen: mockListener } as any;
|
||||
FlashMessagesLogic.actions.listenToHistory(history);
|
||||
|
||||
expect(mockListener).toHaveBeenCalled();
|
||||
expect(FlashMessagesLogic.actions.setHistoryListener).toHaveBeenCalled();
|
||||
|
||||
const mockHistoryChange = (mockListener.mock.calls[0] as any)[0];
|
||||
const mockHistoryChange = (mockHistory.listen.mock.calls[0] as any)[0];
|
||||
mockHistoryChange();
|
||||
expect(FlashMessagesLogic.actions.clearFlashMessages).toHaveBeenCalled();
|
||||
expect(FlashMessagesLogic.actions.setFlashMessages).toHaveBeenCalledWith([
|
||||
|
@ -116,19 +113,20 @@ describe('FlashMessagesLogic', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('beforeUnmount', () => {
|
||||
it('removes history listener on unmount', () => {
|
||||
describe('on unmount', () => {
|
||||
it('removes history listener', () => {
|
||||
const mockUnlistener = jest.fn();
|
||||
const unmount = FlashMessagesLogic.mount();
|
||||
mockHistory.listen.mockReturnValueOnce(mockUnlistener);
|
||||
|
||||
FlashMessagesLogic.actions.setHistoryListener(mockUnlistener);
|
||||
const unmount = mount();
|
||||
unmount();
|
||||
|
||||
expect(mockUnlistener).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not crash if no listener exists', () => {
|
||||
const unmount = FlashMessagesLogic.mount();
|
||||
const unmount = mount();
|
||||
FlashMessagesLogic.actions.setHistoryListener(null as any);
|
||||
unmount();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -24,7 +24,6 @@ export interface IFlashMessagesActions {
|
|||
clearFlashMessages(): void;
|
||||
setQueuedMessages(messages: IFlashMessage | IFlashMessage[]): { messages: IFlashMessage[] };
|
||||
clearQueuedMessages(): void;
|
||||
listenToHistory(history: History): History;
|
||||
setHistoryListener(historyListener: Function): { historyListener: Function };
|
||||
}
|
||||
|
||||
|
@ -38,7 +37,6 @@ export const FlashMessagesLogic = kea<MakeLogicType<IFlashMessagesValues, IFlash
|
|||
clearFlashMessages: () => null,
|
||||
setQueuedMessages: (messages) => ({ messages: convertToArray(messages) }),
|
||||
clearQueuedMessages: () => null,
|
||||
listenToHistory: (history) => history,
|
||||
setHistoryListener: (historyListener) => ({ historyListener }),
|
||||
},
|
||||
reducers: {
|
||||
|
@ -63,21 +61,31 @@ export const FlashMessagesLogic = kea<MakeLogicType<IFlashMessagesValues, IFlash
|
|||
},
|
||||
],
|
||||
},
|
||||
listeners: ({ values, actions }) => ({
|
||||
listenToHistory: (history) => {
|
||||
events: ({ props, values, actions }) => ({
|
||||
afterMount: () => {
|
||||
// On React Router navigation, clear previous flash messages and load any queued messages
|
||||
const unlisten = history.listen(() => {
|
||||
const unlisten = props.history.listen(() => {
|
||||
actions.clearFlashMessages();
|
||||
actions.setFlashMessages(values.queuedMessages);
|
||||
actions.clearQueuedMessages();
|
||||
});
|
||||
actions.setHistoryListener(unlisten);
|
||||
},
|
||||
}),
|
||||
events: ({ values }) => ({
|
||||
beforeUnmount: () => {
|
||||
const { historyListener: removeHistoryListener } = values;
|
||||
if (removeHistoryListener) removeHistoryListener();
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
/**
|
||||
* Mount/props helper
|
||||
*/
|
||||
interface IFlashMessagesLogicProps {
|
||||
history: History;
|
||||
}
|
||||
export const mountFlashMessagesLogic = (props: IFlashMessagesLogicProps) => {
|
||||
FlashMessagesLogic(props);
|
||||
const unmount = FlashMessagesLogic.mount();
|
||||
return unmount;
|
||||
};
|
||||
|
|
|
@ -1,46 +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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import '../../__mocks__/shallow_usecontext.mock';
|
||||
import '../../__mocks__/kea.mock';
|
||||
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { useValues, useActions } from 'kea';
|
||||
|
||||
import { mockHistory } from '../../__mocks__';
|
||||
|
||||
import { FlashMessagesProvider } from './';
|
||||
|
||||
describe('FlashMessagesProvider', () => {
|
||||
const props = { history: mockHistory as any };
|
||||
const listenToHistory = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
(useActions as jest.Mock).mockImplementationOnce(() => ({ listenToHistory }));
|
||||
});
|
||||
|
||||
it('does not render', () => {
|
||||
const wrapper = shallow(<FlashMessagesProvider {...props} />);
|
||||
|
||||
expect(wrapper.isEmptyRender()).toBe(true);
|
||||
});
|
||||
|
||||
it('listens to history on mount', () => {
|
||||
shallow(<FlashMessagesProvider {...props} />);
|
||||
|
||||
expect(listenToHistory).toHaveBeenCalledWith(mockHistory);
|
||||
});
|
||||
|
||||
it('does not add another history listener if one already exists', () => {
|
||||
(useValues as jest.Mock).mockImplementationOnce(() => ({ historyListener: 'exists' as any }));
|
||||
|
||||
shallow(<FlashMessagesProvider {...props} />);
|
||||
|
||||
expect(listenToHistory).not.toHaveBeenCalledWith(props);
|
||||
});
|
||||
});
|
|
@ -1,26 +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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
import { useValues, useActions } from 'kea';
|
||||
import { History } from 'history';
|
||||
|
||||
import { FlashMessagesLogic } from './flash_messages_logic';
|
||||
|
||||
interface IFlashMessagesProviderProps {
|
||||
history: History;
|
||||
}
|
||||
|
||||
export const FlashMessagesProvider: React.FC<IFlashMessagesProviderProps> = ({ history }) => {
|
||||
const { historyListener } = useValues(FlashMessagesLogic);
|
||||
const { listenToHistory } = useActions(FlashMessagesLogic);
|
||||
|
||||
useEffect(() => {
|
||||
if (!historyListener) listenToHistory(history);
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
};
|
|
@ -10,7 +10,7 @@ export {
|
|||
IFlashMessage,
|
||||
IFlashMessagesValues,
|
||||
IFlashMessagesActions,
|
||||
mountFlashMessagesLogic,
|
||||
} from './flash_messages_logic';
|
||||
export { FlashMessagesProvider } from './flash_messages_provider';
|
||||
export { flashAPIErrors } from './handle_api_errors';
|
||||
export { setSuccessMessage, setErrorMessage, setQueuedSuccessMessage } from './set_message_helpers';
|
||||
|
|
|
@ -4,8 +4,11 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { mockHistory } from '../../__mocks__';
|
||||
|
||||
import {
|
||||
FlashMessagesLogic,
|
||||
mountFlashMessagesLogic,
|
||||
setSuccessMessage,
|
||||
setErrorMessage,
|
||||
setQueuedSuccessMessage,
|
||||
|
@ -15,7 +18,7 @@ describe('Flash Message Helpers', () => {
|
|||
const message = 'I am a message';
|
||||
|
||||
beforeEach(() => {
|
||||
FlashMessagesLogic.mount();
|
||||
mountFlashMessagesLogic({ history: mockHistory as any });
|
||||
});
|
||||
|
||||
it('setSuccessMessage()', () => {
|
||||
|
|
|
@ -8,31 +8,20 @@ import { resetContext } from 'kea';
|
|||
|
||||
import { httpServiceMock } from 'src/core/public/mocks';
|
||||
|
||||
import { HttpLogic } from './http_logic';
|
||||
import { HttpLogic, mountHttpLogic } from './http_logic';
|
||||
|
||||
describe('HttpLogic', () => {
|
||||
const mockHttp = httpServiceMock.createSetupContract();
|
||||
const DEFAULT_VALUES = {
|
||||
http: null,
|
||||
httpInterceptors: [],
|
||||
errorConnecting: false,
|
||||
readOnlyMode: false,
|
||||
};
|
||||
const mount = () => mountHttpLogic({ http: mockHttp });
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
resetContext({});
|
||||
});
|
||||
|
||||
it('has expected default values', () => {
|
||||
HttpLogic.mount();
|
||||
expect(HttpLogic.values).toEqual(DEFAULT_VALUES);
|
||||
});
|
||||
|
||||
describe('initializeHttp()', () => {
|
||||
it('sets values based on passed props', () => {
|
||||
HttpLogic.mount();
|
||||
HttpLogic.actions.initializeHttp({
|
||||
describe('mounts', () => {
|
||||
it('sets values from props', () => {
|
||||
mountHttpLogic({
|
||||
http: mockHttp,
|
||||
errorConnecting: true,
|
||||
readOnlyMode: true,
|
||||
|
@ -40,7 +29,7 @@ describe('HttpLogic', () => {
|
|||
|
||||
expect(HttpLogic.values).toEqual({
|
||||
http: mockHttp,
|
||||
httpInterceptors: [],
|
||||
httpInterceptors: expect.any(Array),
|
||||
errorConnecting: true,
|
||||
readOnlyMode: true,
|
||||
});
|
||||
|
@ -49,7 +38,9 @@ describe('HttpLogic', () => {
|
|||
|
||||
describe('setErrorConnecting()', () => {
|
||||
it('sets errorConnecting value', () => {
|
||||
HttpLogic.mount();
|
||||
mount();
|
||||
expect(HttpLogic.values.errorConnecting).toEqual(false);
|
||||
|
||||
HttpLogic.actions.setErrorConnecting(true);
|
||||
expect(HttpLogic.values.errorConnecting).toEqual(true);
|
||||
|
||||
|
@ -60,7 +51,9 @@ describe('HttpLogic', () => {
|
|||
|
||||
describe('setReadOnlyMode()', () => {
|
||||
it('sets readOnlyMode value', () => {
|
||||
HttpLogic.mount();
|
||||
mount();
|
||||
expect(HttpLogic.values.readOnlyMode).toEqual(false);
|
||||
|
||||
HttpLogic.actions.setReadOnlyMode(true);
|
||||
expect(HttpLogic.values.readOnlyMode).toEqual(true);
|
||||
|
||||
|
@ -72,10 +65,8 @@ describe('HttpLogic', () => {
|
|||
describe('http interceptors', () => {
|
||||
describe('initializeHttpInterceptors()', () => {
|
||||
beforeEach(() => {
|
||||
HttpLogic.mount();
|
||||
mount();
|
||||
jest.spyOn(HttpLogic.actions, 'setHttpInterceptors');
|
||||
HttpLogic.actions.initializeHttp({ http: mockHttp });
|
||||
HttpLogic.actions.initializeHttpInterceptors();
|
||||
});
|
||||
|
||||
it('calls http.intercept and sets an array of interceptors', () => {
|
||||
|
@ -165,7 +156,7 @@ describe('HttpLogic', () => {
|
|||
});
|
||||
|
||||
it('sets httpInterceptors and calls all valid remove functions on unmount', () => {
|
||||
const unmount = HttpLogic.mount();
|
||||
const unmount = mount();
|
||||
const httpInterceptors = [jest.fn(), undefined, jest.fn()] as any;
|
||||
|
||||
HttpLogic.actions.setHttpInterceptors(httpInterceptors);
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
import { kea, MakeLogicType } from 'kea';
|
||||
|
||||
import { HttpSetup, HttpInterceptorResponseError, HttpResponse } from 'src/core/public';
|
||||
import { IHttpProviderProps } from './http_provider';
|
||||
|
||||
import { READ_ONLY_MODE_HEADER } from '../../../../common/constants';
|
||||
|
||||
|
@ -18,7 +17,6 @@ export interface IHttpValues {
|
|||
readOnlyMode: boolean;
|
||||
}
|
||||
export interface IHttpActions {
|
||||
initializeHttp({ http, errorConnecting, readOnlyMode }: IHttpProviderProps): IHttpProviderProps;
|
||||
initializeHttpInterceptors(): void;
|
||||
setHttpInterceptors(httpInterceptors: Function[]): { httpInterceptors: Function[] };
|
||||
setErrorConnecting(errorConnecting: boolean): { errorConnecting: boolean };
|
||||
|
@ -28,19 +26,13 @@ export interface IHttpActions {
|
|||
export const HttpLogic = kea<MakeLogicType<IHttpValues, IHttpActions>>({
|
||||
path: ['enterprise_search', 'http_logic'],
|
||||
actions: {
|
||||
initializeHttp: (props) => props,
|
||||
initializeHttpInterceptors: () => null,
|
||||
setHttpInterceptors: (httpInterceptors) => ({ httpInterceptors }),
|
||||
setErrorConnecting: (errorConnecting) => ({ errorConnecting }),
|
||||
setReadOnlyMode: (readOnlyMode) => ({ readOnlyMode }),
|
||||
},
|
||||
reducers: {
|
||||
http: [
|
||||
(null as unknown) as HttpSetup,
|
||||
{
|
||||
initializeHttp: (_, { http }) => http,
|
||||
},
|
||||
],
|
||||
reducers: ({ props }) => ({
|
||||
http: [props.http, {}],
|
||||
httpInterceptors: [
|
||||
[],
|
||||
{
|
||||
|
@ -48,20 +40,18 @@ export const HttpLogic = kea<MakeLogicType<IHttpValues, IHttpActions>>({
|
|||
},
|
||||
],
|
||||
errorConnecting: [
|
||||
false,
|
||||
props.errorConnecting || false,
|
||||
{
|
||||
initializeHttp: (_, { errorConnecting }) => !!errorConnecting,
|
||||
setErrorConnecting: (_, { errorConnecting }) => errorConnecting,
|
||||
},
|
||||
],
|
||||
readOnlyMode: [
|
||||
false,
|
||||
props.readOnlyMode || false,
|
||||
{
|
||||
initializeHttp: (_, { readOnlyMode }) => !!readOnlyMode,
|
||||
setReadOnlyMode: (_, { readOnlyMode }) => readOnlyMode,
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
listeners: ({ values, actions }) => ({
|
||||
initializeHttpInterceptors: () => {
|
||||
const httpInterceptors = [];
|
||||
|
@ -103,7 +93,10 @@ export const HttpLogic = kea<MakeLogicType<IHttpValues, IHttpActions>>({
|
|||
actions.setHttpInterceptors(httpInterceptors);
|
||||
},
|
||||
}),
|
||||
events: ({ values }) => ({
|
||||
events: ({ values, actions }) => ({
|
||||
afterMount: () => {
|
||||
actions.initializeHttpInterceptors();
|
||||
},
|
||||
beforeUnmount: () => {
|
||||
values.httpInterceptors.forEach((removeInterceptorFn?: Function) => {
|
||||
if (removeInterceptorFn) removeInterceptorFn();
|
||||
|
@ -112,6 +105,20 @@ export const HttpLogic = kea<MakeLogicType<IHttpValues, IHttpActions>>({
|
|||
}),
|
||||
});
|
||||
|
||||
/**
|
||||
* Mount/props helper
|
||||
*/
|
||||
interface IHttpLogicProps {
|
||||
http: HttpSetup;
|
||||
errorConnecting?: boolean;
|
||||
readOnlyMode?: boolean;
|
||||
}
|
||||
export const mountHttpLogic = (props: IHttpLogicProps) => {
|
||||
HttpLogic(props);
|
||||
const unmount = HttpLogic.mount();
|
||||
return unmount;
|
||||
};
|
||||
|
||||
/**
|
||||
* Small helper that checks whether or not an http call is for an Enterprise Search API
|
||||
*/
|
||||
|
|
|
@ -1,45 +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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import '../../__mocks__/shallow_usecontext.mock';
|
||||
import '../../__mocks__/kea.mock';
|
||||
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { useActions } from 'kea';
|
||||
|
||||
import { HttpProvider } from './';
|
||||
|
||||
describe('HttpProvider', () => {
|
||||
const props = {
|
||||
http: {} as any,
|
||||
errorConnecting: false,
|
||||
readOnlyMode: false,
|
||||
};
|
||||
const initializeHttp = jest.fn();
|
||||
const initializeHttpInterceptors = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
(useActions as jest.Mock).mockImplementationOnce(() => ({
|
||||
initializeHttp,
|
||||
initializeHttpInterceptors,
|
||||
}));
|
||||
});
|
||||
|
||||
it('does not render', () => {
|
||||
const wrapper = shallow(<HttpProvider {...props} />);
|
||||
|
||||
expect(wrapper.isEmptyRender()).toBe(true);
|
||||
});
|
||||
|
||||
it('calls initialization actions on mount', () => {
|
||||
shallow(<HttpProvider {...props} />);
|
||||
|
||||
expect(initializeHttp).toHaveBeenCalledWith(props);
|
||||
expect(initializeHttpInterceptors).toHaveBeenCalled();
|
||||
});
|
||||
});
|
|
@ -1,29 +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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
import { useActions } from 'kea';
|
||||
|
||||
import { HttpSetup } from 'src/core/public';
|
||||
|
||||
import { HttpLogic } from './http_logic';
|
||||
|
||||
export interface IHttpProviderProps {
|
||||
http: HttpSetup;
|
||||
errorConnecting?: boolean;
|
||||
readOnlyMode?: boolean;
|
||||
}
|
||||
|
||||
export const HttpProvider: React.FC<IHttpProviderProps> = (props) => {
|
||||
const { initializeHttp, initializeHttpInterceptors } = useActions(HttpLogic);
|
||||
|
||||
useEffect(() => {
|
||||
initializeHttp(props);
|
||||
initializeHttpInterceptors();
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
};
|
|
@ -4,5 +4,4 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { HttpLogic, IHttpValues, IHttpActions } from './http_logic';
|
||||
export { HttpProvider } from './http_provider';
|
||||
export { HttpLogic, IHttpValues, IHttpActions, mountHttpLogic } from './http_logic';
|
||||
|
|
|
@ -4,11 +4,14 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import '../../__mocks__/kea.mock';
|
||||
import '../../__mocks__/shallow_usecontext.mock';
|
||||
import { mockHttpValues } from '../../__mocks__';
|
||||
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { httpServiceMock } from 'src/core/public/mocks';
|
||||
import { JSON_HEADER as headers } from '../../../../common/constants';
|
||||
import { mountWithKibanaContext } from '../../__mocks__';
|
||||
|
||||
import {
|
||||
sendTelemetry,
|
||||
|
@ -18,8 +21,6 @@ import {
|
|||
} from './';
|
||||
|
||||
describe('Shared Telemetry Helpers', () => {
|
||||
const httpMock = httpServiceMock.createSetupContract();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
@ -27,13 +28,13 @@ describe('Shared Telemetry Helpers', () => {
|
|||
describe('sendTelemetry', () => {
|
||||
it('successfully calls the server-side telemetry endpoint', () => {
|
||||
sendTelemetry({
|
||||
http: httpMock,
|
||||
http: mockHttpValues.http,
|
||||
product: 'enterprise_search',
|
||||
action: 'viewed',
|
||||
metric: 'setup_guide',
|
||||
});
|
||||
|
||||
expect(httpMock.put).toHaveBeenCalledWith('/api/enterprise_search/stats', {
|
||||
expect(mockHttpValues.http.put).toHaveBeenCalledWith('/api/enterprise_search/stats', {
|
||||
headers,
|
||||
body: '{"product":"enterprise_search","action":"viewed","metric":"setup_guide"}',
|
||||
});
|
||||
|
@ -50,33 +51,27 @@ describe('Shared Telemetry Helpers', () => {
|
|||
|
||||
describe('React component helpers', () => {
|
||||
it('SendEnterpriseSearchTelemetry component', () => {
|
||||
mountWithKibanaContext(<SendEnterpriseSearchTelemetry action="viewed" metric="page" />, {
|
||||
http: httpMock,
|
||||
});
|
||||
shallow(<SendEnterpriseSearchTelemetry action="viewed" metric="page" />);
|
||||
|
||||
expect(httpMock.put).toHaveBeenCalledWith('/api/enterprise_search/stats', {
|
||||
expect(mockHttpValues.http.put).toHaveBeenCalledWith('/api/enterprise_search/stats', {
|
||||
headers,
|
||||
body: '{"product":"enterprise_search","action":"viewed","metric":"page"}',
|
||||
});
|
||||
});
|
||||
|
||||
it('SendAppSearchTelemetry component', () => {
|
||||
mountWithKibanaContext(<SendAppSearchTelemetry action="clicked" metric="button" />, {
|
||||
http: httpMock,
|
||||
});
|
||||
shallow(<SendAppSearchTelemetry action="clicked" metric="button" />);
|
||||
|
||||
expect(httpMock.put).toHaveBeenCalledWith('/api/enterprise_search/stats', {
|
||||
expect(mockHttpValues.http.put).toHaveBeenCalledWith('/api/enterprise_search/stats', {
|
||||
headers,
|
||||
body: '{"product":"app_search","action":"clicked","metric":"button"}',
|
||||
});
|
||||
});
|
||||
|
||||
it('SendWorkplaceSearchTelemetry component', () => {
|
||||
mountWithKibanaContext(<SendWorkplaceSearchTelemetry action="error" metric="not_found" />, {
|
||||
http: httpMock,
|
||||
});
|
||||
shallow(<SendWorkplaceSearchTelemetry action="error" metric="not_found" />);
|
||||
|
||||
expect(httpMock.put).toHaveBeenCalledWith('/api/enterprise_search/stats', {
|
||||
expect(mockHttpValues.http.put).toHaveBeenCalledWith('/api/enterprise_search/stats', {
|
||||
headers,
|
||||
body: '{"product":"workplace_search","action":"error","metric":"not_found"}',
|
||||
});
|
||||
|
|
|
@ -4,11 +4,12 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useValues } from 'kea';
|
||||
|
||||
import { HttpSetup } from 'src/core/public';
|
||||
import { JSON_HEADER as headers } from '../../../../common/constants';
|
||||
import { KibanaContext, IKibanaContext } from '../../index';
|
||||
import { HttpLogic } from '../http';
|
||||
|
||||
interface ISendTelemetryProps {
|
||||
action: 'viewed' | 'error' | 'clicked';
|
||||
|
@ -41,7 +42,7 @@ export const SendEnterpriseSearchTelemetry: React.FC<ISendTelemetryProps> = ({
|
|||
action,
|
||||
metric,
|
||||
}) => {
|
||||
const { http } = useContext(KibanaContext) as IKibanaContext;
|
||||
const { http } = useValues(HttpLogic);
|
||||
|
||||
useEffect(() => {
|
||||
sendTelemetry({ http, action, metric, product: 'enterprise_search' });
|
||||
|
@ -51,7 +52,7 @@ export const SendEnterpriseSearchTelemetry: React.FC<ISendTelemetryProps> = ({
|
|||
};
|
||||
|
||||
export const SendAppSearchTelemetry: React.FC<ISendTelemetryProps> = ({ action, metric }) => {
|
||||
const { http } = useContext(KibanaContext) as IKibanaContext;
|
||||
const { http } = useValues(HttpLogic);
|
||||
|
||||
useEffect(() => {
|
||||
sendTelemetry({ http, action, metric, product: 'app_search' });
|
||||
|
@ -61,7 +62,7 @@ export const SendAppSearchTelemetry: React.FC<ISendTelemetryProps> = ({ action,
|
|||
};
|
||||
|
||||
export const SendWorkplaceSearchTelemetry: React.FC<ISendTelemetryProps> = ({ action, metric }) => {
|
||||
const { http } = useContext(KibanaContext) as IKibanaContext;
|
||||
const { http } = useValues(HttpLogic);
|
||||
|
||||
useEffect(() => {
|
||||
sendTelemetry({ http, action, metric, product: 'workplace_search' });
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import '../../../../__mocks__/kea.mock';
|
||||
import '../../../../__mocks__/shallow_usecontext.mock';
|
||||
|
||||
import React from 'react';
|
||||
|
|
|
@ -5,17 +5,19 @@
|
|||
*/
|
||||
|
||||
import React, { useContext } from 'react';
|
||||
import { useValues } from 'kea';
|
||||
|
||||
import { EuiButton, EuiButtonProps, EuiLinkProps } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import { sendTelemetry } from '../../../../shared/telemetry';
|
||||
import { HttpLogic } from '../../../../shared/http';
|
||||
import { KibanaContext, IKibanaContext } from '../../../../index';
|
||||
|
||||
export const ProductButton: React.FC = () => {
|
||||
const { http } = useValues(HttpLogic);
|
||||
const {
|
||||
externalUrl: { getWorkplaceSearchUrl },
|
||||
http,
|
||||
} = useContext(KibanaContext) as IKibanaContext;
|
||||
|
||||
const buttonProps = {
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import '../../../__mocks__/kea.mock';
|
||||
import '../../../__mocks__/shallow_usecontext.mock';
|
||||
|
||||
import React from 'react';
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import React, { useContext } from 'react';
|
||||
import { useValues } from 'kea';
|
||||
|
||||
import {
|
||||
EuiButton,
|
||||
|
@ -17,7 +18,9 @@ import {
|
|||
EuiButtonEmptyProps,
|
||||
EuiLinkProps,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { sendTelemetry } from '../../../shared/telemetry';
|
||||
import { HttpLogic } from '../../../shared/http';
|
||||
import { KibanaContext, IKibanaContext } from '../../../index';
|
||||
|
||||
interface IOnboardingCardProps {
|
||||
|
@ -39,8 +42,8 @@ export const OnboardingCard: React.FC<IOnboardingCardProps> = ({
|
|||
actionPath,
|
||||
complete,
|
||||
}) => {
|
||||
const { http } = useValues(HttpLogic);
|
||||
const {
|
||||
http,
|
||||
externalUrl: { getWorkplaceSearchUrl },
|
||||
} = useContext(KibanaContext) as IKibanaContext;
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import sharedSourcesIcon from '../../components/shared/assets/share_circle.svg';
|
||||
import { sendTelemetry } from '../../../shared/telemetry';
|
||||
import { HttpLogic } from '../../../shared/http';
|
||||
import { KibanaContext, IKibanaContext } from '../../../index';
|
||||
import { ORG_SOURCES_PATH, USERS_PATH, ORG_SETTINGS_PATH } from '../../routes';
|
||||
|
||||
|
@ -135,8 +136,8 @@ export const OnboardingSteps: React.FC = () => {
|
|||
};
|
||||
|
||||
export const OrgNameOnboarding: React.FC = () => {
|
||||
const { http } = useValues(HttpLogic);
|
||||
const {
|
||||
http,
|
||||
externalUrl: { getWorkplaceSearchUrl },
|
||||
} = useContext(KibanaContext) as IKibanaContext;
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ import { FormattedMessage } from '@kbn/i18n/react';
|
|||
|
||||
import { ContentSection } from '../../components/shared/content_section';
|
||||
import { sendTelemetry } from '../../../shared/telemetry';
|
||||
import { HttpLogic } from '../../../shared/http';
|
||||
import { KibanaContext, IKibanaContext } from '../../../index';
|
||||
import { SOURCE_DETAILS_PATH, getContentSourcePath } from '../../routes';
|
||||
|
||||
|
@ -93,8 +94,8 @@ export const RecentActivityItem: React.FC<IFeedActivity> = ({
|
|||
timestamp,
|
||||
sourceId,
|
||||
}) => {
|
||||
const { http } = useValues(HttpLogic);
|
||||
const {
|
||||
http,
|
||||
externalUrl: { getWorkplaceSearchUrl },
|
||||
} = useContext(KibanaContext) as IKibanaContext;
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue