diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.scss
index d7740724204a7..529fb9c4bd63e 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.scss
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.scss
@@ -15,3 +15,11 @@
margin-top: $euiSizeXS;
}
}
+
+.appSearchNavIcons {
+ // EUI override
+ &.euiFlexItem {
+ flex-grow: 0;
+ flex-direction: row;
+ }
+}
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.test.tsx
index 43dc592660564..4efbf88d1957f 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.test.tsx
@@ -9,7 +9,7 @@ import { unmountHandler } from '../../../__mocks__/shallow_useeffect.mock';
import { setMockValues, setMockActions } from '../../../__mocks__/kea.mock';
import React from 'react';
-import { shallow, mount } from 'enzyme';
+import { shallow } from 'enzyme';
import { Switch, Redirect, useParams } from 'react-router-dom';
import { EuiBadge } from '@elastic/eui';
@@ -82,106 +82,153 @@ describe('EngineRouter', () => {
});
describe('EngineNav', () => {
+ const values = { myRole: {}, engineName: 'some-engine', dataLoading: false, engine: {} };
+
beforeEach(() => {
- (useParams as jest.Mock).mockReturnValue({ engineName: 'some-engine' });
+ setMockValues(values);
+ });
+
+ it('does not render if async data is still loading', () => {
+ setMockValues({ ...values, dataLoading: true });
+ const wrapper = shallow();
+ expect(wrapper.isEmptyRender()).toBe(true);
});
it('does not render without an engine name', () => {
- setMockValues({ myRole: {} });
- (useParams as jest.Mock).mockReturnValue({ engineName: '' });
+ setMockValues({ ...values, engineName: '' });
const wrapper = shallow();
expect(wrapper.isEmptyRender()).toBe(true);
});
- it('renders an engine label', () => {
- setMockValues({ myRole: {} });
- const wrapper = mount();
+ it('renders an engine label and badges', () => {
+ setMockValues({ ...values, isSampleEngine: false, isMetaEngine: false });
+ const wrapper = shallow();
+ const label = wrapper.find('[data-test-subj="EngineLabel"]').find('.eui-textTruncate');
+
+ expect(label.text()).toEqual('SOME-ENGINE');
+ expect(wrapper.find(EuiBadge)).toHaveLength(0);
- const label = wrapper.find('[data-test-subj="EngineLabel"]').last();
- expect(label.text()).toEqual(expect.stringContaining('SOME-ENGINE'));
+ setMockValues({ ...values, isSampleEngine: true });
+ wrapper.setProps({}); // Re-render
+ expect(wrapper.find(EuiBadge).prop('children')).toEqual('SAMPLE ENGINE');
- // TODO: Test sample & meta engine conditional rendering
- expect(label.find(EuiBadge).text()).toEqual('SAMPLE ENGINE');
+ setMockValues({ ...values, isMetaEngine: true });
+ wrapper.setProps({}); // Re-render
+ expect(wrapper.find(EuiBadge).prop('children')).toEqual('META ENGINE');
});
it('renders a default engine overview link', () => {
- setMockValues({ myRole: {} });
const wrapper = shallow();
expect(wrapper.find('[data-test-subj="EngineOverviewLink"]')).toHaveLength(1);
});
it('renders an analytics link', () => {
- setMockValues({ myRole: { canViewEngineAnalytics: true } });
+ setMockValues({ ...values, myRole: { canViewEngineAnalytics: true } });
const wrapper = shallow();
expect(wrapper.find('[data-test-subj="EngineAnalyticsLink"]')).toHaveLength(1);
});
it('renders a documents link', () => {
- setMockValues({ myRole: { canViewEngineDocuments: true } });
+ setMockValues({ ...values, myRole: { canViewEngineDocuments: true } });
const wrapper = shallow();
expect(wrapper.find('[data-test-subj="EngineDocumentsLink"]')).toHaveLength(1);
});
it('renders a schema link', () => {
- setMockValues({ myRole: { canViewEngineSchema: true } });
+ setMockValues({ ...values, myRole: { canViewEngineSchema: true } });
const wrapper = shallow();
expect(wrapper.find('[data-test-subj="EngineSchemaLink"]')).toHaveLength(1);
+ });
+
+ it('renders schema nav icons', () => {
+ const myRole = { canViewEngineSchema: true };
+ const wrapper = shallow();
+
+ setMockValues({ ...values, myRole, hasUnconfirmedSchemaFields: true });
+ wrapper.setProps({}); // Re-render
+ expect(wrapper.find('[data-test-subj="EngineNavSchemaUnconfirmedFields"]')).toHaveLength(1);
- // TODO: Schema warning icon
+ setMockValues({ ...values, myRole, hasSchemaConflicts: true });
+ wrapper.setProps({}); // Re-render
+ expect(wrapper.find('[data-test-subj="EngineNavSchemaConflicts"]')).toHaveLength(1);
});
- // TODO: Unskip when EngineLogic is migrated
- it.skip('renders a crawler link', () => {
- setMockValues({ myRole: { canViewEngineCrawler: true } });
+ it('renders a crawler link', () => {
+ const myRole = { canViewEngineCrawler: true };
+ setMockValues({ ...values, myRole });
const wrapper = shallow();
expect(wrapper.find('[data-test-subj="EngineCrawlerLink"]')).toHaveLength(1);
- // TODO: Test that the crawler link does NOT show up for meta/sample engines
+ // Does not render for meta engines
+ setMockValues({ ...values, myRole, isMetaEngine: true });
+ wrapper.setProps({}); // Re-render
+ expect(wrapper.find('[data-test-subj="EngineCrawlerLink"]')).toHaveLength(0);
+
+ // Does not render for sample engine
+ setMockValues({ ...values, myRole, isSampleEngine: true });
+ wrapper.setProps({}); // Re-render
+ expect(wrapper.find('[data-test-subj="EngineCrawlerLink"]')).toHaveLength(0);
});
- // TODO: Unskip when EngineLogic is migrated
- it.skip('renders a meta engine source engines link', () => {
- setMockValues({ myRole: { canViewMetaEngineSourceEngines: true } });
+ it('renders a meta engine source engines link', () => {
+ const myRole = { canViewMetaEngineSourceEngines: true };
+ setMockValues({ ...values, myRole, isMetaEngine: true });
const wrapper = shallow();
expect(wrapper.find('[data-test-subj="MetaEngineEnginesLink"]')).toHaveLength(1);
- // TODO: Test that the crawler link does NOT show up for non-meta engines
+ // Does not render if engine is not a meta-engine
+ setMockValues({ ...values, myRole, isMetaEngine: false });
+ wrapper.setProps({}); // Re-render
+ expect(wrapper.find('[data-test-subj="MetaEngineEnginesLink"]')).toHaveLength(0);
});
it('renders a relevance tuning link', () => {
- setMockValues({ myRole: { canManageEngineRelevanceTuning: true } });
+ setMockValues({ ...values, myRole: { canManageEngineRelevanceTuning: true } });
const wrapper = shallow();
expect(wrapper.find('[data-test-subj="EngineRelevanceTuningLink"]')).toHaveLength(1);
+ });
+
+ it('renders relevance tuning nav icons', () => {
+ setMockValues({
+ ...values,
+ myRole: { canManageEngineRelevanceTuning: true },
+ engine: {
+ unsearchedUnconfirmedFields: true,
+ invalidBoosts: true,
+ },
+ });
+ const wrapper = shallow();
- // TODO: Boost error icon
+ expect(wrapper.find('[data-test-subj="EngineNavRelevanceTuningInvalidBoosts"]')).toHaveLength(1); // prettier-ignore
+ expect(wrapper.find('[data-test-subj="EngineNavRelevanceTuningUnsearchedFields"]')).toHaveLength(1); // prettier-ignore
});
it('renders a synonyms link', () => {
- setMockValues({ myRole: { canManageEngineSynonyms: true } });
+ setMockValues({ ...values, myRole: { canManageEngineSynonyms: true } });
const wrapper = shallow();
expect(wrapper.find('[data-test-subj="EngineSynonymsLink"]')).toHaveLength(1);
});
it('renders a curations link', () => {
- setMockValues({ myRole: { canManageEngineCurations: true } });
+ setMockValues({ ...values, myRole: { canManageEngineCurations: true } });
const wrapper = shallow();
expect(wrapper.find('[data-test-subj="EngineCurationsLink"]')).toHaveLength(1);
});
it('renders a results settings link', () => {
- setMockValues({ myRole: { canManageEngineResultSettings: true } });
+ setMockValues({ ...values, myRole: { canManageEngineResultSettings: true } });
const wrapper = shallow();
expect(wrapper.find('[data-test-subj="EngineResultSettingsLink"]')).toHaveLength(1);
});
it('renders a Search UI link', () => {
- setMockValues({ myRole: { canManageEngineSearchUi: true } });
+ setMockValues({ ...values, myRole: { canManageEngineSearchUi: true } });
const wrapper = shallow();
expect(wrapper.find('[data-test-subj="EngineSearchUILink"]')).toHaveLength(1);
});
it('renders an API logs link', () => {
- setMockValues({ myRole: { canViewEngineApiLogs: true } });
+ setMockValues({ ...values, myRole: { canViewEngineApiLogs: true } });
const wrapper = shallow();
expect(wrapper.find('[data-test-subj="EngineAPILogsLink"]')).toHaveLength(1);
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx
index 8c03f59702639..9efb0b7804b86 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx
@@ -8,7 +8,7 @@ import React, { useEffect } from 'react';
import { Route, Switch, Redirect, useParams } from 'react-router-dom';
import { useValues, useActions } from 'kea';
-import { EuiText, EuiBadge } from '@elastic/eui';
+import { EuiText, EuiBadge, EuiIcon, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { SideNavLink, SideNavItem } from '../../../shared/layout';
@@ -48,6 +48,7 @@ import {
} from './constants';
import { EngineLogic } from './';
+import { EngineDetails } from './types';
import './engine_nav.scss';
@@ -114,14 +115,22 @@ export const EngineNav: React.FC = () => {
},
} = useValues(AppLogic);
- // TODO: Use EngineLogic
- const isSampleEngine = true;
- const isMetaEngine = false;
- const { engineName } = useParams() as { engineName: string };
- const engineRoute = engineName && getEngineRoute(engineName);
+ const {
+ engineName,
+ dataLoading,
+ isSampleEngine,
+ isMetaEngine,
+ hasSchemaConflicts,
+ hasUnconfirmedSchemaFields,
+ engine,
+ } = useValues(EngineLogic);
+ if (dataLoading) return null;
if (!engineName) return null;
+ const engineRoute = getEngineRoute(engineName);
+ const { invalidBoosts, unsearchedUnconfirmedFields } = engine as Required;
+
return (
<>
@@ -166,8 +175,33 @@ export const EngineNav: React.FC = () => {
to={getAppSearchUrl(engineRoute + ENGINE_SCHEMA_PATH)}
data-test-subj="EngineSchemaLink"
>
- {SCHEMA_TITLE}
- {/* TODO: Engine schema warning icon */}
+
+ {SCHEMA_TITLE}
+
+ {hasUnconfirmedSchemaFields && (
+
+ )}
+ {hasSchemaConflicts && (
+
+ )}
+
+
)}
{canViewEngineCrawler && !isMetaEngine && !isSampleEngine && (
@@ -194,8 +228,33 @@ export const EngineNav: React.FC = () => {
to={getAppSearchUrl(engineRoute + ENGINE_RELEVANCE_TUNING_PATH)}
data-test-subj="EngineRelevanceTuningLink"
>
- {RELEVANCE_TUNING_TITLE}
- {/* TODO: invalid boosts error icon */}
+
+ {RELEVANCE_TUNING_TITLE}
+
+ {invalidBoosts && (
+
+ )}
+ {unsearchedUnconfirmedFields && (
+
+ )}
+
+
)}
{canManageEngineSynonyms && (