Skip to content

Commit

Permalink
[App Search] Empty shell view for engine source engines view (#96896)
Browse files Browse the repository at this point in the history
* Add route to get source engines for meta engines

* New empty SourceEngines component

* Add SourceEngines to EngineRouter

* New SourceEnginesLogic file

* Hook up SourceEngines component to SourceEnginesLogic

* Update EngineNav link to manage source engines to point internally

* PR Feedback:
- Copy fixes
- Markup improvements
- Spleling erors
- Test improvements

* Move EnginesAPIResponse to shared types file

* Use existing mock for EngineLogic

* setSourceEngines -> onSourceEnginesFetch

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
byronhulcher and kibanamachine authored Apr 27, 2021
1 parent e2933b5 commit 9cade42
Show file tree
Hide file tree
Showing 11 changed files with 364 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,7 @@ export const EngineNav: React.FC = () => {
)}
{canViewMetaEngineSourceEngines && isMetaEngine && (
<SideNavLink
isExternal
to={getAppSearchUrl(generateEnginePath(META_ENGINE_SOURCE_ENGINES_PATH))}
to={generateEnginePath(META_ENGINE_SOURCE_ENGINES_PATH)}
data-test-subj="MetaEngineEnginesLink"
>
{ENGINES_TITLE}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { EngineOverview } from '../engine_overview';
import { RelevanceTuning } from '../relevance_tuning';
import { ResultSettings } from '../result_settings';
import { SearchUI } from '../search_ui';
import { SourceEngines } from '../source_engines';
import { Synonyms } from '../synonyms';

import { EngineRouter } from './engine_router';
Expand Down Expand Up @@ -143,4 +144,11 @@ describe('EngineRouter', () => {

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

it('renders a source engines view', () => {
setMockValues({ ...values, myRole: { canViewMetaEngineSourceEngines: true } });
const wrapper = shallow(<EngineRouter />);

expect(wrapper.find(SourceEngines)).toHaveLength(1);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
ENGINE_DOCUMENT_DETAIL_PATH,
// ENGINE_SCHEMA_PATH,
// ENGINE_CRAWLER_PATH,
// META_ENGINE_SOURCE_ENGINES_PATH,
META_ENGINE_SOURCE_ENGINES_PATH,
ENGINE_RELEVANCE_TUNING_PATH,
ENGINE_SYNONYMS_PATH,
ENGINE_CURATIONS_PATH,
Expand All @@ -41,6 +41,7 @@ import { EngineOverview } from '../engine_overview';
import { RelevanceTuning } from '../relevance_tuning';
import { ResultSettings } from '../result_settings';
import { SearchUI } from '../search_ui';
import { SourceEngines } from '../source_engines';
import { Synonyms } from '../synonyms';

import { EngineLogic, getEngineBreadcrumbs } from './';
Expand All @@ -52,7 +53,7 @@ export const EngineRouter: React.FC = () => {
// canViewEngineDocuments,
// canViewEngineSchema,
// canViewEngineCrawler,
// canViewMetaEngineSourceEngines,
canViewMetaEngineSourceEngines,
canManageEngineRelevanceTuning,
canManageEngineSynonyms,
canManageEngineCurations,
Expand Down Expand Up @@ -128,6 +129,11 @@ export const EngineRouter: React.FC = () => {
<SearchUI />
</Route>
)}
{canViewMetaEngineSourceEngines && (
<Route path={META_ENGINE_SOURCE_ENGINES_PATH}>
<SourceEngines />
</Route>
)}
<Route>
<SetPageChrome trail={getEngineBreadcrumbs()} />
<EngineOverview />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@

import { kea, MakeLogicType } from 'kea';

import { Meta } from '../../../../../../../common/types';
import { flashAPIErrors } from '../../../../../shared/flash_messages';

import { HttpLogic } from '../../../../../shared/http';

import { EngineDetails } from '../../../engine/types';
import { EnginesAPIResponse } from '../../types';

interface MetaEnginesTableValues {
expandedRows: { [id: string]: boolean };
Expand All @@ -30,11 +28,6 @@ interface MetaEnginesTableActions {
hideRow(itemId: string): { itemId: string };
}

interface EnginesAPIResponse {
results: EngineDetails[];
meta: Meta;
}

export const MetaEnginesTableLogic = kea<
MakeLogicType<MetaEnginesTableValues, MetaEnginesTableActions>
>({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { updateMetaPageIndex } from '../../../shared/table_pagination';
import { EngineDetails, EngineTypes } from '../engine/types';

import { DELETE_ENGINE_MESSAGE } from './constants';
import { EnginesAPIResponse } from './types';

interface EnginesValues {
dataLoading: boolean;
Expand All @@ -27,10 +28,6 @@ interface EnginesValues {
metaEnginesLoading: boolean;
}

interface EnginesAPIResponse {
results: EngineDetails[];
meta: Meta;
}
interface EnginesActions {
deleteEngine(engine: EngineDetails): { engine: EngineDetails };
onDeleteEngineSuccess(engine: EngineDetails): { engine: EngineDetails };
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* 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 { Meta } from '../../../../../common/types';
import { EngineDetails } from '../engine/types';

export interface EnginesAPIResponse {
results: EngineDetails[];
meta: Meta;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* 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.
*/

export { SourceEngines } from './source_engines';
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* 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 '../../../__mocks__/shallow_useeffect.mock';
import { setMockActions, setMockValues } from '../../../__mocks__';
import '../../__mocks__/engine_logic.mock';

import React from 'react';

import { shallow } from 'enzyme';

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

import { Loading } from '../../../shared/loading';

import { SourceEngines } from '.';

const MOCK_ACTIONS = {
// SourceEnginesLogic
fetchSourceEngines: jest.fn(),
};

const MOCK_VALUES = {
dataLoading: false,
sourceEngines: [],
};

describe('SourceEngines', () => {
beforeEach(() => {
jest.clearAllMocks();
setMockActions(MOCK_ACTIONS);
});

describe('non-happy-path states', () => {
it('renders a loading component before data has loaded', () => {
setMockValues({ ...MOCK_VALUES, dataLoading: true });
const wrapper = shallow(<SourceEngines />);

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

describe('happy-path states', () => {
it('renders and calls a function to initialize data', () => {
setMockValues(MOCK_VALUES);
const wrapper = shallow(<SourceEngines />);

expect(wrapper.find(EuiCodeBlock)).toHaveLength(1);
expect(MOCK_ACTIONS.fetchSourceEngines).toHaveBeenCalled();
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* 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, { useEffect } from 'react';

import { useActions, useValues } from 'kea';

import { EuiCodeBlock, EuiPageHeader } from '@elastic/eui';

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

import { FlashMessages } from '../../../shared/flash_messages';
import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome';
import { Loading } from '../../../shared/loading';
import { getEngineBreadcrumbs } from '../engine';

import { SourceEnginesLogic } from './source_engines_logic';

const SOURCE_ENGINES_TITLE = i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.souceEngines.title',
{
defaultMessage: 'Manage engines',
}
);

export const SourceEngines: React.FC = () => {
const { fetchSourceEngines } = useActions(SourceEnginesLogic);
const { dataLoading, sourceEngines } = useValues(SourceEnginesLogic);

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

if (dataLoading) return <Loading />;

return (
<>
<SetPageChrome trail={getEngineBreadcrumbs([SOURCE_ENGINES_TITLE])} />
<EuiPageHeader pageTitle={SOURCE_ENGINES_TITLE} />
<FlashMessages />
<EuiCodeBlock language="json">{JSON.stringify(sourceEngines, null, 2)}</EuiCodeBlock>
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
* 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 { LogicMounter, mockFlashMessageHelpers, mockHttpValues } from '../../../__mocks__';
import '../../__mocks__/engine_logic.mock';

import { nextTick } from '@kbn/test/jest';

import { EngineDetails } from '../engine/types';

import { SourceEnginesLogic } from './source_engines_logic';

const DEFAULT_VALUES = {
dataLoading: true,
sourceEngines: [],
};

describe('SourceEnginesLogic', () => {
const { http } = mockHttpValues;
const { mount } = new LogicMounter(SourceEnginesLogic);
const { flashAPIErrors } = mockFlashMessageHelpers;

beforeEach(() => {
jest.clearAllMocks();
mount();
});

it('initializes with default values', () => {
expect(SourceEnginesLogic.values).toEqual(DEFAULT_VALUES);
});

describe('setSourceEngines', () => {
beforeEach(() => {
SourceEnginesLogic.actions.onSourceEnginesFetch([
{ name: 'source-engine-1' },
{ name: 'source-engine-2' },
] as EngineDetails[]);
});

it('sets the source engines', () => {
expect(SourceEnginesLogic.values.sourceEngines).toEqual([
{ name: 'source-engine-1' },
{ name: 'source-engine-2' },
]);
});

it('sets dataLoading to false', () => {
expect(SourceEnginesLogic.values.dataLoading).toEqual(false);
});
});

describe('fetchSourceEngines', () => {
it('calls addSourceEngines and displayRow when it has retrieved all pages', async () => {
http.get.mockReturnValueOnce(
Promise.resolve({
meta: {
page: {
total_pages: 1,
},
},
results: [{ name: 'source-engine-1' }, { name: 'source-engine-2' }],
})
);
jest.spyOn(SourceEnginesLogic.actions, 'onSourceEnginesFetch');

SourceEnginesLogic.actions.fetchSourceEngines();
await nextTick();

expect(http.get).toHaveBeenCalledWith('/api/app_search/engines/some-engine/source_engines', {
query: {
'page[current]': 1,
'page[size]': 25,
},
});
expect(SourceEnginesLogic.actions.onSourceEnginesFetch).toHaveBeenCalledWith([
{ name: 'source-engine-1' },
{ name: 'source-engine-2' },
]);
});

it('display a flash message on error', async () => {
http.get.mockReturnValueOnce(Promise.reject());
mount();

SourceEnginesLogic.actions.fetchSourceEngines();
await nextTick();

expect(flashAPIErrors).toHaveBeenCalledTimes(1);
});

it('recursively fetches a number of pages', async () => {
mount();
jest.spyOn(SourceEnginesLogic.actions, 'onSourceEnginesFetch');

// First page
http.get.mockReturnValueOnce(
Promise.resolve({
meta: {
page: {
total_pages: 2,
},
},
results: [{ name: 'source-engine-1' }],
})
);

// Second and final page
http.get.mockReturnValueOnce(
Promise.resolve({
meta: {
page: {
total_pages: 2,
},
},
results: [{ name: 'source-engine-2' }],
})
);

SourceEnginesLogic.actions.fetchSourceEngines();
await nextTick();

expect(SourceEnginesLogic.actions.onSourceEnginesFetch).toHaveBeenCalledWith([
// First page
{ name: 'source-engine-1' },
// Second and final page
{ name: 'source-engine-2' },
]);
});
});
});
Loading

0 comments on commit 9cade42

Please sign in to comment.