From 9fca3e5261ad9dce003c1ce17b3388e4078ba077 Mon Sep 17 00:00:00 2001 From: Jason Stoltzfus Date: Mon, 5 Apr 2021 13:07:00 -0400 Subject: [PATCH 1/3] Added query performance rating --- .../query_performance/index.ts | 8 ++ .../query_performance.test.tsx | 59 ++++++++++ .../query_performance/query_performance.tsx | 108 ++++++++++++++++++ .../result_settings/result_settings.test.tsx | 18 ++- .../result_settings/result_settings.tsx | 6 +- .../result_settings_logic.test.ts | 71 ++++++++++++ .../result_settings/result_settings_logic.ts | 32 +++++- 7 files changed, 296 insertions(+), 6 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/query_performance/index.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/query_performance/query_performance.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/query_performance/query_performance.tsx 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..b074c7bc14bf3 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/query_performance/query_performance.tsx @@ -0,0 +1,108 @@ +/* + * 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, EuiBadgeProps } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { ResultSettingsLogic } from '../result_settings_logic'; + +enum QueryPerformanceRating { + Optimal = 'Optimal', + Good = 'Good', + Standard = 'Standard', + Delayed = 'Delayed', +} + +type QueryPerformanceBadgePropsCollection = { + [x in QueryPerformanceRating]: EuiBadgeProps; +}; + +type QueryPerformanceBadgeContentCollection = { + [x in QueryPerformanceRating]: string; +}; + +const QueryPerformanceBadgeProps: QueryPerformanceBadgePropsCollection = { + [QueryPerformanceRating.Optimal]: { + color: '#59deb4', + }, + [QueryPerformanceRating.Good]: { + color: '#40bfff', + }, + [QueryPerformanceRating.Standard]: { + color: '#fed566', + }, + [QueryPerformanceRating.Delayed]: { + color: '#ff9173', + }, +}; + +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 QueryPerformanceBadgeContents: QueryPerformanceBadgeContentCollection = { + [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 getQueryPerformanceRatingForScore = (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 queryPerformanceRating = getQueryPerformanceRatingForScore(queryPerformanceScore); + return ( + + {QueryPerformanceBadgeContents[queryPerformanceRating]} + + ); +}; 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..7c33e0f705c50 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,20 +7,27 @@ import '../../../__mocks__/shallow_useeffect.mock'; -import { setMockActions } from '../../../__mocks__'; +import { setMockValues, setMockActions } from '../../../__mocks__'; import React from 'react'; import { shallow } from 'enzyme'; +import { QueryPerformance } from './query_performance'; import { ResultSettings } from './result_settings'; import { ResultSettingsTable } from './result_settings_table'; describe('RelevanceTuning', () => { + const values = { + dataLoading: false, + }; + const actions = { initializeResultSettingsData: jest.fn(), }; + beforeEach(() => { + setMockValues(values); setMockActions(actions); jest.clearAllMocks(); }); @@ -28,10 +35,19 @@ describe('RelevanceTuning', () => { it('renders', () => { const wrapper = shallow(); expect(wrapper.find(ResultSettingsTable).exists()).toBe(true); + expect(wrapper.find(QueryPerformance).exists()).toBe(true); }); it('initializes result settings data when mounted', () => { shallow(); expect(actions.initializeResultSettingsData).toHaveBeenCalled(); }); + + it('does not show a query performance rating when data is still loading', () => { + setMockValues({ + dataLoading: true, + }); + const wrapper = shallow(); + expect(wrapper.find(QueryPerformance).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 38db5c60e98a9..42d96f0a4e7c0 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,7 +7,7 @@ import React, { useEffect } from 'react'; -import { useActions } from 'kea'; +import { useActions, useValues } from 'kea'; import { EuiPageHeader, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; @@ -15,6 +15,7 @@ import { FlashMessages } from '../../../shared/flash_messages'; import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; import { RESULT_SETTINGS_TITLE } from './constants'; +import { QueryPerformance } from './query_performance'; import { ResultSettingsTable } from './result_settings_table'; import { ResultSettingsLogic } from '.'; @@ -24,6 +25,7 @@ interface Props { } export const ResultSettings: React.FC = ({ engineBreadcrumb }) => { + const { dataLoading } = useValues(ResultSettingsLogic); const { initializeResultSettingsData } = useActions(ResultSettingsLogic); useEffect(() => { @@ -40,7 +42,7 @@ export const ResultSettings: React.FC = ({ engineBreadcrumb }) => { -
TODO
+
{!dataLoading && }
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 }) => { From d443dcec66914b7c7123628344845db0d1559a9b Mon Sep 17 00:00:00 2001 From: Jason Stoltzfus Date: Mon, 5 Apr 2021 13:15:20 -0400 Subject: [PATCH 2/3] Added a live region or a11y --- .../result_settings/query_performance/query_performance.tsx | 2 ++ 1 file changed, 2 insertions(+) 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 index b074c7bc14bf3..4ce55ddf7ad3e 100644 --- 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 @@ -100,6 +100,8 @@ export const QueryPerformance: React.FC = () => { className="response-feedback" data-test-subj="serverResultFieldsPerformance" tabIndex={-1} + role="region" + aria-live="polite" {...QueryPerformanceBadgeProps[queryPerformanceRating]} > {QueryPerformanceBadgeContents[queryPerformanceRating]} From cab6ee6dc9e5d2492b04478d57aee860d7145daa Mon Sep 17 00:00:00 2001 From: Jason Stoltzfus Date: Tue, 6 Apr 2021 10:48:45 -0400 Subject: [PATCH 3/3] PR Feedback --- .../query_performance/query_performance.tsx | 49 +++++-------------- 1 file changed, 13 insertions(+), 36 deletions(-) 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 index 4ce55ddf7ad3e..e3dfddc35d88c 100644 --- 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 @@ -9,7 +9,7 @@ import React from 'react'; import { useValues } from 'kea'; -import { EuiBadge, EuiBadgeProps } from '@elastic/eui'; +import { EuiBadge } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { ResultSettingsLogic } from '../result_settings_logic'; @@ -21,29 +21,6 @@ enum QueryPerformanceRating { Delayed = 'Delayed', } -type QueryPerformanceBadgePropsCollection = { - [x in QueryPerformanceRating]: EuiBadgeProps; -}; - -type QueryPerformanceBadgeContentCollection = { - [x in QueryPerformanceRating]: string; -}; - -const QueryPerformanceBadgeProps: QueryPerformanceBadgePropsCollection = { - [QueryPerformanceRating.Optimal]: { - color: '#59deb4', - }, - [QueryPerformanceRating.Good]: { - color: '#40bfff', - }, - [QueryPerformanceRating.Standard]: { - color: '#fed566', - }, - [QueryPerformanceRating.Delayed]: { - color: '#ff9173', - }, -}; - const QUERY_PERFORMANCE_LABEL = (performanceValue: string) => i18n.translate('xpack.enterpriseSearch.appSearch.engine.resultSettings.queryPerformanceLabel', { defaultMessage: 'Query performance: {performanceValue}', @@ -72,14 +49,21 @@ const QUERY_PERFORMANCE_DELAYED = i18n.translate( { defaultMessage: 'delayed' } ); -const QueryPerformanceBadgeContents: QueryPerformanceBadgeContentCollection = { +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 getQueryPerformanceRatingForScore = (score: number) => { +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; @@ -94,17 +78,10 @@ const getQueryPerformanceRatingForScore = (score: number) => { export const QueryPerformance: React.FC = () => { const { queryPerformanceScore } = useValues(ResultSettingsLogic); - const queryPerformanceRating = getQueryPerformanceRatingForScore(queryPerformanceScore); + const performanceRating = getPerformanceRating(queryPerformanceScore); return ( - - {QueryPerformanceBadgeContents[queryPerformanceRating]} + + {badgeText[performanceRating]} ); };