diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/query_performance/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/query_performance/index.ts
new file mode 100644
index 0000000000000..0bd18ea640850
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/query_performance/index.ts
@@ -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 { QueryPerformance } from './query_performance';
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/query_performance/query_performance.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/query_performance/query_performance.test.tsx
new file mode 100644
index 0000000000000..0c62b783a47ff
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/query_performance/query_performance.test.tsx
@@ -0,0 +1,59 @@
+/*
+ * 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 { setMockValues } from '../../../../__mocks__/kea.mock';
+
+import React from 'react';
+
+import { shallow } from 'enzyme';
+
+import { EuiBadge } from '@elastic/eui';
+
+import { QueryPerformance } from './query_performance';
+
+describe('QueryPerformance', () => {
+ const values = {
+ queryPerformanceScore: 1,
+ };
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ setMockValues(values);
+ });
+
+ it('renders as green with the text "optimal" for a performance score of less than 6', () => {
+ const wrapper = shallow();
+ expect(wrapper.find(EuiBadge).prop('color')).toEqual('#59deb4');
+ expect(wrapper.find(EuiBadge).children().text()).toEqual('Query performance: optimal');
+ });
+
+ it('renders as blue with the text "good" for a performance score of less than 11', () => {
+ setMockValues({
+ queryPerformanceScore: 10,
+ });
+ const wrapper = shallow();
+ expect(wrapper.find(EuiBadge).prop('color')).toEqual('#40bfff');
+ expect(wrapper.find(EuiBadge).children().text()).toEqual('Query performance: good');
+ });
+
+ it('renders as yellow with the text "standard" for a performance score of less than 21', () => {
+ setMockValues({
+ queryPerformanceScore: 20,
+ });
+ const wrapper = shallow();
+ expect(wrapper.find(EuiBadge).prop('color')).toEqual('#fed566');
+ expect(wrapper.find(EuiBadge).children().text()).toEqual('Query performance: standard');
+ });
+
+ it('renders as red with the text "delayed" for a performance score of 21 or more', () => {
+ setMockValues({
+ queryPerformanceScore: 100,
+ });
+ const wrapper = shallow();
+ expect(wrapper.find(EuiBadge).prop('color')).toEqual('#ff9173');
+ expect(wrapper.find(EuiBadge).children().text()).toEqual('Query performance: delayed');
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/query_performance/query_performance.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/query_performance/query_performance.tsx
new file mode 100644
index 0000000000000..e3dfddc35d88c
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/query_performance/query_performance.tsx
@@ -0,0 +1,87 @@
+/*
+ * 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 from 'react';
+
+import { useValues } from 'kea';
+
+import { EuiBadge } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+
+import { ResultSettingsLogic } from '../result_settings_logic';
+
+enum QueryPerformanceRating {
+ Optimal = 'Optimal',
+ Good = 'Good',
+ Standard = 'Standard',
+ Delayed = 'Delayed',
+}
+
+const QUERY_PERFORMANCE_LABEL = (performanceValue: string) =>
+ i18n.translate('xpack.enterpriseSearch.appSearch.engine.resultSettings.queryPerformanceLabel', {
+ defaultMessage: 'Query performance: {performanceValue}',
+ values: {
+ performanceValue,
+ },
+ });
+
+const QUERY_PERFORMANCE_OPTIMAL = i18n.translate(
+ 'xpack.enterpriseSearch.appSearch.engine.resultSettings.queryPerformance.optimalValue',
+ { defaultMessage: 'optimal' }
+);
+
+const QUERY_PERFORMANCE_GOOD = i18n.translate(
+ 'xpack.enterpriseSearch.appSearch.engine.resultSettings.queryPerformance.goodValue',
+ { defaultMessage: 'good' }
+);
+
+const QUERY_PERFORMANCE_STANDARD = i18n.translate(
+ 'xpack.enterpriseSearch.appSearch.engine.resultSettings.queryPerformance.standardValue',
+ { defaultMessage: 'standard' }
+);
+
+const QUERY_PERFORMANCE_DELAYED = i18n.translate(
+ 'xpack.enterpriseSearch.appSearch.engine.resultSettings.queryPerformance.delayedValue',
+ { defaultMessage: 'delayed' }
+);
+
+const badgeText: Record = {
+ [QueryPerformanceRating.Optimal]: QUERY_PERFORMANCE_LABEL(QUERY_PERFORMANCE_OPTIMAL),
+ [QueryPerformanceRating.Good]: QUERY_PERFORMANCE_LABEL(QUERY_PERFORMANCE_GOOD),
+ [QueryPerformanceRating.Standard]: QUERY_PERFORMANCE_LABEL(QUERY_PERFORMANCE_STANDARD),
+ [QueryPerformanceRating.Delayed]: QUERY_PERFORMANCE_LABEL(QUERY_PERFORMANCE_DELAYED),
+};
+
+const badgeColors: Record = {
+ [QueryPerformanceRating.Optimal]: '#59deb4',
+ [QueryPerformanceRating.Good]: '#40bfff',
+ [QueryPerformanceRating.Standard]: '#fed566',
+ [QueryPerformanceRating.Delayed]: '#ff9173',
+};
+
+const getPerformanceRating = (score: number) => {
+ switch (true) {
+ case score < 6:
+ return QueryPerformanceRating.Optimal;
+ case score < 11:
+ return QueryPerformanceRating.Good;
+ case score < 21:
+ return QueryPerformanceRating.Standard;
+ default:
+ return QueryPerformanceRating.Delayed;
+ }
+};
+
+export const QueryPerformance: React.FC = () => {
+ const { queryPerformanceScore } = useValues(ResultSettingsLogic);
+ const performanceRating = getPerformanceRating(queryPerformanceScore);
+ return (
+
+ {badgeText[performanceRating]}
+
+ );
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.test.tsx
index 3388894c230a0..9eda1362e04fc 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.test.tsx
@@ -7,7 +7,7 @@
import '../../../__mocks__/shallow_useeffect.mock';
-import { setMockActions } from '../../../__mocks__';
+import { setMockValues, setMockActions } from '../../../__mocks__';
import React from 'react';
@@ -15,12 +15,19 @@ import { shallow } from 'enzyme';
import { ResultSettings } from './result_settings';
import { ResultSettingsTable } from './result_settings_table';
+import { SampleResponse } from './sample_response';
describe('RelevanceTuning', () => {
+ const values = {
+ dataLoading: false,
+ };
+
const actions = {
initializeResultSettingsData: jest.fn(),
};
+
beforeEach(() => {
+ setMockValues(values);
setMockActions(actions);
jest.clearAllMocks();
});
@@ -28,10 +35,20 @@ describe('RelevanceTuning', () => {
it('renders', () => {
const wrapper = shallow();
expect(wrapper.find(ResultSettingsTable).exists()).toBe(true);
+ expect(wrapper.find(SampleResponse).exists()).toBe(true);
});
it('initializes result settings data when mounted', () => {
shallow();
expect(actions.initializeResultSettingsData).toHaveBeenCalled();
});
+
+ it('renders a loading screen if data has not loaded yet', () => {
+ setMockValues({
+ dataLoading: true,
+ });
+ const wrapper = shallow();
+ expect(wrapper.find(ResultSettingsTable).exists()).toBe(false);
+ expect(wrapper.find(SampleResponse).exists()).toBe(false);
+ });
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.tsx
index 7f4373835f8d5..336f3f663119f 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.tsx
@@ -7,13 +7,15 @@
import React, { useEffect } from 'react';
-import { useActions } from 'kea';
+import { useActions, useValues } from 'kea';
import { EuiPageHeader, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { FlashMessages } from '../../../shared/flash_messages';
import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome';
+import { Loading } from '../../../shared/loading';
+
import { RESULT_SETTINGS_TITLE } from './constants';
import { ResultSettingsTable } from './result_settings_table';
@@ -26,12 +28,15 @@ interface Props {
}
export const ResultSettings: React.FC = ({ engineBreadcrumb }) => {
+ const { dataLoading } = useValues(ResultSettingsLogic);
const { initializeResultSettingsData } = useActions(ResultSettingsLogic);
useEffect(() => {
initializeResultSettingsData();
}, []);
+ if (dataLoading) return ;
+
return (
<>
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts
index e7bb065b596c3..a9c161b2bb5be 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts
@@ -40,6 +40,7 @@ describe('ResultSettingsLogic', () => {
stagedUpdates: false,
nonTextResultFields: {},
textResultFields: {},
+ queryPerformanceScore: 0,
};
// Values without selectors
@@ -487,6 +488,76 @@ describe('ResultSettingsLogic', () => {
});
});
});
+
+ describe('queryPerformanceScore', () => {
+ describe('returns a score for the current query performance based on the result settings', () => {
+ it('considers a text value with raw set (but no size) as worth 1.5', () => {
+ mount({
+ resultFields: { foo: { raw: true } },
+ schema: { foo: 'text' as SchemaTypes },
+ });
+ expect(ResultSettingsLogic.values.queryPerformanceScore).toEqual(1.5);
+ });
+
+ it('considers a text value with raw set and a size over 250 as also worth 1.5', () => {
+ mount({
+ resultFields: { foo: { raw: true, rawSize: 251 } },
+ schema: { foo: 'text' as SchemaTypes },
+ });
+ expect(ResultSettingsLogic.values.queryPerformanceScore).toEqual(1.5);
+ });
+
+ it('considers a text value with raw set and a size less than or equal to 250 as worth 1', () => {
+ mount({
+ resultFields: { foo: { raw: true, rawSize: 250 } },
+ schema: { foo: 'text' as SchemaTypes },
+ });
+ expect(ResultSettingsLogic.values.queryPerformanceScore).toEqual(1);
+ });
+
+ it('considers a text value with a snippet set as worth 2', () => {
+ mount({
+ resultFields: { foo: { snippet: true, snippetSize: 50, snippetFallback: true } },
+ schema: { foo: 'text' as SchemaTypes },
+ });
+ expect(ResultSettingsLogic.values.queryPerformanceScore).toEqual(2);
+ });
+
+ it('will sum raw and snippet values if both are set', () => {
+ mount({
+ resultFields: { foo: { snippet: true, raw: true } },
+ schema: { foo: 'text' as SchemaTypes },
+ });
+ // 1.5 (raw) + 2 (snippet) = 3.5
+ expect(ResultSettingsLogic.values.queryPerformanceScore).toEqual(3.5);
+ });
+
+ it('considers a non-text value with raw set as 0.2', () => {
+ mount({
+ resultFields: { foo: { raw: true } },
+ schema: { foo: 'number' as SchemaTypes },
+ });
+ expect(ResultSettingsLogic.values.queryPerformanceScore).toEqual(0.2);
+ });
+
+ it('can sum variations of all the prior', () => {
+ mount({
+ resultFields: {
+ foo: { raw: true },
+ bar: { raw: true, snippet: true },
+ baz: { raw: true },
+ },
+ schema: {
+ foo: 'text' as SchemaTypes,
+ bar: 'text' as SchemaTypes,
+ baz: 'number' as SchemaTypes,
+ },
+ });
+ // 1.5 (foo) + 3.5 (bar) + baz (.2) = 5.2
+ expect(ResultSettingsLogic.values.queryPerformanceScore).toEqual(5.2);
+ });
+ });
+ });
});
describe('listeners', () => {
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.ts
index 22f4c44f8b543..c345ae7e02e8d 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.ts
@@ -71,18 +71,19 @@ interface ResultSettingsValues {
dataLoading: boolean;
saving: boolean;
openModal: OpenModal;
- nonTextResultFields: FieldResultSettingObject;
- textResultFields: FieldResultSettingObject;
resultFields: FieldResultSettingObject;
- serverResultFields: ServerFieldResultSettingObject;
lastSavedResultFields: FieldResultSettingObject;
schema: Schema;
schemaConflicts: SchemaConflicts;
// Selectors
+ textResultFields: FieldResultSettingObject;
+ nonTextResultFields: FieldResultSettingObject;
+ serverResultFields: ServerFieldResultSettingObject;
resultFieldsAtDefaultSettings: boolean;
resultFieldsEmpty: boolean;
stagedUpdates: true;
reducedServerResultFields: ServerFieldResultSettingObject;
+ queryPerformanceScore: number;
}
export const ResultSettingsLogic = kea>({
@@ -221,6 +222,31 @@ export const ResultSettingsLogic = kea [selectors.serverResultFields, selectors.schema],
+ (serverResultFields: ServerFieldResultSettingObject, schema: Schema) => {
+ return Object.entries(serverResultFields).reduce((acc, [fieldName, resultField]) => {
+ let newAcc = acc;
+ if (resultField.raw) {
+ if (schema[fieldName] !== 'text') {
+ newAcc += 0.2;
+ } else if (
+ typeof resultField.raw === 'object' &&
+ resultField.raw.size &&
+ resultField.raw.size <= 250
+ ) {
+ newAcc += 1.0;
+ } else {
+ newAcc += 1.5;
+ }
+ }
+ if (resultField.snippet) {
+ newAcc += 2.0;
+ }
+ return newAcc;
+ }, 0);
+ },
+ ],
}),
listeners: ({ actions, values }) => ({
clearRawSizeForField: ({ fieldName }) => {
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/sample_response/sample_response.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/sample_response/sample_response.tsx
index ae91b9648356c..2d0cced3730ba 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/sample_response/sample_response.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/sample_response/sample_response.tsx
@@ -20,6 +20,7 @@ import {
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
+import { QueryPerformance } from '../query_performance';
import { ResultSettingsLogic } from '../result_settings_logic';
import { SampleResponseLogic } from './sample_response_logic';
@@ -48,7 +49,7 @@ export const SampleResponse: React.FC = () => {
- {/* TODO */}
+