mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[App Search] Added a query performance rating to the Result Settings page (#96230)
This commit is contained in:
parent
532145b418
commit
818a740033
8 changed files with 280 additions and 6 deletions
|
@ -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';
|
|
@ -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(<QueryPerformance />);
|
||||
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(<QueryPerformance />);
|
||||
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(<QueryPerformance />);
|
||||
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(<QueryPerformance />);
|
||||
expect(wrapper.find(EuiBadge).prop('color')).toEqual('#ff9173');
|
||||
expect(wrapper.find(EuiBadge).children().text()).toEqual('Query performance: delayed');
|
||||
});
|
||||
});
|
|
@ -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, string> = {
|
||||
[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, string> = {
|
||||
[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 (
|
||||
<EuiBadge role="region" aria-live="polite" color={badgeColors[performanceRating]}>
|
||||
{badgeText[performanceRating]}
|
||||
</EuiBadge>
|
||||
);
|
||||
};
|
|
@ -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(<ResultSettings engineBreadcrumb={['test']} />);
|
||||
expect(wrapper.find(ResultSettingsTable).exists()).toBe(true);
|
||||
expect(wrapper.find(SampleResponse).exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('initializes result settings data when mounted', () => {
|
||||
shallow(<ResultSettings engineBreadcrumb={['test']} />);
|
||||
expect(actions.initializeResultSettingsData).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('renders a loading screen if data has not loaded yet', () => {
|
||||
setMockValues({
|
||||
dataLoading: true,
|
||||
});
|
||||
const wrapper = shallow(<ResultSettings engineBreadcrumb={['test']} />);
|
||||
expect(wrapper.find(ResultSettingsTable).exists()).toBe(false);
|
||||
expect(wrapper.find(SampleResponse).exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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<Props> = ({ engineBreadcrumb }) => {
|
||||
const { dataLoading } = useValues(ResultSettingsLogic);
|
||||
const { initializeResultSettingsData } = useActions(ResultSettingsLogic);
|
||||
|
||||
useEffect(() => {
|
||||
initializeResultSettingsData();
|
||||
}, []);
|
||||
|
||||
if (dataLoading) return <Loading />;
|
||||
|
||||
return (
|
||||
<>
|
||||
<SetPageChrome trail={[...engineBreadcrumb, RESULT_SETTINGS_TITLE]} />
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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<MakeLogicType<ResultSettingsValues, ResultSettingsActions>>({
|
||||
|
@ -221,6 +222,31 @@ export const ResultSettingsLogic = kea<MakeLogicType<ResultSettingsValues, Resul
|
|||
{}
|
||||
),
|
||||
],
|
||||
queryPerformanceScore: [
|
||||
() => [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 }) => {
|
||||
|
|
|
@ -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 = () => {
|
|||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
{/* TODO <QueryPerformance queryPerformanceRating={queryPerformanceRating} /> */}
|
||||
<QueryPerformance />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer />
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue